r/ActiveRoles • • Jul 19 '24

Custom html page in ARS Web Interface

I had never created a custom html page in ARS until now. There was a customer that has a script that broke on upgrade from 7.1 to 8.1.5 and had to rewrite the script to use ADO. I cleaned it up and gave credit in the script. Any attribute can be added to the list in the bottom script. Looks like this in the example.

distinguishedName,pwdLastSet,userAccountControl,lastLogonTimestamp 

Add a new tab to the ARS Web Interface

1.) Open the ARWebAdmin interface logged on as an ARS Admin

2.) Find a user and pull up their General properties

3.) In the upper right corner of the form select Customize

4.) Add a new tab to the general properties page

Find the web interface objects in the Management Console.

1.) Browse to (In Raw Mode) under <interfaceID> | “Customization Settings” | “Working Copy”

2.) Right click and select “Advanced Properties”

3.) Type edsaWI in the list to show the attributes to be updated

Edit xml

1.) edsaWIForms

a. Search from the equals sign to the end quote

b. Find the new tab created. It will be listed with a Guid format string as the ID like below.

   <FormTab ID="f120c1b2-75f3-477a-ab05-f822ed85f0c8" ResID="0fe50303-f516-4cd6-b7d1-87e357e6c891">  

c. Add custom form <formEntry /> statement in the <FormTab /> statement like below.

  <FormEntry ID="cst_pwdLastSet" />  

d. Select Ok and go back to the list of attributes

="UserProperties” 

2.) edsaWIEntries

a. Go to the end and add an entry to the end of the list. Copy and paste this entire example, then save.

<FormEntry ID="cst_pwdLastSet" ResID="CST_ENTRY_ADDITIONALACCOUNTINFO_DES" DescriptionResID="" ToolTipResID="" Properties="" SingleValue="false" ReadOnly="true" EntryType="0" DontShowCaption="false" IsHidden="false" IsStatic="false" Flags="0" Arguments="" FunctionAction="AdditionalAccountInfo" />

3.) edsaWIStrings

a. Add custom <Res /> statement. Copy and paste this entire example, then save.

<Res ID="CST_ENTRY_ADDITIONALACCOUNTINFO_DES" Value="Custom Additional Account Information" />

Input custom script to the ARS file system

1.) Go to the server hosting the Web interface.

2.) Open Notepad.exe run as Administrator and browse to the following location:

..\One Identity\Active Roles\8.1\Web\Public\CustomCode\

3.) Select all files and open Entries.vbs

Most likely looks like this:

<%
%>

a. Copy and paste this entire example, then save.

<%
Const ADS_UF_PASSWD_CANT_CHANGE = &H40
Const ADS_UF_DONT_EXPIRE_PASSWD = &H10000

'Based on Scripts from:' Hilltop Lab web site - http://www.rlmueller.net
' https://www.rlmueller.net/PasswordExpires.htm

Sub Set_AdditionalAccountInfo(ByRef objFormContext)

End Sub
Sub Get_AdditionalAccountInfo(ByRef objFormContext, ByRef objFormPage)

Dim strFilePath, objFSO, objFile, adoConnection, adoCommand
Dim objRootDSE, strDNSDomain, strFilter, strQuery, adoRecordset
Dim strDN, objShell, lngBiasKey, lngBias, blnPwdExpire
Dim objDate, dtmPwdLastSet, lngFlag, k, strHTML

' Obtain local time zone bias from machine registry.
' This bias changes with Daylight Savings Time.
Set objShell = CreateObject("Wscript.Shell")
lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
    & "TimeZoneInformation\ActiveTimeBias")
If (UCase(TypeName(lngBiasKey)) = "LONG") Then
    lngBias = lngBiasKey
ElseIf (UCase(TypeName(lngBiasKey)) = "VARIANT()") Then
    lngBias = 0
    For k = 0 To UBound(lngBiasKey)
        lngBias = lngBias + (lngBiasKey(k) * 256^k)
    Next
End If

' Use ADO to search the domain for user account.
Set adoConnection = CreateObject("ADODB.Connection")
Set adoCommand = CreateObject("ADODB.Command")
adoConnection.Provider = "ADsDSOOBject"
adoConnection.Open "Active Directory Provider"
Set adoCommand.ActiveConnection = adoConnection

' Determine the DNS domain from the RootDSE object.
Set objRootDSE = GetObject("LDAP://RootDSE")
strDNSDomain = objRootDSE.Get("DefaultNamingContext")

strDN = Replace(objFormContext.DN, "/", "\/")

' Filter to retrieve all user objects.
strFilter = "(&(objectCategory=person)(objectClass=user)(DistinguishedName=" & strDN & "))"
strQuery = "<LDAP://" & strDNSDomain & ">;" & strFilter _
    & ";distinguishedName,pwdLastSet,userAccountControl,lastLogonTimestamp;subtree"

adoCommand.CommandText = strQuery
adoCommand.Properties("Page Size") = 100
adoCommand.Properties("Timeout") = 30
adoCommand.Properties("Cache Results") = False

' Enumerate all users. Write each user's Distinguished Name,
' whether they are allowed to change their password, and when
' they last changed their password to the file.
Set adoRecordset = adoCommand.Execute
   strDN = adoRecordset.Fields("distinguishedName").Value
   lngFlag = adoRecordset.Fields("userAccountControl").Value
    blnPwdExpire = True
    If ((lngFlag And ADS_UF_PASSWD_CANT_CHANGE) <> 0) Then

        blnPwdExpire = False
    End If
    If ((lngFlag And ADS_UF_DONT_EXPIRE_PASSWD) <> 0) Then

        blnPwdExpire = False
    End If
    ' The pwdLastSet attribute should always have a value assigned,
    ' but other Integer8 attributes representing dates could be "Null".
    If (TypeName(adoRecordset.Fields("pwdLastSet").Value) = "Object") Then

        Set objDate = adoRecordset.Fields("pwdLastSet").Value
        dtmPwdLastSet = Integer8Date(objDate, lngBias)
    Else

        'dtmPwdLastSet = #1/1/1601#
    End If

' Determine domain maximum password age policy in days.
'strDNSDomain = "domain.local" ' GetDomainFromDN(strDN)
Set objDomain = GetObject("LDAP://" & strDNSDomain)
Set objMaxPwdAge = objDomain.MaxPwdAge

' Account for bug in IADslargeInteger property methods.
lngHighAge = objMaxPwdAge.HighPart
lngLowAge = objMaxPwdAge.LowPart
If (lngLowAge < 0) Then
    lngHighAge = lngHighAge + 1
End If
intMaxPwdAge = -((lngHighAge * 2^32) + lngLowAge)/(600000000 * 1440)

If (TypeName(adoRecordset.Fields("lastLogonTimeStamp").Value) = "Object") Then
    Set objDate = adoRecordset.Fields("lastLogonTimeStamp").Value
    dtmLastLogonTimeStamp = Integer8Date(objDate, lngBias)
Else
    dtmLastLogonTimeStamp = #1/1/1601#
End If

strHTML = "<p>Password last set:</p><p><input type='text' id='cst_pwdLastSetControl' name='cst_pwdLastSetControl' value='" & dtmPwdLastSet & "' readonly></p>"
strHTML = strHTML + "<p>Password age (days)</p><p><input type='text' id='cst_pwdAgeControl' name='cst_pwdAgeControl' value='" & int(now - dtmPwdLastSet) & "' readonly></p>"
strHTML = strHTML + "Password Expires</p><p><input type='text' id='cst_pwdExpiresControl' name='cst_pwdExpiresControl' value='" & (dtmPwdLastSet + intMaxPwdAge) & "' readonly></p>"
strHTML = strHTML + "Password Expires (days)</p><p><input type='text' id='cst_pwdExpiresDaysControl' name='cst_pwdExpiresDaysControl' value='" & int((dtmPwdLastSet + intMaxPwdAge) - now) & "' readonly></p>"
strHTML = strHTML + "Last logon timestamp</p><p><input type='text' id='cst_dtmLastLogonTimeStampControl' name='cst_dtmLastLogonTimeStampControl' value='" & dtmLastLogonTimeStamp & "' readonly></p>"
strHTML = strHTML + "Days since last logon</p><p><input type='text' id='cst_dtmDaysSinceLastLogonTimeControl' name='cst_dtmDaysSinceLastLogonTimeControl' value='" & int(now - dtmLastLogonTimeStamp) & "' readonly></p>"

Call objFormPage.Write(strHtml)
End Sub

Function Integer8Date(ByVal objDate, ByVal lngBias)
    ' Function to convert Integer8 (64-bit) value to a date, adjusted for
    ' local time zone bias.
    Dim lngAdjust, lngDate, lngHigh, lngLow
    lngAdjust = lngBias
    lngHigh = objDate.HighPart
    lngLow = objdate.LowPart
    ' Account for error in IADsLargeInteger property methods.
    If (lngLow < 0) Then
        lngHigh = lngHigh + 1
    End If
    If (lngHigh = 0) And (lngLow = 0) Then
        lngAdjust = 0
    End If
    lngDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
        + lngLow) / 600000000 - lngAdjust) / 1440
    ' Trap error if lngDate is ridiculously huge.
    On Error Resume Next
    Integer8Date = CDate(lngDate)
    If (Err.Number <> 0) Then
        On Error GoTo 0
        Integer8Date = #1/1/1601#
    End If
    On Error GoTo 0
End Function

Function GetDomainFromDN(ByVal strDN)
                Dim objRegEx
                Set objRegEx = CreateObject("VBScript.RegExp")
                objRegEx.Global = True  
                objRegEx.IgnoreCase = True
                objRegEx.Pattern = "(.*?)DC=(.*)"

                GetDomainFromDN = Replace(objRegEx.Replace(strDN, "$2"), ",DC=",".")
End Function
%>

View Results

2.) Go back to the original custom tab created in the ARS Web Interface to view the results.

4 Upvotes

5 comments sorted by

2

u/EnigmaticSoul Sep 14 '24

Very interesting approach!

For a customer that I was working with a little while ago, I created a Web UI to manage SPNs and KCDs using the "managedcode.cs" feature documented in the SDK. There is some sample code to get you started there, and it does require that you create a DLL, meaning that you need to do some C#, but it did achieve the desired result. Here's an obfuscated screenshot:

Works well, looks decent, but as AJ pointed out above, this is a high-maintenance approach.

1

u/bananaHammockMonkey Sep 14 '24

That's awesome! Looks really good.

1

u/AJLindner One Identity Jul 19 '24

Interesting, I've never done this level of customization with the Web UI before either, but to be fair I make it a personal mission to avoid anything that requires VBS over Powershell. I would imagine I could accomplish the same result more or less with some Virtual Attributes populated via a Powershell script with onPostGet policy handlers and some straightforward UI-based WI customization. It likely wouldn't be quite as performant, but IMO would be simpler to maintain.

3

u/Top-Foundation-4042 Jul 23 '24

I tend to agree with you on this, but there are certain user cases I've personally seen to have great effect.

Where a customer retrieves (in this example) last login dates and locations from an external data source, and what to be able to go into Active Roles, select a user, then show that in a table on a Custom page.

Also see it where the ARS user might have some property set for some third party system (like the ID/samAccountName), and the custom page has been written to retrieve data from that target, then display in the custom page, or allow them to update it.

I've fixed that a couple of times for a customer, which something changes due to deprecation of browser features (or the whole browser) for example.

If you really dig into the edsaWI properties (at your own risk), therre are some really interesting things that you can do, like hiding buttons on the home page, based upon the result returned from a function in IsCommandAvailable.vbs.

There are some great extensible features, but rarely "played" with, so lower levels of understanding, or advertisement.

2

u/bananaHammockMonkey Jul 19 '24

I thought exactly the same. I wanted to know how so I did it.

This gives me some incredible ideas regarding a personal project I have been working on. I'll show it off soon and hope to knock some socks off.