I'm trying to perform SAML authentication against the CyberArk Password Vault application. I was given this PowerShell code by CyberArk for performing this SAML authentication, and it works great and I get the SAML Response token for performing subsequent calls. The issue is that I need to perform this SAML authentication in Python instead of PowerShell, and so far I've been unable to replicate the successful PowerShell code into Python.
My initial attempt was to use Selenium with the Edge webdriver, but that fails, and the PowerShell code doesn't seem to use any particular browser? Any ideas how to replicate this PowerShell code into Python?
Successful PowerShell code for SAML authentication, that gives me the SAML Response token from the IDP
<# ###########################################################################
NAME: CyberArk SAML Authentication via REST API
AUTHOR: Shay Tevet
########################################################################### #>
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Web
$PVWAAddress = "https://passwordvault.acme.net"
function CA_API_SAMLAuth($PVWAAddress)
{
$global:tkn = ""
try{
$Logon_Body = @{}|ConvertTo-Json
$Logon_URI = "$PVWAAddress/PasswordVault/api/auth/saml/Logon"
$IdpUrl = Invoke-RestMethod -Uri $Logon_URI -Body $Logon_Body -Method POST -ContentType "application/json" -SessionVariable websession
$cookies = $websession.Cookies.GetCookies("$PVWAAddress/PasswordVault/api/auth/saml/Logon")
foreach ($cookie in $cookies) {
if ($cookie.name -eq "CA88888"){$CA8 = $cookie.value}
}
}
catch{
Write-Host "StatusMessage:" $_
return
}
try{
$SAML_Form = New-Object Windows.Forms.Form
$SAML_Form.StartPosition = 'CenterScreen'
$SAML_Form.Size = New-Object System.Drawing.Size(650,750)
$SAML_WB = New-Object Windows.Forms.WebBrowser
$SAML_WB.Dock = 'Fill'
$SAML_WB.ScriptErrorsSuppressed = $true
$SAML_Form.Controls.Add($SAML_WB)
# Navigate to the IDP URL
$SAML_WB.Navigate($IdpUrl)
# Do something before we go anywhere else
$SAML_WB.add_Navigating({
if ($SAML_WB.DocumentText.Contains("SAMLResponse")){
$_.cancel = $true
$SAMLElement = $SAML_WB.Document.GetElementsByTagName("input").GetElementsByName("SAMLResponse")[0].GetAttribute("value");
$SAMLRes = $($SAMLElement -replace ' ', '')
try{
$sessioncc = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
$cookie8 = [System.Net.Cookie]::new('CA88888', $CA8)
$cookie8.HttpOnly=$true
$cookie8.Secure=$true
$cookie8.Domain = $PVWAAddress.Split("/")[2]
$cookie8.Path = "/"
$sessioncc.Cookies.Add($PVWAAddress, $cookie8)
$body = @{concurrentSession='true';apiUse='true';SAMLResponse="$($SAMLRes.Trim())"}
$contentType = 'application/x-www-form-urlencoded'
$SessionToken = Invoke-WebRequest -Method POST -Uri $Logon_URI -body $body -ContentType $contentType -WebSession $sessioncc
$global:tkn = $SessionToken.Content -replace '"', ''
}
catch{
Write-Host "StatusMessage:" $_
return
}
$SAML_Form.Close()
}
})
$SAML_Form.ShowDialog()
$SAML_Form.Dispose()
if($global:tkn){
Write-Host SessionToken: $global:tkn
return $global:tkn
}else{
Write-Host "Something went wrong during the authentication process.\nPlease try signing in again."
return
}
}
catch{
Write-Host "StatusMessage:" $_
return
}
}
$Token = CA_API_SAMLAuth($PVWAAddress)
The Python code I've tried, using Selenium. This Python code gives me the MFA prompt in the automated Edge browser, but I never get the SAML Response here in Python like I do in the above PowerShell code. I'm wondering if I need to open the IDP URL not in an Edge browser, but just in a generic frame, and somehow handle the SAML redirects?
import time
import os
import re
import urllib.request
import requests
from selenium import webdriver
from selenium.webdriver.edge.service import Service as EdgeService
from selenium.webdriver.edge.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import (
ElementClickInterceptedException,
NoSuchElementException,
ElementNotInteractableException,
SessionNotCreatedException
)
# Webdriver details
ms_edge_webdriver_path = f"C:\\Users\\bugsbunny\\Downloads\\edgedriver_win64\\msedgedriver.exe"
ms_edge_binary_path = "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
pwv_saml_url = f"https://passwordvault.acme.net/PasswordVault/api/auth/saml/Logon"
headers = {
'Content-Type': 'application/json'
}
initial_pwv_cookie = ""
initial_idp_url = ""
body = {}
with requests.post(pwv_saml_url, headers=headers, verify=False) as initial_idp_response:
initial_idp_url = json.loads(initial_idp_response.content)
for cookie in initial_idp_response.cookies:
if cookie.name == "CA88888":
initial_pwv_cookie = cookie.value
# --- Setup Selenium Edge driver --- #
options = Options()
# https://stackoverflow.com/questions/51865300/python-selenium-keep-browser-open
options.add_experimental_option("detach", True)
# Suppress the stupid "Personalize your web experience" prompt
# that can appear randomly and throw things off. You can launch the browser
# in "guest" mode but if we do SSO I don't want that, so instead we'll
# just turn off the Edge toggle that initiates that prompt.
# https://stackoverflow.com/questions/76377363/how-can-i-disable-personalize-your-web-experience-ms-edge-prompt-for-selenium
# https://stackoverflow.com/questions/77609588/selenium-edge-webdriver-notification-disable
options.add_experimental_option("prefs",
{"user_experience_metrics": {"personalization_data_consent_enabled": True}})
# suppressing any console output (FYI, currently this does not totally work when used with
# the --headless or --headless=new options, so be aware if you are using this with those options).
# https://github.com/SeleniumHQ/selenium/issues/13095
# https://stackoverflow.com/questions/69919930/selenium-edge-python-errors-auto-close-edge-browser-after-test-execution
options.add_experimental_option('excludeSwitches', ['enable-logging'])
service = EdgeService(executable_path=ms_edge_webdriver_path)
driver = webdriver.Edge(service=service, options=options)
# Maximize the window
driver.maximize_window()
# driver = webdriver.Edge(service=service)
driver.get(initial_idp_url)
requestsand not Selenium and a browser.