automated wordpress saml

root 2021-05-29 10:10:57 +02:00
parent ab559dd35a
commit be28c1ae30
13 changed files with 532 additions and 53 deletions

View File

@ -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
Flask-SocketIO==2.8.6
mysql-connector-python==8.0.25

View File

@ -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())

View File

@ -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()

View File

@ -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
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 );

View File

@ -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()

View File

@ -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',

View File

@ -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

View File

@ -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 '<md:EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Name="urn:keycloak"><md:EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master"><md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo><ds:KeyName>'+rsa['name']+'</ds:KeyName><ds:X509Data><ds:X509Certificate>'+rsa['certificate']+'</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml/resolve" index="0"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/></md:IDPSSODescriptor></md:EntityDescriptor></md:EntitiesDescriptor>'
def set_keycloak_moodle_saml_plugin(self):
keycloak=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()

View File

@ -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()

295
admin/src/wordpress_saml.py Normal file
View File

@ -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()

View File

@ -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:

View File

@ -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