diff --git a/admin/docker/requirements.pip3 b/admin/docker/requirements.pip3 index 938db29..aa385c7 100644 --- a/admin/docker/requirements.pip3 +++ b/admin/docker/requirements.pip3 @@ -15,4 +15,5 @@ Werkzeug==1.0.1 zope.event==4.4 zope.interface==5.1.0 psycopg2==2.8.6 -Flask-SocketIO==2.8.6 \ No newline at end of file +Flask-SocketIO==2.8.6 +mysql-connector-python==8.0.25 \ No newline at end of file diff --git a/admin/src/admin/lib/admin.py b/admin/src/admin/lib/admin.py index 63ffb6f..df080b2 100644 --- a/admin/src/admin/lib/admin.py +++ b/admin/src/admin/lib/admin.py @@ -1,11 +1,13 @@ from admin import app -from .keycloak import Keycloak +from .keycloak_client import KeycloakClient from .moodle import Moodle from .nextcloud import Nextcloud import logging as log from pprint import pprint -import traceback +import traceback, os + +from time import sleep from .nextcloud_exc import * from .helpers import filter_roles_list, filter_roles_listofdicts @@ -13,12 +15,42 @@ from .helpers import filter_roles_list, filter_roles_listofdicts from flask_socketio import SocketIO, emit, join_room, leave_room, \ close_room, rooms, disconnect, send socketio = SocketIO(app) +import json class Admin(): def __init__(self): - self.keycloak=Keycloak(verify=app.config['VERIFY']) - self.moodle=Moodle(verify=app.config['VERIFY']) - self.nextcloud=Nextcloud(verify=app.config['VERIFY']) + ready=False + while not ready: + try: + self.keycloak=KeycloakClient(verify=app.config['VERIFY']) + ready=True + except: + log.error(traceback.format_exc()) + log.error('Could not connect to keycloak, waiting to be online...') + sleep(2) + log.warning('Keycloak connected.') + + ready=False + while not ready: + try: + self.moodle=Moodle(verify=app.config['VERIFY']) + ready=True + except: + log.error('Could not connect to moodle, waiting to be online...') + sleep(2) + log.warning('Moodle connected.') + + ready=False + while not ready: + try: + self.nextcloud=Nextcloud(verify=app.config['VERIFY']) + ready=True + except: + log.error('Could not connect to nextcloud, waiting to be online...') + sleep(2) + log.warning('Nextcloud connected.') + + self.default_setup() self.internal={} self.resync_data() @@ -26,6 +58,52 @@ class Admin(): 'groups':[], 'roles':[]} + ## This function should be moved to postup.py + def default_setup(self): + log.warning('Setting defaults...') + dduser=os.environ['DDADMIN_USER'] + ddpassword=os.environ['DDADMIN_PASSWORD'] + ddmail=os.environ['DDADMIN_EMAIL'] + + try: + log.warning('KEYCLOAK: Adding group admin and user admin to this group') + self.keycloak.add_group('admin') + ## Add default admin user to group admin (for nextcloud, just in case we go there) + admin_uid=self.keycloak_admin.get_user_id('admin') + self.keycloak_admin.group_user_add(uid,gid) + log.warning('KEYCLOAK: OK') + except: + log.warning('KEYCLOAK: Seems to be there already') + + try: + log.warning('KEYCLOAK: Adding user ddadmin and adding to group and role admin') + ## Assign group admin to this dduser for nextcloud + uid=self.keycloak.add_user(dduser,'DD','Admin',ddmail,ddpassword,group='admin') + ## Assign role admin to this user for keycloak, moodle and wordpress + self.keycloak.assign_realm_roles(uid,'admin') + log.warning('KEYCLOAK: OK') + except: + log.warning('KEYCLOAK: Seems to be there already') + + try: + log.warning('NEXTCLOUD: Adding user ddadmin and adding to group admin') + self.nextcloud.add_user(dduser,ddpassword,group='admin',email=ddmail,displayname='DD Admin') + log.warning('NEXTCLOUD: OK') + except ProviderItemExists: + log.warning('NEXTCLOUD: Seems to be there already') + except: + log.error(traceback.format_exc()) + exit(1) + + try: + log.warning('MOODLE: Adding user ddadmin and adding to siteadmins') + self.moodle.create_user(ddmail,dduser,ddpassword,'DD','Admin') + uid=self.moodle.get_user_by('username',dduser)['users'][0]['id'] + self.moodle.add_user_to_siteadmin(uid) + log.warning('MOODLE: OK') + except: + log.warning('MOODLE: Seems to be there already') + def resync_data(self): self.internal={'users':self._get_mix_users(), 'groups':self._get_mix_groups(), @@ -146,15 +224,15 @@ class Admin(): return filter_roles_listofdicts(self.keycloak.get_roles()) def get_keycloak_groups(self): - log.warning('Loading keycloak groups... can take a long time...') + log.warning('Loading keycloak groups...') return self.keycloak.get_groups() def get_moodle_groups(self): - log.warning('Loading moodle groups... can take a long time...') + log.warning('Loading moodle groups...') return self.moodle.get_cohorts() def get_nextcloud_groups(self): - log.warning('Loading nextcloud groups... can take a long time...') + log.warning('Loading nextcloud groups...') return self.nextcloud.get_groups_list() def get_mix_groups(self): @@ -244,24 +322,24 @@ class Admin(): def sync_external(self): for u in self.external['users']: - log.error('Creating user: '+u['username']) + log.info('Creating user: '+u['username']) self.keycloak.add_user(u['username'],u['first'],u['last'],u['email'],'1Provaprovaprova',group=u['groups'][0]) def sync_to_moodle(self): for u in self.internal['users']: if not u['moodle']: - log.error('Creating moodle user: '+u['username']) + log.info('Creating moodle user: '+u['username']) self.moodle.create_user(u['email'],u['username'],'-1Provaprovaprova',u['first'],u['last']) def sync_to_nextcloud(self): for u in self.internal['users']: if not u['nextcloud']: - log.error('Creating nextcloud user: '+u['username']) + log.info('Creating nextcloud user: '+u['username']) group=u['keycloak_groups'][0] if len(u['keycloak_groups']) else False try: self.nextcloud.add_user(u['username'],'-1Provaprovaprova',1000,group,u['email'],u['first']+' '+u['last']) except ProviderItemExists: - log.error('User '+u['username']+' already exists. Skipping...') + log.info('User '+u['username']+' already exists. Skipping...') continue except: log.error(traceback.format_exc()) diff --git a/admin/src/admin/lib/keycloak.py b/admin/src/admin/lib/keycloak_client.py similarity index 87% rename from admin/src/admin/lib/keycloak.py rename to admin/src/admin/lib/keycloak_client.py index 2a0a278..12650c5 100644 --- a/admin/src/admin/lib/keycloak.py +++ b/admin/src/admin/lib/keycloak_client.py @@ -14,7 +14,7 @@ from jinja2 import Environment, FileSystemLoader from keycloak import KeycloakAdmin from .postgres import Postgres -class Keycloak(): +class KeycloakClient(): """https://www.keycloak.org/docs-api/13.0/rest-api/index.html https://github.com/marcospereirampj/python-keycloak https://gist.github.com/kaqfa/99829941121188d7cef8271f93f52f1f @@ -31,7 +31,7 @@ class Keycloak(): self.realm=realm self.verify=verify - self.keycloak_pg=Postgres('isard-apps-postgresql','keycloak',app.config['KEYCLOAK_POSTGRES_USER'],app.config['KEYCLOAK_POSTGRES_PASSWORD']) + self.keycloak_pg=Postgres('isard-apps-postgresql','keycloak',os.environ['KEYCLOAK_DB_USER'],os.environ['KEYCLOAK_DB_PASSWORD']) def connect(self): self.keycloak_admin = KeycloakAdmin(server_url=self.url, @@ -39,7 +39,7 @@ class Keycloak(): password=self.password, realm_name=self.realm, verify=self.verify) - +# from keycloak import KeycloakAdmin # keycloak_admin = KeycloakAdmin(server_url="http://isard-sso-keycloak:8080/auth/",username="admin",password="keycloakkeycloak",realm_name="master",verify=False) ######## Example create group and subgroup @@ -110,7 +110,6 @@ class Keycloak(): def add_user(self,username,first,last,email,password,group=False): # Returns user id - log.error('Creating group: '+str(group)) self.connect() username=username.lower() try: @@ -123,22 +122,21 @@ class Keycloak(): "value":password, "temporary":False}]}) except: - uid=self.keycloak_admin.get_user_id(username) - log.error(uid) + log.error(traceback.format_exc()) + if group: + path = '/'+group if group[1:] != '/' else group try: - gid=self.keycloak_admin.get_group_by_path(path=group,search_in_subgroups=False)['id'] - log.error('group created with gid: '+str(gid)) + gid=self.keycloak_admin.get_group_by_path(path=path,search_in_subgroups=False)['id'] except: self.keycloak_admin.create_group({"name":group}) - gid=self.keycloak_admin.get_group_by_path(group)['id'] - log.error(gid) + gid=self.keycloak_admin.get_group_by_path(path)['id'] self.keycloak_admin.group_user_add(uid,gid) - + return uid def add_user_role(self,client_id,user_id,role_id,role_name): self.connect() - return self.keycloak_admin.assign_client_role(client_id="client_id", user_id="user_id", role_id="role_id", role_name="test") + return self.keycloak_admin.assign_client_role(client_id=client_id, user_id=user_id, role_id=role_id, role_name="test") def delete_user(self,userid): self.connect() @@ -196,8 +194,9 @@ class Keycloak(): self.connect() return self.keycloak_admin.get_client_roles(client_id=client_id) - # def add_client_role(self,client_id,roleName): - # return self.keycloak_admin.create_client_role(client_id=client_id, {'name': roleName, 'clientRole': True}) + def add_client_role(self,client_id,name,description=''): + self.connect() + return self.keycloak_admin.create_client_role(client_id, {'name': name, 'description':description, 'clientRole': True}) ## SYSTEM @@ -214,6 +213,15 @@ class Keycloak(): rsa_key = [k for k in self.keycloak_admin.get_keys()['keys'] if k['type']=='RSA'][0] return {'name':rsa_key['kid'],'certificate':rsa_key['certificate']} + ## REALM + def assign_realm_roles(self, user_id, role): + self.connect() + try: + role=[r for r in self.keycloak_admin.get_realm_roles() if r['name']==role] + except: + return False + return self.keycloak_admin.assign_realm_roles(user_id=user_id, client_id=None, roles=role) + ## CLIENTS def delete_client(self,clientid): self.connect() diff --git a/admin/src/admin/lib/moodle.py b/admin/src/admin/lib/moodle.py index 33514ad..39e64b1 100644 --- a/admin/src/admin/lib/moodle.py +++ b/admin/src/admin/lib/moodle.py @@ -121,4 +121,37 @@ class Moodle(): cohorts=self.get_cohorts() for cohort in cohorts: if user_id in self.get_cohort_members(cohort['id']): user_cohorts.append(cohort) - return user_cohorts \ No newline at end of file + return user_cohorts + + def add_user_to_siteadmin(self,user_id): + q = """SELECT value FROM mdl_config WHERE name='siteadmins'""" + value=self.moodle_pg.select(q)[0][0] + if str(user_id) not in value: + value=value+','+str(user_id) + q = """UPDATE mdl_config SET value = '%s' WHERE name='siteadmins'""" % (value) + self.moodle_pg.update(q) + log.warning('MOODLE:ADDING THE USER TO ADMINS: This needs a purge cache in moodle!') + + # def add_role_to_user(self, user_id, role='admin', context='missing'): + # if role=='admin': + # role_id=1 + # else: + # return False + # assignments = [{'roleid': role_id, 'userid': user_id, 'contextid': 0}] + # self.call('core_role_assign_roles', assignments=assignments) + # userid=user_id, role_id=role_id) + # 'contextlevel': 1, + +# define('CONTEXT_SYSTEM', 10); +# define('CONTEXT_USER', 30); +# define('CONTEXT_COURSECAT', 40); +# define('CONTEXT_COURSE', 50); +# define('CONTEXT_MODULE', 70); +# define('CONTEXT_BLOCK', 80); + +# 'contextlevel': , 'instanceid' +# $assignment = array( 'roleid' => $role_id, 'userid' => $user_id, 'contextid' => $context_id ); +# $assignments = array( $assignment ); +# $params = array( 'assignments' => $assignments ); + +# $response = call_moodle( 'core_role_assign_roles', $params, $token ); \ No newline at end of file diff --git a/admin/src/admin/lib/mysql.py b/admin/src/admin/lib/mysql.py new file mode 100644 index 0000000..e901cb1 --- /dev/null +++ b/admin/src/admin/lib/mysql.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# coding=utf-8 +import time +from admin import app +from datetime import datetime, timedelta +import pprint + +import logging as log +import traceback +import yaml, json + +import mysql.connector + +class Mysql(): + + def __init__(self,host,database,user,password): + self.conn = mysql.connector.connect( + host=host, + database=database, + user=user, + password=password) + + def select(self,sql): + self.cur = self.conn.cursor() + self.cur.execute(sql) + data=self.cur.fetchall() + self.cur.close() + return data + + def update(self,sql): + self.cur = self.conn.cursor() + self.cur.execute(sql) + self.conn.commit() + self.cur.close() diff --git a/admin/src/admin/lib/nextcloud.py b/admin/src/admin/lib/nextcloud.py index e018833..d6b2cb6 100644 --- a/admin/src/admin/lib/nextcloud.py +++ b/admin/src/admin/lib/nextcloud.py @@ -126,11 +126,14 @@ class Nextcloud(): # log.error(traceback.format_exc()) # raise - def add_user(self,userid,userpassword,quota,group=False,email='',displayname=''): - if group: - data={'userid':userid,'password':userpassword,'quota':quota,'groups[]':group,'email':email,'displayname':displayname} - else: - data={'userid':userid,'password':userpassword,'quota':quota,'email':email,'displayname':displayname} + def add_user(self,userid,userpassword,quota=False,group=False,email='',displayname=''): + data={'userid':userid,'password':userpassword,'quota':quota,'groups[]':group,'email':email,'displayname':displayname} + if not group: del data['group'] + if not quota: del data['quota'] + # if group: + # data={'userid':userid,'password':userpassword,'quota':quota,'groups[]':group,'email':email,'displayname':displayname} + # else: + # data={'userid':userid,'password':userpassword,'quota':quota,'email':email,'displayname':displayname} url = self.apiurl + "users?format=json" headers = { 'Content-Type': 'application/x-www-form-urlencoded', diff --git a/admin/src/admin/lib/postup.py b/admin/src/admin/lib/postup.py index bf05492..4902cf3 100644 --- a/admin/src/admin/lib/postup.py +++ b/admin/src/admin/lib/postup.py @@ -82,6 +82,7 @@ class Postup(): (3, 'core_cohort_delete_cohorts'), (3, 'core_cohort_search_cohorts'), (3, 'core_cohort_update_cohorts'), + (3, 'core_role_assign_roles'), (3, 'core_cohort_get_cohorts');""") self.pg.update("""INSERT INTO "mdl_external_services_users" ("externalserviceid", "userid", "iprestriction", "validuntil", "timecreated") VALUES diff --git a/admin/src/nextcloud_gencerts.sh b/admin/src/generate_certificates.sh similarity index 100% rename from admin/src/nextcloud_gencerts.sh rename to admin/src/generate_certificates.sh diff --git a/admin/src/moodle_saml.py b/admin/src/moodle_saml.py index 911b2dc..3e058a8 100644 --- a/admin/src/moodle_saml.py +++ b/admin/src/moodle_saml.py @@ -11,7 +11,7 @@ import yaml, json import psycopg2 from admin.lib.postgres import Postgres -from admin.lib.keycloak import Keycloak +from admin.lib.keycloak_client import KeycloakClient import string, random @@ -102,6 +102,8 @@ class MoodleSaml(): except: print('Error adding saml on keycloak') + self.add_client_roles() + def activate_saml_plugin(self): ## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php return self.pg.update("""UPDATE "mdl_config" SET value = 'email,saml2' WHERE "name" = 'auth'""") @@ -110,18 +112,18 @@ class MoodleSaml(): return self.pg.select("""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'""")[0][2] def parse_idp_metadata(self): - keycloak=Keycloak() + keycloak=KeycloakClient() rsa=keycloak.get_server_rsa_key() keycloak=None return ''+rsa['name']+''+rsa['certificate']+'urn:oasis:names:tc:SAML:2.0:nameid-format:persistenturn:oasis:names:tc:SAML:2.0:nameid-format:transienturn:oasis:names:tc:SAML:1.1:nameid-format:unspecifiedurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' def set_keycloak_moodle_saml_plugin(self): - keycloak=Keycloak() + keycloak=KeycloakClient() keycloak.add_moodle_client() keycloak=None def delete_keycloak_moodle_saml_plugin(self): - keycloak=Keycloak() + keycloak=KeycloakClient() keycloak.delete_client('a92d5417-92b6-4678-9cb9-51bc0edcee8c') keycloak=None @@ -240,9 +242,16 @@ class MoodleSaml(): "manage" : True } } - keycloak=Keycloak() + keycloak=KeycloakClient() keycloak.add_client(client) keycloak=None + def add_client_roles(self): + keycloak=KeycloakClient() + keycloak.add_client_role('a92d5417-92b6-4678-9cb9-51bc0edcee8c','admin','Moodle admins') + keycloak.add_client_role('a92d5417-92b6-4678-9cb9-51bc0edcee8c','manager','Moodle managers') + keycloak.add_client_role('a92d5417-92b6-4678-9cb9-51bc0edcee8c','teacher','Moodle teachers') + keycloak.add_client_role('a92d5417-92b6-4678-9cb9-51bc0edcee8c','student','Moodle students') + keycloak=None m=MoodleSaml() diff --git a/admin/src/nextcloud_saml.py b/admin/src/nextcloud_saml.py index 9a2f09f..6ea4400 100644 --- a/admin/src/nextcloud_saml.py +++ b/admin/src/nextcloud_saml.py @@ -11,7 +11,7 @@ import yaml, json import psycopg2 from admin.lib.postgres import Postgres -from admin.lib.keycloak import Keycloak +from admin.lib.keycloak_client import KeycloakClient import string, random @@ -20,6 +20,12 @@ app['config']={} class NextcloudSaml(): def __init__(self): + self.url="http://isard-sso-keycloak:8080/auth/" + self.username=os.environ['KEYCLOAK_USER'] + self.password=os.environ['KEYCLOAK_PASSWORD'] + self.realm='master' + self.verify=True + ready=False while not ready: try: @@ -81,6 +87,7 @@ class NextcloudSaml(): try: self.set_nextcloud_saml_plugin() except: + log.error(traceback.format_exc()) print('Error adding saml on nextcloud') try: @@ -88,6 +95,13 @@ class NextcloudSaml(): except: print('Error adding saml on keycloak') + def connect(self): + self.keycloak= KeycloakClient(url=self.url, + username=self.username, + password=self.password, + realm=self.realm, + verify=self.verify) + # def activate_saml_plugin(self): # ## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php # return self.pg.update("""UPDATE "mdl_config" SET value = 'email,saml2' WHERE "name" = 'auth'""") @@ -96,20 +110,20 @@ class NextcloudSaml(): # return self.pg.select("""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'""")[0][2] def parse_idp_cert(self): - keycloak=Keycloak() - rsa=keycloak.get_server_rsa_key() - keycloak=None + self.connect() + rsa=self.keycloak.get_server_rsa_key() + self.keycloak=None return rsa['certificate'] def set_keycloak_nextcloud_saml_plugin(self): - keycloak=Keycloak() - keycloak.add_nextcloud_client() - keycloak=None + self.connect() + self.keycloak.add_nextcloud_client() + self.keycloak=None def delete_keycloak_nextcloud_saml_plugin(self): - keycloak=Keycloak() - keycloak.delete_client('bef873f0-2079-4876-8657-067de27d01b7') - keycloak=None + self.connect() + self.keycloak.delete_client('bef873f0-2079-4876-8657-067de27d01b7') + self.keycloak=None def set_nextcloud_saml_plugin(self): self.pg.update("""INSERT INTO "oc_appconfig" ("appid", "configkey", "configvalue") VALUES @@ -235,10 +249,8 @@ class NextcloudSaml(): "manage" : True } } - keycloak=Keycloak() - keycloak.add_client(client) - keycloak=None - - + self.connect() + self.keycloak.add_client(client) + self.keycloak=None n=NextcloudSaml() \ No newline at end of file diff --git a/admin/src/wordpress_saml.py b/admin/src/wordpress_saml.py new file mode 100644 index 0000000..fcd4358 --- /dev/null +++ b/admin/src/wordpress_saml.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python +# coding=utf-8 +import time, os +from datetime import datetime, timedelta +import pprint + +import logging as log +import traceback +import yaml, json + +import psycopg2 + +from admin.lib.mysql import Mysql +from admin.lib.keycloak_client import KeycloakClient + +import string, random + +app={} +app['config']={} + +class WordpressSaml(): + def __init__(self): + self.url="http://isard-sso-keycloak:8080/auth/" + self.username=os.environ['KEYCLOAK_USER'] + self.password=os.environ['KEYCLOAK_PASSWORD'] + self.realm='master' + self.verify=True + + ready=False + while not ready: + try: + self.db=Mysql('isard-apps-mariadb','wordpress','root',os.environ['MARIADB_PASSWORD']) + ready=True + except: + log.warning('Could not connect to wordpress database. Retrying...') + time.sleep(2) + log.info('Connected to wordpress database.') + + ready=False + while not ready: + try: + with open(os.path.join("./saml_certs/public.cert"),"r") as crt: + app['config']['PUBLIC_CERT_RAW']=crt.read() + app['config']['PUBLIC_CERT']=self.cert_prepare(app['config']['PUBLIC_CERT_RAW']) + ready=True + except IOError: + log.warning('Could not get public certificate to be used in wordpress. Retrying...') + log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert') + time.sleep(2) + except: + log.error(traceback.format_exc()) + log.info('Got moodle srt certificate to be used in wordpress.') + + ready=False + while not ready: + try: + with open(os.path.join("./saml_certs/private.key"),"r") as pem: + app['config']['PRIVATE_KEY']=self.cert_prepare(pem.read()) + ready=True + except IOError: + log.warning('Could not get private key to be used in wordpress. Retrying...') + log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert') + time.sleep(2) + log.info('Got moodle pem certificate to be used in wordpress.') + + # ## This seems related to the fact that the certificate generated the first time does'nt work. + # ## And when regenerating the certificate de privatekeypass seems not to be used and instead it + # ## will use always this code as filename: 0f635d0e0f3874fff8b581c132e6c7a7 + # ## As this bug I'm not able to solve, the process is: + # ## 1.- Bring up moodle and regenerate certificates on saml2 plugin in plugins-authentication + # ## 2.- Execute this script + # ## 3.- Cleanup all caches in moodle (Development tab) + # # with open(os.path.join("./moodledata/saml2/"+os.environ['NEXTCLOUD_SAML_PRIVATEKEYPASS'].replace("moodle."+os.environ['DOMAIN'],'')+'.idp.xml'),"w") as xml: + # # xml.write(self.parse_idp_metadata()) + # with open(os.path.join("./moodledata/saml2/0f635d0e0f3874fff8b581c132e6c7a7.idp.xml"),"w") as xml: + # xml.write(self.parse_idp_metadata()) + + try: + self.reset_saml() + except: + print(traceback.format_exc()) + print('Error resetting saml on wordpress') + try: + self.delete_keycloak_wordpress_saml_plugin() + except: + print('Error resetting saml on keycloak') + + try: + self.set_wordpress_saml_plugin() + except: + print(traceback.format_exc()) + print('Error adding saml on wordpress') + + try: + self.add_keycloak_wordpress_saml() + except: + print('Error adding saml on keycloak') + + self.add_client_roles() + + def connect(self): + self.keycloak= KeycloakClient(url=self.url, + username=self.username, + password=self.password, + realm=self.realm, + verify=self.verify) + + # def activate_saml_plugin(self): + # ## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php + # return self.db.update("""UPDATE "mdl_config" SET value = 'email,saml2' WHERE "name" = 'auth'""") + + # def get_privatekey_pass(self): + # return self.db.select("""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'""")[0][2] + + def cert_prepare(self,cert): + return ''.join(cert.split('-----')[2].splitlines()) + + def parse_idp_cert(self): + self.connect() + rsa=self.keycloak.get_server_rsa_key() + self.keycloak=None + return rsa['certificate'] + + def set_keycloak_wordpress_saml_plugin(self): + self.connect() + self.keycloak.add_wordpress_client() + self.keycloak=None + + def delete_keycloak_wordpress_saml_plugin(self): + self.connect() + self.keycloak.delete_client('630601f8-25d1-4822-8741-c93affd2cd84') + self.keycloak=None + + def set_wordpress_saml_plugin(self): + # ('active_plugins', 'a:2:{i:0;s:33:\"edwiser-bridge/edwiser-bridge.php\";i:1;s:17:\"onelogin_saml.php\";}', 'yes'), + self.db.update("""INSERT INTO wp_options (option_name, option_value, autoload) VALUES +('onelogin_saml_enabled', 'on', 'yes'), +('onelogin_saml_idp_entityid', 'Saml Login', 'yes'), +('onelogin_saml_idp_sso', 'https://sso.%s/auth/realms/master/protocol/saml', 'yes'), +('onelogin_saml_idp_slo', 'https://sso.%s/auth/realms/master/protocol/saml', 'yes'), +('onelogin_saml_idp_x509cert', '%s', 'yes'), +('onelogin_saml_autocreate', 'on', 'yes'), +('onelogin_saml_updateuser', 'on', 'yes'), +('onelogin_saml_forcelogin', 'on', 'yes'), +('onelogin_saml_slo', 'on', 'yes'), +('onelogin_saml_keep_local_login', '', 'yes'), +('onelogin_saml_alternative_acs', '', 'yes'), +('onelogin_saml_account_matcher', 'username', 'yes'), +('onelogin_saml_trigger_login_hook', '', 'yes'), +('onelogin_saml_multirole', '', 'yes'), +('onelogin_saml_trusted_url_domains', '', 'yes'), +('onelogin_saml_attr_mapping_username', 'username', 'yes'), +('onelogin_saml_attr_mapping_mail', 'email', 'yes'), +('onelogin_saml_attr_mapping_firstname', 'givenName', 'yes'), +('onelogin_saml_attr_mapping_lastname', 'sn', 'yes'), +('onelogin_saml_attr_mapping_nickname', '', 'yes'), +('onelogin_saml_attr_mapping_role', 'Role', 'yes'), +('onelogin_saml_attr_mapping_rememberme', '', 'yes'), +('onelogin_saml_role_mapping_administrator', 'admin', 'yes'), +('onelogin_saml_role_mapping_editor', 'manager', 'yes'), +('onelogin_saml_role_mapping_author', 'coursecreator', 'yes'), +('onelogin_saml_role_mapping_contributor', 'teacher', 'yes'), +('onelogin_saml_role_mapping_subscriber', '', 'yes'), +('onelogin_saml_role_mapping_multivalued_in_one_attribute_value', 'on', 'yes'), +('onelogin_saml_role_mapping_multivalued_pattern', '', 'yes'), +('onelogin_saml_role_order_administrator', '', 'yes'), +('onelogin_saml_role_order_editor', '', 'yes'), +('onelogin_saml_role_order_author', '', 'yes'), +('onelogin_saml_role_order_contributor', '', 'yes'), +('onelogin_saml_role_order_subscriber', '', 'yes'), +('onelogin_saml_customize_action_prevent_local_login', '', 'yes'), +('onelogin_saml_customize_action_prevent_reset_password', '', 'yes'), +('onelogin_saml_customize_action_prevent_change_password', '', 'yes'), +('onelogin_saml_customize_action_prevent_change_mail', '', 'yes'), +('onelogin_saml_customize_stay_in_wordpress_after_slo', 'on', 'yes'), +('onelogin_saml_customize_links_user_registration', '', 'yes'), +('onelogin_saml_customize_links_lost_password', '', 'yes'), +('onelogin_saml_customize_links_saml_login', '', 'yes'), +('onelogin_saml_advanced_settings_debug', '', 'yes'), +('onelogin_saml_advanced_settings_strict_mode', '', 'yes'), +('onelogin_saml_advanced_settings_sp_entity_id', '', 'yes'), +('onelogin_saml_advanced_idp_lowercase_url_encoding', '', 'yes'), +('onelogin_saml_advanced_settings_nameid_encrypted', '', 'yes'), +('onelogin_saml_advanced_settings_authn_request_signed', 'on', 'yes'), +('onelogin_saml_advanced_settings_logout_request_signed', 'on', 'yes'), +('onelogin_saml_advanced_settings_logout_response_signed', 'on', 'yes'), +('onelogin_saml_advanced_settings_want_message_signed', '', 'yes'), +('onelogin_saml_advanced_settings_want_assertion_signed', '', 'yes'), +('onelogin_saml_advanced_settings_want_assertion_encrypted', '', 'yes'), +('onelogin_saml_advanced_settings_retrieve_parameters_from_server', '', 'yes'), +('onelogin_saml_advanced_nameidformat', 'unspecified', 'yes'), +('onelogin_saml_advanced_requestedauthncontext', '', 'yes'), +('onelogin_saml_advanced_settings_sp_x509cert', '%s', 'yes'), +('onelogin_saml_advanced_settings_sp_privatekey', '%s', 'yes'), +('onelogin_saml_advanced_signaturealgorithm', 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 'yes'), +('onelogin_saml_advanced_digestalgorithm', 'http://www.w3.org/2000/09/xmldsig#sha1', 'yes');""" % (os.environ['DOMAIN'],os.environ['DOMAIN'],self.parse_idp_cert(),app['config']['PUBLIC_CERT'],app['config']['PRIVATE_KEY'])) + + + def reset_saml(self): + self.db.update("""DELETE FROM wp_options WHERE option_name LIKE 'onelogin_saml_%'""") + + def add_keycloak_wordpress_saml(self): + client={"id" : "630601f8-25d1-4822-8741-c93affd2cd84", + "clientId" : "php-saml", + "surrogateAuthRequired" : False, + "enabled" : True, + "alwaysDisplayInConsole" : False, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "https://wp."+os.environ['DOMAIN']+"/wp-login.php?saml_acs" ], + "webOrigins" : [ "https://wp."+os.environ['DOMAIN'] ], + "notBefore" : 0, + "bearerOnly" : False, + "consentRequired" : False, + "standardFlowEnabled" : True, + "implicitFlowEnabled" : False, + "directAccessGrantsEnabled" : False, + "serviceAccountsEnabled" : False, + "publicClient" : False, + "frontchannelLogout" : True, + "protocol" : "saml", + "attributes" : { + "saml.force.post.binding" : True, + "saml_assertion_consumer_url_post" : "https://wp."+os.environ['DOMAIN']+"/wp-login.php?saml_acs", + "saml.server.signature" : True, + "saml.server.signature.keyinfo.ext" : False, + "saml.signing.certificate" : app['config']['PUBLIC_CERT_RAW'], + "saml_single_logout_service_url_redirect" : "https://wp."+os.environ['DOMAIN']+"/wp-login.php?saml_sls", + "saml.signature.algorithm" : "RSA_SHA256", + "saml_force_name_id_format" : False, + "saml.client.signature" : True, + "saml.authnstatement" : True, + "saml_name_id_format" : "username", + "saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : True, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "72c6175e-bd07-4c27-abd6-4e4ae38d834b", + "name" : "username", + "protocol" : "saml", + "protocolMapper" : "saml-user-attribute-mapper", + "consentRequired" : False, + "config" : { + "attribute.nameformat" : "Basic", + "user.attribute" : "username", + "friendly.name" : "username", + "attribute.name" : "username" + } + }, { + "id" : "abd6562f-4732-4da9-987f-b1a6ad6605fa", + "name" : "roles", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : False, + "config" : { + "single" : True, + "attribute.nameformat" : "Basic", + "friendly.name" : "Roles", + "attribute.name" : "Role" + } + }, { + "id" : "50aafb71-d91c-4bc7-bb60-e1ae0222aab3", + "name" : "email", + "protocol" : "saml", + "protocolMapper" : "saml-user-property-mapper", + "consentRequired" : False, + "config" : { + "attribute.nameformat" : "Basic", + "user.attribute" : "email", + "friendly.name" : "email", + "attribute.name" : "email" + } + } ], + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ], + "access" : { + "view" : True, + "configure" : True, + "manage" : True + } + } + self.connect() + self.keycloak.add_client(client) + self.keycloak=None + + def add_client_roles(self): + self.connect() + self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','admin','Moodle admins') + self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','manager','Moodle managers') + self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','teacher','Moodle teachers') + self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','student','Moodle students') + self.keycloak=None + +nw=WordpressSaml() \ No newline at end of file diff --git a/docker-compose-parts/keycloak.yml b/docker-compose-parts/keycloak.yml index 1d702cd..240d4c3 100644 --- a/docker-compose-parts/keycloak.yml +++ b/docker-compose-parts/keycloak.yml @@ -28,6 +28,8 @@ services: - KEYCLOAK_PASSWORD=${KEYCLOAK_PASSWORD} - PROXY_ADDRESS_FORWARDING=true - KEYCLOAK_FRONTEND_URL=https://sso.${DOMAIN}/auth/ + - DDADMIN_USER=${DDADMIN_USER} + - DDADMIN_PASSWORD=${DDADMIN_PASSWORD} #- KEYCLOAK_LOGLEVEL=ALL #- Dkeycloak.profile.feature.upload_scripts=enabled depends_on: diff --git a/init/keycloak/keycloak.sh b/init/keycloak/keycloak.sh index 9a08175..bce2a6f 100644 --- a/init/keycloak/keycloak.sh +++ b/init/keycloak/keycloak.sh @@ -15,3 +15,6 @@ # #curl https://moodle.isardvdi.site/auth/saml2/sp/metadata.php # # Import as client provider + + +# get-roles --cclientid test-client --rolename operations