digitaldemocratic/admin/src/nextcloud_saml.py

354 lines
14 KiB
Python

#!/usr/bin/env python
# coding=utf-8
import json
import logging as log
import os
import pprint
import random
import string
import time
import traceback
from datetime import datetime, timedelta
import psycopg2
import yaml
from admin.lib.keycloak_client import KeycloakClient
from admin.lib.postgres import Postgres
app = {}
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:
self.pg = Postgres(
"isard-apps-postgresql",
"nextcloud",
os.environ["NEXTCLOUD_POSTGRES_USER"],
os.environ["NEXTCLOUD_POSTGRES_PASSWORD"],
)
ready = True
except:
log.warning("Could not connect to nextcloud database. Retrying...")
time.sleep(2)
log.info("Connected to nextcloud database.")
ready = False
while not ready:
try:
with open(os.path.join("./saml_certs/public.cert"), "r") as crt:
app["config"]["PUBLIC_CERT"] = crt.read()
ready = True
except IOError:
log.warning(
"Could not get public certificate to be used in nextcloud. 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 nextcloud.")
ready = False
while not ready:
try:
with open(os.path.join("./saml_certs/private.key"), "r") as pem:
app["config"]["PRIVATE_KEY"] = pem.read()
ready = True
except IOError:
log.warning(
"Could not get private key to be used in nextcloud. 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 nextcloud.")
# ## 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("Error resetting saml on nextcloud")
try:
self.delete_keycloak_nextcloud_saml_plugin()
except:
print("Error resetting saml on keycloak")
try:
self.set_nextcloud_saml_plugin()
except:
log.error(traceback.format_exc())
print("Error adding saml on nextcloud")
try:
self.add_keycloak_nextcloud_saml()
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'""")
# def get_privatekey_pass(self):
# return self.pg.select("""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'""")[0][2]
def parse_idp_cert(self):
self.connect()
rsa = self.keycloak.get_server_rsa_key()
self.keycloak = None
return rsa["certificate"]
def set_keycloak_nextcloud_saml_plugin(self):
self.connect()
self.keycloak.add_nextcloud_client()
self.keycloak = None
def delete_keycloak_nextcloud_saml_plugin(self):
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
('user_saml', 'general-uid_mapping', 'username'),
('user_saml', 'type', 'saml'),
('user_saml', 'sp-privateKey', '%s'),
('user_saml', 'saml-attribute-mapping-email_mapping', 'email'),
('user_saml', 'saml-attribute-mapping-quota_mapping', 'quota'),
('user_saml', 'saml-attribute-mapping-displayName_mapping', 'displayname'),
('user_saml', 'saml-attribute-mapping-group_mapping', 'member'),
('user_saml', 'idp-entityId', 'https://sso.%s/auth/realms/master'),
('user_saml', 'idp-singleSignOnService.url', 'https://sso.%s/auth/realms/master/protocol/saml'),
('user_saml', 'idp-x509cert', '%s'),
('user_saml', 'security-authnRequestsSigned', '1'),
('user_saml', 'security-logoutRequestSigned', '1'),
('user_saml', 'security-logoutResponseSigned', '1'),
('user_saml', 'security-wantMessagesSigned', '1'),
('user_saml', 'security-wantAssertionsSigned', '1'),
('user_saml', 'general-idp0_display_name', 'SAML Login'),
('user_saml', 'sp-x509cert', '%s'),
('user_saml', 'idp-singleLogoutService.url', 'https://sso.%s/auth/realms/master/protocol/saml');"""
% (
app["config"]["PRIVATE_KEY"],
os.environ["DOMAIN"],
os.environ["DOMAIN"],
self.parse_idp_cert(),
app["config"]["PUBLIC_CERT"],
os.environ["DOMAIN"],
)
)
def reset_saml(self):
cfg_list = [
"general-uid_mapping",
"sp-privateKey",
"saml-attribute-mapping-email_mapping",
"saml-attribute-mapping-quota_mapping",
"saml-attribute-mapping-displayName_mapping",
"saml-attribute-mapping-group_mapping",
"idp-entityId",
"idp-singleSignOnService.url",
"idp-x509cert",
"security-authnRequestsSigned",
"security-logoutRequestSigned",
"security-logoutResponseSigned",
"security-wantMessagesSigned",
"security-wantAssertionsSigned",
"general-idp0_display_name",
"type",
"sp-x509cert",
"idp-singleLogoutService.url",
]
for cfg in cfg_list:
self.pg.update(
"""DELETE FROM "oc_appconfig" WHERE appid = 'user_saml' AND configkey = '%s'"""
% (cfg)
)
def add_keycloak_nextcloud_saml(self):
client = {
"id": "bef873f0-2079-4876-8657-067de27d01b7",
"name": "nextcloud",
"description": "nextcloud",
"clientId": "https://nextcloud."
+ os.environ["DOMAIN"]
+ "/apps/user_saml/saml/metadata",
"surrogateAuthRequired": False,
"enabled": True,
"alwaysDisplayInConsole": False,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"https://nextcloud." + os.environ["DOMAIN"] + "/apps/user_saml/saml/acs"
],
"webOrigins": ["https://nextcloud." + 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.assertion.signature": True,
"saml.force.post.binding": True,
"saml_assertion_consumer_url_post": "https://nextcloud."
+ os.environ["DOMAIN"]
+ "/apps/user_saml/saml/acs",
"saml.server.signature": True,
"saml.server.signature.keyinfo.ext": False,
"saml.signing.certificate": app["config"]["PUBLIC_CERT"],
"saml_single_logout_service_url_redirect": "https://nextcloud."
+ os.environ["DOMAIN"]
+ "/apps/user_saml/saml/sls",
"saml.signature.algorithm": "RSA_SHA256",
"saml_force_name_id_format": False,
"saml.client.signature": False,
"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": "e8e4acff-da2b-46aa-8bdb-ba42171671d6",
"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": "8ab13cd7-822a-40d5-a1e1-9f556aed2332",
"name": "quota",
"protocol": "saml",
"protocolMapper": "saml-user-attribute-mapper",
"consentRequired": False,
"config": {
"attribute.nameformat": "Basic",
"user.attribute": "quota",
"friendly.name": "quota",
"attribute.name": "quota",
},
},
{
"id": "28206b59-757b-4e3c-81cb-0b6053b1fd3d",
"name": "email",
"protocol": "saml",
"protocolMapper": "saml-user-property-mapper",
"consentRequired": False,
"config": {
"attribute.nameformat": "Basic",
"user.attribute": "email",
"friendly.name": "email",
"attribute.name": "email",
},
},
{
"id": "5176a593-180f-4924-b294-b83a0d8d5972",
"name": "displayname",
"protocol": "saml",
"protocolMapper": "saml-javascript-mapper",
"consentRequired": False,
"config": {
"single": False,
"Script": '/**\n * Available variables: \n * user - the current user\n * realm - the current realm\n * clientSession - the current clientSession\n * userSession - the current userSession\n * keycloakSession - the current keycloakSession\n */\n\n\n//insert your code here...\nvar Output = user.getFirstName()+" "+user.getLastName();\nOutput;\n',
"attribute.nameformat": "Basic",
"friendly.name": "displayname",
"attribute.name": "displayname",
},
},
{
"id": "e51e04b9-f71a-42de-819e-dd9285246ada",
"name": "Roles",
"protocol": "saml",
"protocolMapper": "saml-role-list-mapper",
"consentRequired": False,
"config": {
"single": True,
"attribute.nameformat": "Basic",
"friendly.name": "Roles",
"attribute.name": "Roles",
},
},
{
"id": "9c101249-bb09-4cc8-8f75-5a18fcb307e6",
"name": "group_list",
"protocol": "saml",
"protocolMapper": "saml-group-membership-mapper",
"consentRequired": False,
"config": {
"single": True,
"attribute.nameformat": "Basic",
"full.path": False,
"friendly.name": "member",
"attribute.name": "member",
},
},
],
"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
n = NextcloudSaml()