Jump to content

How to use RunAs for WebDriver based sessions


Go to solution Solved by abs,

Recommended Posts

First of all my sincere thanks to the developers of WebDriver wrappers. I have read the AutoIT wiki about webdriver and managed to write script that is meant to launch the website via CyberArk. The script works pretty well. The requirement I have though is to create the browser session under a domain account. Reason for such ask is due to the other websites embedded within the parent website (Kafka) that use integrated windows authentication and for that, the session must use RunAs capability to create the browser session under domain account, enter the credentials in primary website and login. Then if end users open the other websites hyperlinked within the primary website, it should use the IWA instead of popping up the windows authentication dialog. 

The other alternative is to simply launch msedge executable using RunAs and use the Send/ ControlSend commands to sign in to primary website. It works that way and child websites open just fine. However, this approach is highly prone to errors due to losing focus, sending credentials in wrong fields and no way to read a certain class before credentials are sent.

Can someone please help me with my goal of running the webdriver session using RunAs please ? I have written following script and have used RunAs for running the driver but the win authentication dialog is still coming up meaning the session didn't use RunAs at all.

 

#AutoIt3Wrapper_UseX64=n
Opt("MustDeclareVars", 1)
AutoItSetOption("WinTitleMatchMode", 3) ; EXACT_MATCH!

;============================================================
;           Kafka Web Portal
;           ----------------------------------------------------------
; Description : PSM Dispatcher for CDP WEB PORTAL
; Created : May 28, 2024
; Abhishek Singh
; Developed and compiled in AutoIt 3.3.14.1
;============================================================
; Uses AutoIT Web driver UDF
;============================================================

#include "PSMGenericClientWrapper.au3"
#include <GUIConstantsEx.au3>
#include <wd_Core.au3>
#include <wd_helper.au3>
#include <wd_cdp.au3>
#include <wd_capabilities.au3>
#include <Json.au3>
#include <BinaryCall.au3>
#include <WinHttp.au3>
#include <WinHttpConstants.au3>

;================================
; Consts & Globals
;================================

Global Const $DISPATCHER_NAME           = "Kafka WebApp"
Global Const $MESSAGE_TITLE = "PSM-Kafka-WebApp"
Global Const $ERROR_MESSAGE_TITLE   = "PSM " & $DISPATCHER_NAME & " Dispatcher error message"
Global Const $LOG_MESSAGE_PREFIX        = $DISPATCHER_NAME & " Dispatcher - "

Global $TargetUsername   ;Will be fetched from the PSM Session
Global $TargetPassword   ;Will be fetched from the PSM Session
Global $TargetAddress    ;Will be fetched from the PSM Session
Global $DriverPath ; Will be fetched from the PSM Session
Global $BrowserPath ; Will be fetched from the PSM Session
Global $TargetDomain
Global $ConnectionClientPID = 0
Global $sDesiredCapabilities, $sSession
;=======================================
; Code
;=======================================
Exit Main()

; #FUNCTION# ====================================================================================================================
; Name...........: FetchSessionProperties
; Description ...: Fetches properties required for the session from the PSM
; Parameters ....: None
; Return values .: None
; ===============================================================================================================================
Func FetchSessionProperties()

    If (PSMGenericClient_GetSessionProperty("Username", $TargetUsername) <> $PSM_ERROR_SUCCESS) Then
        Error(PSMGenericClient_PSMGetLastErrorString())
    EndIf

    If (PSMGenericClient_GetSessionProperty("Password", $TargetPassword) <> $PSM_ERROR_SUCCESS) Then
        Error(PSMGenericClient_PSMGetLastErrorString())
    EndIf
    
    if (PSMGenericClient_GetSessionProperty("PSMRemoteMachine", $TargetAddress) <> $PSM_ERROR_SUCCESS) Then
        Error(PSMGenericClient_PSMGetLastErrorString())
    EndIf
    
    if (PSMGenericClient_GetSessionProperty("LogonDomain", $TargetDomain) <> $PSM_ERROR_SUCCESS) Then
        Error(PSMGenericClient_PSMGetLastErrorString())
    EndIf
    
    if (PSMGenericClient_GetSessionProperty("DriverPath", $DriverPath) <> $PSM_ERROR_SUCCESS) Then
        Error(PSMGenericClient_PSMGetLastErrorString())
    EndIf
    
    if (PSMGenericClient_GetSessionProperty("BrowserPath", $BrowserPath) <> $PSM_ERROR_SUCCESS) Then
        Error(PSMGenericClient_PSMGetLastErrorString())
    EndIf
    
EndFunc

;*=*=*=*=*=*=*=*=*=*=*=*=*=
; DO NOT CHANGE FROM HERE
;*=*=*=*=*=*=*=*=*=*=*=*=*=

;=======================================
; Main
;=======================================
Func Main()
    ; Init PSM Dispatcher utils wrapper
   ; MessageUserOn($MESSAGE_TITLE, "The PSM is about to log you on automatically which may take several seconds...")

    If (PSMGenericClient_Init() <> $PSM_ERROR_SUCCESS) Then
        Error(PSMGenericClient_PSMGetLastErrorString())
    EndIf

    LogWrite("Successfully initialized Dispatcher Utils Wrapper")
    LogWrite("Mapping local drives")

    If (PSMGenericClient_MapTSDrives() <> $PSM_ERROR_SUCCESS) Then
        Error(PSMGenericClient_PSMGetLastErrorString())
    EndIf

    ; Get the dispatcher parameters
    FetchSessionProperties()
    
    MessageUserOn($MESSAGE_TITLE, "Starting " & $DISPATCHER_NAME & "...")
    LogWrite("Starting client application")
    ;Open MS Edge. SetupEdge() will terminate the process If Edge wasn't loaded properly. No need to check PID here.
    
    $_WD_DEBUG = $_WD_DEBUG_None
    
    $sSession = SetupEdge()
    
    ;_WD_Navigate($sSession, $sURL) ; Used for navigating to open browser session
    
    _WD_LoadWait($sSession)
    LogWrite("Finished loading chrome")
    
    $ConnectionClientPID = WinGetProcess("[CLASS:Chrome_WidgetWin_1]")
    LogWrite($ConnectionClientPID)
    if ($ConnectionClientPID == 0) Then
        Error(StringFormat("Failed to execute process [%s]", $ConnectionClientPID, @error))
    EndIf

    LogWrite("Entered LoginProcess()")

    ; References to HTML elements in the login process
    _WD_LoadWait($sSession)

   BlockAllInput()

   Local $oUserName     = _WD_FindElement($sSession, $_WD_LOCATOR_ByXPath, "//*[@id='j_username']")
   _WD_ElementAction($sSession, $oUserName, 'value', $TargetUsername)
   
   Local $oPassword     = _WD_FindElement($sSession, $_WD_LOCATOR_ByXPath, "//*[@id='j_password']")
   _WD_ElementAction($sSession, $oPassword, 'value', $TargetPassword)
   
   Local $SignIn = _WD_FindElement($sSession, $_WD_LOCATOR_ByLinkText, "Sign In")
   _WD_ElementAction($sSession, $SignIn, 'click')
   
    Send("{Enter}")
    _WD_LoadWait($sSession)
    _WD_Shutdown()
    UnblockAllBlockProhibited()
    Local $FinalWindow = WinWaitActive("[REGEXPTITLE:(?i)(Cloudera.*|Manager.*)]", "", 20)
    WinActivate($FinalWindow)
    WinSetState($FinalWindow, "", @SW_SHOW)
    LogWrite("Finished LoginProcess() successfully")
    ;Send PID to PSM as early as possible so recording/monitoring can begin
    LogWrite("sending PID to PSM")
    If (PSMGenericClient_SendPID($ConnectionClientPID) <> $PSM_ERROR_SUCCESS) Then
        Error(PSMGenericClient_PSMGetLastErrorString())
    EndIf

    MessageUserOff()
    LogWrite("Terminating Dispatcher Utils Wrapper")
    PSMGenericClient_Term()

    Return $PSM_ERROR_SUCCESS
EndFunc

;==================================
; Functions
;==================================

; #FUNCTION# ====================================================================================================================
; Name...........: Error
; Description ...: An exception handler - displays an error message and terminates the dispatcher
; Parameters ....: $ErrorMessage - Error message to display
;                  $Code         - [Optional] Exit error code
; ===============================================================================================================================
Func Error($ErrorMessage, $Code = -1)

   ; If the dispatcher utils DLL was already initialized, write an error log message and terminate the wrapper
   If (PSMGenericClient_IsInitialized()) Then
        LogWrite($ErrorMessage, $LOG_LEVEL_ERROR)
        PSMGenericClient_Term()
   EndIf

   MessageUserOn("ERROR - PROCESS IS SHUTTING DOWN", $ErrorMessage)
    sleep($g_ErrorMessageTime)
    ; If the connection component was already invoked, terminate it
    If ($ConnectionClientPID <> 0) Then
        ProcessClose($ConnectionClientPID)
        $ConnectionClientPID = 0
    EndIf
    Exit $Code

EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: LogWrite
; Description ...: Write a PSMWinSCPDispatcher log message to standard PSM log file
; Parameters ....: $sMessage - [IN] The message to write
;                  $LogLevel - [Optional] [IN] Defined If the message should be handled as an error message or as a trace messge
; Return values .: $PSM_ERROR_SUCCESS - Success, otherwise error - Use PSMGenericClient_PSMGetLastErrorString for details.
; ===============================================================================================================================
Func LogWrite($sMessage, $LogLevel = $LOG_LEVEL_TRACE)
    Return PSMGenericClient_LogWrite($LOG_MESSAGE_PREFIX & $sMessage, $LogLevel)
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: AssertErrorLevel
; Description ...: Checks If error level is <> 0. If so, write to log and call error.
; Parameters ....: $error_code - the error code from last function call (@error)
;                  $message - Message to show to user as well as write to log
;                  $code - exit code (default -1)
; Return values .: None
; ===============================================================================================================================
Func AssertErrorLevel($error_code, $message, $code = -1)
   ;Unblock input so user can exit
    If ($error_code <> 0) Then
        LogWrite(StringFormat("AssertErrorLevel - %s :: @error = %d", $message, $error_code), $LOG_LEVEL_ERROR)
        Error($message, $code)
    EndIf
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: MessageUserOn
; Description ...: Writes a message to the user, and keeps it indefinitely (until function call to MessageUserOff)
; Parameters ....: $msgTitle - Title of the message
;                  $msgBody - Body of the message
; Return values .: none
; ===============================================================================================================================
Func MessageUserOn(Const ByRef $msgTitle, Const ByRef $msgBody)
    SplashOff()
    SplashTextOn ($msgTitle, $msgBody, -1, 54, -1, -1, 0, "Tahoma", 9, -1)
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: MessageUserOff
; Description ...: See SplashOff()
; Parameters ....:
;
; Return values .: none
; ===============================================================================================================================
Func MessageUserOff()
    SplashOff()
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: BlockAllInput
; Description ...: Blocks all input (mouse & keyboard). Use when login process runs and visible, so user can't
;                  manipulate the process
; Parameters ....:
; Return values .: none
; ===============================================================================================================================
Func BlockAllInput()
    LogWrite("Blocking Input")
    ;Block all input - mouse and keyboard
    If IsDeclared("s_KeyboardKeys_Buffer") <> 0 Then
        _BlockInputEx(1)
        AssertErrorLevel(@error, StringFormat("Could not block all input. Aborting... @error: %d", @error))
    Else
        BlockInput(1)
        AssertErrorLevel(@error, StringFormat("Could not block all input. Aborting... @error: %d", @error))
    EndIf

EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: UnblockAllBlockProhibited
; Description ...: Allows all input from the user, except for prohibited keys (such as F11).
; Parameters ....:
;
; Return values .: none
; ===============================================================================================================================
Func UnblockAllBlockProhibited()
    If IsDeclared("s_KeyboardKeys_Buffer") <> 0 Then
        _BlockInputEx(0)
        _BlockInputEx(3, "", "{F11}|{Ctrl}") ;Ctrl - also +C? +V?...
    Else
        BlockInput(0)
    EndIf
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: SetupEdge
; Description ...: Prepares Webdriver capabilities
; Parameters ....: None
; Return values .: $sDesiredCapabilities
; ===============================================================================================================================
Func SetupEdge()
    Local $sURL = "https://" & $TargetAddress
    Local $sDriver  = RunAs($TargetUsername, $TargetDomain, $TargetPassword, 2, $DriverPath, "", @SW_HIDE)
    _WD_Option('Driver', $sDriver)
    _WD_Option('Port', 9515)
    _WD_Option('DriverParams', '--verbose --log-path="' & @ScriptDir & '\msedge.log"')
    Local $sDesiredCapabilities = '{"capabilities": {"alwaysMatch": {"ms:edgeOptions": {"args": [ "start-maximized" , "--app=' & $sURL & '"], "binary": "' & StringReplace (@ProgramFilesDir, "\", "/") & '/Microsoft/Edge/Application/msedge.exe", "excludeSwitches": [ "enable-automation"]}}}}'
    
    _WD_Startup()
    $sSession = _WD_CreateSession($sDesiredCapabilities)
    Return $sSession
    
 EndFunc

 

Link to comment
Share on other sites

22 hours ago, abs said:
    Local $sDriver  = RunAs($TargetUsername, $TargetDomain, $TargetPassword, 2, $DriverPath, "", @SW_HIDE)

This is supposed to be the name of the driver executable, not a PID.

Try the following before calling _WD_Startup --

  • Use RunAs to launch the executable manually
  • _WD_Option('driverdetect', true)

 

Link to comment
Share on other sites

4 hours ago, abs said:

Thanks @Danp2, I will try your suggestion today. Is there anything I remove from the existing code when defining the _WD_Option ?

 

Hi @Danp2, I updated the SetupEdge() function with below but the session still doesn't inherit the domain credentials. It is worth mentioning that the executables are launched by local user and hence the need to use RunAs capability to support the IWA.

I did write another script too that uses RunAs to open the msedge executable and open website but that uses Send commands to enter the credentials and highly sensitive to losing screen focus, send password in wrong field etc. When it does login successfully, the embedded URLs (use windows authentication) open just fine in next tab.

The webdriver script returns error 403 in browser when sub URLs are opened.

 

Func SetupEdge()
    Local $sURL = "https://" & $TargetAddress
    
    RunAs($TargetUsername, $TargetDomain, $TargetPassword, 2, $DriverPath, "", @SW_HIDE) ; As advised by DanP2
    _WD_Option('driverdetect', true)
    _WD_Option('Port', 9515)
    _WD_Option('DriverParams', '--verbose --log-path="' & @ScriptDir & '\msedge.log"')
    
    Local $sDesiredCapabilities = '{"capabilities": {"alwaysMatch": {"ms:edgeOptions": {"args": [ "start-maximized" , "--app=' & $sURL & '"], "binary": "' & StringReplace (@ProgramFilesDir, "\", "/") & '/Microsoft/Edge/Application/msedge.exe", "excludeSwitches": [ "enable-automation"]}}}}'
    
    _WD_Startup()
    $sSession = _WD_CreateSession($sDesiredCapabilities)
    Return $sSession
    
 EndFunc

 

Link to comment
Share on other sites

You still need to have something like this in your setup routine --

_WD_Option('Driver', 'msedgedriver.exe')

Without this, I suspect that _WD_Startup is aborting with an error. Also, I would recommend commenting out this line in your code --

$_WD_DEBUG = $_WD_DEBUG_None

That way you can examine the debugging logs to troubleshoot your script.

Link to comment
Share on other sites

9 minutes ago, Danp2 said:

You still need to have something like this in your setup routine --

_WD_Option('Driver', 'msedgedriver.exe')

Without this, I suspect that _WD_Startup is aborting with an error. Also, I would recommend commenting out this line in your code --

$_WD_DEBUG = $_WD_DEBUG_None

That way you can examine the debugging logs to troubleshoot your script.

Thanks Dan. I updated the function and also removed the Debug_None from script. The 403 is still there. Can't wrap my head round what might cause the domain creds to not work for webdriver utility. I will update the script to use chromedriver instead and see if that helps.

Func SetupEdge()
    Local $sURL = "https://" & $TargetAddress
    
    
    _WD_Option('Driver', 'msedgedriver.exe')
    _WD_Option('Port', 9515)
    _WD_Option('DriverParams', '--verbose --log-path="' & @ScriptDir & '\msedge.log"')
    RunAs($TargetUsername, $TargetDomain, $TargetPassword, 2, $DriverPath, "", @SW_HIDE) ; As advised by DanP2
    _WD_Option('driverdetect', true)
    
    Local $sDesiredCapabilities = '{"capabilities": {"alwaysMatch": {"ms:edgeOptions": {"args": [ "start-maximized" , "--app=' & $sURL & '"], "binary": "' & StringReplace (@ProgramFilesDir, "\", "/") & '/Microsoft/Edge/Application/msedge.exe", "excludeSwitches": [ "enable-automation"]}}}}'
    
    _WD_Startup()
    $sSession = _WD_CreateSession($sDesiredCapabilities)
    Return $sSession
    
 EndFunc

 

Link to comment
Share on other sites

58 minutes ago, Danp2 said:

You just reminded me that this won't work with Edgedriver. See here for full details.

Thanks Dan. In my case, the edge webdriver does open the browser and interact with DOM elements and finish the signin in seconds just fine. No error. What it fails to do is open the other links within the main website and return with 403 error. I also noticed the webdriver session doesn't really honour tabbed browsing. When you hit a link within the session, the new page with error 403 opens in a new window instead of another tab. That's not the default behaviour of Edge though (tabbed browsing is enabled in GPO). If I launch the website manually, all the pages open in next tab.

It kind of all makes sense to me now. As I mentioned earlier, the driver is run by a local account (not domain), so RunAs is a must to let that local account create browser session as a domain account to honour windows authentication used by other URLs within parent website. Presumably, these embedded websites are configured with kerberos authentication in which case all the individual DNS entries have to be allowed in sites to zones assignment list through GPO and also enable ntlm or negotiate under HTTP basic authentication policy.

I will give it a go on Monday and share the findings here. May be the problem wasn't RunAs at all.

Link to comment
Share on other sites

  • 3 weeks later...
  • Solution

Update:

Problem was with Kerberos authentication breaking sso. RunAs is working like a charm with below.

Func SetupEdge()
        
    _WD_Option('Driver', 'msedgedriver.exe')
    _WD_Option('Port', 9515)
    _WD_Option('DriverParams', '--debug --log-path="' & @appDataDir & '\msedge.log"')
    RunAs($TargetUsername, $TargetDomain, $TargetPassword, 2, $DriverPath, "", @SW_HIDE) ; As advised by DanP2
    _WD_Option('driverdetect', true)
    
    ; Run the website in application mode
    Local $sURL = "https://" & $TargetAddress
    Local $sDesiredCapabilities = '{"capabilities": {"alwaysMatch": {"ms:edgeOptions": {"args": [ "start-maximized" , "--app=' & $sURL & '"], "binary": "' & StringReplace (@ProgramFilesDir, "\", "/") & '/Microsoft/Edge/Application/msedge.exe", "excludeSwitches": [ "enable-automation"]}}}}'
    
    ;Local $sDesiredCapabilities = '{"capabilities": {"alwaysMatch": {"ms:edgeOptions": {"args": [ "start-maximized", "user-data-dir=C:\\Users\\' & @UserName & '\\AppData\\Local\\Microsoft\\Edge\\User Data\\", "profile-directory=Default"], "excludeSwitches": [ "enable-automation"]}}}}'
    
    _WD_Startup()
    $sSession = _WD_CreateSession($sDesiredCapabilities)
    Return $sSession
    
 EndFunc

Thanks @Danp2

 

Regards

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...