From 918f755f7d10ae5f919e56289bcbede6370efb1c Mon Sep 17 00:00:00 2001 From: Pascal Phelipot Date: Mon, 4 Aug 2025 01:32:52 +0200 Subject: [PATCH] Updated to WSO2 with start of config scrip --- .gitignore | 3 +- app_request_content.json | 86 ++++++++++++++++++++++++++++ docker-compose.yml | 15 ++--- wso2_api.py | 118 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 app_request_content.json create mode 100644 wso2_api.py diff --git a/.gitignore b/.gitignore index f109b50..eaa82d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -data_*/* \ No newline at end of file +data_*/* +*_data/* \ No newline at end of file diff --git a/app_request_content.json b/app_request_content.json new file mode 100644 index 0000000..78aac0b --- /dev/null +++ b/app_request_content.json @@ -0,0 +1,86 @@ +{ + "name": "test_saml_app", + "inboundProtocolConfiguration": { + "saml": { + "manualConfiguration": { + "assertionConsumerUrls": [ + "https://app-server-a:8080/" + ], + "attributeProfile": { + "alwaysIncludeAttributesInResponse": false, + "enabled": true + }, + "defaultAssertionConsumerUrl": "https://app-server-a:8080/", + "enableAssertionQueryProfile": false, + "idpEntityIdAlias": "", + "issuer": "saml-test-issuer", + "requestValidation": { + "enableSignatureValidation": true, + "signatureValidationCertAlias": "wso2carbon" + }, + "responseSigning": { + "enabled": true, + "signingAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" + }, + "serviceProviderQualifier": "", + "singleLogoutProfile": { + "enabled": false, + "idpInitiatedSingleLogout": { + "enabled": false, + "returnToUrls": [] + }, + "logoutMethod": "BACKCHANNEL", + "logoutRequestUrl": "", + "logoutResponseUrl": "" + }, + "singleSignOnProfile": { + "assertion": { + "audiences": [], + "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256", + "encryption": { + "assertionEncryptionAlgorithm": "http://www.w3.org/2009/xmlenc11#aes256-gcm", + "enabled": true, + "keyEncryptionAlgorithm": "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" + }, + "nameIdFormat": "urn/oasis/names/tc/SAML/1.1/nameid-format/emailAddress", + "recipients": [] + }, + "attributeConsumingServiceIndex": "", + "bindings": [ + "HTTP_POST", + "HTTP_REDIRECT" + ], + "enableIdpInitiatedSingleSignOn": false, + "enableSignatureValidationForArtifactBinding": false + } + } + } + }, + "authenticationSequence": { + "type": "DEFAULT", + "steps": [ + { + "id": 1, + "options": [ + { + "idp": "LOCAL", + "authenticator": "basic" + } + ] + } + ], + "subjectStepId": 1, + "attributeStepId": 1 + }, + "advancedConfigurations": { + "discoverableByEndUsers": false + }, + "description": "Regular web applications which use redirection inside browsers.", + "templateId": "776a73da-fd8e-490b-84ff-93009f8ede85", + "provisioningConfigurations": { + "inboundProvisioning": { + "provisioningUserstoreDomain": "RemoteLDAP2", + "proxyMode": false + } + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 533ccb0..ab4c99c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,19 +17,14 @@ services: - action: rebuild path: ./conf/**/* idp: - image: quay.io/keycloak/keycloak:24.0.4 + image: wso2/wso2is:5.11.0 restart: unless-stopped - container_name: keycloak + container_name: wso2 command: start-dev ports: - - '8080:8080' - environment: - - KEYCLOAK_ADMIN=admin - - KEYCLOAK_ADMIN_PASSWORD=admin - #- KC_LOG_LEVEL=DEBUG - #- KC_HOSTNAME=localhost + - '9443:9443' volumes: - - ./data_keycloak:/opt/keycloak/data/ + - ./data_wso2:/home/wso2/ grafana: image: grafana/grafana-oss restart: unless-stopped @@ -38,4 +33,4 @@ services: - '3000:3000' volumes: - ./conf/grafana.ini:/etc/grafana/grafana.ini:ro - - ./data_grafana:/var/lib/grafana \ No newline at end of file + - ./data_grafana:/var/lib/grafana diff --git a/wso2_api.py b/wso2_api.py new file mode 100644 index 0000000..014622d --- /dev/null +++ b/wso2_api.py @@ -0,0 +1,118 @@ +import httpx +import asyncio +import base64 +from dataclasses import dataclass, asdict +import logging +import pprint +import difflib + +logging.basicConfig(level=logging.DEBUG) +logging.getLogger("httpcore").setLevel(logging.WARNING) +logging.getLogger("httpx").setLevel(logging.WARNING) + +@dataclass +class UniqueIDReadOnlyLDAPUserStoreManagerProperties(): + ConnectionURL: str = "ldap://" + ConnectionName: str = "uid=,ou=" + ConnectionPassword: str = "password" + UserSearchBase: str = "ou=Users,dc=wso2,dc=org" + UserNameAttribute: str = "uid" + UserNameSearchFilter: str = "(&(objectClass=person)(uid=?))" + UserNameListFilter: str = "(objectClass=person)" + UserIDAttribute: str = "uid" + UserIdSearchFilter: str = "(&(objectClass=person)(uid=?))" + BackLinksEnabled: bool = True + MemberOfAttribute: str = "memberOf" + + # Optionnal mon cul + Disabled: bool = False + ReadGroups: bool = True + GroupSearchBase: str = "ou=Groups,dc=wso2,dc=org" + GroupNameAttribute: str = "cn" + GroupNameSearchFilter: str = "(&(objectClass=groupOfNames)(cn=?))" + GroupNameListFilter: str = "(objectClass=groupOfNames)" + CaseInsensitiveUsername: bool = True + MembershipAttribute: str = "member" + + # Undocumented ??? + UserEntryObjectClass: str = "inetOrgPerson" + +def compare_dicts(d1, d2): + diff = ('\n' + '\n'.join(difflib.ndiff( + pprint.pformat(d1).splitlines(), + pprint.pformat(d2).splitlines())) + ) + logging.info("diff: %s", diff) + +async def create_keystore(client: httpx.AsyncClient): + url = "https://localhost:9443/t/carbon.super/api/server/v1/userstores/" + data = { + "typeId": base64.urlsafe_b64encode(b"UniqueIDReadOnlyLDAPUserStoreManager").decode("utf-8").rstrip("="), + "description": "New user store from API", + "name": "RemoteLDAP2", + "properties": [ + { + "name": key, + "value": value + } + for key, value in asdict(UniqueIDReadOnlyLDAPUserStoreManagerProperties()).items() + ] + } + # FIXME: this works to create the userstore but the Java is not very happy from it + resp = await client.post(url, json=data) + logging.debug("request: %s", resp.request.content) + logging.debug("resp [%s]: %s", resp.status_code, resp.json()) + +async def create_idp(client: httpx.AsyncClient): + url = "https://localhost:9443/t/carbon.super/api/server/v1/identity-providers" + data = { + "name": "test_idp", + "description": "A test IDP", + "provisioning": { + "jit": { + "isEnabled": True, + "scheme": "PROVISION_SILENTLY", + "userstore": "RemoteLDAP2" # Name of the userstore + }, + "outboundConnectors": None + } + } + resp = await client.post(url, json=data) + logging.debug("request: %s", resp.request.content) + logging.debug("resp [%s]: %s", resp.status_code, resp.json()) + +async def create_sp(client: httpx.AsyncClient, sp_name: str): + url = "https://localhost:9443/t/carbon.super/api/server/v1/applications" + data = { + "name": sp_name, + "description": f"A SP for {sp_name}", + "accessUrl": "https://example.com/login", + "inboundProtocolConfiguration": { + "saml": { + + } + }, + "outboundProvisioningIdps": [ + { + "idp": "test_idp" + } + ] + } + resp = await client.post(url, json=data) + logging.debug("request: %s", resp.request.content) + logging.debug("resp [%s]: %s", resp.status_code, resp.json()) + +async def main(): + auth = httpx.BasicAuth(username="admin", password="admin") + + async with httpx.AsyncClient(auth=auth, verify=False) as client: + try: + # await create_keystore(client) + # await create_idp(client) + await create_sp(client, "portal") + + except Exception as err: + logging.exception("Failure during request to WSO2") + +if __name__ == "__main__": + asyncio.run(main())