#!/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') # SAML clients don't work well with composite roles so disabling and adding on realm # 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','Wordpress admins') self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','manager','Wordpress managers') self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','teacher','Wordpress teachers') self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','student','Wordpress students') self.keycloak=None nw=WordpressSaml()