Question
· Aug 22, 2023

GetCredentials() within ZAUTHENTICATE

My original ZAUTHENTICATE.mac to use Delegated sign on did not include GetCredentials(), however I am being told it probably should have it so I am eliminate an error I am seeing when trying to troubleshoot the ZAUTHENTICATION. I am trying to add the GetCredentials() from the documentation to the existing ZAUTHENTICATE.mac  but I am getting an error

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)
}


ERROR: ZAUTHENTICATE.int(74) #1044: PUBLIC label not allowed : 'Public' : Offset:74 [GetCredentials^ZAUTHENTICATE]
 TEXT: GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public {
ERROR: ZAUTHENTICATE.int(252) #1026: Invalid command : '}' [}^ZAUTHENTICATE]
 TEXT: }

 

If I remove PUBLIC...

ERROR: ZAUTHENTICATE.int(74) #1026: Invalid command : 'GetCredentials(ServiceName,Namespace,Username,Password,Credentials){' : Offset:67 [GetCredentials^ZAUTHENTICATE]
 TEXT: GetCredentials(ServiceName,Namespace,Username,Password,Credentials){
ERROR: ZAUTHENTICATE.int(252) #1026: Invalid command : '}' [}^ZAUTHENTICATE]
 TEXT: }

Product version: IRIS 2023.1
Discussion (7)3
Log in or sign up to continue

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...

First I have had success just leaving the GetCredentials out, but you are correct in that the documentation says you should have this.  Change your GetCredentials code to be just

return $$$GetCredentialsFailed

This will cause the process to revert to normal username and password prompting.   You only need to implement GetCredentials code if you are pulling the username and password from somewhere else such as taking the authentication header out of a REST call.

Link to the docs   https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GAUTHN_delegated#GAUTHN_delegated_zauthgetcreds

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.

Please consider returning the error code as shown below for when you are NOT providing a delegated set of credentials. We use this to allow for "break the glass" access by an authorized Systems Administrator using sudo irisdb without having them required to enter another password:
 

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

 s $zt="Error"

 // The following example sets the processes Username/Password without prompting them
 // to _SYSTEM/SYS if a console,and Admin/SYS if a telnet user.
 // If a Bindings service, then we will go through the normal login screen.

 ; i ServiceName="%Service_Console" {
 ;      s Username="_SYSTEM"
 ;      s Password="SYS"
 ;      q $SYSTEM.Status.OK()
 ; }

    ;do ##class(%SYS.System).WriteToConsoleLog("GetCredentials("_$g(ServiceName)_","_$g(Namespace)_","_$g(Username)_","_$g(Password)_","_$g(Credentials)_")",0,0)
    if (ServiceName = "%Service_Terminal") && ($username = "root") {
        set Username = "root"
        set Password = "should match whatever is set for local user"
        quit $SYSTEM.Status.OK()
    }

 // For Bindings applications, force them to normally enter a username
 ; i ServiceName="%Service_Bindings" q $SYSTEM.Status.Error($$$GetCredentialsFailed)
 // If a CSP application, we can test for a specific application by looking at
 // the request object. If it exists, then the request is from a CSP application.

 ;If $isobject($get(%request)) {
 ;  if %request.Application="/csp/samples/" {
 ;      set Username="_SYSTEM"
 ;      set Password="SYS"
 ;      quit $System.Status.OK()
 ;  }
 ;}

 // Here is an example where I get the username and password out of a http request where the password
 // was set the following way:
 // d httpreq.InsertParam("username","TESTDEL")
 // d httpreq.InsertParam("password","SYS")
 ;If $isobject($get(%request)) {
 ;  if %request.Application="/csp/samples/" {
 ;      s Username=$get(%request.Data("username",1))
 ;      s Password=$get(%request.Data("password",1))
 ;      quit $System.Status.OK()
 ;  }
 ;}

 // For any other service, deny them access
 ;q $SYSTEM.Status.Error($$$AccessDenied)

 // By default, we allow the system to do its normal Username/Password prompting.
 q $SYSTEM.Status.Error(1419 /*$$$GetCredentialsFailed*/)

Error //Handle any COS errors here
 //Reset error trap to avoid infinite loop
 s $zt=""

 //Return the generalized COS error message #5002
 q $SYSTEM.Status.Error(5002 /*$$$CacheError*/,$ze)

}