...
- Call the ServiceValidate method from the main (Proxier) application for CAS authentication and include a pgtUrl attribute.
Note title About the pgtUrl The pgtUrl MUST be part of the same application as the CAS Proxier AND be on a secure server with a valid RSA or Verisign SSL Certificate
- The pgtUrl receives back from CAS the pgtIou/pgtId pair and stores them in an Application variable (e.g. Application(<value of pgtIou>) = <value of pgtId>)
Info title The use of Application variables Application variables MUST be used instead of Session variables. This is because of the extra trip CAS has to make to the pgtUrl. Your application would lose it's session variables otherwise. This is NOT true in basic CAS Authentication so, you will notice through out these examples that I chose to use Application variables instead of Session variables for this reason. Also, note the use of the name of the Application variable being the value of the pgtIou. This is important because the main (Proxier) application will need to retrieve the pgtId sent to the pgtUrl and the only value it will know is the proxyGrantingTicket IOU (pgtIou).
- The main (Proxier) application retrieves the pgtId value from the Application variable stored by the pgtUrl and then calls the RequestProxyTicket
method passing in the pgtId and the proxy application complete Url.Info title The CAS Proxied Application The actual proxy application can be any CAS-enabled application that can call proxyValidate on the supplied proxyTicket and it does not have to reside on the same application server like the pgtUrl and main Proxier applications do._
- With the "real" proxyTicket in it's grasp, the main (Proxier) application now calls AddProxyArgument as many times as it needs to add the arguments the proxied application will need. At the bare minimum, the proxyTicket itself MUST be added as an argument.
Info title The Proxy Ticket The CAS-enabled proxied application MUST have a proxyTicket in order to send it to CAS in the proxyValidate method. The AddProxyArgument method is used in this case by the main application to add the proxyTicket. Then when you call MakeWebRequest , you specify an http method of GET or POST which would determine whether the proxyTicket was sent in the query string or the form post.
- Finally, call the MakeWebRequest method passing in the Url to the CAS-enabled application to be proxied along with the http method (e.g. GET or POST).
The examples I've outlined rely on two classes (source code at the end of this article) that must be included in an include file in order for them to work.Tip title Making an Http Request in ASP The MakeWebRequest method is sort of an ASP version of the System.Net.WebClient class in ASP.Net. You can call this method in any ASP page to make an http GET or POST request and receive an html or xml response depending on the request. This method works very well for CAS proxying because we are just going to Response.Write the returned html of the proxied application to the browser.
The first is a simple string class since ASP doesn't have the very useful StringBuilder that is always available in ASP.Net,
I created a vbscript mocked up version called clsString (see below). The second class is the CAS_Authenticator and does all the dirty work,
not just for basic CAS authentication with ASP but also supports CAS proxying which is what my examples will demonstrate.VBScript Code
Panel borderColor #cccccc bgColor #ffffff titleBGColor #eeeeee title CASProxier.asp borderStyle dashed <%@ Language=VBScript %>
<%Option Explicit %>
<!-- #Include File = "../Includes/Public.asp" -->
<HTML>
<HEAD>
<title>Test CAS Proxy Application</title>
</HEAD>
<body>
<%
Dim objCAS, serviceUrl
Dim pgtId
Dim proxyTicket
Dim proxyAppUrl
'Initialize CAS Authenticator Class
Set objCAS = New CAS_Authenticator
'Set the CAS URL
objCAS.CASURL = "https://auth-test.berkeley.edu/cas"
'Set the current request page Url as the CAS service name
serviceUrl = GetRequestUrl()
If Request.ServerVariables.Item("REQUEST_METHOD") = "POST" Then
'Get the pgtId, the proxy callback would have stored this as an application variable
'with the key name as the pgtIou
pgtId = Application(objCAS.ProxyGrantingTicketIOU)
If IsEmpty(pgtId) Then
Response.Write "No pgtId found, please make sure your pgtUrl is correct!"
Response.Write "<BR>"
Response.End
End If
'Send off the pgtId as well as the full Url to the CAS-enabled app to be proxied to the
'RequestProxyTicket method. It will return the "real" proxy ticket to call the proxy with
proxyAppUrl = "https://orimaging.berkeley.edu/ASP/Proxy/CASProxy.asp"
proxyTicket = objCAS.RequestProxyTicket(pgtId, proxyAppUrl)
If IsEmpty(proxyTicket) Then
Response.Write "The proxyTicket is Empty! " & objCAS.LastError
Response.Write "<BR>"
Response.End
End If
'Add arguments to send to proxy, proxyTicket is required as an argument!
objCAS.AddProxyArgument "proxyTicket", proxyTicket
'Make a request to the proxy and output the response
Response.Write "Proxy Response:"
Response.Write "<BR>"
Response.Write objCAS.MakeWebRequest(proxyAppUrl, "POST")
Else
If IsEmpty(Application("UID")) Then
'Since this application will proxy other CAS-enabled apps, we will request
'a pgtIou/pgtId pair from CAS by supplying a secure Url to our CAS Callback
objCAS.CASpgtUrl = "https://orimaging.berkeley.edu/ASP/Proxy/ProxyCallback.asp"
'Validate the URL of this application which will get assigned a ticket
'unless it fails, in which case exit and explain why it failed
If Not objCAS.ServiceValidate(serviceUrl) Then
Response.Write objCAS.LastError
Response.Write "<BR>"
Response.Write "Service: " & serviceUrl
Response.End
End If
'Set Application variables
Application.Lock
Application("UID") = objCAS.netID
Application.UnLock
End If
%>
<FORM action="Default.asp" method=post>
<TABLE align=center width="75%">
<TR>
<td valign=top align=left>
<Ahref="../Logout.asp?url=<%=serviceUrl%>">Log out of CAS</A>
</td>
</TR>
<TR>
<td valign=top align=left>
The current time is: <%=now()%>
</td>
</TR>
<TR>
<TD> </TD>
</TR>
<TR>
<td valign=top align=left>
<span style="FONT-SIZE: 14px; COLOR: teal; FONT-FAMILY: tahoma">
Welcome <B><%=Application("UID")%></B>, you have successfully authenticated with CAS
</span>
</td>
</TR>
<TR>
<td valign=top align=left>
<INPUT type="submit" value="Test Call Proxy" id="btnCallProxy" name="btnCallProxy">
</td>
</TR>
</TABLE>
</FORM>
<%
End If
Set objCAS = Nothing
%>
</body>
</HTML>Panel borderColor #cccccc bgColor #ffffff titleBGColor #eeeeee title pgtUrl borderStyle dashed <%@ Language=VBScript %>
<%Option Explicit %>
<!-- #Include File = "../Includes/Public.asp" -->
<HTML>
<HEAD>
<title>CAS Proxy Callback App</title>
</HEAD>
<body>
<%
Dim pgtIou, pgtId
'Get pgtIou in Query String sent from CAS
pgtIou = Request.QueryString.Item("pgtIou")
'Get pgtId in Query String sent from CAS
pgtId = Request.QueryString.Item("pgtId")
'Check to see if they exist
If Len(pgtIou) = 0 OR Len(pgtId) = 0 Then
Response.Write "No pgtIou/pgtId pair supplied!"
Response.End
End If
'Store pgtId in application variable accessable from any page within this application
Application.Lock
Application(pgtIou) = pgtId
Application.UnLock
%>
</BODY>
</HTML>Panel borderColor #cccccc bgColor #ffffff titleBGColor #eeeeee title CAS Proxied Application borderStyle dashed <%@ Language=VBScript %>
<%Option Explicit %>
<!-- #Include File = "../Includes/Public.asp" -->
<HTML>
<HEAD>
<title>Test CAS Proxy</title>
</HEAD>
<body>
<%
Dim proxyArgKey
Dim proxyTicket
Dim objCAS
'Initialize CAS Authenticator Class
Set objCAS = New CAS_Authenticator
'Set the CAS URL, change accordingly
objCAS.CASURL = "https://auth-test.berkeley.edu/cas"
'proxy ticket should be in either the query string or the form post
proxyTicket = Request.Item("proxyTicket")
If Not IsEmpty(proxyTicket) Then
' validate the proxy application with CAS
objCAS.ValidateProxy proxyTicket
If NOT IsEmpty(objCAS.LastError) Then
Response.Write objCAS.LastError
Response.Write "<BR>"
Response.End
End If
%>
<P>
<span style="font-size: 14px;font-family: tahoma;color:teal">
Welcome <B><%=objCAS.netID%></B>, you have successfully authenticated with CAS
</span>
</P>
<%
'
'Since this is the proxy application, we will check its arguments it was sent
'Normally we would know if the arguments were sent in the query string or Form post
'but for the purposes of this example, we will check both
'
If Request.QueryString.Count > 0 Then
'get arguments from querystring object
For Each proxyArgKey In Request.QueryString
Response.Write proxyArgKey & "=" & Request.QueryString.Item(proxyArgKey) & "(query string)"
Response.Write "<BR>"
Next
Else
'get arguments from form object
For Each proxyArgKey In Request.Form
Response.Write proxyArgKey & "=" & Request.Form.Item(proxyArgKey) & "(form post)"
Response.Write "<BR>"
Next
End If
If Not IsEmpty(objCAS.Proxies) Then
Response.Write "<BR>"
Response.Write "CAS Proxies:"
Response.Write "<BR>"
Response.Write objCAS.Proxies
Response.Write "<BR>"
End If
Else
Response.Write "No proxyTicket found in either the query string or the form post!"
Response.Write "<BR>"
End If
Set objCAS = Nothing
%>
</BODY>
</HTML>Panel borderColor #cccccc bgColor #ffffff titleBGColor #eeeeee title clsString borderStyle dashed '*****************************************************************
' clsString : A String Class helper to aid in string concatenation
'*****************************************************************
ClassclsString
Private m_intLength
Private m_intCounter
Private m_arrString()
Private SubClass_Initialize()
'Dim an array and set position counter
m_intCounter = 1
m_intLength = 100
Redim m_arrString(m_intLength)
End Sub
Public SubReset
'Erase current array and recreate
Erase m_arrString
Call Class_Initialize()
End Sub
Public PropertyGet Value
'Use Join function to create final string
Value = Join(m_arrString,"")
End Property
Public PropertyGet Delim(ByValdelimeter)
'Use Join function to create final string
Redim Preservem_arrString(Limit)
Delim = Join(m_arrString,delimeter)
End Property
Public PropertyGet Element(ByVal nj)
'Use Join function to create final string
Element = m_arrString(nj)
End Property
Public PropertyGet Limit
'Use Join function to create final string
Limit = m_intCounter - 1
End Property
Public SubAdd(byval strValue)
Dim intArrLen
'Add value to string array
intArrLen = Ubound(m_arrString)
If m_intCounter >intArrLen Then _
Redim Preservem_arrString(intArrLen + m_intLength)
m m_arrString(m_intCounter) = Cstr(strValue)
'Increment Incriment position counter
m_intCounter = m_intCounter + 1
End Sub
Public SubRemoveLast(byval intLastIndex)
'Remove the last intLastIndex elements from string array
Redim Preservem_arrString(Limit - intLastIndex)
End Sub
End Class
'Start Public Functions
'This function is basically an ASP version of the ASP.Net - Request.Url.GetLeftPart(UriPartial.Path)
Public Function GetRequestUrl()
Dim Port
Dim Local_Addr
Dim Path_Info Info
Dim Server_Name
Dim Protocol
Protocol = Request.ServerVariables("SERVER_PORT_SECURE")
Server_Name = Request.ServerVariables("SERVER_NAME")
Port = Request.ServerVariables("SERVER_PORT")
Path_Info = Request.ServerVariables("PATH_INFO")
If Port = 80 ORPort = 443 Then
Port = ""
Else
Port = ":" & Port
End If
Local_Addr = Request.ServerVariables("LOCAL_ADDR")
If Protocol = ""OR Protocol = "0"Then
If Local_Addr = "127.0.0.1"Then
Protocol = "http://"
Else
Protocol = "https://"
End If
Else
Protocol = "https://"
End If
GetRequestUrl = Protocol & Server_Name & Port & Path_Info
End Function
' End Public Functions:Panel borderColor #cccccc bgColor #ffffff titleBGColor #eeeeee title CAS Authenticator borderStyle dashed '***************************************************************************
' CAS_Authenticator : A Class wrapper that encapsulates authentication via CAS
'***************************************************************************
ClassCAS_Authenticator
'*********************************************************
'Private Global variables
'*********************************************************
Private m_ErrorText
Private m_CASURL
Private m_netID
Private objXML
Private srvXmlHttp
Private proxyArgHash
Private m_pgtUrl
Private m_pgtId
Private m_Proxies
Private queryCollection queryCollection
Private SubClass_Initialize()
Set proxyArgHash = Server.CreateObject("Scripting.Dictionary")
Set objXML = Server.CreateObject("Microsoft.XMLDOM")
'set async for XML Dom
objXML.async = False
Set srvXmlHttp = Server.CreateObject("Msxml2.ServerXMLHTTP")
Set queryCollection = Server.CreateObject("Scripting.Dictionary")
End Sub
Private SubClass_Terminate()
Set objXML = Nothing
Set srvXMLHttp = Nothing
Set proxyArgHash = Nothing
Set queryCollection = Nothing
End Sub
Public SubAddProxyArgument(ByVal name, ByVal value)
If NotproxyArgHash.Exists(name) Then
proxyArgHash proxyArgHash.Add name,value
End If
End Sub
PublicProperty GetProxyGrantingTicketIOU
ProxyGrantingTicketIOU ProxyGrantingTicketIOU = Application("pgtIou")
End Property
Public PropertyGet Proxies
Proxies = m_Proxies
End Property
Public PropertyGet netID
netID = m_netID
End Property
Public PropertyGet LastError
LastError = m_ErrorText
End Property
Public PropertyLet CASpgtUrl(ByVal vNewValue)
m_pgtUrl = vNewValue
End Property
Public PropertyLet CASURL(ByValvNewValue)
m_CASURL = vNewValue
End Property
Public SubLogOut(ByVal url)
Session.Abandon()
Response.Redirect m_CASURL & "/logout?url=" & url
End Sub
Public FunctionMakeWebRequest(ByVal requestUrl, ByVal httpMethod)
On Error Resume Next
Err.Clear
Clear
Select CasehttpMethod
Case "GET"
'request method is a GET
'Check the proxy arguments
If proxyArgHash.Count >0 Then
requestUrl requestUrl = requestUrl & "?"& CollectionToQueryString(proxyArgHash)
End If
srvXmlHttp
srvXmlHttp.open "GET", requestUrl, false
srvXmlHttp srvXmlHttp.send()
Case "POST"
'request method is a POST
srvXmlHttp srvXmlHttp.open "POST", requestUrl, false
srvXmlHttp. srvXmlHttp.setRequestHeader "Content-Type","application/x-www-form-urlencoded"
'Check the proxy arguments
IfproxyArgHash.Count > 0 Then
srvXmlHttp srvXmlHttp.send CollectionToQueryString(proxyArgHash)
Else
srvXmlHttp srvXmlHttp.send()
End If
Case Else
'Return error
m m_ErrorText = "Expecting either 'GET' OR 'POST' http method!"
MakeWebRequest MakeWebRequest = Empty
Exit Function
End Select
If Err <>0 Then
m_ErrorText = Err.Description
MakeWebRequest = Empty
Exit Function
End If
If srvXmlHttp.Status <>200 Then
m_ErrorText = srvXmlHttp.Status & " - " & srvXmlHttp.StatusText
MakeWebRequest = Empty
Exit Function
End If
MakeWebRequest = srvXmlHttp.responseText
End Function
Private FunctionCollectionToQueryString(ByValsrcCollection)
Dim srcKey
Dim strCollItems
Set strCollItems = NewclsString
For EachsrcKey in srcCollection.Keys
strCollItems strCollItems.Add srcKey & "="& srcCollection.Item(srcKey)
Next
CollectionToQueryString
CollectionToQueryString = Mid(strCollItems.Delim("&"), 2)
Set strCollItems = Nothing
End Function
Private FunctionCASRequest(ByVal validateurl)
Dim proxyNodeText
Dim objCASResponse
Dim objCASAuthenticationNode
Dim objCASUser
Dim objCASProxies
Dim objCASpgt
Dim objCASProxy
Dim objCASproxyTicket
Dim objProxyList
Dim htmlResponse
Dim i j
validateUrl = validateUrl & "?" & CollectionToQueryString(queryCollection)
htmlResponse = MakeWebRequest(validateUrl, "GET")
If IsEmpty(htmlResponse) Then
CASRequest = False
Exit Function
End If
If objXML.LoadXml(htmlResponse) Then
'Get reference to cas:serviceResponse XML Node
Set objCASResponse = objXML.getElementsByTagName("cas:serviceResponse")
If objCASResponse.length = 0 then
m m_ErrorText = "cas:serviceResponse XML Node is Empty!"
CASRequest = False
Exit Function
End If
Set objCASAuthenticationNode = objCASResponse.item(0).firstChild
Select CaseobjCASAuthenticationNode.nodeName
Case "cas:authenticationSuccess"
'Get CAS user nodeText
Set objCASUser = objCASAuthenticationNode.getElementsByTagName("cas:user")
If objCASUser.length = 0 then
m m_ErrorText = "cas:user element NOT present in source CAS XML!"
CASRequest CASRequest = False
Exit Function
End If
m
m_netID = objCASUser.item(0).nodeTypedValue
'Get CAS proxies nodeText
Set objCASProxy = objCASAuthenticationNode.getElementsByTagName("cas:proxies")
If objCASProxy.length >0 then
Set objProxyList = NewclsString
Set objCASProxies = objCASProxy.item(0).childNodes
For i childNodes
For j = 0 ToobjCASProxies.length - 1
objProxyList objProxyList.Add(objCASProxies.item(ij).nodeTypedValue)
Next
m Next
m_Proxies = objProxyList.Delim(vbcrlf)
Set objProxyList = Nothing
End If
'Get CAS proxyGT nodeText
Set objCASpgt = objCASAuthenticationNode.getElementsByTagName("cas:proxyGrantingTicket")
IfobjCASpgt.length > 0 Then
Application Application.Lock
Application Application("pgtIou") = objCASpgt.item(0).nodeTypedValue
Application.UnLock
Application.UnLock
End If
Case "cas:authenticationFailure"
m m_ErrorText = objCASAuthenticationNode.nodeTypedValue
CASRequest CASRequest = False
Exit Function
Case "cas:proxySuccess"
'Sample success returned from CAS
'<cas:proxySuccess xmlns:cas="http://www.yale.edu/tp/cas">
' <cas:proxyTicket>
' ST-1625-6YBqesAL6ywgfCwOPQYcK72G6ikQIHc6lab-aws-p1
' </cas:proxyTicket>
'</cas:proxySuccess>
Set objCASproxyTicket = objCASAuthenticationNode.getElementsByTagName("cas:proxyTicket")
If objCASproxyTicket.length > 0 Then
m m_pgtId = objCASproxyTicket.item(0).nodeTypedValue
End If
Case "cas:proxyFailure"
'Sample error returned from CAS
'<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
' <cas:proxyFailure code="INVALID_TICKET">
' ticket 'TGT-3053-jqon6B5AIUQ4A6Pb2RlgykerJ99PhUrF0ON-aws-p1' not recognized
' </cas:proxyFailure>
'</cas:serviceResponse>
m
m_ErrorText = objCASAuthenticationNode.nodeTypedValue
CASRequest CASRequest = False
Exit Function
Case Else
m m_ErrorText = "Undefined CAS error!"
CASRequest CASRequest = False
Exit Function
End Select
Else
m_ErrorText = "XML Failed to load<XMP>" & htmlResponse & "</XMP>"
CASRequest = False
Exit Function
End If
'CAS is Authenticated
CASRequest = True
End Function
Public FunctionServiceValidate(ByVal serviceUrl)
Dim tkt
Dim URLToValidate Dim URLToValidate
tkt = Request.QueryString.Item("ticket")
URLToValidate = m_CASURL & "/serviceValidate"
'?ticket=" & tkt & "&service=" & m_ServiceName
If IsEmpty(tkt) Then
'if no ticket in URL then send user to CAS to get one
Response 'send the user back to CAS
'set-up to avoid endless loop to CAS
Response.Redirect m_CASURL & "/login?service="& serviceUrl
ServiceValidate
Application.Lock
Application("ReturnUrl") = Request.QueryString.Item("ReturnUrl")
Application.UnLock
ServiceValidate = True
Exit Function
End If
' Second time (back from CAS) there is a ticket= to validate
queryCollection queryCollection.RemoveAll
queryCollection.Add"ticket", tkt
queryCollection queryCollection.Add "service", serviceUrl
If NOTIsEmpty(m_pgtUrl) Then
queryCollection queryCollection.Add "pgtUrl", m_pgtUrl
End If
If NOTCASRequest(URLToValidate) Then
ServiceValidate ServiceValidate = False
Exit Function
End If
' If there was a problem, leave the message on the screen. Otherwise, return to original page.
If IsEmpty(m_netID) Then
m_ErrorText = "CAS returned to this application, but then refused to validate your identity."
ServiceValidate ServiceValidate = False
Exit Function
End If
ServiceValidate = True
End Function
Public FunctionRequestProxyTicket(ByVal pgtId, ByVal proxyAppUrl)
Dim validateurl
validateurl = m_CASURL & "/proxy"
queryCollection
queryCollection.RemoveAll
queryCollection queryCollection.Add "targetService", proxyAppUrl
queryCollection queryCollection.Add "pgt", pgtId pgtId
If NotCASRequest(validateurl) Then
RequestProxyTicket RequestProxyTicket = Empty
Exit Function
End If
RequestProxyTicket = m_pgtId
End Function
Public SubValidateProxy(ByVal proxyTicket)
Dim validateurl
' Validate proxy ticket
validateurl = m_CASURL & "/proxyValidate"
queryCollection
queryCollection.RemoveAll
queryCollection queryCollection.Add "ticket", proxyTicket
queryCollection queryCollection.Add "service", GetRequestUrl()
If NotCASRequest(validateurl) Then
Exit Sub
EndIf
' If there was a problem, leave the message on the screen. Otherwise, return to original page.
If IsEmpty(m_netID) Then
m_ErrorText = "CAS returned to this application, but then refused to validate your identity."
Exit Sub
End If
End Sub
End Class