Merge branch 'add-user-modal' into 'develop'
Fixed add/update/delete ops for users and groups See merge request isard/isard-sso!45
commit
da43fd10df
|
@ -17,23 +17,27 @@ RUN apk add --no-cache curl py3-yaml yarn libpq openssl
|
||||||
RUN wget -O /usr/lib/python3.8/site-packages/diceware/wordlists/wordlist_cat_ascii.txt https://raw.githubusercontent.com/1ma/diceware-cat/master/cat-wordlist-ascii.txt
|
RUN wget -O /usr/lib/python3.8/site-packages/diceware/wordlists/wordlist_cat_ascii.txt https://raw.githubusercontent.com/1ma/diceware-cat/master/cat-wordlist-ascii.txt
|
||||||
|
|
||||||
# SSH configuration
|
# SSH configuration
|
||||||
ARG SSH_ROOT_PWD
|
# ARG SSH_ROOT_PWD
|
||||||
RUN apk add openssh
|
# RUN apk add openssh
|
||||||
RUN echo "root:$SSH_ROOT_PWD" |chpasswd
|
# RUN echo "root:$SSH_ROOT_PWD" |chpasswd
|
||||||
RUN sed -i \
|
# RUN sed -i \
|
||||||
-e 's|[#]*PermitRootLogin prohibit-password|PermitRootLogin yes|g' \
|
# -e 's|[#]*PermitRootLogin prohibit-password|PermitRootLogin yes|g' \
|
||||||
-e 's|[#]*PasswordAuthentication yes|PasswordAuthentication yes|g' \
|
# -e 's|[#]*PasswordAuthentication yes|PasswordAuthentication yes|g' \
|
||||||
-e 's|[#]*ChallengeResponseAuthentication yes|ChallengeResponseAuthentication yes|g' \
|
# -e 's|[#]*ChallengeResponseAuthentication yes|ChallengeResponseAuthentication yes|g' \
|
||||||
-e 's|[#]*UsePAM yes|UsePAM yes|g' \
|
# -e 's|[#]*UsePAM yes|UsePAM yes|g' \
|
||||||
-e 's|[#]#Port 22|Port 22|g' \
|
# -e 's|[#]#Port 22|Port 22|g' \
|
||||||
/etc/ssh/sshd_config
|
# /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
RUN apk add --no-cache git && \
|
||||||
|
git clone -b delete_realm_roles https://github.com/isard-vdi/python-keycloak.git && \
|
||||||
|
cd python-keycloak && \
|
||||||
|
python3 setup.py install && \
|
||||||
|
apk del git
|
||||||
|
|
||||||
COPY admin/src /admin
|
COPY admin/src /admin
|
||||||
RUN cd /admin/admin && yarn install
|
RUN cd /admin/admin && yarn install
|
||||||
|
|
||||||
COPY admin/docker/docker-entrypoint.sh /
|
COPY admin/docker/run.sh /run.sh
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
#EXPOSE 7039
|
#EXPOSE 7039
|
||||||
WORKDIR /admin
|
CMD [ "/run.sh" ]
|
||||||
CMD [ "python3", "start.py" ]
|
|
|
@ -1,4 +1,4 @@
|
||||||
python-keycloak==0.24.0
|
#python-keycloak==0.24.0
|
||||||
bcrypt==3.1.7
|
bcrypt==3.1.7
|
||||||
cffi==1.14.0
|
cffi==1.14.0
|
||||||
click==7.1.2
|
click==7.1.2
|
||||||
|
@ -26,5 +26,5 @@ python-engineio==3.8.1
|
||||||
python-socketio==4.1.0
|
python-socketio==4.1.0
|
||||||
|
|
||||||
minio==7.0.3
|
minio==7.0.3
|
||||||
|
urllib3==1.26.6
|
||||||
flask-oidc==1.4.0
|
flask-oidc==1.4.0
|
|
@ -1,10 +1,11 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
ssh-keygen -A
|
# ssh-keygen -A
|
||||||
## Only in development
|
## Only in development
|
||||||
cd /admin/admin
|
cd /admin/admin
|
||||||
yarn install
|
yarn install
|
||||||
## End Only in development
|
## End Only in development
|
||||||
cd /admin
|
cd /admin
|
||||||
export PYTHONWARNINGS="ignore:Unverified HTTPS request"
|
export PYTHONWARNINGS="ignore:Unverified HTTPS request"
|
||||||
python3 start.py &
|
python3 start.py
|
||||||
/usr/sbin/sshd -D -e -f /etc/ssh/sshd_config
|
#&
|
||||||
|
# /usr/sbin/sshd -D -e -f /etc/ssh/sshd_config
|
|
@ -18,8 +18,12 @@ options = diceware.handle_options(None)
|
||||||
options.wordlist = 'cat_ascii'
|
options.wordlist = 'cat_ascii'
|
||||||
options.num = 3
|
options.num = 3
|
||||||
|
|
||||||
|
from .helpers import rand_password
|
||||||
|
from .exceptions import UserExists, UserNotFound
|
||||||
from .events import Events
|
from .events import Events
|
||||||
|
|
||||||
|
import secrets
|
||||||
|
|
||||||
class Admin():
|
class Admin():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
ready=False
|
ready=False
|
||||||
|
@ -70,7 +74,8 @@ class Admin():
|
||||||
try:
|
try:
|
||||||
self.resync_data()
|
self.resync_data()
|
||||||
ready=True
|
ready=True
|
||||||
except:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
log.error('Could not connect to moodle, waiting to be online...')
|
log.error('Could not connect to moodle, waiting to be online...')
|
||||||
sleep(2)
|
sleep(2)
|
||||||
|
|
||||||
|
@ -270,7 +275,10 @@ class Admin():
|
||||||
"last": u['displayname'].split(' ')[1] if u['displayname'] is not None and len(u['displayname'].split(' '))>1 else '',
|
"last": u['displayname'].split(' ')[1] if u['displayname'] is not None and len(u['displayname'].split(' '))>1 else '',
|
||||||
"email": u.get('email',''),
|
"email": u.get('email',''),
|
||||||
"groups": u['groups'],
|
"groups": u['groups'],
|
||||||
"roles": False}
|
"roles": False,
|
||||||
|
"quota": u['quota'],
|
||||||
|
"quota_used_bytes": str(int(int(u['total_bytes'])/1024/1024/2))+' MB' if u['total_bytes'] != None else "0 MB"}
|
||||||
|
# The theoretical bytes returned do not map to any known unit. The division is just an approach while we investigate.
|
||||||
for u in self.nextcloud.get_users_list() if u['username'] not in ['guest','ddadmin','admin'] and not u['username'].startswith('system')]
|
for u in self.nextcloud.get_users_list() if u['username'] not in ['guest','ddadmin','admin'] and not u['username'].startswith('system')]
|
||||||
|
|
||||||
## TOO SLOW
|
## TOO SLOW
|
||||||
|
@ -332,12 +340,19 @@ class Admin():
|
||||||
theuser['nextcloud']=True
|
theuser['nextcloud']=True
|
||||||
theuser['nextcloud_groups']=nextcloud_exists[0]['groups']
|
theuser['nextcloud_groups']=nextcloud_exists[0]['groups']
|
||||||
theuser['nextcloud_id']=nextcloud_exists[0]['id']
|
theuser['nextcloud_id']=nextcloud_exists[0]['id']
|
||||||
|
theuser['quota']=theuser['quota'] if theuser['quota'] != None and theuser['quota'] != 'none' else False
|
||||||
else:
|
else:
|
||||||
theuser['nextcloud']=False
|
theuser['nextcloud']=False
|
||||||
theuser['nextcloud_groups']=[]
|
theuser['nextcloud_groups']=[]
|
||||||
|
theuser['quota']=False
|
||||||
|
theuser['quota_used_bytes']=False
|
||||||
del theuser['groups']
|
del theuser['groups']
|
||||||
users.append(theuser)
|
|
||||||
|
|
||||||
|
if not len(theuser['roles']):
|
||||||
|
log.error(' SKIPPING USER WITHOUT ANY ROLE!!: '+theuser['username']+' . Should be fixed at keycloak level.')
|
||||||
|
continue
|
||||||
|
|
||||||
|
users.append(theuser)
|
||||||
return users
|
return users
|
||||||
|
|
||||||
def get_roles(self):
|
def get_roles(self):
|
||||||
|
@ -611,7 +626,7 @@ class Admin():
|
||||||
if u['first'] == '': u['first']=' '
|
if u['first'] == '': u['first']=' '
|
||||||
if u['last'] == '': u['last']=' '
|
if u['last'] == '': u['last']=' '
|
||||||
try:
|
try:
|
||||||
pprint(self.moodle.create_user(u['email'],u['username'],'1Random 1String',u['first'],u['last'])[0])
|
pprint(self.moodle.create_user(u['email'],u['username'],secrets.token_urlsafe(16),u['first'],u['last'])[0])
|
||||||
except:
|
except:
|
||||||
log.error(' -->> Error creating on moodle the user: '+u['username'])
|
log.error(' -->> Error creating on moodle the user: '+u['username'])
|
||||||
# user_id=user['id']
|
# user_id=user['id']
|
||||||
|
@ -677,7 +692,7 @@ class Admin():
|
||||||
log.warning(' NEXTCLOUD USERS: Creating nextcloud user: '+u['username']+' in groups '+str(u['keycloak_groups']))
|
log.warning(' NEXTCLOUD USERS: Creating nextcloud user: '+u['username']+' in groups '+str(u['keycloak_groups']))
|
||||||
try:
|
try:
|
||||||
ev.increment({'name':u['username']})
|
ev.increment({'name':u['username']})
|
||||||
self.nextcloud.add_user_with_groups(u['username'],'1Random 1String',500000000000,u['keycloak_groups'],u['email'],u['first']+' '+u['last'])
|
self.nextcloud.add_user_with_groups(u['username'],secrets.token_urlsafe(16),"500 GB",u['keycloak_groups'],u['email'],u['first']+' '+u['last'])
|
||||||
except ProviderItemExists:
|
except ProviderItemExists:
|
||||||
log.warning('User '+u['username']+' already exists. Skipping...')
|
log.warning('User '+u['username']+' already exists. Skipping...')
|
||||||
continue
|
continue
|
||||||
|
@ -802,8 +817,130 @@ class Admin():
|
||||||
def user_update_password(self,userid,password,temporary):
|
def user_update_password(self,userid,password,temporary):
|
||||||
return self.keycloak.update_user_pwd(userid,password,temporary)
|
return self.keycloak.update_user_pwd(userid,password,temporary)
|
||||||
|
|
||||||
|
def user_update(self,user):
|
||||||
|
log.warning('Updating user moodle, nextcloud keycloak')
|
||||||
|
ev=Events('Updating user','Updating user in keycloak')
|
||||||
|
|
||||||
|
|
||||||
|
## Get actual user role
|
||||||
|
try:
|
||||||
|
internaluser = [u for u in self.internal['users'] if u['username'] == user['username']][0]
|
||||||
|
except:
|
||||||
|
raise UserNotFound
|
||||||
|
|
||||||
|
if set(user['groups']) != set(internaluser['keycloak_groups']):
|
||||||
|
add_user_groups = list(set(user['groups']) - set(internaluser['keycloak_groups']))
|
||||||
|
user_groups_with_system=user['groups']+['/admin','/manager','/teacher','/student']
|
||||||
|
remove_user_groups = list(set(internaluser['keycloak_groups']) - set(user_groups_with_system))
|
||||||
|
else:
|
||||||
|
add_user_groups=[]
|
||||||
|
remove_user_groups=[]
|
||||||
|
|
||||||
|
print('PREVIOUS ADD USER GROUPS')
|
||||||
|
print(add_user_groups)
|
||||||
|
print('PREVIOUS REMOVE USER GROUPS')
|
||||||
|
print(remove_user_groups)
|
||||||
|
|
||||||
|
if user['roles'][0] not in internaluser['roles']:
|
||||||
|
# Remove internaluser['roles']
|
||||||
|
add_user_roles=user['roles']
|
||||||
|
if '/'+user['roles'][0] not in add_user_groups:
|
||||||
|
add_user_groups.append('/'+user['roles'][0])
|
||||||
|
remove_user_roles=internaluser['roles'] #Remove the previous role
|
||||||
|
if '/'+user['roles'][0] not in remove_user_groups:
|
||||||
|
remove_user_groups.append('/'+internaluser['roles'][0])
|
||||||
|
else:
|
||||||
|
add_user_roles=[]
|
||||||
|
remove_user_roles=[]
|
||||||
|
# Add user['role']
|
||||||
|
|
||||||
|
print('ADD USER GROUPS')
|
||||||
|
print(add_user_groups)
|
||||||
|
print('REMOVE USER GROUPS')
|
||||||
|
print(remove_user_groups)
|
||||||
|
print('ADD USER ROLES')
|
||||||
|
print(add_user_roles)
|
||||||
|
print('REMOVE USER ROLES')
|
||||||
|
print(remove_user_roles)
|
||||||
|
|
||||||
|
self.update_keycloak_user(internaluser['id'],user,remove_user_roles,remove_user_groups)
|
||||||
|
ev.update_text('Syncing data from applications...')
|
||||||
|
self.resync_data()
|
||||||
|
|
||||||
|
ev.update_text('Updating user in moodle')
|
||||||
|
self.update_moodle_user(internaluser['id'],user,remove_user_groups)
|
||||||
|
|
||||||
|
ev.update_text('Updating user in nextcloud')
|
||||||
|
self.update_nextcloud_user(internaluser['id'],user,remove_user_groups)
|
||||||
|
|
||||||
|
ev.update_text('User updated')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_keycloak_user(self,user_id,user,remove_user_roles,remove_user_groups):
|
||||||
|
if len(remove_user_roles):
|
||||||
|
toremove=[]
|
||||||
|
# pprint(self.keycloak.get_user_realm_roles(user_id))
|
||||||
|
for role in remove_user_roles:
|
||||||
|
toremove.append(self.keycloak.get_role(role))
|
||||||
|
|
||||||
|
# Also remove from group identical to role:
|
||||||
|
group_id = self.keycloak.get_group_by_path('/'+role)['id']
|
||||||
|
self.keycloak.group_user_remove(user_id,group_id)
|
||||||
|
|
||||||
|
self.keycloak.remove_user_roles(user_id,toremove)
|
||||||
|
|
||||||
|
self.keycloak.assign_realm_roles(user_id,user['roles'][0])
|
||||||
|
# Also add it to the group identical to role
|
||||||
|
group_id = self.keycloak.get_group_by_path('/'+user['roles'][0])['id']
|
||||||
|
self.keycloak.group_user_add(user_id,group_id)
|
||||||
|
|
||||||
|
if len(remove_user_groups):
|
||||||
|
for group in remove_user_groups:
|
||||||
|
group_id = self.keycloak.get_group_by_path(group)['id']
|
||||||
|
self.keycloak.group_user_remove(user_id,group_id)
|
||||||
|
|
||||||
|
for group in user['groups']:
|
||||||
|
group_id = self.keycloak.get_group_by_path(group)['id']
|
||||||
|
self.keycloak.group_user_add(user_id,group_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.keycloak.user_update(user_id,user['enabled'],user['email'],user['firstname'],user['lastname'])
|
||||||
|
# So we should add it to the correct role
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_moodle_user(self,user_id,user,remove_user_groups):
|
||||||
|
internaluser=[u for u in self.internal['users'] if u['id']==user_id][0]
|
||||||
|
cohorts=self.moodle.get_cohorts()
|
||||||
|
if len(remove_user_groups):
|
||||||
|
for group in remove_user_groups:
|
||||||
|
cohort=[c for c in cohorts if c['name']==group][0]
|
||||||
|
try:
|
||||||
|
self.moodle.delete_user_in_cohort(user_id,cohort['id'])
|
||||||
|
except:
|
||||||
|
log.error('MOODLE: User '+user['username']+' it is not in cohort '+group)
|
||||||
|
|
||||||
|
for group in user['groups']:
|
||||||
|
cohort=[c for c in cohorts if c['name']==group][0]
|
||||||
|
self.moodle.add_user_to_cohort(internaluser['moodle_id'],cohort['id'])
|
||||||
|
self.moodle.update_user(username=user['username'], email=user['email'], first_name=user['firstname'], last_name=user['lastname'], enabled=user['enabled'])
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_nextcloud_user(self,user_id, user, remove_user_groups):
|
||||||
|
self.nextcloud.update_user(user['username'],{"quota":user['quota'],"email":user['email'],"displayname":user['firstname']+' '+user['lastname']})
|
||||||
|
## TODO: Disable de user? Is really needed? it is disabled in keycloak, so can't login again
|
||||||
|
## ocs/v1.php/cloud/users/{userid}/disable
|
||||||
|
if len(remove_user_groups):
|
||||||
|
for group in remove_user_groups:
|
||||||
|
self.nextcloud.remove_user_from_group(user['username'],group)
|
||||||
|
|
||||||
|
for group in user['groups']:
|
||||||
|
self.nextcloud.add_user_to_group(user['username'],group)
|
||||||
|
|
||||||
|
|
||||||
def delete_user(self,userid):
|
def delete_user(self,userid):
|
||||||
log.warning('deleting user moodle, nextcloud keycloak')
|
log.warning('Deleting user moodle, nextcloud keycloak')
|
||||||
ev=Events('Deleting user','Deleting from moodle')
|
ev=Events('Deleting user','Deleting from moodle')
|
||||||
self.delete_moodle_user(userid)
|
self.delete_moodle_user(userid)
|
||||||
ev.update_text('Deleting from nextcloud')
|
ev.update_text('Deleting from nextcloud')
|
||||||
|
@ -816,4 +953,125 @@ class Admin():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_user(self,userid):
|
def get_user(self,userid):
|
||||||
return [u for u in self.internal['users'] if u['id']==userid][0]
|
user = [u for u in self.internal['users'] if u['id']==userid]
|
||||||
|
if not len(user): return False
|
||||||
|
return user[0]
|
||||||
|
|
||||||
|
def get_user_username(self,username):
|
||||||
|
user = [u for u in self.internal['users'] if u['username']==username]
|
||||||
|
if not len(user): return False
|
||||||
|
return user[0]
|
||||||
|
|
||||||
|
def add_user(self,u):
|
||||||
|
### KEYCLOAK
|
||||||
|
ev=Events('Add user',u['username'],total=5)
|
||||||
|
log.warning(' KEYCLOAK USERS: Adding user: '+u['username'])
|
||||||
|
uid=self.keycloak.add_user(u['username'],u['first'],u['last'],u['email'],u['password'],enabled=u['enabled'])
|
||||||
|
self.av.add_user_default_avatar(uid,u['role'])
|
||||||
|
# Add user to role and group rolename
|
||||||
|
log.warning(' KEYCLOAK USERS: Assign user '+u['username']+' with initial pwd '+ u['password']+' to role '+u['role'])
|
||||||
|
self.keycloak.assign_realm_roles(uid,u['role'])
|
||||||
|
gid=self.keycloak.get_group_by_path(path='/'+u['role'])['id']
|
||||||
|
self.keycloak.group_user_add(uid,gid)
|
||||||
|
ev.increment({'name':'Added to system','data':[]})
|
||||||
|
# Add user to groups
|
||||||
|
for g in u['groups']:
|
||||||
|
parts=g.split('/')
|
||||||
|
sub=''
|
||||||
|
if len(parts)==0:
|
||||||
|
log.warning(' KEYCLOAK USERS: Skip assign user '+u['username']+' to any group as does not have one')
|
||||||
|
continue # NO GROUP
|
||||||
|
for i in range(1,len(parts)):
|
||||||
|
sub=sub+'/'+parts[i]
|
||||||
|
if sub=='/': continue # User with no path
|
||||||
|
log.warning(' KEYCLOAK USERS: Assign user '+u['username']+' to group '+ str(sub))
|
||||||
|
gid=self.keycloak.get_group_by_path(path=sub)['id']
|
||||||
|
self.keycloak.group_user_add(uid,gid)
|
||||||
|
ev.increment({'name':'Added to system groups','data':[]})
|
||||||
|
### MOODLE
|
||||||
|
# Add user
|
||||||
|
log.warning('Creating moodle user: '+u['username'])
|
||||||
|
try:
|
||||||
|
self.moodle.create_user(u['email'],u['username'],'*'+secrets.token_urlsafe(16),u['first'],u['last'])
|
||||||
|
ev.increment({'name':'Added to moodle','data':[]})
|
||||||
|
except UserExists:
|
||||||
|
log.error(' -->> User already exists')
|
||||||
|
error=Events('User already exists.',str(se),type='error')
|
||||||
|
except SystemError as se:
|
||||||
|
log.error('Moodle create user error: '+str(se))
|
||||||
|
error=Events('Moodle create user error',str(se),type='error')
|
||||||
|
except:
|
||||||
|
log.error(' -->> Error creating on moodle the user: '+u['username'])
|
||||||
|
print(traceback.format_exc())
|
||||||
|
error=Events('Internal error','Check logs',type='error')
|
||||||
|
|
||||||
|
# Add user to cohort
|
||||||
|
## Get all existing moodle cohorts
|
||||||
|
cohorts=self.moodle.get_cohorts()
|
||||||
|
for g in u['groups']:
|
||||||
|
try:
|
||||||
|
cohort=[c for c in cohorts if c['name']==g][0]
|
||||||
|
except:
|
||||||
|
# print(traceback.format_exc())
|
||||||
|
log.error(' MOODLE USER GROUPS: keycloak group '+g+' does not exist as moodle cohort. This should not happen. User '+u['username']+ ' not added.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.moodle.add_user_to_cohort(u['username'],cohort['id'])
|
||||||
|
except:
|
||||||
|
log.error(' MOODLE USER GROUPS: User '+u['username']+' already exists in cohort '+cohort['name'])
|
||||||
|
|
||||||
|
ev.increment({'name':'Added to moodle cohorts','data':[]})
|
||||||
|
### NEXTCLOUD
|
||||||
|
log.warning(' NEXTCLOUD USERS: Creating nextcloud user: '+u['username']+' in groups '+str(u['groups']))
|
||||||
|
try:
|
||||||
|
# Quota is in MB
|
||||||
|
self.nextcloud.add_user_with_groups(u['username'],secrets.token_urlsafe(16),u['quota'],u['groups'],u['email'],u['first']+' '+u['last'])
|
||||||
|
ev.increment({'name':'Added to nextcloud','data':[]})
|
||||||
|
except ProviderItemExists:
|
||||||
|
log.warning('User '+u['username']+' already exists. Skipping...')
|
||||||
|
except:
|
||||||
|
log.error(traceback.format_exc())
|
||||||
|
|
||||||
|
self.resync_data()
|
||||||
|
|
||||||
|
def add_group(self,g):
|
||||||
|
# TODO: Check if exists
|
||||||
|
|
||||||
|
new_path=self.keycloak.add_group(g['name'],g['parent'])
|
||||||
|
if g['parent'] != None:
|
||||||
|
new_path=new_path['path']
|
||||||
|
else:
|
||||||
|
new_path='/'+g['name']
|
||||||
|
self.moodle.add_system_cohort(new_path,g['description'])
|
||||||
|
self.nextcloud.add_group(new_path)
|
||||||
|
|
||||||
|
def delete_group_by_id(self,group_id):
|
||||||
|
# TODO: Check if exists
|
||||||
|
group = self.keycloak.get_group_by_id(group_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.keycloak.delete_group(group['id'])
|
||||||
|
except:
|
||||||
|
log.error('KEYCLOAK: Could no delete group '+group['path'])
|
||||||
|
cohorts=self.moodle.get_cohorts()
|
||||||
|
|
||||||
|
cohort = [c['id'] for c in cohorts if c['name'] == group['path']]
|
||||||
|
self.moodle.delete_cohorts(cohort)
|
||||||
|
|
||||||
|
self.nextcloud.delete_group(group['path'])
|
||||||
|
# group = [g for g in self.internal['groups'] if g['id'] == group_id][0]
|
||||||
|
|
||||||
|
def delete_group_by_path(self,path):
|
||||||
|
group =self.keycloak.get_group_by_path(path)
|
||||||
|
if group != None:
|
||||||
|
try:
|
||||||
|
self.keycloak.delete_group(group['id'])
|
||||||
|
except:
|
||||||
|
log.error('KEYCLOAK: Could no delete group '+group['path'])
|
||||||
|
cohorts=self.moodle.get_cohorts()
|
||||||
|
cohort = [c['id'] for c in cohorts if c['name'] == path]
|
||||||
|
if len(cohort):
|
||||||
|
self.moodle.delete_cohorts(cohort)
|
||||||
|
|
||||||
|
self.nextcloud.delete_group(path)
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,15 @@ from flask_socketio import SocketIO, emit, join_room, leave_room, \
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
class Events():
|
class Events():
|
||||||
def __init__(self,title,text='',total=0,table=False):
|
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.eid=str(base64.b64encode(os.urandom(32))[:8])
|
||||||
self.title=title
|
self.title=title
|
||||||
self.text=text
|
self.text=text
|
||||||
self.total=total
|
self.total=total
|
||||||
self.table=table
|
self.table=table
|
||||||
self.item=0
|
self.item=0
|
||||||
|
self.type=type
|
||||||
self.create()
|
self.create()
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
|
@ -28,7 +30,8 @@ class Events():
|
||||||
app.socketio.emit('notify-create',
|
app.socketio.emit('notify-create',
|
||||||
json.dumps({'id':self.eid,
|
json.dumps({'id':self.eid,
|
||||||
'title':self.title,
|
'title':self.title,
|
||||||
'text':self.text}),
|
'text':self.text,
|
||||||
|
'type':self.type}),
|
||||||
namespace='/sio',
|
namespace='/sio',
|
||||||
room='admin')
|
room='admin')
|
||||||
sleep(0.001)
|
sleep(0.001)
|
||||||
|
@ -69,6 +72,7 @@ class Events():
|
||||||
'item':self.item,
|
'item':self.item,
|
||||||
'total':self.total,
|
'total':self.total,
|
||||||
'table':self.table,
|
'table':self.table,
|
||||||
|
'type':self.type,
|
||||||
'data':data}),
|
'data':data}),
|
||||||
namespace='/sio',
|
namespace='/sio',
|
||||||
room='admin')
|
room='admin')
|
||||||
|
@ -84,6 +88,7 @@ class Events():
|
||||||
'item':self.item,
|
'item':self.item,
|
||||||
'total':self.total,
|
'total':self.total,
|
||||||
'table':self.table,
|
'table':self.table,
|
||||||
|
'type':self.type,
|
||||||
'data':data}),
|
'data':data}),
|
||||||
namespace='/sio',
|
namespace='/sio',
|
||||||
room='admin')
|
room='admin')
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
class UserExists(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UserNotFound(Exception):
|
||||||
|
pass
|
|
@ -1,3 +1,5 @@
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
def filter_roles_list(role_list):
|
def filter_roles_list(role_list):
|
||||||
client_roles=['admin','manager','teacher','student']
|
client_roles=['admin','manager','teacher','student']
|
||||||
|
@ -6,3 +8,10 @@ def filter_roles_list(role_list):
|
||||||
def filter_roles_listofdicts(role_listofdicts):
|
def filter_roles_listofdicts(role_listofdicts):
|
||||||
client_roles=['admin','manager','teacher','student']
|
client_roles=['admin','manager','teacher','student']
|
||||||
return [r for r in role_listofdicts if r['name'] in client_roles]
|
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))
|
||||||
|
while not any(ele.isupper() for ele in passwd):
|
||||||
|
passwd = ''.join(random.choice(characters) for i in range(lenght))
|
||||||
|
return passwd
|
|
@ -138,14 +138,14 @@ class KeycloakClient():
|
||||||
# user['roles']= [r['name'] for r in self.keycloak_admin.get_realm_roles_of_user(user_id=user['id'])]
|
# user['roles']= [r['name'] for r in self.keycloak_admin.get_realm_roles_of_user(user_id=user['id'])]
|
||||||
# return users
|
# return users
|
||||||
|
|
||||||
def add_user(self,username,first,last,email,password,group=False,temporary=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)
|
# RETURNS string with keycloak user id (the main id in this app)
|
||||||
self.connect()
|
self.connect()
|
||||||
username=username.lower()
|
username=username.lower()
|
||||||
try:
|
try:
|
||||||
uid=self.keycloak_admin.create_user({"email": email,
|
uid=self.keycloak_admin.create_user({"email": email,
|
||||||
"username": username,
|
"username": username,
|
||||||
"enabled": True,
|
"enabled": enabled,
|
||||||
"firstName": first,
|
"firstName": first,
|
||||||
"lastName": last,
|
"lastName": last,
|
||||||
"credentials":[{"type":"password",
|
"credentials":[{"type":"password",
|
||||||
|
@ -172,17 +172,30 @@ class KeycloakClient():
|
||||||
self.connect()
|
self.connect()
|
||||||
return self.keycloak_admin.update_user( user_id, payload)
|
return self.keycloak_admin.update_user( user_id, payload)
|
||||||
|
|
||||||
def remove_user_group(self,user_id,group_id):
|
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}
|
||||||
self.connect()
|
self.connect()
|
||||||
pass
|
return self.keycloak_admin.update_user( user_id, payload)
|
||||||
|
|
||||||
|
def group_user_remove(self,user_id,group_id):
|
||||||
|
self.connect()
|
||||||
|
return self.keycloak_admin.group_user_remove(user_id,group_id)
|
||||||
|
|
||||||
# def add_user_role(self,user_id,role_id):
|
# def add_user_role(self,user_id,role_id):
|
||||||
# self.connect()
|
# self.connect()
|
||||||
# return self.keycloak_admin.assign_role(client_id=client_id, user_id=user_id, role_id=role_id, role_name="test")
|
# return self.keycloak_admin.assign_role(client_id=client_id, user_id=user_id, role_id=role_id, role_name="test")
|
||||||
|
|
||||||
def remove_user_role(self,user_id,role_id):
|
def remove_user_roles(self,user_id,roles):
|
||||||
self.connect()
|
self.connect()
|
||||||
pass
|
print(roles)
|
||||||
|
return self.keycloak_admin.delete_realm_roles_of_user(user_id,roles)
|
||||||
|
|
||||||
def delete_user(self,userid):
|
def delete_user(self,userid):
|
||||||
self.connect()
|
self.connect()
|
||||||
|
@ -192,6 +205,10 @@ class KeycloakClient():
|
||||||
self.connect()
|
self.connect()
|
||||||
return self.keycloak_admin.get_user_groups(user_id=userid)
|
return self.keycloak_admin.get_user_groups(user_id=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()
|
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")
|
||||||
|
@ -225,7 +242,8 @@ class KeycloakClient():
|
||||||
|
|
||||||
def add_group(self,name,parent=None,skip_exists=False):
|
def add_group(self,name,parent=None,skip_exists=False):
|
||||||
self.connect()
|
self.connect()
|
||||||
if parent is not None: parent=self.get_group_by_path(parent)['id']
|
if parent != None:
|
||||||
|
parent=self.get_group_by_path(parent)['id']
|
||||||
return self.keycloak_admin.create_group({"name":name}, parent=parent)
|
return self.keycloak_admin.create_group({"name":name}, parent=parent)
|
||||||
|
|
||||||
def delete_group(self,group_id):
|
def delete_group(self,group_id):
|
||||||
|
@ -313,7 +331,7 @@ class KeycloakClient():
|
||||||
|
|
||||||
def get_role(self,name):
|
def get_role(self,name):
|
||||||
self.connect()
|
self.connect()
|
||||||
return self.keycloak_admin.get_realm_role(name=name)
|
return self.keycloak_admin.get_realm_role(name)
|
||||||
|
|
||||||
def add_role(self,name,description=''):
|
def add_role(self,name,description=''):
|
||||||
self.connect()
|
self.connect()
|
||||||
|
@ -356,7 +374,8 @@ class KeycloakClient():
|
||||||
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:
|
except:
|
||||||
return False
|
return False
|
||||||
return self.keycloak_admin.assign_realm_roles(user_id=user_id, client_id=None, roles=role)
|
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
|
## CLIENTS
|
||||||
def delete_client(self,clientid):
|
def delete_client(self,clientid):
|
||||||
|
|
|
@ -3,8 +3,10 @@ from admin import app
|
||||||
|
|
||||||
import logging as log
|
import logging as log
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
import traceback
|
||||||
|
|
||||||
from .postgres import Postgres
|
from .postgres import Postgres
|
||||||
|
from .exceptions import UserExists, UserNotFound
|
||||||
|
|
||||||
# Module variables to connect to moodle api
|
# Module variables to connect to moodle api
|
||||||
|
|
||||||
|
@ -63,14 +65,33 @@ class Moodle():
|
||||||
response = post(self.url+self.endpoint, parameters, verify=self.verify)
|
response = post(self.url+self.endpoint, parameters, verify=self.verify)
|
||||||
response = response.json()
|
response = response.json()
|
||||||
if type(response) == dict and response.get('exception'):
|
if type(response) == dict and response.get('exception'):
|
||||||
raise SystemError("Error calling Moodle API\n", response)
|
raise SystemError(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def create_user(self, email, username, password, first_name='-', last_name='-'):
|
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,
|
data = [{'username': username, 'email':email,
|
||||||
'password': password, 'firstname':first_name, 'lastname':last_name}]
|
'password': password, 'firstname':first_name, 'lastname':last_name}]
|
||||||
user = self.call('core_user_create_users', users=data)
|
user = self.call('core_user_create_users', users=data)
|
||||||
return user
|
return user
|
||||||
|
except SystemError as se:
|
||||||
|
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]
|
||||||
|
print(user)
|
||||||
|
if not len(user):
|
||||||
|
raise UserNotFound
|
||||||
|
try:
|
||||||
|
data = [{'id':user['id'],'username': username, 'email':email,
|
||||||
|
'firstname':first_name, 'lastname':last_name,
|
||||||
|
'suspended':0 if not enabled else 1}]
|
||||||
|
user = self.call('core_user_update_users', users=data)
|
||||||
|
return user
|
||||||
|
except SystemError as se:
|
||||||
|
raise SystemError(se.args[0]['message'])
|
||||||
|
|
||||||
def delete_user(self, user_id):
|
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])
|
||||||
|
@ -82,7 +103,10 @@ class Moodle():
|
||||||
|
|
||||||
def get_user_by(self, key, value):
|
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
|
return user
|
||||||
|
|
||||||
def get_users_with_groups_and_roles(self):
|
def get_users_with_groups_and_roles(self):
|
||||||
|
@ -142,6 +166,10 @@ class Moodle():
|
||||||
user = self.call('core_cohort_add_cohort_members', members=members)
|
user = self.call('core_cohort_add_cohort_members', members=members)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def delete_user_in_cohort(self,userid,cohortid):
|
||||||
|
user = self.call('core_cohort_delete_cohort_members', cohortid=cohortid, userid=userid)
|
||||||
|
return user
|
||||||
|
|
||||||
def get_cohort_members(self, cohort_ids):
|
def get_cohort_members(self, cohort_ids):
|
||||||
members = self.call('core_cohort_get_cohort_members', cohortids=cohort_ids)
|
members = self.call('core_cohort_get_cohort_members', cohortids=cohort_ids)
|
||||||
#[0]['userids']
|
#[0]['userids']
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#from ..lib.log import *
|
#from ..lib.log import *
|
||||||
from admin import app
|
from admin import app
|
||||||
import time,requests,json,pprint,os
|
import time,requests,json,pprint,os
|
||||||
|
import urllib
|
||||||
import traceback
|
import traceback
|
||||||
import logging as log
|
import logging as log
|
||||||
from .nextcloud_exc import *
|
from .nextcloud_exc import *
|
||||||
|
@ -32,6 +33,7 @@ class Nextcloud():
|
||||||
response = requests.request(method, url, data=data, auth=auth, verify=self.verify_cert, headers=headers)
|
response = requests.request(method, url, data=data, auth=auth, verify=self.verify_cert, headers=headers)
|
||||||
if 'meta' in response.text:
|
if 'meta' in response.text:
|
||||||
if '<statuscode>997</statuscode>' in response.text: raise ProviderUnauthorized
|
if '<statuscode>997</statuscode>' in response.text: raise ProviderUnauthorized
|
||||||
|
# if '<statuscode>998</statuscode>' in response.text: raise ProviderInvalidQuery
|
||||||
return response.text
|
return response.text
|
||||||
|
|
||||||
## At least the ProviderSslError is not being catched or not raised correctly
|
## At least the ProviderSslError is not being catched or not raised correctly
|
||||||
|
@ -102,7 +104,18 @@ class Nextcloud():
|
||||||
# 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]
|
# 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]
|
# list_dict_users = [dict(zip(fields, r)) for r in users_with_lists]
|
||||||
def get_users_list(self):
|
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
|
# 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"""
|
||||||
|
|
||||||
|
# With quotas
|
||||||
|
q = """select u.uid as username, configvalue as quota, sum(size) as total_bytes, 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_group_user as gu on gu.uid = u.uid
|
||||||
left join oc_groups as g on gu.gid = g.gid
|
left join oc_groups as g on gu.gid = g.gid
|
||||||
|
@ -110,7 +123,10 @@ class Nextcloud():
|
||||||
left join oc_groups as gg on gg.gid = ga.gid
|
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 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'
|
left join oc_accounts_data as ade on ade.uid = u.uid and ade.name = 'email'
|
||||||
group by u.uid, adn.value, ade.value"""
|
left join oc_preferences as pref on u.uid=pref.userid and appid='files' and configkey='quota'
|
||||||
|
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)
|
(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]
|
||||||
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]
|
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]
|
||||||
|
@ -163,6 +179,68 @@ class Nextcloud():
|
||||||
# 106 - no group specified (required for subadmins)
|
# 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)
|
# 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 update_user(self, userid, key_values):
|
||||||
|
# key_values={'quota':quota,'email':email,'displayname':displayname}
|
||||||
|
|
||||||
|
url = self.apiurl + "users/" + userid + "?format=json"
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'OCS-APIRequest': 'true',
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
raise ProviderOpError
|
||||||
|
except:
|
||||||
|
log.error(traceback.format_exc())
|
||||||
|
raise
|
||||||
|
|
||||||
|
def add_user_to_group(self, userid, 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',
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
raise ProviderOpError
|
||||||
|
except:
|
||||||
|
log.error(traceback.format_exc())
|
||||||
|
raise
|
||||||
|
|
||||||
|
def remove_user_from_group(self, userid, 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',
|
||||||
|
}
|
||||||
|
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:
|
||||||
|
self.add_group(group)
|
||||||
|
# raise ProviderGroupNotExists
|
||||||
|
log.error('Get Nextcloud provider user add error: '+str(result))
|
||||||
|
raise ProviderOpError
|
||||||
|
except:
|
||||||
|
log.error(traceback.format_exc())
|
||||||
|
raise
|
||||||
|
|
||||||
def add_user_with_groups(self,userid,userpassword,quota=False,groups=[],email='',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}
|
data={'userid':userid,'password':userpassword,'quota':quota,'groups[]':groups,'email':email,'displayname':displayname}
|
||||||
# if not group: del data['groups[]']
|
# if not group: del data['groups[]']
|
||||||
|
@ -325,7 +403,8 @@ class Nextcloud():
|
||||||
# 103 - failed to add the group
|
# 103 - failed to add the group
|
||||||
|
|
||||||
def delete_group(self,groupid):
|
def delete_group(self,groupid):
|
||||||
url = self.apiurl + "groups/"+groupid+"?format=json"
|
group = urllib.parse.quote(groupid, safe='')
|
||||||
|
url = self.apiurl + "groups/"+group+"?format=json"
|
||||||
headers = {
|
headers = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'OCS-APIRequest': 'true',
|
'OCS-APIRequest': 'true',
|
||||||
|
|
|
@ -5,6 +5,33 @@ $(document).on('shown.bs.modal', '#modalAddDesktop', function () {
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
"url": "/api/groups",
|
||||||
|
success: function(data)
|
||||||
|
{
|
||||||
|
$(".groups-select").append(
|
||||||
|
'<option value="" default>None</option>'
|
||||||
|
)
|
||||||
|
data.forEach(element => {
|
||||||
|
var groupOrigins = [];
|
||||||
|
['keycloak'].forEach(o => {
|
||||||
|
if (element[o]) {
|
||||||
|
groupOrigins.push(o)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(".groups-select").append(
|
||||||
|
'<option value="' + element.path + '">' + element.path + '</option>'
|
||||||
|
)
|
||||||
|
});
|
||||||
|
$('.groups-select').select2();
|
||||||
|
},
|
||||||
|
error: function(data)
|
||||||
|
{
|
||||||
|
alert('Something went wrong on our side...')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('.btn-global-resync').on('click', function () {
|
$('.btn-global-resync').on('click', function () {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
|
@ -37,19 +64,44 @@ $(document).ready(function() {
|
||||||
formdata = form.serializeObject()
|
formdata = form.serializeObject()
|
||||||
console.log('NEW GROUP')
|
console.log('NEW GROUP')
|
||||||
console.log(formdata)
|
console.log(formdata)
|
||||||
// $.ajax({
|
|
||||||
// type: "POST",
|
$.ajax({
|
||||||
// "url": "/groups_list",
|
type: "POST",
|
||||||
// success: function(data)
|
"url": "/api/group",
|
||||||
// {
|
data: JSON.stringify(formdata),
|
||||||
// console.log('SUCCESS')
|
complete: function(jqXHR, textStatus) {
|
||||||
// // $("#modalAddGroup").modal('hide');
|
switch (jqXHR.status) {
|
||||||
// },
|
case 200:
|
||||||
// error: function(data)
|
$("#modalAddGroup").modal('hide');
|
||||||
// {
|
break;
|
||||||
// alert('Something went wrong on our side...')
|
case 409:
|
||||||
// }
|
new PNotify({
|
||||||
// });
|
title: "Add group error",
|
||||||
|
text: $.parseJSON(jqXHR.responseText)['msg'],
|
||||||
|
hide: true,
|
||||||
|
delay: 3000,
|
||||||
|
icon: 'fa fa-alert-sign',
|
||||||
|
opacity: 1,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 412:
|
||||||
|
new PNotify({
|
||||||
|
title: "Add group error",
|
||||||
|
text: $.parseJSON(jqXHR.responseText)['msg'],
|
||||||
|
hide: true,
|
||||||
|
delay: 3000,
|
||||||
|
icon: 'fa fa-alert-sign',
|
||||||
|
opacity: 1,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
alert("Server error.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.btn-delete_keycloak').on('click', function () {
|
$('.btn-delete_keycloak').on('click', function () {
|
||||||
|
@ -84,16 +136,87 @@ $(document).ready(function() {
|
||||||
"deferRender": true,
|
"deferRender": true,
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"className": 'details-control',
|
"className": 'actions-control',
|
||||||
"orderable": false,
|
"orderable": false,
|
||||||
"data": null,
|
"data": null,
|
||||||
"width": "10px",
|
"width": "80px",
|
||||||
"defaultContent": '<button class="btn btn-xs btn-info" type="button" data-placement="top" ><i class="fa fa-plus"></i></button>'
|
"defaultContent": '<button id="btn-delete" class="btn btn-xs" type="button" data-placement="top" ><i class="fa fa-times" style="color:darkred"></i></button>'
|
||||||
|
// \
|
||||||
|
// <button id="btn-edit" class="btn btn-xs" type="button" data-placement="top" ><i class="fa fa-pencil" style="color:darkblue"></i></button>'
|
||||||
},
|
},
|
||||||
{ "data": "name", "width": "10px" },
|
{ "data": "name", "width": "10px" },
|
||||||
{ "data": "path", "width": "10px" },
|
{ "data": "path", "width": "10px" },
|
||||||
],
|
],
|
||||||
"order": [[2, 'asc']],
|
"order": [[2, 'asc']],
|
||||||
// "columnDefs": [ ]
|
// "columnDefs": [ ]
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
$('#groups').find(' tbody').on( 'click', 'button', function () {
|
||||||
|
var data = table.row( $(this).parents('tr') ).data();
|
||||||
|
// var closest=$(this).closest("div").parent();
|
||||||
|
// var pk=closest.attr("data-pk");
|
||||||
|
// console.log(pk)
|
||||||
|
switch($(this).attr('id')){
|
||||||
|
case 'btn-edit':
|
||||||
|
$("#modalEditGroupForm")[0].reset();
|
||||||
|
$('#modalEditGroup').modal({
|
||||||
|
backdrop: 'static',
|
||||||
|
keyboard: false
|
||||||
|
}).modal('show');
|
||||||
|
// $('#modalEditGroup #user-avatar').attr("src","/avatar/"+data.id)
|
||||||
|
// setUserDefault('#modalEditGroup', data.id);
|
||||||
|
$('#modalEdit').parsley();
|
||||||
|
break;
|
||||||
|
case 'btn-delete':
|
||||||
|
new PNotify({
|
||||||
|
title: 'Confirmation Needed',
|
||||||
|
text: "Are you sure you want to delete group: "+data['name']+"?",
|
||||||
|
hide: false,
|
||||||
|
opacity: 0.9,
|
||||||
|
confirm: {
|
||||||
|
confirm: true
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
closer: false,
|
||||||
|
sticker: false
|
||||||
|
},
|
||||||
|
history: {
|
||||||
|
history: false
|
||||||
|
},
|
||||||
|
addclass: 'pnotify-center'
|
||||||
|
}).get().on('pnotify.confirm', function() {
|
||||||
|
console.log(data)
|
||||||
|
if(data.id == false){
|
||||||
|
$.ajax({
|
||||||
|
type: "DELETE",
|
||||||
|
url:"/api/group",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function(data)
|
||||||
|
{
|
||||||
|
table.ajax.reload();
|
||||||
|
},
|
||||||
|
error: function(data)
|
||||||
|
{
|
||||||
|
alert('Something went wrong on our side...')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
$.ajax({
|
||||||
|
type: "DELETE",
|
||||||
|
url:"/api/group/"+data.id,
|
||||||
|
success: function(data)
|
||||||
|
{
|
||||||
|
table.ajax.reload();
|
||||||
|
},
|
||||||
|
error: function(data)
|
||||||
|
{
|
||||||
|
alert('Something went wrong on our side...')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).on('pnotify.cancel', function() {
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
|
@ -45,16 +45,15 @@ $(document).ready(function() {
|
||||||
"rowId": "id",
|
"rowId": "id",
|
||||||
"deferRender": true,
|
"deferRender": true,
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
|
||||||
"className": 'details-control',
|
|
||||||
"orderable": false,
|
|
||||||
"data": null,
|
|
||||||
"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": "id", "width": "10px" },
|
||||||
{ "data": "name", "width": "10px" },
|
{ "data": "name", "width": "10px" },
|
||||||
],
|
],
|
||||||
"order": [[1, 'asc']],
|
"order": [[1, 'asc']],
|
||||||
|
// "columnDefs": [ {
|
||||||
|
// "targets": 0,
|
||||||
|
// "render": function ( data, type, full, meta ) {
|
||||||
|
// // return '<object data="/static/img/missing.jpg" type="image/jpeg" width="25" height="25"><img src="/avatar/'+full.id+'" title="'+full.id+'" width="25" height="25"></object>'
|
||||||
|
// return '<img src="/avatar/'+full.name+'" title="'+full.name+'" width="25" height="25" onerror="if (this.src != \'/static/img/missing.jpg\') this.src = \'/static/img/missing.jpg\';">'
|
||||||
|
// }}]
|
||||||
} );
|
} );
|
||||||
});
|
});
|
|
@ -21,7 +21,8 @@ socket.on('notify-create', function(data) {
|
||||||
notice[data.id] = new PNotify({
|
notice[data.id] = new PNotify({
|
||||||
title: data.title,
|
title: data.title,
|
||||||
text: data.text,
|
text: data.text,
|
||||||
hide: false
|
hide: false,
|
||||||
|
type: data.type
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -36,7 +37,8 @@ socket.on('notify-increment', function(data) {
|
||||||
notice[data.id] = new PNotify({
|
notice[data.id] = new PNotify({
|
||||||
title: data.title,
|
title: data.title,
|
||||||
text: data.text,
|
text: data.text,
|
||||||
hide: false
|
hide: false,
|
||||||
|
type: data.type
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// console.log(data.text)
|
// console.log(data.text)
|
||||||
|
|
|
@ -87,33 +87,168 @@ $(document).ready(function() {
|
||||||
|
|
||||||
// Open new user modal
|
// Open new user modal
|
||||||
$('.btn-new-user').on('click', function () {
|
$('.btn-new-user').on('click', function () {
|
||||||
|
$("#modalAddUserForm")[0].reset();
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
"url": "/api/user_password",
|
||||||
|
success: function(data)
|
||||||
|
{
|
||||||
|
$('#modalAddUser #password').val(data)
|
||||||
|
},
|
||||||
|
error: function(data)
|
||||||
|
{
|
||||||
|
alert('Something went wrong on our side...')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#modalAddUser #enabled').prop('checked',true).iCheck('update');
|
||||||
$('#modalAddUser').modal({
|
$('#modalAddUser').modal({
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
keyboard: false
|
keyboard: false
|
||||||
}).modal('show');
|
}).modal('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//has uppercase
|
||||||
|
window.Parsley.addValidator('uppercase', {
|
||||||
|
requirementType: 'number',
|
||||||
|
validateString: function(value, requirement) {
|
||||||
|
var uppercases = value.match(/[A-Z]/g) || [];
|
||||||
|
return uppercases.length >= requirement;
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
en: 'Your password must contain at least (%s) uppercase letter.'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//has lowercase
|
||||||
|
window.Parsley.addValidator('lowercase', {
|
||||||
|
requirementType: 'number',
|
||||||
|
validateString: function(value, requirement) {
|
||||||
|
var lowecases = value.match(/[a-z]/g) || [];
|
||||||
|
return lowecases.length >= requirement;
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
en: 'Your password must contain at least (%s) lowercase letter.'
|
||||||
|
}
|
||||||
|
});
|
||||||
// Send new user form
|
// Send new user form
|
||||||
$('#modalAddUser #send').on('click', function () {
|
$('#modalAddUser #send').on('click', function () {
|
||||||
var form = $('#modalAddUserForm');
|
var form = $('#modalAddUserForm');
|
||||||
|
form.parsley().validate();
|
||||||
|
if (form.parsley().isValid()){
|
||||||
formdata = form.serializeObject()
|
formdata = form.serializeObject()
|
||||||
console.log('NEW USER')
|
// console.log('NEW USER')
|
||||||
console.log(formdata)
|
// console.log(formdata)
|
||||||
// $.ajax({
|
|
||||||
// type: "POST",
|
$.ajax({
|
||||||
// "url": "/groups_list",
|
type: "POST",
|
||||||
// success: function(data)
|
"url": "/api/user",
|
||||||
// {
|
data: JSON.stringify(formdata),
|
||||||
// console.log('SUCCESS')
|
complete: function(jqXHR, textStatus) {
|
||||||
// // $("#modalAddUser").modal('hide');
|
switch (jqXHR.status) {
|
||||||
// },
|
case 200:
|
||||||
// error: function(data)
|
$("#modalAddUser").modal('hide');
|
||||||
// {
|
break;
|
||||||
// alert('Something went wrong on our side...')
|
case 409:
|
||||||
// }
|
new PNotify({
|
||||||
// });
|
title: "Add user error",
|
||||||
|
text: $.parseJSON(jqXHR.responseText)['msg'],
|
||||||
|
hide: true,
|
||||||
|
delay: 3000,
|
||||||
|
icon: 'fa fa-alert-sign',
|
||||||
|
opacity: 1,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 412:
|
||||||
|
new PNotify({
|
||||||
|
title: "Add user error",
|
||||||
|
text: $.parseJSON(jqXHR.responseText)['msg'],
|
||||||
|
hide: true,
|
||||||
|
delay: 3000,
|
||||||
|
icon: 'fa fa-alert-sign',
|
||||||
|
opacity: 1,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
alert("Server error.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
table.ajax.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// $("#modalEditUser #send").on('click', function(e){
|
||||||
|
// var form = $('#modalEditUserForm');
|
||||||
|
// form.parsley().validate();
|
||||||
|
// if (form.parsley().isValid()){
|
||||||
|
// data=$('#modalEditUserForm').serializeObject();
|
||||||
|
// data['id']=$('#modalEditUserForm #id').val();
|
||||||
|
// console.log('Editing user...')
|
||||||
|
// console.log(data)
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
$('#modalEditUser #send').on('click', function () {
|
||||||
|
var form = $('#modalEditUserForm');
|
||||||
|
form.parsley().validate();
|
||||||
|
if (form.parsley().isValid()){
|
||||||
|
formdata = form.serializeObject()
|
||||||
|
formdata['id']=$('#modalEditUserForm #id').val();
|
||||||
|
formdata['username']=$('#modalEditUserForm #username').val();
|
||||||
|
console.log('UPDATE USER')
|
||||||
|
console.log(formdata)
|
||||||
|
$.ajax({
|
||||||
|
type: "PUT",
|
||||||
|
"url": "/api/user/"+formdata['id'],
|
||||||
|
data: JSON.stringify(formdata),
|
||||||
|
complete: function(jqXHR, textStatus) {
|
||||||
|
switch (jqXHR.status) {
|
||||||
|
case 200:
|
||||||
|
$("#modalEditUser").modal('hide');
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
new PNotify({
|
||||||
|
title: "Update user error",
|
||||||
|
text: $.parseJSON(jqXHR.responseText)['msg'],
|
||||||
|
hide: true,
|
||||||
|
delay: 3000,
|
||||||
|
icon: 'fa fa-alert-sign',
|
||||||
|
opacity: 1,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 409:
|
||||||
|
new PNotify({
|
||||||
|
title: "Add user error",
|
||||||
|
text: $.parseJSON(jqXHR.responseText)['msg'],
|
||||||
|
hide: true,
|
||||||
|
delay: 3000,
|
||||||
|
icon: 'fa fa-alert-sign',
|
||||||
|
opacity: 1,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 412:
|
||||||
|
new PNotify({
|
||||||
|
title: "Add user error",
|
||||||
|
text: $.parseJSON(jqXHR.responseText)['msg'],
|
||||||
|
hide: true,
|
||||||
|
delay: 3000,
|
||||||
|
icon: 'fa fa-alert-sign',
|
||||||
|
opacity: 1,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
alert("Server error.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
table.ajax.reload();
|
||||||
|
});
|
||||||
//DataTable Main renderer
|
//DataTable Main renderer
|
||||||
var table = $('#users').DataTable({
|
var table = $('#users').DataTable({
|
||||||
"ajax": {
|
"ajax": {
|
||||||
|
@ -127,7 +262,6 @@ $(document).ready(function() {
|
||||||
"rowId": "id",
|
"rowId": "id",
|
||||||
"deferRender": true,
|
"deferRender": true,
|
||||||
"columns": [
|
"columns": [
|
||||||
{"data": null, "defaultContent":'',"width": "1px"},
|
|
||||||
// {
|
// {
|
||||||
// "className": 'details-control',
|
// "className": 'details-control',
|
||||||
// "orderable": false,
|
// "orderable": false,
|
||||||
|
@ -135,9 +269,9 @@ $(document).ready(function() {
|
||||||
// "width": "10px",
|
// "width": "10px",
|
||||||
// "defaultContent": '<button class="btn btn-xs btn-info" type="button" data-placement="top" ><i class="fa fa-plus"></i></button>'
|
// "defaultContent": '<button class="btn btn-xs btn-info" type="button" data-placement="top" ><i class="fa fa-plus"></i></button>'
|
||||||
// },
|
// },
|
||||||
|
{ "data": "enabled", "width": "1px" },
|
||||||
{ "data": "id", "width": "10px" },
|
{ "data": "id", "width": "10px" },
|
||||||
{ "data": "enabled", "width": "10px" },
|
{ "data": "roles", "width": "10px" },
|
||||||
{ "data": "username", "width": "10px"},
|
|
||||||
{
|
{
|
||||||
"className": 'actions-control',
|
"className": 'actions-control',
|
||||||
"orderable": false,
|
"orderable": false,
|
||||||
|
@ -147,11 +281,12 @@ $(document).ready(function() {
|
||||||
<button id="btn-password" class="btn btn-xs" type="button" data-placement="top" ><i class="fa fa-lock" style="color:orange"></i></button> \
|
<button id="btn-password" class="btn btn-xs" type="button" data-placement="top" ><i class="fa fa-lock" style="color:orange"></i></button> \
|
||||||
<button id="btn-edit" class="btn btn-xs" type="button" data-placement="top" ><i class="fa fa-pencil" style="color:darkblue"></i></button>'
|
<button id="btn-edit" class="btn btn-xs" type="button" data-placement="top" ><i class="fa fa-pencil" style="color:darkblue"></i></button>'
|
||||||
},
|
},
|
||||||
|
{ "data": "username", "width": "10px"},
|
||||||
{ "data": "first", "width": "10px"},
|
{ "data": "first", "width": "10px"},
|
||||||
{ "data": "last", "width": "150px"},
|
{ "data": "last", "width": "150px"},
|
||||||
{ "data": "email", "width": "10px"},
|
{ "data": "email", "width": "10px"},
|
||||||
{ "data": "keycloak_groups", "width": "50px" },
|
{ "data": "keycloak_groups", "width": "50px" },
|
||||||
{ "data": "roles", "width": "10px" },
|
{ "data": "quota", "width": "10px", "default": "-"},
|
||||||
],
|
],
|
||||||
"order": [[6, 'asc']],
|
"order": [[6, 'asc']],
|
||||||
"columnDefs": [ {
|
"columnDefs": [ {
|
||||||
|
@ -161,7 +296,7 @@ $(document).ready(function() {
|
||||||
return '<img src="/avatar/'+full.id+'" title="'+full.id+'" width="25" height="25" onerror="if (this.src != \'/static/img/missing.jpg\') this.src = \'/static/img/missing.jpg\';">'
|
return '<img src="/avatar/'+full.id+'" title="'+full.id+'" width="25" height="25" onerror="if (this.src != \'/static/img/missing.jpg\') this.src = \'/static/img/missing.jpg\';">'
|
||||||
}},
|
}},
|
||||||
{
|
{
|
||||||
"targets": 2,
|
"targets": 0,
|
||||||
"render": function ( data, type, full, meta ) {
|
"render": function ( data, type, full, meta ) {
|
||||||
if(full.enabled){
|
if(full.enabled){
|
||||||
return '<i class="fa fa-check" style="color:lightgreen"></i>'
|
return '<i class="fa fa-check" style="color:lightgreen"></i>'
|
||||||
|
@ -170,7 +305,12 @@ $(document).ready(function() {
|
||||||
};
|
};
|
||||||
}},
|
}},
|
||||||
{
|
{
|
||||||
"targets": 3,
|
"targets": 2,
|
||||||
|
"render": function ( data, type, full, meta ) {
|
||||||
|
return full.roles[0][0].toUpperCase() + full.roles[0].slice(1);
|
||||||
|
}},
|
||||||
|
{
|
||||||
|
"targets": 4,
|
||||||
"render": function ( data, type, full, meta ) {
|
"render": function ( data, type, full, meta ) {
|
||||||
return '<b>'+full.username+'</b>'
|
return '<b>'+full.username+'</b>'
|
||||||
}},
|
}},
|
||||||
|
@ -182,32 +322,41 @@ $(document).ready(function() {
|
||||||
grups += '<span class="label label-primary" style="margin: 5px;">' + element + '</span>'
|
grups += '<span class="label label-primary" style="margin: 5px;">' + element + '</span>'
|
||||||
})
|
})
|
||||||
return grups
|
return grups
|
||||||
|
}},
|
||||||
|
{
|
||||||
|
"targets": 9,
|
||||||
|
"render": function ( data, type, full, meta ) {
|
||||||
|
if(full.quota == false){
|
||||||
|
return 'Unlimited'
|
||||||
|
}else{
|
||||||
|
return full.quota
|
||||||
|
}
|
||||||
}},
|
}},
|
||||||
]
|
]
|
||||||
} );
|
} );
|
||||||
|
|
||||||
$template = $(".template-detail-users");
|
// $template = $(".template-detail-users");
|
||||||
|
|
||||||
$('#users').find('tbody').on('click', 'td.details-control', function () {
|
// $('#users').find('tbody').on('click', 'td.details-control', function () {
|
||||||
var tr = $(this).closest('tr');
|
// var tr = $(this).closest('tr');
|
||||||
var row = table.row( tr );
|
// var row = table.row( tr );
|
||||||
|
|
||||||
if ( row.child.isShown() ) {
|
// if ( row.child.isShown() ) {
|
||||||
// This row is already open - close it
|
// // This row is already open - close it
|
||||||
row.child.hide();
|
// row.child.hide();
|
||||||
tr.removeClass('shown');
|
// tr.removeClass('shown');
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
// Close other rows
|
// // Close other rows
|
||||||
if ( table.row( '.shown' ).length ) {
|
// if ( table.row( '.shown' ).length ) {
|
||||||
$('.details-control', table.row( '.shown' ).node()).click();
|
// $('.details-control', table.row( '.shown' ).node()).click();
|
||||||
}
|
// }
|
||||||
// Open this row
|
// // Open this row
|
||||||
row.child( addUserDetailPannel(row.data()) ).show();
|
// row.child( addUserDetailPannel(row.data()) ).show();
|
||||||
tr.addClass('shown');
|
// tr.addClass('shown');
|
||||||
actionsUserDetail()
|
// actionsUserDetail()
|
||||||
}
|
// }
|
||||||
} );
|
// } );
|
||||||
|
|
||||||
$('#users').find(' tbody').on( 'click', 'button', function () {
|
$('#users').find(' tbody').on( 'click', 'button', function () {
|
||||||
var data = table.row( $(this).parents('tr') ).data();
|
var data = table.row( $(this).parents('tr') ).data();
|
||||||
|
@ -291,7 +440,7 @@ $(document).ready(function() {
|
||||||
id=$('#modalPasswdUserForm #id').val();
|
id=$('#modalPasswdUserForm #id').val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
url:"/api/user//+" + id,
|
url:"/api/user_password/" + id,
|
||||||
data: JSON.stringify(formdata),
|
data: JSON.stringify(formdata),
|
||||||
success: function(data)
|
success: function(data)
|
||||||
{
|
{
|
||||||
|
@ -320,26 +469,15 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#modalEditUser #send").on('click', function(e){
|
// function addUserDetailPannel ( d ) {
|
||||||
var form = $('#modalEditUserForm');
|
// $newPanel = $template.clone();
|
||||||
form.parsley().validate();
|
// $newPanel.html(function(i, oldHtml){
|
||||||
if (form.parsley().isValid()){
|
// return oldHtml.replace(/d.id/g, d.id).replace(/d.username/g, d.username);
|
||||||
data=$('#modalEditUserForm').serializeObject();
|
// });
|
||||||
data['id']=$('#modalEditUserForm #id').val();
|
// return $newPanel
|
||||||
console.log('Editing user...')
|
// }
|
||||||
console.log(data)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function addUserDetailPannel ( d ) {
|
// function actionsUserDetail(){
|
||||||
$newPanel = $template.clone();
|
|
||||||
$newPanel.html(function(i, oldHtml){
|
|
||||||
return oldHtml.replace(/d.id/g, d.id).replace(/d.username/g, d.username);
|
|
||||||
});
|
|
||||||
return $newPanel
|
|
||||||
}
|
|
||||||
|
|
||||||
function actionsUserDetail(){
|
|
||||||
|
|
||||||
// $('.btn-passwd').on('click', function () {
|
// $('.btn-passwd').on('click', function () {
|
||||||
// var closest=$(this).closest("div").parent();
|
// var closest=$(this).closest("div").parent();
|
||||||
|
@ -392,13 +530,15 @@ $(document).ready(function() {
|
||||||
// }).on('pnotify.cancel', function() {
|
// }).on('pnotify.cancel', function() {
|
||||||
// });
|
// });
|
||||||
// });
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
function setUserDefault(div_id, user_id) {
|
function setUserDefault(div_id, user_id) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url:"/api/user/" + user_id,
|
url:"/api/user/" + user_id,
|
||||||
success: function(data)
|
success: function(data)
|
||||||
{
|
{
|
||||||
|
console.log(data)
|
||||||
if (data.enabled) {
|
if (data.enabled) {
|
||||||
$(div_id + ' #enabled').iCheck('check')
|
$(div_id + ' #enabled').iCheck('check')
|
||||||
}
|
}
|
||||||
|
@ -407,11 +547,18 @@ $(document).ready(function() {
|
||||||
$(div_id + ' #email').val(data.email);
|
$(div_id + ' #email').val(data.email);
|
||||||
$(div_id + ' #firstname').val(data.first);
|
$(div_id + ' #firstname').val(data.first);
|
||||||
$(div_id + ' #lastname').val(data.last);
|
$(div_id + ' #lastname').val(data.last);
|
||||||
|
if(data.quota == false){
|
||||||
|
$(div_id + ' #quota').val('false')
|
||||||
|
}else{
|
||||||
|
$(div_id + ' #quota').val(data.quota);
|
||||||
|
}
|
||||||
$(div_id + ' .groups-select').val(data.keycloak_groups);
|
$(div_id + ' .groups-select').val(data.keycloak_groups);
|
||||||
|
|
||||||
// $(div_id + ' .role-moodle-select').val(data.keycloak_roles);
|
// $(div_id + ' .role-moodle-select').val(data.keycloak_roles);
|
||||||
// $(div_id + ' .role-nextcloud-select').val(data.roles);
|
// $(div_id + ' .role-nextcloud-select').val(data.roles);
|
||||||
$(div_id + ' .role-keycloak-select').val(data.roles[0]);
|
$(div_id + ' .role-keycloak-select').val(data.roles[0]);
|
||||||
$('.groups-select').trigger('change');
|
$('.groups-select').trigger('change');
|
||||||
|
|
||||||
// $('.groups-select, .role-moodle-select, .role-nextcloud-select, .role-keycloak-select').trigger('change');
|
// $('.groups-select, .role-moodle-select, .role-nextcloud-select, .role-keycloak-select').trigger('change');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<h3><i class="fa fa-users"></i> Groups</h3>
|
<h3><i class="fa fa-users"></i> Groups</h3>
|
||||||
<ul class="nav navbar-right panel_toolbox">
|
<ul class="nav navbar-right panel_toolbox">
|
||||||
<li>
|
<li>
|
||||||
<!-- <a class="btn-new"><span style="color: #5499c7; "><i class="fa fa-plus"></i> Add new</span></a> -->
|
<a class="btn-new"><span style="color: #5499c7; "><i class="fa fa-plus"></i> Add new</span></a>
|
||||||
<!-- <a class="btn-delete_keycloak"><span style="color: #c75454; "><i class="fa fa-cross"></i> Delete all keycloak</span></a> -->
|
<!-- <a class="btn-delete_keycloak"><span style="color: #c75454; "><i class="fa fa-cross"></i> Delete all keycloak</span></a> -->
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -36,6 +36,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="x_panel">
|
||||||
|
<div class="x_title">
|
||||||
|
<h4><i class="fa fa-users" aria-hidden="true"></i> Parent group</h4>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
<div class="x_content" style="padding: 0px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 col-xs-12">
|
||||||
|
<label class="control-label" for="id">Select parent group
|
||||||
|
</label>
|
||||||
|
<select class="groups-select roundbox" id="parent" name="parent" style="width:100%" required>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<!-- Modal Footer -->
|
<!-- Modal Footer -->
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label class="">
|
<label class="">
|
||||||
<div class="icheckbox_flat-green" style="position: relative;">
|
<div class="icheckbox_flat-green" style="position: relative;">
|
||||||
<input type="checkbox" id="jumperurl-check" name="jumperurl-check" class="flat" style="position: absolute; opacity: 0;">
|
<input type="checkbox" id="enabled" name="enabled" class="flat" style="position: absolute; opacity: 0;">
|
||||||
<ins class="iCheck-helper" style="position: absolute; top: 0%; left: 0%; display: block; width: 100%; height: 100%; margin: 0px; padding: 0px; background: rgb(255, 255, 255); border: 0px; opacity: 0;">
|
<ins class="iCheck-helper" style="position: absolute; top: 0%; left: 0%; display: block; width: 100%; height: 100%; margin: 0px; padding: 0px; background: rgb(255, 255, 255); border: 0px; opacity: 0;">
|
||||||
</ins>
|
</ins>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,31 +42,61 @@
|
||||||
<div class="col-md-6 col-xs-12">
|
<div class="col-md-6 col-xs-12">
|
||||||
<label class="control-label" for="name">Username <span class="required">*</span>
|
<label class="control-label" for="name">Username <span class="required">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input id="username" class="roundbox" maxlength="40" pattern="^[-_àèìòùáéíóúñçÀÈÌÒÙÁÉÍÓÚÑÇ .a-zA-Z0-9]+$" data-parsley-length="[4, 40]" name="name" placeholder="Username" data-parsley-trigger="change" required type="text" style="width:100%">
|
<input id="username" class="roundbox" maxlength="40" pattern="^[-_àèìòùáéíóúñçÀÈÌÒÙÁÉÍÓÚÑÇ .a-zA-Z0-9]+$" data-parsley-length="[4, 40]" name="username" placeholder="Username" data-parsley-trigger="change" required type="text" style="width:100%">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xs-12">
|
<div class="col-md-6 col-xs-12">
|
||||||
<label class="control-label" for="email">Email <span class="required">*</span>
|
<label class="control-label" for="email">Email <span class="required">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input id="email" class="roundbox" data-validate-length-range="4,40" name="email" placeholder="Email" type="email" data-parsley-trigger="change" style="width:100%">
|
<input id="email" class="roundbox" data-validate-length-range="4,40" name="email" placeholder="Email" type="email" data-parsley-trigger="change" style="width:100%" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 col-xs-12">
|
<div class="col-md-6 col-xs-12">
|
||||||
<label class="control-label" for="firstname">First name <span class="required">*</span>
|
<label class="control-label" for="first">First name <span class="required">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input id="firstname" class="roundbox" name="firstname" placeholder="First name" type="text" style="width:100%">
|
<input id="first" class="roundbox" name="first" placeholder="First name" type="text" style="width:100%" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xs-12">
|
<div class="col-md-6 col-xs-12">
|
||||||
<label class="control-label" for="lastname">Last name <span class="required">*</span>
|
<label class="control-label" for="last">Last name <span class="required">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input id="lastname" class="roundbox" name="lastname" placeholder="First name" type="text" style="width:100%">
|
<input id="last" class="roundbox" name="last" placeholder="Last name" type="text" style="width:100%" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-xs-12">
|
||||||
|
<label class="control-label" for="quota">Quota <span class="required">*</span>
|
||||||
|
</label>
|
||||||
|
<select class="roundbox" name="quota" style="width:100%" required>
|
||||||
|
<option value="500 MB">500 MB</option>
|
||||||
|
<option value="1 GB">1 GB</option>
|
||||||
|
<option value="3 GB">3 GB</option>
|
||||||
|
<option value="5 GB">5 GB</option>
|
||||||
|
<option value=false>Unlimited</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-xs-12">
|
||||||
|
<label class="control-label" for="password">Password <span class="required">*</span>
|
||||||
|
</label>
|
||||||
|
<input id="password" class="roundbox" name="password" placeholder="Password" type="text" style="width:100%"
|
||||||
|
data-parsley-minlength="10"
|
||||||
|
data-parsley-uppercase="2"
|
||||||
|
data-parsley-lowercase="2"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="x_panel">
|
||||||
|
<div class="x_title">
|
||||||
|
<h4><i class="fa fa-users" aria-hidden="true"></i> Groups</h4>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
<div class="x_content" style="padding: 0px;">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 col-xs-12">
|
<div class="col-md-12 col-xs-12">
|
||||||
<label class="control-label" for="id">Group
|
<label class="control-label" for="id">Select group(s)
|
||||||
</label>
|
</label>
|
||||||
<select class="groups-select roundbox" name="groups[]" multiple="multiple" style="width:100%">
|
<select class="groups-select roundbox" name="groups[]" multiple="multiple" style="width:100%" required>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,7 +104,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="x_panel">
|
<div class="x_panel">
|
||||||
<div class="x_title">
|
<div class="x_title">
|
||||||
<h4><i class="fa fa-user-secret" aria-hidden="true"></i> Roles</h4>
|
<h4><i class="fa fa-user-secret" aria-hidden="true"></i> Role</h4>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="x_content" style="padding: 0px;">
|
<div class="x_content" style="padding: 0px;">
|
||||||
|
@ -92,9 +122,9 @@
|
||||||
</select>
|
</select>
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="col-md-12 col-xs-12">
|
<div class="col-md-12 col-xs-12">
|
||||||
<label class="control-label" for="id">Keycloak
|
<label class="control-label" for="id">Select role
|
||||||
</label>
|
</label>
|
||||||
<select class="role-keycloak-select" name="keycloak" style="width:100%">
|
<select class="role-keycloak-select" name="role" style="width:100%" required>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -114,6 +144,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="modalPasswdUser" tabindex="-1" role="dialog" aria-labelledby="modalPasswdUser" aria-hidden="true">
|
<div class="modal fade" id="modalPasswdUser" tabindex="-1" role="dialog" aria-labelledby="modalPasswdUser" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
@ -165,6 +196,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="modalEditUser" tabindex="-1" role="dialog" aria-labelledby="modalEditUser" aria-hidden="true">
|
<div class="modal fade" id="modalEditUser" tabindex="-1" role="dialog" aria-labelledby="modalEditUser" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
@ -234,12 +266,35 @@
|
||||||
<input id="lastname" class="roundbox" name="lastname" placeholder="First name" type="text" style="width:100%">
|
<input id="lastname" class="roundbox" name="lastname" placeholder="First name" type="text" style="width:100%">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-xs-12">
|
||||||
|
<label class="control-label" for="quota">Quota <span class="required">*</span>
|
||||||
|
</label>
|
||||||
|
<select class="roundbox" id="quota" name="quota" style="width:100%" required>
|
||||||
|
<option value="500 MB">500 MB</option>
|
||||||
|
<option value="1 GB">1 GB</option>
|
||||||
|
<option value="3 GB">3 GB</option>
|
||||||
|
<option value="5 GB">5 GB</option>
|
||||||
|
<option value=false>Unlimited</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="x_panel">
|
||||||
|
<div class="x_title">
|
||||||
|
<h4><i class="fa fa-users" aria-hidden="true"></i> Groups</h4>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
<div class="x_content" style="padding: 0px;">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 col-xs-12">
|
<div class="col-md-12 col-xs-12">
|
||||||
<label class="control-label" for="id">Groups</label>
|
<label class="control-label" for="id">Select group(s)
|
||||||
|
</label>
|
||||||
<select class="groups-select roundbox" name="groups[]" multiple="multiple" style="width:100%">
|
<select class="groups-select roundbox" name="groups[]" multiple="multiple" style="width:100%">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
<table id="roles" class="table" width="100%">
|
<table id="roles" class="table" width="100%">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
|
||||||
<th>Id</th>
|
<th>Id</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -24,17 +24,16 @@
|
||||||
<table id="users" class="table" width="100%">
|
<table id="users" class="table" width="100%">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
|
||||||
<th>Avatar</th>
|
|
||||||
<th>Enabled</th>
|
<th>Enabled</th>
|
||||||
<th>Username</th>
|
<th>Avatar</th>
|
||||||
|
<th>Role</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
|
<th>Username</th>
|
||||||
<th>First</th>
|
<th>First</th>
|
||||||
<th>Last</th>
|
<th>Last</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Groups</th>
|
<th>Groups</th>
|
||||||
<th>Roles</th>
|
<th>Quota</th>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
@ -9,7 +9,9 @@ import time,json
|
||||||
import sys,os
|
import sys,os
|
||||||
from flask import render_template, Response, request, redirect, url_for, jsonify
|
from flask import render_template, Response, request, redirect, url_for, jsonify
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from flask_login import login_required
|
from flask_login import current_user, login_required
|
||||||
|
from .decorators import is_admin
|
||||||
|
|
||||||
# import Queue
|
# import Queue
|
||||||
import threading
|
import threading
|
||||||
threads={}
|
threads={}
|
||||||
|
@ -17,6 +19,8 @@ threads={}
|
||||||
|
|
||||||
from keycloak.exceptions import KeycloakGetError
|
from keycloak.exceptions import KeycloakGetError
|
||||||
|
|
||||||
|
from ..lib.exceptions import UserExists, UserNotFound
|
||||||
|
|
||||||
@app.route('/api/resync')
|
@app.route('/api/resync')
|
||||||
@login_required
|
@login_required
|
||||||
def resync():
|
def resync():
|
||||||
|
@ -27,6 +31,7 @@ def resync():
|
||||||
@login_required
|
@login_required
|
||||||
def users(provider=False):
|
def users(provider=False):
|
||||||
if request.method == 'DELETE':
|
if request.method == 'DELETE':
|
||||||
|
if current_user.role != 'admin': return json.dumps({}), 301, {'Content-Type': 'application/json'}
|
||||||
if provider == 'keycloak':
|
if provider == 'keycloak':
|
||||||
return json.dumps(app.admin.delete_keycloak_users()), 200, {'Content-Type': 'application/json'}
|
return json.dumps(app.admin.delete_keycloak_users()), 200, {'Content-Type': 'application/json'}
|
||||||
if provider == 'nextcloud':
|
if provider == 'nextcloud':
|
||||||
|
@ -34,11 +39,18 @@ def users(provider=False):
|
||||||
if provider == 'moodle':
|
if provider == 'moodle':
|
||||||
return json.dumps(app.admin.delete_moodle_users()), 200, {'Content-Type': 'application/json'}
|
return json.dumps(app.admin.delete_moodle_users()), 200, {'Content-Type': 'application/json'}
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
if current_user.role != 'admin': return json.dumps({}), 301, {'Content-Type': 'application/json'}
|
||||||
if provider == 'moodle':
|
if provider == 'moodle':
|
||||||
return json.dumps(app.admin.sync_to_moodle()), 200, {'Content-Type': 'application/json'}
|
return json.dumps(app.admin.sync_to_moodle()), 200, {'Content-Type': 'application/json'}
|
||||||
if provider == 'nextcloud':
|
if provider == 'nextcloud':
|
||||||
return json.dumps(app.admin.sync_to_nextcloud()), 200, {'Content-Type': 'application/json'}
|
return json.dumps(app.admin.sync_to_nextcloud()), 200, {'Content-Type': 'application/json'}
|
||||||
return json.dumps(app.admin.get_mix_users()), 200, {'Content-Type': 'application/json'}
|
|
||||||
|
users = app.admin.get_mix_users()
|
||||||
|
|
||||||
|
if current_user.role != 'admin':
|
||||||
|
for user in users:
|
||||||
|
user['keycloak_groups'] = [g for g in user['keycloak_groups'] if g not in ['/admin','/manager','/teacher','/student']]
|
||||||
|
return json.dumps(users), 200, {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
|
||||||
# Update pwd
|
# Update pwd
|
||||||
|
@ -56,35 +68,84 @@ def user_password(userid=False):
|
||||||
res = app.admin.user_update_password(userid,password,temporary)
|
res = app.admin.user_update_password(userid,password,temporary)
|
||||||
return json.dumps({}), 200, {'Content-Type': 'application/json'}
|
return json.dumps({}), 200, {'Content-Type': 'application/json'}
|
||||||
except KeycloakGetError as e:
|
except KeycloakGetError as e:
|
||||||
print(e.error_message.decode("utf-8"))
|
log.error(e.error_message.decode("utf-8"))
|
||||||
return e.error_message, e.response_code, {'Content-Type': 'application/json'}
|
return json.dumps({'msg':'Update password error.'}), 500, {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
return json.dumps({}), 301, {'Content-Type': 'application/json'}
|
return json.dumps({}), 405, {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
# User
|
# User
|
||||||
@app.route('/api/user/<userid>', methods=['POST', 'PUT', 'GET', 'DELETE'])
|
@app.route('/api/user', methods=['POST'])
|
||||||
|
@app.route('/api/user/<userid>', methods=['PUT', 'GET', 'DELETE'])
|
||||||
@login_required
|
@login_required
|
||||||
def user(userid=None):
|
def user(userid=None):
|
||||||
if request.method == 'DELETE':
|
if request.method == 'DELETE':
|
||||||
res = app.admin.delete_user(userid)
|
app.admin.delete_user(userid)
|
||||||
return json.dumps({}), 200, {'Content-Type': 'application/json'}
|
return json.dumps({}), 200, {'Content-Type': 'application/json'}
|
||||||
# return json.dumps(), 301, {'Content-Type': 'application/json'}
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
pass
|
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
|
||||||
|
try:
|
||||||
|
threads['external'] = threading.Thread(target=app.admin.add_user, args=(data,))
|
||||||
|
threads['external'].start()
|
||||||
|
return json.dumps({}), 200, {'Content-Type': 'application/json'}
|
||||||
|
except:
|
||||||
|
log.error(traceback.format_exc())
|
||||||
|
return json.dumps({'msg':'Add user error.'}), 500, {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
if request.method == 'PUT':
|
if request.method == 'PUT':
|
||||||
pass
|
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'}
|
||||||
|
except UserNotFound:
|
||||||
|
return json.dumps({'msg':'User not found.'}), 404, {'Content-Type': 'application/json'}
|
||||||
if request.method == 'DELETE':
|
if request.method == 'DELETE':
|
||||||
pass
|
pass
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
res = app.admin.get_user(userid)
|
user = app.admin.get_user(userid)
|
||||||
return json.dumps(res), 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
|
@login_required
|
||||||
def roles():
|
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'}
|
return json.dumps(sorted_roles), 200, {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
@app.route('/api/group', methods=['POST','DELETE'])
|
||||||
|
@app.route('/api/group/<group_id>', methods=['PUT', 'GET', 'DELETE'])
|
||||||
|
@login_required
|
||||||
|
def group(group_id=False):
|
||||||
|
if request.method == 'POST':
|
||||||
|
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)
|
||||||
|
print(data)
|
||||||
|
except:
|
||||||
|
data=False
|
||||||
|
|
||||||
|
if data:
|
||||||
|
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'}
|
||||||
|
|
||||||
@app.route('/api/groups')
|
@app.route('/api/groups')
|
||||||
@app.route('/api/groups/<provider>', methods=['POST', 'PUT', 'GET', 'DELETE'])
|
@app.route('/api/groups/<provider>', methods=['POST', 'PUT', 'GET', 'DELETE'])
|
||||||
|
@ -92,6 +153,11 @@ def roles():
|
||||||
def groups(provider=False):
|
def groups(provider=False):
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
sorted_groups = sorted(app.admin.get_mix_groups(), key=lambda k: k['name'])
|
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 sg['path'] not in ['/admin','/manager','/teacher','/student'] and sg['path'].startswith('/')]
|
||||||
|
else:
|
||||||
|
sorted_groups = [sg for sg in sorted_groups if sg['path'].startswith('/')]
|
||||||
return json.dumps(sorted_groups), 200, {'Content-Type': 'application/json'}
|
return json.dumps(sorted_groups), 200, {'Content-Type': 'application/json'}
|
||||||
if request.method == 'DELETE':
|
if request.method == 'DELETE':
|
||||||
if provider == 'keycloak':
|
if provider == 'keycloak':
|
||||||
|
|
|
@ -10,6 +10,7 @@ import sys,os
|
||||||
from flask import render_template, Response, request, redirect, url_for, jsonify, send_file
|
from flask import render_template, Response, request, redirect, url_for, jsonify, send_file
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
from .decorators import is_admin
|
||||||
|
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from ..lib.avatars import Avatars
|
from ..lib.avatars import Avatars
|
||||||
|
@ -70,17 +71,20 @@ def avatar(userid):
|
||||||
|
|
||||||
@app.route('/sysadmin/users')
|
@app.route('/sysadmin/users')
|
||||||
@login_required
|
@login_required
|
||||||
|
@is_admin
|
||||||
def web_sysadmin_users():
|
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
|
@login_required
|
||||||
|
@is_admin
|
||||||
def web_sysadmin_groups():
|
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
|
@login_required
|
||||||
|
@is_admin
|
||||||
## SysAdmin role
|
## SysAdmin role
|
||||||
def web_sysadmin_external():
|
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")
|
||||||
|
|
|
@ -3,9 +3,16 @@
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask import request, redirect, url_for
|
from flask import request, redirect, url_for
|
||||||
from flask_login import logout_user
|
from flask_login import current_user, logout_user
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
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'))
|
||||||
|
return decorated_view
|
||||||
|
|
||||||
def is_internal(fn):
|
def is_internal(fn):
|
||||||
@wraps(fn)
|
@wraps(fn)
|
||||||
def decorated_view(*args, **kwargs):
|
def decorated_view(*args, **kwargs):
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
version: '3.7'
|
||||||
|
services:
|
||||||
|
isard-sso-admin:
|
||||||
|
volumes:
|
||||||
|
- ${BUILD_ROOT_PATH}/admin/src:/admin:rw
|
||||||
|
command: /bin/sleep infinity
|
|
@ -6,14 +6,14 @@ services:
|
||||||
context: ${BUILD_ROOT_PATH}
|
context: ${BUILD_ROOT_PATH}
|
||||||
dockerfile: admin/docker/Dockerfile
|
dockerfile: admin/docker/Dockerfile
|
||||||
target: production
|
target: production
|
||||||
args: ## DEVELOPMENT
|
# args: ## DEVELOPMENT
|
||||||
SSH_ROOT_PWD: ${IPA_ADMIN_PWD}
|
# SSH_ROOT_PWD: ${IPA_ADMIN_PWD}
|
||||||
SSH_PORT: 2022
|
# SSH_PORT: 2022
|
||||||
networks:
|
networks:
|
||||||
- isard_net
|
- isard_net
|
||||||
ports:
|
# ports:
|
||||||
- "2022:22"
|
# - "2022:22"
|
||||||
- "9000:9000"
|
# - "9000:9000"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
@ -26,4 +26,3 @@ services:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- VERIFY="false" # In development do not verify certificates
|
- VERIFY="false" # In development do not verify certificates
|
||||||
command: sleep infinity
|
|
||||||
|
|
Loading…
Reference in New Issue