Well found that my Certificate had expired, so I replaced the certificate with one that I had gotten created for something else I was working on. But it is still not working through the LDAP Test or when I attempt to sign on with my AD account...

Using Test LDAP Authentication I am receiving... SearchExts error: 1 - Operations error

When I attempt to sign into the port with my AD account I am getting the following...

Error message: ERROR #798: Password login failed
ERROR #838: User roth16 does not exist
ERROR #798: LDAP login failed
ERROR #5002: ObjectScript error: <ILLEGAL VALUE>LDAPLogin+47^%SYS.LDAP
Web Application: /csp/sys
$I: |TCP|1972|2013912
$P: |TCP|1972|2013912

Since I moved from Delegated Sign on using ZAUTHENTICATE to LDAP do I need to remove my ZAUTHENTICATE? or does the LDAP functionality know not to use ZAUTHENTICATE?

When I attempted that...

ROUTINE ZAUTHENTICATE
ZAUTHENTICATE(ServiceName,Namespace,Username,Password,Credentials,Properties) PUBLIC {

#include %occErrors
  
#include %sySecurity
 
 //Make sure we have an error trap to handle unexpected errors
 s $ZT="Error"
 s Name=$zcvt(Username,"U")
 s Name=$p(Username,"@",1)
 s UserRecord=$g(^USERS(Name))
 i UserRecord="" q $SYSTEM.Status.Error($$$UserDoesNotExist,Username)
 d $System.Security.Users.ImpersonateUser()
 s $ZE="No ZAUTHENTICATE routine found" g Error
 q $SYSTEM.Status.OK()
Error //Handle any COS errors here
 s $zt=""
 q $SYSTEM.Status.Error(5002 /*$$$CacheError*/,$ze)
 }
GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public {
#include %occErrors
#include %sySecurity
#include %sySite
#include %syLDAP
#include %occErrors    
    
    Quit $SYSTEM.Status.Error($$$GetCredentialsFailed)

}
#include %occErrors
#include %sySecurity
#include %sySite
#include %syLDAP
#include %occErrors
#define LDAPServer $Get(^OSUMCLDAP("Server"))
#define WindowsLDAPServer 1
#define WindowsCacheClient 0
#define UseSecureConnection 1
#define UnixCertificateFile ##class(%Library.File).ManagerDirectory()_"LDAPKeyStore/ProdAll.pem"
#define WindowsBaseDN "dc="_$Get(^OSUMCLDAP("Domain"))_",dc=edu"
#define WindowsFilter "sAMAccountname"
#define WindowsAttributeList $lb("displayName","department","mail")

 w !,"Initializing LDAP connection to "_$$$LDAPServer,!
 Read !,"Username: ",Username
 Read !,"Password: ",Password
 
 //Make sure we have an error trap to handle unexpected errors
 s $zt="Error"
 
 s Status = 0
 i Password="" {
         s Status = $SYSTEM.Status.Error($$$UserInvalidPassword)
        g Error
 }

 i $$$WindowsLDAPServer{
    s AdminDN="CN="_$Get(^OSUMCLDAP("User"))_",OU=Authentication Accounts,DC="_$Get(^OSUMCLDAP("Domain"))_",DC=edu"
    s AdminPW=$Get(^OSUMCLDAP("Pass"))
 }
 
 #;The following line sets up the internal LDAP structures. 
 i $$$ISWINDOWS,$$$UseSecureConnection {
    s LD=##Class(%SYS.LDAP).Init($$$LDAPServer,$$$LDAPPORT)
 } else {
     s LD=##Class(%SYS.LDAP).Init($$$LDAPServer)
 }

 i LD=0 {
    s Status=##Class(%SYS.LDAP).GetLastError()
    s Status="Init error: "_Status_" - "_##Class(%SYS.LDAP).Err2String(Status)
    g Error
 }
 s Status=##Class(%SYS.LDAP).SetOption(LD,$$$LDAPOPTXTLSCACERTFILE,$$$UnixCertificateFile)
 i Status'=$$$LDAPSUCCESS{
     s Status ="ldap_SetOption(Certificate) error: "_Status_" - "_##Class(%SYS.LDAP).Err2String(Status) 
     #;w Status,!
     g Error
 }.....

I am getting the following error when I attempt to use my AD account...

Error message: ERROR #798: Delegated login failed
ERROR #1403: Routine ZAUTHENTICATE requires the following parameters: (ServiceName,Namespace,Username,Password,.Credentials,.Properties)

If I change Quit $SYSTEM.Status as OK instead of ERROR I get a SYSTEM UNAVAILABLE message.

The issue was that I was trying to call GetCredentials within the ZAUTHENTICATE{}, if I move GetCredentials outside of }, the Delegation Authentication no longer works and I get...

Event Data Error message: ERROR #798: Delegated login failed
ERROR #822: Access Denied
Web Application: /csp/sys
$I: |TCP|1972|1659953
$P: |TCP|1972|1659953

its sort of like...

ZAUTHENTICATE(ServiceName,Namespace,Username,Password,Credentials,Properties) PUBLIC {

#include %occErrors
#include %sySecurity
#include %sySite
#include %syLDAP
#include %occErrors....

}

GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public {

  // For console sessions, authenticate as _SYSTEM.
  If ServiceName="%Service_Console" {
    Set Username="_SYSTEM"
    Set Password="SYS"
    Quit $SYSTEM.Status.OK()
  }

  // For a web application, authenticate as AdminUser.
  If $isobject($get(%request)) { 
    If %request.Application="/csp/samples/" {
      Set Username="AdminUser"
      Set Password="Test"
      Quit $System.Status.OK()
    }

  }

  // For bindings connections, use regular prompting.
  If ServiceName="%Service_Bindings" {
    Quit $SYSTEM.Status.Error($$$GetCredentialsFailed)
  }  // For all other connections, deny access.
  Quit $SYSTEM.Status.Error($$$AccessDenied)
}

When GetCredentials is called I want it to use the code in ZAUTHENTICATE, so what is the proper format or order that this needs to be done to make this happen...

Double check that the JDBC 12.2.0 driver has the appropriate permissions, and is still compatible with SQL 2005. It doesn't look like that driver is compatible anymore with 2005. 

Microsoft JDBC Driver Support Matrix

Might need to use the jTDS driver but it has it own flaws..jTDS Driver

You can try testing the connection via Terminal..https://docs.intersystems.com/iris20232/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQLGateway by getting the connection and using TestConnection() to see if it will give you a more verbose output or errors to variables.

Thanks for the suggestion. 

I was able to find the following error...

ERROR #9404: Unexpected field in input, LastName, using class base mapping.

It shouldn't matter the order in which the Object Class was defined would it?

as my Base class references another Class

class User.REST.Epic.Msg.GetPatientLocationResponse Extends (Ens.Response, %JSON.Adaptor)
{
Property Results As list Of User.REST.Epic.dt.Response;
}
Class User.REST.Epic.dt.Response Extends (%SerialObject, %XML.Adaptor, %JSON.Adaptor)
{
Property AppointmentSchedules As User.REST.Epic.dt.ArrayOfScheduleProviderReturn(%JSONFIELDNAME = "AppointmentSchedules");
Property AttendingPhysicians As User.REST.Epic.dt.ArrayOfAttendingPhysician(%JSONFIELDNAME = "AttendingPhysicians");
Property CareTeamPCPs As User.REST.Epic.dt.ArrayOfCareTeamPCP(%JSONFIELDNAME = "CareTeamPCPs");
Property ChargeSlipNumber As %String(%JSONFIELDNAME = "ChargeSlipNumber", MAXLEN = "");
Property DateOfBirth As %String(%JSONFIELDNAME = "DateOfBirth", MAXLEN = "");
Property DepartmentIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "DepartmentIDs");
Property EncounterDate As %String(%JSONFIELDNAME = "EncounterDate", MAXLEN = "");
Property FacilityIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "FacilityIDs");
Property FirstName As %String(%JSONFIELDNAME = "FirstName", MAXLEN = "");
Property HospitalAccountIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "HospitalAccountIDs");
Property HospitalService As %String(%JSONFIELDNAME = "HospitalService", MAXLEN = "");
Property LastName As %String(%JSONFIELDNAME = "LastName", MAXLEN = "");
Property MiddleName As %String(%JSONFIELDNAME = "MiddleName", MAXLEN = "");
Property PatientClass As %String(%JSONFIELDNAME = "PatientClass", MAXLEN = "");
Property PatientPhoneNumbers As User.REST.Epic.dt.ArrayOfPhone(%JSONFIELDNAME = "PatientPhoneNumbers");
Property ProviderTeams As User.REST.Epic.dt.ArrayOfProviderTeam(%JSONFIELDNAME = "ProviderTeams");
Property Sex As %String(%JSONFIELDNAME = "Sex", MAXLEN = "");
}

and in User.REST.Epic.dt.Response definitely does not have LastName listed first, but in the JSON result I am seeing it listed as the first Property.

{"LastName":.......}

@Ashok Kumar 
So, I tried...

 set tSC = ..Adapter.SendFormDataArray(.tHTTPResposne,"POST",tHTTPRequest,,,tURL)
 set pResponse = ##class(User.REST.Epic.Msg.GetPatientLocationResponse).%New()
 set dynObject = {}.%FromJSON(tHTTPResposne.Data)
 do pResponse.%JSONImport(dynObject)
 
and tried..
set tSC = ..Adapter.SendFormDataArray(.tHTTPResposne,"POST",tHTTPRequest,,,tURL)
set pResponse = ##class(User.REST.Epic.Msg.GetPatientLocationResponse).%New()
do pResponse.%JSONImport(tHTTPResposne.Data)
But within the trace viewer GetPatientLocationResponse comes up blank

Not sure I follow... I do all the SetHeader lines, then call  Do tHTTPRequest.EntityBody.Write() is that not writting the headers to tHTTPRequest?

    do tHTTPRequest.EntityBody.Write()
    do tHTTPRequest.OutputHeaders()
    set tURL= "/../../../../"  //..Adapter.URL
    set jsonRequest = {"........."}
    SET tSC = tHTTPRequest.EntityBody.Write(jsonRequest.%ToJSON())
    set tHTTPResposne = ##class(%Net.HttpResponse).%New()
    do tHTTPRequest.OutputParams()
    set tSC = ..Adapter.SendFormDataArray(.tHTTPResposne,"POST",,,tHTTPRequest)

The issue was with the code that I was using in my Business Operation that was not calling the SSL Configuration. I have since reverted my Business Operation code back to the more standard code that is described in the Documentation, however I am still running into issues making sure the correct format of the Header is being sent in the REST API call, and how to interpret the JSON that is being returned by the REST API.

According to WRC there is a memory leak with the way the Class Path of the driver is being handled. The reason for this issue was that I had the Class Path defined within the SQL Gateway. I thought this was only due to the jTDS driver we were using, but it is an overall issue with any Java Gateway Service. So I updated the Java Gateway service I was using for the Microsoft JDBC Connection for that Database to now have the Class Path of the driver, and I have not seen issues since.

I have tried that but still unable to see the Request or Response being sent...

Class User.REST.Epic.EpicOperation Extends (EnsLib.REST.Operation, Ens.Util.JSON)

{

Parameter DEBUG As %Integer = 2;

Parameter INVOCATION = "Queue";

Method getPatientLocationRequest(pRequest As User.REST.Epic.Msg.GetPatientLocationRequest, Output pResponse As EnsLib.HTTP.GenericMessage) As %Status

{

    set tSC = $$$OK

    try{

      set tHTTPRequest = ##class(%Net.HttpRequest).%New()

      do tHTTPRequest.SetHeader("Epic-Client-ID","ed7ca30e-c16a-4053-9233-bff4f5661bb4")

      $$$TRACE("HttpRequest: "_tHTTPRequest) <this returns me just a pointer 8@%Net.HttpRequest>

      set tRequest = ##class(%DynamicObject).%New()

      set tRequest.PatientID = pRequest.PatientID

      set tRequest.PatientIDType = pRequest.PatientIDType

      set tRequest.ContactID = pRequest.ContactID

      set tRequest.ContactIDType = pRequest.ContactIDType

      set tRequest.UserID = pRequest.UserID

      set tRequest.UserIDType = pRequest.UserIDType

      set reqMsg = tHTTPRequest.EntityBody.Write(tRequest)

      $$$TRACE("reqMsg: "_reqMsg)

      set tSC = tHTTPRequest.EntityBody.Write(tRequest.%ToJSON())

      set tURL = ..Adapter.URL

      set tSC = ..Adapter.Post(tURL)

      set stream = "".....

For others I figured out the issue. Had to use the Base64 formatted Certificate Chain (p7b) from Windows ADCS (Active Directory Certificate Service).

  1. Download Base64 p7b to /etc/pki/ca-trust/source/anchors/ in RedHat
  2. Change ownership group to include irisusr
  3. Change permissions to Read (666)
  4. Convert p7b to pem
  • sudo openssl pkcs7 -in xxxxx.p7b -print_certs -out xxxxx.pem

When I went through testing the request I got the following...

DEVCLIN>set request=##class(%Net.HttpRequest).%New()

DEVCLIN>set request.Server = "xxxxxxxxxxxx"

DEVCLIN>set request.Port=443

DEVCLIN>set request.SSLConfiguration="OSUWMC"

DEVCLIN>set request.Https=1

DEVCLIN>set tSC=request.Get("/",2)
HTTP/1.1 200 OK
ACCEPT-RANGES: bytes
CACHE-CONTROL: private
CONTENT-ENCODING: gzip
CONTENT-LENGTH: 467
CONTENT-TYPE: text/html
DATE: Thu, 20 Jul 2023 20:08:54 GMT
ETAG: "b072b0f23afdd01:0"
LAST-MODIFIED: Fri, 02 Oct 2015 17:51:21 GMT
NTCOENT-LENGTH: 701
SERVER: Microsoft-IIS/8.5
X-POWERED-BY: ASP.NET

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>IIS Windows Server</title>
<style type="text/css">
<!--
body {
        color:#000000;
        background-color:#0072C6;
        margin:0;
}

#container {
        margin-left:auto;
        margin-right:auto;
        text-align:center;
        }

a img {
        border:none;
}

-->
</style>
</head>
<body>
<div id="container">
<a href="http://go.microsoft.com/fwlink/?linkid=66138&amp;clcid=0x409"><img src="iis-85.png" alt="IIS" width="960" height="600" /></a>
</div>
</body>
</html
>

I am not sure I understand... I was using the example that was provided in the documentation.

https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI.Page.cls?KEY=EREST_operation#EREST_operation_json

If I have to have a Custom Header do I not have to define in this case the Client ID as part of the %DynamicObject?

    set tRequest = ##class(%DynamicAbstractObject).%New()

    set tRequest.clientID = "Epic-Client-ID = xxxxxxxx"

if I am using a Request Class Structure (osuwmc.Epic.Access.Request.GetPatientLocationByVisit2JsonRequest) isn't the set tRequest.payload = pRequest line correct?