JDBC Source: Create/Modify Operation Intermittently Working for Custom Built UserID Generator

Hello,

This may be a continuation of Provisioning Plan Request Not Including Attributes From Create Account

We moved from an old connector that was used to create the userid. The old connector database was migrated, with some unnecessary columns removed, but the existing data intact.

When an identity is introduced (new or existing), we have some source accounts to be created/enabled via the identity profile provisioning; this source is not a part of the selected provisioning off this identity profile. We have the source configured to be ran if the identity has a specific role and access profile.

When a new identity is created and the record does not already exist in the connector database, life is seemingly fine. It goes through the CREATE operation and the userID is returned to sailpoint for other downstream connectors.

When an existing identity is re-activated, the EEID(nativeIdentity) is correlated, and a MODIFY operation is done. It seems to repeatedly re-issue the MODIFY operation, for around 4 times.

The first will update it correctly, but then will not exit the IF and repeat the initial loop at line 24 if ( plan instanceof ProvisioningPlan ) {. Instead of the EEID(nativeIdentity) being referenced again, it returns ??? for the attributeValue. It repeats this until it exits and issues a single identity lookup using the single account query that is configured for the connector. It says object not found, so I assume it is attempting to pass the ???, rather than the real nativeIdentity. If I manually execute the sql with the assumed nativeIdentity, the expected record is returned.

I believe this may be due to new lines; however, I am not strong in Java and may be wrong? If anyone could please confirm that, it’d be great. I’ve attached the code:

import java.sql.Types;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.Date;
import java.util.HashMap;
import java.util.Map;

import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
import sailpoint.object.ProvisioningResult;

import sailpoint.tools.Util;

// Start of JDBC UserID Provisioning rule
log.error(\"Enter UserID Provisioning\");

ProvisioningResult result = new ProvisioningResult();
CallableStatement cStatement = null;
String defaultRole = \"RolesAllUsers\";
Map attributeMap = new HashMap();

// Check if plan is null
if ( plan instanceof ProvisioningPlan ) {
    // Get all account requests from plan
    List accounts = plan.getAccountRequests();
    // If the plan contains one or more account requests, we'll iterate through them
    if ((accounts != null) && (accounts.size() > 0)) {
      for (AccountRequest account : accounts) {
        String nativeId = (String) account.getNativeIdentity();
        log.error(\"native identity: \" + nativeId);
        log.error(\"application: \" + account.getApplicationName());
        log.error(\"operation: \" + account.getOperation());

        List attributeRequests = account.getAttributeRequests();

        if (attributeRequests != null) {
          log.error(\"Num of AttributeRequests: \" + attributeRequests.size());
          // iterate through each attribute request
          for (AttributeRequest attributeRequest : attributeRequests) {
            String attributeName = attributeRequest.getName();
            Object attributeValue = attributeRequest.getValue();
            attributeMap.put(attributeName,attributeValue);
          }
        log.error(\"map: \" + attributeMap.toString());
        }

        try {
          // CREATE procedure
          if ( AccountRequest.Operation.Create == account.getOperation()) {
            log.error(\"Entering Create Operation\");
            cStatement = connection.prepareCall(\"EXEC dbo.LoadUniqSAM ?, ?, ?, ?, ?, ?, ?, ?\");
            // Add attributes from account create profile
            log.error(\"setting value for index 1\");
            cStatement.setString(1, attributeMap.get(\"empID\"));
            log.error(\"map attr-empID: \" + attributeMap.get(\"empID\"));
            log.error(\"setting value for index 2\");
            cStatement.setString(2, attributeMap.get(\"firstname\"));
            log.error(\"map attr-firstname: \" + attributeMap.get(\"firstname\"));
            log.error(\"setting value for index 3\");
            cStatement.setString(3, attributeMap.get(\"lastname\"));
            log.error(\"map attr-lastname: \" + attributeMap.get(\"lastname\"));
            log.error(\"setting value for index 4\");
            cStatement.setString(4, attributeMap.get(\"empStatusCode\"));
            log.error(\"map attr-empStatusCode: \" + attributeMap.get(\"empStatusCode\"));
            log.error(\"setting value for index 5\");
            cStatement.setString(5, attributeMap.get(\"empLastHireDate\"));
            log.error(\"map attr-empLastHireDate: \" + attributeMap.get(\"empLastHireDate\"));
            log.error(\"setting value for index 6\");
            cStatement.setString(6, attributeMap.get(\"empEndDate\"));
            log.error(\"map attr-empEndDate: \" + attributeMap.get(\"empEndDate\"));
            log.error(\"setting value for index 7\");
            cStatement.setString(7, (String) account.getNativeIdentity());
            log.error(\"account attr-EEID: \" + account.getNativeIdentity());
            cStatement.setString(8, attributeMap.get(\"Roles\"));
            log.error(\"map attr-Roles: \" + attributeMap.get(\"Roles\"));
            log.error(\"LoadUniqSAM payload: \" + cStatement.toString());
            cStatement.executeQuery();
            cStatement.close();

            // Successful Create, so mark result as COMMITTED
            log.error(\"Create Account Execution successfully completed\");
            result.setStatus(ProvisioningResult.STATUS_COMMITTED);
            log.error(\"Exiting Create Operation\");
          }

          else if (AccountRequest.Operation.Modify == account.getOperation()) {
            log.error(\"Entering Modify Operation\");
            cStatement = connection.prepareCall(\"EXEC dbo.LoadUniqSAM ?, ?, ?, ?, ?, ?, ?, ?\");
            // Add attributes from account modify profile
            log.error(\"setting value for index 1\");
            cStatement.setString(1, attributeMap.get(\"empID\"));
            log.error(\"map attr-empID: \" + attributeMap.get(\"empID\"));
            log.error(\"setting value for index 2\");
            cStatement.setString(2, attributeMap.get(\"firstname\"));
            log.error(\"map attr-firstname: \" + attributeMap.get(\"firstname\"));
            log.error(\"setting value for index 3\");
            cStatement.setString(3, attributeMap.get(\"lastname\"));
            log.error(\"map attr-lastname: \" + attributeMap.get(\"lastname\"));
            log.error(\"setting value for index 4\");
            cStatement.setString(4, attributeMap.get(\"empStatusCode\"));
            log.error(\"map attr-empStatusCode: \" + attributeMap.get(\"empStatusCode\"));
            log.error(\"setting value for index 5\");
            cStatement.setString(5, attributeMap.get(\"empLastHireDate\"));
            log.error(\"map attr-empLastHireDate: \" + attributeMap.get(\"empLastHireDate\"));
            log.error(\"setting value for index 6\");
            cStatement.setString(6, attributeMap.get(\"empEndDate\"));
            log.error(\"map attr-empEndDate: \" + attributeMap.get(\"empEndDate\"));
            log.error(\"setting value for index 7\");
            cStatement.setString(7, (String) account.getNativeIdentity());
            log.error(\"account attr-EEID: \" + account.getNativeIdentity());
            cStatement.setString(8, attributeMap.get(\"Roles\"));
            log.error(\"map attr-Roles: \" + attributeMap.get(\"Roles\"));
            log.error(\"LoadUniqSAM payload: \" + cStatement.toString());
            cStatement.executeQuery();
            cStatement.close();
            // Successful Modify, so mark result as COMMITTED
            log.error(\"Modify Account Execution successfully completed\");
            result.setStatus(ProvisioningResult.STATUS_COMMITTED);

            log.error(\"Exiting Modify Operation\");
          }

        } catch(Exception e) {
          result.setStatus(ProvisioningResult.STATUS_FAILED);
          result.addError(e);
          log.error(\"UserID Provisioning SQL Error: \" + e.getMessage());
        } finally {
         cStatement.close();
         }
      }
    }
}

log.error(\"Exit UserID Provisioning\");
cStatement.close();
return result;

I’m hoping if I strip out the new lines, that will cause the if to properly exit once the last statement fires.

This may be related as a result, but the provisioning rule does not return the UserID from that is generated during these situations for MODIFY operation (possibly due to the nativeIdentity being referenced as ???). The sproc attempts to correlate the record on nativeIdentity, and enters the update block if found. If the record doesn’t exist based on nativeIdentity, the sproc inserts the attributes and generates the UserID if it doesn’t exist.

The sproc should return the generated userID regardless of an UPDATE or INSERT into the table as I use an output variable that holds the needed UserID value.

SPROC:

USE [SailPoint-UserID]
GO

/****** Object:  StoredProcedure [dbo].[LoadUniqSAM]    Script Date: 12/9/2024 4:16:41 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE     PROCEDURE [dbo].[LoadUniqSAM]
    (
    @empID              NVARCHAR(50)  = NULL,
    @firstname          NVARCHAR(50)  = NULL,
    @lastname           NVARCHAR(50)  = NULL,
    @empStatusCode      NVARCHAR(1)   = NULL,
    @empLastHireDate    DATETIME      = NULL,
    @empEndDate         DATETIME      = NULL,
    @EEID               NVARCHAR(255) = NULL,
    @Roles              NVARCHAR(255) = NULL,
    @SearchSAM     NVARCHAR(10) = NULL OUTPUT
    )
AS
    DECLARE @rowcnt INT, @rowcntEEID INT, @rowcntSearchSAM INT = 0, @seqValue INT, @SearchSAMSQL NVARCHAR(500),@SearchSAMBase NVARCHAR(10), @errorCode INT, @rowcntSearchSAMSeq INT = 0,@seqMax INT, @origLastName NVARCHAR(50), @origFirstName NVARCHAR(50);
    SET @rowcntEEID = 0;
    SET NOCOUNT ON;
    SELECT @EEID AS 'EEID'
    IF ISNULL(@EEID, '') <> ''
        BEGIN
            SELECT @rowcnt = COUNT(*)
            FROM   dbo.UniqSAM
            WHERE   EEID = @EEID
            SELECT @rowcnt AS 'rowcount'
        END;
        -- Enter MODIFY operation.
        IF @rowcnt > 0
                BEGIN TRY
                    BEGIN
                    BEGIN TRANSACTION
                    SELECT @empStatusCode
                    UPDATE dbo.UniqSAM
                        SET empStatusCode = COALESCE(@empStatusCode, empStatusCode),
                            empLastHireDate = COALESCE(@empLastHireDate, empLastHireDate),
                            empEndDate = @empEndDate
                    WHERE EEID = @EEID
                    COMMIT
                    SELECT @SearchSAM = SearchSAM FROM dbo.UniqSAM WHERE EEID = @EEID
                    RETURN
                    END
                END TRY
                BEGIN CATCH
                  THROW 18,'Update Failure', 1;
                END CATCH;
        ELSE
        -- Enter CREATE operation.
        /*
        Build the concatenated base userid from agreed upon standard convention -- (firstname (4 letters),lastname (2 letters))
        The sequence is addressed after the base userid has been generated appropriately, padded with zeroes to meet 6 characters if needed.
        example base userid for John Smith: johnsm and Bo B: bob000
        */
        -- Preserve First and Last Name as is for appropriate columns.
        SELECT @origFirstName = @firstname
        SELECT @origLastName = @lastname
        GOTO SearchSAMBase
                SearchSAMBase:
                    -- strip any non alpha characters from the firstname and lastname.
                    SELECT @firstname = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@firstname, ' ', ''), '-', ''), '~', ''), '''', ''), '`', ''), '’', '');
                    SELECT @lastname = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@lastname, ' ', ''), '-', ''), '~', ''), '''', ''), '`', ''), '’', '');
                    -- generate the base userid from firstname and lastname with collation to remove diacritical marks.
                    SELECT @SearchSAMBase = CAST(LOWER(SUBSTRING(@firstname,1,4)) AS VARCHAR(MAX)) COLLATE SQL_Latin1_General_Cp1251_CS_AS + CAST(LOWER(SUBSTRING(@lastname,1,2)) AS VARCHAR(MAX)) COLLATE SQL_Latin1_General_Cp1251_CS_AS;
                    SELECT @SearchSAMBase AS SearchSAMBase
                    -- after generated base, go to sequence check to get correct sequence to append.
                    GOTO SearchSAMCheck

                SearchSAMCheck:
                    -- use CTE to query existing userids that follow the same base userid generated, return the highest sequence only.
                    WITH SearchSAM_CTE (SearchSAM)
                        AS
                        (
                        SELECT MAX(CAST(SUBSTRING(dbo.udf_GetNumeric(SearchSAM), PATINDEX('%[^0 ]%',SearchSAM + ' '), LEN(searchSAM)) AS INT))
                            FROM dbo.UniqSAM WHERE SearchSAM LIKE @SearchSAMBase + '%'
                        )
                    -- set the seqMax variable to the highest value.
                    SELECT @seqMax = SearchSAM
                    FROM SearchSAM_CTE a;
                    -- set the sequence value for the current max sequence + 1 to increment the next sequence to be used in the userid base.
                    IF ISNUMERIC(@seqMax) <> 1
                        SET @seqValue = 1
                    ELSE
                        SET @seqValue = @seqMax + 1

                    SELECT @seqValue AS CurrentSeqValue
                    -- once seqValue is set, go to the SearchSAMTrim to set the correct number of zeros for userid base and the seqValue.
                    GOTO SearchSAMTrim

                SearchSAMTrim:
                    -- checks the seqValue, and pads zeroes as needed to the SearchSAM base variable and concatenates the current sequence value to use.
                    IF @seqValue < 10
                    BEGIN
                        SELECT @SearchSAM = @SearchSAMBase + REPLICATE('0',7-LEN(@SearchSAMBase)) + cast((@seqValue) as NVARCHAR)
                        SELECT @SearchSAM AS SEARCHSAM
                        SELECT @rowcntSearchSAMSeq = COUNT(*) FROM dbo.UniqSAM WHERE SearchSAM = @SearchSAM
                        SELECT @rowcntSearchSAMSeq
                        GOTO SearchSAMGen
                    END;

                    ELSE IF @seqValue > 9
                    BEGIN
                        SELECT @SearchSAM = @SearchSAMBase + REPLICATE('0',6-LEN(@SearchSAMBase)) + cast((@seqValue) as NVARCHAR)
                        SELECT @SearchSAM AS SEARCHSAM
                        GOTO SearchSAMGen
                    END;

                    ELSE IF @seqValue > 99
                    BEGIN
                        SELECT @SearchSAM = @SearchSAMBase + REPLICATE('0',5-LEN(@SearchSAMBase)) + cast((@seqValue) as NVARCHAR)
                        SELECT @SearchSAM AS SEARCHSAM
                        GOTO SearchSAMGen
                    END;
                    ELSE
                    BEGIN
                        RAISERROR('Sequence Over 999', 18, 1)
                    END;

                SearchSAMGen:
                    -- insert newly formed SearchSAM concatenated value.
                    BEGIN
                        BEGIN TRY
                            BEGIN TRANSACTION
                                INSERT INTO dbo.UniqSAM(   empID
                                                        ,  firstname
                                                        ,  lastname
                                                        ,  SearchSAM
                                                        ,  EEID
                                                        ,  Roles
                                                        ,  empStatusCode
                                                        ,  empLastHireDate
                                                        ,  empEndDate)
                                               VALUES(    @empID
                                                       ,  @origFirstName
                                                       ,  @origLastName
                                                       ,  @SearchSAM
                                                       ,  @EEID
                                                       ,  @Roles
                                                       ,  @empStatusCode
                                                       ,  @empLastHireDate
                                                       ,  @empEndDate)
                                -- check for error code, ensure 0 is returned and finalize transaction with COMMIT or ROLLBACK.
                                SELECT @errorCode = @@ERROR
                                IF @errorCode = 0
                                    COMMIT TRANSACTION
                                ELSE
                                    ROLLBACK TRANSACTION
                        END TRY
                        -- finalize the try for either MODIFY or CREATE operations with a CATCH to indicate full stored procedure failure.
                        BEGIN CATCH
                        THROW 18,'SPROC Failure', 1;
                        END CATCH;
                    END;
                    RETURN;
GO

I feel that the incorrect MODIFY operation loop in the provisioning rule is throwing off the ability to correctly get the right nativeIdentity to correlate the correct UserID to return. I’m unable to confirm it though.

This is occuring in production, and am currently investigating if this is occurring in our sandbox/test tenant. The databases used for these should be the same, as we copy down the production environment from time to time for this connector.

I had an issue in production for a user that went through MODIFY, rather than CREATE, I noticed that the nativeIdentity it is calling is ???, rather than the correct one (which is referenced in the debug message, where the MODIFY is started)

eg:

“message”:“Provisioning [Modify] for account [nativeIdentity] starting.”
then followed with:

“message”:“Enter UserID Provisioning”,“pipeline”:“1.12.2”,“@timestamp”:“2024-12-10”,“NativeIdentity”:“???”

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.