Pages

Tuesday, February 5, 2013

Create a Pharos Uniprint Logon Script to resolve ADLDAPLogon.exe Short Falls

I got a few calls from users complaining about our Pharos Clients getting "The username and password could not be authenticated against any servers" error Message. Now while a lot of users where getting this error it was only a small percentage of the over all print jobs. After digging around and putting a call into Pharos Support I noticed that this wasn't just a recent start of errors but rather common. If you want to check for it you can search your alerts or table or use Pharos administrator to and a Custom filter on the alerts.

SELECT        TOP (100) PERCENT message, username, client, time
FROM            dbo.alerts
WHERE        (message LIKE '%The username and password could not be authenticated against any servers%')
ORDER BY time DESC

Cause


Turns out the ADLDAPLogon.exe basically just don't do much checking as to why a username failed. From what testing I did I turns out the following all return the same error.
  • If the username appended with any  SMTP Address of @domain.com to the username
  • If the username is incorrect in that no account in Pharos with that name exists.
  • if the username doesn't match AD
  • .... I'm sure there are others.

Solution 

As such I talking with the Pharos Support Rep they could write a Plug-in that called ADLDAPLogon.exe but that it would be a charged Service on their part. As such I wrote my own this morning and figured I'd share.

The following Script will do the following check and then check if the username and password are correct.

  • Confirm that the Username exists in the Pharos Database
  • That the PlugIn.UserName is not empty
  • That the PlugIn.Password is not empty
  • Remove any @domain.com from the username
This script does require that ADLDAPLogon.exe is in place and configured correctly.

Then Create the Script in under system. Go to your bank, and change the Logon event from useing ADLDAPLogon.exe to use the newly created Logon Script instead. Do a change control and you done.




Good luck and feel free to  use and modify this script to fit your needs. 


//  PlugIn Script: Logon - NKU ADLDAPLogon.exe
// 
//  Billing PlugIn to allow stripping the username of @domain.com before passing to adldaplogon.exe 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  Project:        Pharos 8.3
//  Design:         NKU
//  Date:           5-February-2013
//  Where:          Northern Kentucky University 
//  Who:            Chris Towles written from scratch
//  --------------------------------------------------------------
//  Modifications:
//    02-05-13 Chris Towles : Created to strip the username of @domain.com before passing to adldaplogon.exe 
//   
//
// Description:
//    This Logon Script allows a full email address to be used as the username. It strips the '@' and everything that follows then calls
//
// Requirements:
//    1. adldaplogon be installed and configured
//        C:\Program Files (x86)\Pharos\Bin>adldaplogon.exe --list
//
//  
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// import namespaces
import "DB";
import "Win32";
import "String";  
import "IO";

import "User";

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constants (customizable)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//#region Constants

//...string
new sScriptName          = "Logon - NKU ADLDAPLogon.exe";
new sLogPrefix           = "[" + sScriptName + "] -> ";

IO.PrintLine("Solution: " + sScriptName);



// While the path can be hard-coded here as a string, this tends to break in heterogeneous
// environments, e.g. mixed 32- and 64-bit servers.  If possible, install plug-ins relative
// to the Pharos installation folder and query the registry for the starting point:
new sPharosPath = Win32.RegQueryValue("SOFTWARE\\Pharos\\Installed Components", "Path");

// Win32.SearchPath will return the shortened path and file names.
// Be sure to specify the remainder of the path relative to the Pharos folder.
new sADLDAPLogonPath            = Win32.SearchPath(sPharosPath + "\\Bin\\adldaplogon.exe");

//new sADLDAPLogonPath = "C:\\Program Files (x86)\\Pharos\\Bin\\adldaplogon.exe";

new eErrorInvalidUserNamePassword      = "Invalid username or password";
new eErrorInvalidUserName              = "The given username doesn't exist. Please be sure to enter your AD username.";
new eErrorPlugInResultsNotValid        = "Results from Logon plug-in are not valid. Please contact the information desk.";
new eErrorUsernameIsEmpty              = "You must enter a Username.";
new eErrorPasswordIsEmpty              = "You must enter a password.";


// - Logon Plug-in timeout (milliseconds)
new iCmdTimeout = 30000;


//#region Variables
//...boolean
new bResult = false;
new bIsPharosAccountAvailable  = false;


//...integer
new iPos;
new iUserID;


//...string
new UserName = PlugIn.UserName;
new sResultsFile;
new sADLDAPLogonCommand = "";
new sADLDAPLogonResult = "";
new sADLDAPLogonError = "";
new sResult = "";


//---------------------------------------------------------------------------------------
// Functions code
//---------------------------------------------------------------------------------------

function IsAccountAvailable(name)
{
    try
    {
        IO.PrintLine(sLogPrefix + "Get user by logon id.");
        User.GetUserByLogon(name);
        return true;
    }
    catch
    {
        IO.PrintLine(sLogPrefix + "Failed to get user by logon id. Get user by card id.");
        try
        {
            User.GetUserByCardID(name);
            return true;
        }
        catch
        {
            IO.PrintLine(sLogPrefix + "Failed to get user by logon id or card id.");
            return false;

        }
    }

}


//---------------------------------------------------------------------------------------
// PlugIn code
//---------------------------------------------------------------------------------------

//---------------------------------------------------
// Default the script to fail. Set default error
// message to Invalid Username/Password. 
//---------------------------------------------------
PlugIn.Result = false;
PlugIn.Error = eErrorInvalidUserNamePassword;


//---------------------------------------------------------------------------------------
// Clean Up the Username and strip the domain name off of it
//---------------------------------------------------------------------------------------


if (String.IsEmpty(PlugIn.UserName) )
{
    IO.PrintLine(sLogPrefix + " User entered an empty username. Fail the logon.");
    PlugIn.Result = false;
    PlugIn.Error = eErrorUsernameIsEmpty;
}
else { //strip the @Domain.com from the account. 
    
    iPos = String.Find(UserName, "@");
    if (iPos != -1)
    {
        String.Left(UserName, iPos);
    }
    iPos = 0;

    
    
    bIsPharosAccountAvailable = IsAccountAvailable(UserName);

    if (bIsPharosAccountAvailable == false)
    {
        IO.PrintLine(sLogPrefix + "User Account doesn't exist.");
        PlugIn.Result = false;
        PlugIn.Error = eErrorInvalidUserName + " : " + UserName;
    }   
    else 
    {
    
        //From now on in the script user "UserName" instead of PlugIn.UserName

        //---------------------------------------------------
        // Check if the user must enter a password (if
        // enabled.
        //---------------------------------------------------
        if (String.IsEmpty(PlugIn.Password) )
        {
            IO.PrintLine(sLogPrefix + "User entered an empty password. Fail the logon.");
            PlugIn.Result = false;
            PlugIn.Error = eErrorPasswordIsEmpty;
        }
        else
        {
            //---------------------------------------------------
            //Verify the users Password
            //---------------------------------------------------

            //adldaplogon.exe out.txt user  
      
            sResultsFile = Win32.GetTempFileName();
            sADLDAPLogonCommand = sADLDAPLogonPath + " " +
                        sResultsFile + 
                        " user " +
                        UserName + " " +
                        PlugIn.Password;
        
            //Write to the Pharos Print Server log Note this would have the username and password
            //IO.PrintLine ( ">sADLDAPLogonCommand :: " + sADLDAPLogonCommand);
        
            Win32.ExecProcess(sADLDAPLogonCommand, iCmdTimeout);
        
            bResult = IO.LoadFile(sADLDAPLogonResult, sResultsFile);
        
            //Write to the Pharos Print Server log
            IO.PrintLine ( "> Logon bResult :: " + bResult);
            IO.PrintLine ( "> Logon sADLDAPLogonResult :: " + sADLDAPLogonResult);

            //Clean Up and delete the Temp Output file            
            IO.DeleteFile(sResultsFile);

            if (bResult)
            {
              //get first line from sADLDAPLogonResult
              sResult = sADLDAPLogonResult;
              iPos = String.Find(sADLDAPLogonResult, "\r\n");
              if (iPos != -1)
              {
                  String.Left(sResult, iPos);
                  String.UpperCase(sResult);
                  String.Delete(sADLDAPLogonResult, 0, String.Length(sResult));
                  String.TrimLeft(sADLDAPLogonResult);
              }

              if (sResult == "FAIL")
              {
                     //ADLDAPLogon Failed
                  iPos = String.Find(sADLDAPLogonResult, "\r\n");
                  sADLDAPLogonError = sADLDAPLogonResult;
                  if (iPos != -1)
                     {
                    String.Left(sADLDAPLogonError, iPos);
                  }

                     IO.PrintLine(sLogPrefix + "ADLDAPLogon.exe returned a FAIL : " + sADLDAPLogonError);
                    
                     PlugIn.Result = false;
                     PlugIn.Error = eErrorInvalidUserNamePassword + " : " + UserName;
                     IO.PrintLine(sLogPrefix + "Returning the user this error : " + PlugIn.Error );
                 }    
              else 
                 {
                    if (sResult == "OK")
                 {
                        // Logon was successful.
                  IO.PrintLine(sLogPrefix + "ADLDAPLogon.exe login was successful for user '" + UserName + "'");
                        PlugIn.Result = true;
                    }
                   else
                    {
               //...Unkown Error 
                        IO.PrintLine(sLogPrefix + "ADLDAPLogon.exe Result was not Valid");
                    PlugIn.Error = eErrorPlugInResultsNotValid;
                    PlugIn.Result = false;
                    }
                 }
              } //end of  if (bResult)
           } //end of password check
        }// Check if Pharos Account exists
    }//End of Username Check

Tuesday, January 8, 2013

Claims Based Authentication - Setting up Visual Studio 2012 on Localhost to use ADFS

So currently I'm  learning Windows Identity Foundation to do Claims Based Authentication. I'm working an a few posts on the subject and i'll be posting them soon. For now this one focuses on the my need to configure my Visual Studio environment to use my domain ADFS server as a STS. Now I know i can setup a local STS project for testing but I wanted to use our domain ADFS so that everything as close as possible to production configuration as I could for testing.

Turns out this isn’t hard to do but a few configuration steps that are easy to miss. Since I didn't find any guides or posts on how to do this I figured I’d share mine.

Overview

The end result will be a ADFS relaying service provider that will expect inbound connection from https://localhost and a Visual studio configuration set to use IIS, rather than IIS express, hosting the VS Project. What's really interesting is that since i configured this to use https://localhost any developer can use the the same relaying party for their testing rather than creating a Replaying Party Entry in ADFS for every developer machine. Also because the links will only work from the localhost you don’t need to worry about them deploying projects using that Replying Party entry because it won’t work.

Solution

There are two sides of this setup. First I’ll cover the settings I configured on the ADFS with a relaying relaying party and this those of the client machine running Visual studio.

ADFS Configuration

From the ADFS server navigate to the Relying Party Trusts and add a new Relaying Party Trust. We need to supply a FederationMetadata.xml file. I modified this one for our use. Create a text file and copy the contents to it and save it as “Localhost_FederationMetadata.xml”

Localhost_FederationMetadata.xml

<?xml version="1.0" encoding="utf-8"?>
<EntityDescriptor ID="_3d1176b1-236d-4675-8970-674b061daf17" 
                entityID="https://localhost/"
                xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
    <RoleDescriptor xsi:type="fed:ApplicationServiceType" 
                xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706"
                protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <fed:TargetScopes><wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
            <wsa:Address>https://localhost/</wsa:Address></wsa:EndpointReference>
        </fed:TargetScopes>
        <fed:PassiveRequestorEndpoint>
            <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
                <wsa:Address>https://localhost/</wsa:Address>
            </wsa:EndpointReference>
        </fed:PassiveRequestorEndpoint>
    </RoleDescriptor>
</EntityDescriptor>

With the file created Choose “Import data about the relying party from a file”.

clip_image002