# Creating a CyberArk (now Idira) Central Policy Manager plugin for a web application

In [a previous article, we developed a Microsoft Edge-based CyberArk (now Idira) Privileged Session Manager connection component for the web application Shopizer](https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application) and with the knowledge we gained, we will create [a Central Policy Manager plugin for a web application](https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm) to manage the password of Shopizer administrator users.

Note: You can find the platform files for the Shopizer administrator user CPM plugin at [https://github.com/aaearon/cyberark-shopizer-admin-extensions](https://github.com/aaearon/cyberark-shopizer-admin-extensions), including a [`docker-compose.yml`](https://github.com/aaearon/cyberark-shopizer-admin-extensions/blob/main/shopizer/docker-compose.yml) file that can be used to quickly stand up your own Shopizer environment.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This is article is one in a <a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://timschindler.blog/series/shopizer-admin-ext" style="pointer-events: none;">series of articles around creating PSM connectors and CPM plug-ins</a>.</div>
</div>

# Understanding what a CPM plugin for a web app is doing

A CPM plugin for a web application works fundamentally the same as a PSM connection component for a web application.

The [Web Application CPM Plugin Framework](https://cyberark.my.site.com/mplace/s/#a352J000000WU3aQAG-a392J0000013WwCQAU) uses a WebDriver to identify and interact with elements in a web page's DOM based on a set of commands that represent the verify, change, and reconcile operations. We [already covered WebDrivers and the DOM in more depth](https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application#heading-understanding-what-a-psm-connection-component-for-a-web-app-is-doing) when we created a PSM connection component.

But instead of using the `WebFormFields` property, we use `WebFormFieldsFile` -- an `.ini` file -- that will hold the commands for each CPM operation. This file will live in the `bin` folder of the Central Policy Manager and the CPM plugin framework will parse and perform the commands contained.

## A closer look at a `WebFormFieldsFile`

With an out of the box installation of the CPM there is no `WebFormFieldsFile` in the `bin` folder but [the documentation](https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7C_____4#Configureplatformsforwebapplications) tells us what it looks like (step 6): an `.ini` file with three sections -- verify, change, and reconcile.

```ini
[Verify]
# Verify commands go here, i.e. username > {username} (searchby=name)
[Change]
# Change commands go here
[Reconcile]
# Reconcile commands go here
```

The .`ini` file can be named anything so we want to make sure it is something unique and meaningful.

# Developing our Shopizer CPM plugin

We [already understand how to identify and select the best DOM elements](https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application#heading-identifying-elements-in-the-dom) so we can jump right in with creating our CPM plugin. The [Create CPM plugins for Web applications documentation](https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7C_____4#Configureplatformsforwebapplications) has pretty explicit instructions for creating the platform.

## Setting the stage

Before creating the platform, we will create two administrator users in Shopizer for the purpose of development: `shopizeradministrator@timschindler.dev` and `reconcile@timschindler.dev` -- both with full administrator rights. The `shopizeradministrator` user will be the shared user and `reconcile` will serve as the reconciliation account for it.

## Creating a new web application platform

Picking up at step 5 of [the documentation for configuring platforms for web applications](https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7C_____4#Configureplatformsforwebapplications), we need to define `VerifyURL`, `ChangeURL`, `ReconcileURL`, and `WebFormFieldsFile` for our newly created platform.

The `VerifyURL`, `ChangeURL`, and `ReconcileURL` parameters function the same as to that of `LogonURL` for PSM connection components for web apps: before executing the commands in the `WebFormFieldsFile`, the framework navigates to the URL for the relevant section. We can use dynamic parameters corresponding to account properties as part of the parameters, too (`{Address}`, `{Port}`, etc.)

The `WebFormFieldsFile` property will hold the name of the file with all the operations' commands. For our CPM plugin used to manage Shopizer administrators, we will name the file `ShopizerAdministrator.ini`.

![A screenshot of the platform settings in the PVWA showing the WebFormFieldsFile and it's value of ShopizerAdministrator.ini](https://cdn.hashnode.com/res/hashnode/image/upload/v1703686127363/3128e9be-b07a-411f-8945-4aceef83678e.png align="center")

We will keep the default values for the parameters, changing only `BrowserPath` and adding the parameter `Browser` with a value of `Edge` to reflect the fact we will use Microsoft Edge. We set `EnforceCertificate` to `No` as our Shopizer development environment does not have a valid TLS certificate (but we would want to change this to `Yes` for production.) We will leave the `VerifyURL`, `ChangeURL`, and `ReconcileURL` parameters for the time being as we will set them at same time we define the commands for the CPM operations in `ShopizerAdministrator.ini`. Our last change will be defining the value for the `Port` parameter as `4200` -- the port our Shopizer administrator portal is running as -- under `Additional Policy Settings`.

With our (incomplete) platform created, we can onboard our two Shopizer accounts into CyberArk.

![A screenshot of the PVWA with the shopizeradministrator@timschindler.dev and reconcile@timschindler.dev accounts onboarded](https://cdn.hashnode.com/res/hashnode/image/upload/v1703754826283/a65d4fa2-c15e-4007-b58f-b6350dca50ab.png align="center")

# Crafting `ShopizerAdministrator.ini`

For each operation, we need to determine:

1.  All the DOM elements that need to be interacted with to perform the operation. We need these for the commands.
    
2.  The appropriate URL to start the commands at for the particular operation (`VerifyURL`, `ChangeURL`, and `ReconcileURL`.)
    

Important to note: Unlike a PSM connection component where we can disable validations, CPM plugins for web applications require at least one validation per operation so that the CPM knows if it was successful or not.

## Verify section

As a verify operation is simply logging into the website with the vaulted credentials, we can simply [re-use what we already have from our Shopizer PSM connection component](https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application#heading-defining-our-webformfields) plus add the required validation.

This results in our `ShopizerAdministrator.ini` file looking like:

```ini
[Verify]
username>{Username} (SearchBy=name)
password>{Password} (SearchBy=name)
//button[@type="submit"]>(Button) (SearchBy=XPath)
Informations sur le magasin>(Validation) (SearchBy=text)
Store information>(Validation) (SearchBy=text)
```

Note that, unlike the `WebFormFields` for a PSM connection component, we must not escape any brackets. And because the default Shopizer language is French, we search for the text 'Store information' or 'Informations sur le magasin' to validate we have logged in successfully.

We can also use the same value for `LogonURL` that we used in our PSM connection component for `VerifyURL` in our platform however we will not hard code the port and instead use a dynamic parameter, resulting in the final value being `http://{Address}:{Port}/#/auth` .

Clicking Verify in the PVWA, we see a successful verify operation.

![A screenshot of the shopizeradministrator@timschindler.dev account details in the PVWA showing Verify was successful](https://cdn.hashnode.com/res/hashnode/image/upload/v1703758494289/aa5f62ed-f5b1-47fb-81a8-943a1765ec8b.png align="center")

## Change section

For a Shopizer administrator user to change their own password, they need to:

1.  Login to the Shopizer administrator portal.
    
2.  Navigate to their profile.
    
3.  Click Change Password from the pulldown.
    
4.  Enter the current password, the new password, the new password again, and hit save.
    

To simplify the commands in our Change section, we will use `http://{Address}:{Port}/#/auth` as `ChangeURL` and then use the `Navigate` command to have the browser go directly to where we can change the password, `http://{Address}:{Port}/#/pages/user-management/change-password`.

This results in our Change section looking like:

```ini
[Change]
username>{Username} (SearchBy=name)
password>{Password} (SearchBy=name)
//button[@type="submit"]>(Button) (SearchBy=XPath)
Informations sur le magasin>(Validation) (SearchBy=text)
Store information>(Validation) (SearchBy=text)
(Wait=1)
(Navigate=http://{Address}:{Port}/#/pages/user-management/change-password)
password>{password} (SearchBy=id)
newPassword>{newpassword} (SearchBy=id)
confirmNewPassword>{newpassword} (SearchBy=id)
//button[text()=' Save ' or text()=' Sauvegarder ']>(Click) (SearchBy=xpath)
correctement>(Validation) (SearchBy=text)
Password successfully changed>(Validation) (SearchBy=text)
```

There is a lot going on so lets break it apart.

```ini
username>{Username} (SearchBy=name)
password>{Password} (SearchBy=name)
//button[@type="submit"]>(Button) (SearchBy=XPath)
Informations sur le magasin>(Validation) (SearchBy=text)
Store information>(Validation) (SearchBy=text)
```

The first lines are a copy and paste of our Verify section as we need to login first.

```ini
(Wait=1)
(Navigate=http://{Address}:{Port}/#/pages/user-management/change-password)
```

We use a `Wait` to break the validation context and then use `Navigate` to go directly to the profile page where we can change the password.

```ini
password>{password} (SearchBy=id)
newPassword>{newpassword} (SearchBy=id)
confirmNewPassword>{newpassword} (SearchBy=id)
//button[text()=' Save ' or text()=' Sauvegarder ']>(Click) (SearchBy=xpath)
```

After entering the current password and the new password twice we search for a button that has either the text `Save` or `Sauvegarder` , which is the Save button for changing the password, because the site may load in either English or French.

```ini
correctement>(Validation) (SearchBy=text)
Password successfully changed>(Validation) (SearchBy=text)
```

Our validations cover the [Toast notification](https://en.wikipedia.org/wiki/Pop-up_notification) that briefly pops up when the password is changed successfully. What isn't visible in our `WebFormFieldsFile` is a necessary tweak to the `ActionTimeout` value.

`ActionTimeout` determines how long to wait for a command or an action to complete. When the command is clicking on a button, the framework expects the button to disappear from the page as part of the action completing. The framework will wait up to the value of `ActionTimeout` before proceeding with the next command.

Because a successful change does not result in the Save button disappearing and the Toast disappears after 3 or 4 seconds, we have to adjust `ActionTimeout` in our platform to `2` so that the validations can catch the Toasts before they are gone.

The account's password is rotated after clicking Change in the PVWA.

![A screenshot of the shopizeradministrator@timschindler.dev account details in the PVWA showing Change was successful](https://cdn.hashnode.com/res/hashnode/image/upload/v1703843020210/a4354486-ab8a-4e09-84cc-7392f70c7e63.png align="center")

## Reconcile section

Reconcile will be similar to change in that for Shopizer administrators to change the password for another user, they need to:

1.  Login to the Shopizer administrator portal.
    
2.  Navigate to the Users list.
    
3.  Find the user in the list and edit their details.
    
4.  Enter the new password, repeat the new password again, and hit Save.
    

Like the Change section, our Reconcile section will use `http://{Address}:{Port}/#/auth` as `ReconcileURL` and then use `Navigate` to navigate directly to the Users list, `http://{Address}:{Port}/#/pages/user-management/users`.

Our Reconcile section results in:

```ini
[Reconcile]
username>{reconcileaccount\username} (SearchBy=name)
password>{reconcileaccount\password} (SearchBy=name)
//button[@type="submit"]>(Button) (SearchBy=XPath)
Informations sur le magasin>(Validation) (SearchBy=text)
Store information>(Validation) (SearchBy=text)
(Wait=1)
(Navigate=http://{Address}:{Port}/#/pages/user-management/users)
//tbody//tr//td[3]//div[text()="{username}"]/ancestor::tr/td[5]//i>(Click) (Searchby=XPath)
password>{newpassword} (SearchBy=id)
repeatPassword>{newpassword} (SearchBy=id)
//button[text()=' Save ' or text()=' Sauvegarder ']>(Click) (SearchBy=xpath)
utilisateur>(Validation) (SearchBy=text)
User updated>(Validation) (SearchBy=text)
```

Like for Change, lets break it down into manageable pieces:

```ini
username>{reconcileaccount\username} (SearchBy=name)
password>{reconcileaccount\password} (SearchBy=name)
//button[@type="submit"]>(Button) (SearchBy=XPath)
Informations sur le magasin>(Validation) (SearchBy=text)
Store information>(Validation) (SearchBy=text)
```

The first lines are a copy and paste of our Verify and Change sections as however we need to login with the reconcile account so we use `{reconcileaccount\username}` and `{reconcileaccount\password}` instead of `{username}` and `{password}`.

```ini
(Wait=1)
(Navigate=http://{Address}:{Port}/#/pages/user-management/users)
```

Again, we use a `Wait` to break the validation context and then use `Navigate` to go directly to the Users list.

```ini
//tbody//tr//td[3]//div[text()="{username}"]/ancestor::tr/td[5]//i>(Click) (Searchby=XPath)
```

This is the most complex part of our CPM plugin.

The XPath expression identifies the Edit user "button" (in actuality it is an image) used to navigate to a user's details by looking for a `<div>` element with text content that matches the account's username within the third column (`<td>)` of a table row in the User's list table. Then, it goes up to the parent row (`<tr>`) and selects the fifth column (`<td>`), and finally, it searches for an `<i>` element anywhere within that fifth column (`<td>`).

Once found, it clicks on the image to navigate to that user's details page where the password can be updated.

```ini
password>{newpassword} (SearchBy=id)
repeatPassword>{newpassword} (SearchBy=id)
//button[text()=' Save ' or text()=' Sauvegarder ']>(Click) (SearchBy=xpath)
```

The new password is entered twice and the Save button is clicked.

```ini
utilisateur>(Validation) (SearchBy=text)
User updated>(Validation) (SearchBy=text)
```

Similar to changing a password, once we click Save, a toast pops up informing us that the user is updated. As `ActionTimeout` is already set to `2`, the framework spots the notification and the reconciliation should be considered successful.

Let's give it a shot.

![A screenshot of the error message in the PVWA received when trying to Reconcile shopizeradministrator@timschindler.dev](https://cdn.hashnode.com/res/hashnode/image/upload/v1703930882385/ccfdab27-7274-4f68-9d49-0ec128787c3d.png align="center")

Unlike our Verify and Change operations, Reconcile isn't working on the first attempt.

The error message says it cannot press on the Save button, which is different from being unable to find the button. Digging in the Debug logs, we see the following error:

`30/12/2023 11:06:47.438 | ERROR -> WebElementWrapper :: Click -> Failed to press on button. Exception Details: OpenQA.Selenium.ElementClickInterceptedException: element click intercepted: Element <button _ngcontent-dex-c30="" class="success_button appearance-filled size-medium status-basic shape-rectangle nb-transition" nbbutton="" nbspinnersize="large" nbspinnerstatus="control" type="button" _nghost-dex-c4="" aria-disabled="false" tabindex="0">...</button> is not clickable at point (1036, 20). Other element would receive the click: <div _ngcontent-dex-c16="" class="user-container">...</div> (Session info: MicrosoftEdge=120.0.2210.91)`

The significant part is at the end: `Other element would receive the click: <div _ngcontent-dex-c16="" class="user-container">...</div>` .

The framework is telling us that it could not click the Save button that it found at an earlier position because now another element is on top of it.

At this point, we need to see how the framework is interacting with the page and unlike with PSM connection components, we cannot easily flip on switch to see the operations (`EnableTrace=yes`) but we can test the plugin manually without having to invoke an operation in CyberArk.

# Troubleshooting Reconcile by testing the CPM plugin

Testing a CPM plugin is already [well documented by CyberArk](https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm#Step3Testtheplugin). As we already have our `WebFormFieldsFile`, we just need to create a `user.ini` file that will hold the parameters sent to the CPM plugin as part of our testing.

Our `user.ini` file -- which I've named `shopizeruser.ini` as there is no requirement around the filename -- looks like:

```ini
[targetaccount]
username=shopizeradministrator@timschindler.dev
newpassword=Password2
password=Password1
safename=Safename
foldername=Foldername
objectname=Objectname
PolicyID=Policyid
Address=192.168.178.61
Port=4200

[extrapass3]
username=reconcile@timschindler.dev
password=Password1

[extrainfo]
VerifyURL=http://{Address}:{Port}/#/auth
ChangeURL=http://{Address}:{Port}/#/auth
ReconcileURL=http://{Address}:{Port}/#/auth
WebFormFieldsFile=ShopizerAdministrator.ini
RunVerifyAfterChange=No
RunVerifyAfterReconcile=No
ActionTimout=2
PageLoadTimeout=30
EnforceCertificate=No
Debug=Yes
BrowserPath=C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
Browser=Edge
```

The `targetaccount` section holds parameters related to the account we are testing with, `extrapass3` has parameters relating to a reconcile account, and `extrainfo` has the same parameters and values as defined in our platform.

Note: The values for the `safename`, `foldername`, `objectname`, and `policyid` parameters are irrelevant for our testing but are required as they are used to form the name of the log file found in the `Logs\ThirdParty` folder.

With `shopizeruser.ini` saved to the root of the `Password Manager` folder, we can test the CPM plugin. Let's start with an operation we know works -- verify:

`.\bin\CANetPluginInvoker.exe shopizeruser.ini verifypass CyberArk.Extensions.Plugin.WebApp.dll True`

![A video showing that the Verify operation was successful when manually tested](https://cdn.hashnode.com/res/hashnode/image/upload/v1703934800377/e56df251-0f97-40c5-9146-6f04762d1cb9.gif align="center")

The plugin ends with error code 0, which is the error code returned when the operation is successful.

We know we have a working test setup so let's proceed with reconcile, with the expectation that it will fail:

![A video showing that the Reconcile operation was successful when manually tested](https://cdn.hashnode.com/res/hashnode/image/upload/v1703935299131/40dd08e7-e8f9-4983-a4ce-04e392a9825b.gif align="center")

Reconcile works when we test but not when the CPM invokes the plugin.

It's a nice confirmation that our commands for the CPM plugin are correct but we need to understand why we get different results.

Because the error message we received mentions that another element would receive the click, it's worth opening the User details page in a normal Edge browser and experiencing the page when navigating around it. It is also important to change the browser window size as we are not sure what the window size is when the CPM invokes the plugin.

![A video of interacting with the Shopizer administrator portal](https://cdn.hashnode.com/res/hashnode/image/upload/v1703936447052/08b40760-493e-4dc0-8468-4d3ce045030d.gif align="center")

At the resolution we are connected to the CPM with and the browser maximized, at no point is the Save button not visible. It's only when we put the browser in windowed mode is the Save button not visible after we focus on the Password and Repeat Password fields.

Resizing our RDP window to a smaller resolution and testing the plugin once more, we can reproduce the error that we originally received:

![A video showing that the Reconcile operation failed when manually tested](https://cdn.hashnode.com/res/hashnode/image/upload/v1703936731734/42fd05c5-8f79-4d77-8e11-92770087f1df.gif align="center")

Checking the Debug log, we see:

`30/12/2023 12:44:47.066 | ERROR -> WebElementWrapper :: Click -> Failed to press on button. Exception Details: OpenQA.Selenium.ElementClickInterceptedException: element click intercepted: Element <button _ngcontent-bkl-c30="" class="success_button appearance-filled size-medium status-basic shape-rectangle nb-transition" nbbutton="" nbspinnersize="large" nbspinnerstatus="control" type="button" _nghost-bkl-c4="" aria-disabled="false" tabindex="0">...</button> is not clickable at point (792, 20). Other element would receive the click: <div _ngcontent-bkl-c16="" class="user-container">...</div>`

The message details differ slightly but it still complains about another element receiving the click. We can reasonably assume that when the framework focuses on the Password and New Password fields to enter text, the browser window no longer shows the Save button because the floating header is covering it.

How can we move the browser window to the top of the page?

With the framework, there is no command we can define in our `WebFormFieldsFile` to have the browser scroll to the top of the page. Furthermore, we cannot send keys outside of an input field so we could not simply instruct the framework to hit the Home key.

# Fixing the Reconcile operation

The solution to fixing the Reconcile operation isn't elegant.

We know the browser scrolls to an input field when text is being entered so we need to enter text in the `First name` field. This will result in the browser 'scrolling up' and the header not covering the Save button.

Because we use `Shopizer` as the `First name` for our Shopizer users and the value of this user property is irrelevant, we can have commands to send `Shopizer` to the `First name` field before trying to click the Save button. The relevant part of our Reconcile section transforms to:

```ini
password>{newpassword} (SearchBy=id)
repeatPassword>{newpassword} (SearchBy=id)
firstname>Shopizer (SearchBy=id)
//button[text()=' Save ' or text()=' Sauvegarder ']>(Click) (SearchBy=xpath)
```

Testing our reconcile operation, we see a success:

![A video showing that the Reconcile operation was successful when manually tested](https://cdn.hashnode.com/res/hashnode/image/upload/v1703937722677/ce41d640-4aee-4059-a1e6-4bcccbe4505f.gif align="center")

But what matters is if it is successful when the CPM invokes the plugin.

![A screenshot of the shopizeradministrator@timschindler.dev account details in the PVWA showing Reconcile was successful](https://cdn.hashnode.com/res/hashnode/image/upload/v1703937906294/7a174f2e-e31d-4e8a-ab70-97af1a35db94.png align="center")

Adding the command to populate the `First name` field so that the browser would have the Save button in view worked!

Our final Reconcile section is:

```ini
[Reconcile]
username>{reconcileaccount\username} (SearchBy=name)
password>{reconcileaccount\password} (SearchBy=name)
//button[@type="submit"]>(Button) (SearchBy=XPath)
Informations sur le magasin>(Validation) (SearchBy=text)
Store information>(Validation) (SearchBy=text)
(Wait=1)
(Navigate=http://{Address}:{Port}/#/pages/user-management/users)
//tbody//tr//td[3]//div[text()="{username}"]/ancestor::tr/td[5]//i>(Click) (Searchby=XPath)
password>{newpassword} (SearchBy=id)
repeatPassword>{newpassword} (SearchBy=id)
firstname>Shopizer (SearchBy=id)
//button[text()=' Save ' or text()=' Sauvegarder ']>(Click) (SearchBy=xpath)
utilisateur>(Validation) (SearchBy=text)
User updated>(Validation) (SearchBy=text)
```

# Next steps

Our CPM plugin for Shopizer administrator users works but there are some improvements that can be made before rolling out to production:

1.  Each operation should include Failure commands so that meaningful error messages can be shown in the PVWA.
    
2.  We should change `EnforceCertificate` to `Yes` after securing all our Shopizer administrator portals with trusted TLS certificates.
    
3.  Evaluate if using the Web App framework is the best solution. The Shopizer administrator portal leverages REST APIs and our CPM plugin would be less prone to breaking if we call the endpoints directly.
    

If you are interested in developing your own CPM plugin for Shopizer administrator users, you can use [this docker-compose.yml](https://github.com/aaearon/cyberark-shopizer-admin-extensions/blob/main/shopizer/docker-compose.yml) to quickly stand up a containerized Shopizer environment. Check out the [README](https://github.com/aaearon/cyberark-shopizer-admin-extensions/tree/main#standing-up-your-own-shopizer-environment-with-docker-compose) for more information. The same GitHub repository contains the platform files and `WebFormFieldsFile`.
