CAS Proxying with Classic ASP
...
- From the main (Proxier) application, set the CASpgtUrl property to your application Url that will accept the pgtIou/pgtId pair from CAS and then call the ServiceValidate method for CAS authentication.
Note |
---|
|
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 |
---|
|
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).
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 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. 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. Panel |
---|
borderColor | #cccccc |
---|
bgColor | #ffffff |
---|
titleBGColor | #eeeeee |
---|
borderStyle | dashed |
---|
title | CASProxier.asp |
---|
|
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} | @ Language=VBScript Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} | Option Explicit Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
<!-- #Include File = "../Includes/Public.asp" -->
<HTML> <HEAD> <title>Test CAS Proxy Application</title> </HEAD> <body>
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} |
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
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
<FORM action="Default.asp" method=post> <TABLE align=center width="75%"> <TR> <td valign=top align=left> <Ahref="../Logout.asp?url= Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} | =serviceUrl Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} | ">Log out of CAS</A> </td> </TR> <TR> <td valign=top align=left> The current time is: Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} | =now() Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
</td> </TR> <TR> <TD> </TD> </TR> <TR> <td valign=top align=left> <span style="FONT-SIZE: 14px; COLOR: teal; FONT-FAMILY: tahoma"> Welcome <B> Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} | =Application("UID") Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} | </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>
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} |
End If Set objCAS = Nothing Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
</body> </HTML>
|
Panel |
---|
borderColor | #cccccc |
---|
bgColor | #ffffff |
---|
titleBGColor | #eeeeee |
---|
borderStyle | dashed |
---|
title | pgtUrl |
---|
|
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} | @ Language=VBScript Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} | Option Explicit Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
<!-- #Include File = "../Includes/Public.asp" -->
<HTML> <HEAD> <title>CAS Proxy Callback App</title> </HEAD> <body>
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} |
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
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
</BODY> </HTML>
|
Panel |
---|
borderColor | #cccccc |
---|
bgColor | #ffffff |
---|
titleBGColor | #eeeeee |
---|
borderStyle | dashed |
---|
title | CAS Proxied Application |
---|
|
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} | @ Language=VBScript Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} | Option Explicit Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
<!-- #Include File = "../Includes/Public.asp" -->
<HTML> <HEAD> <title>Test CAS Proxy</title> </HEAD> <body>
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} |
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 If NOT objCAS.ValidateProxy(proxyTicket) Then Response.Write objCAS.LastError Response.Write "<BR>" Response.End End If
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
<P> <span style="font-size: 14px;font-family: tahoma;color:teal"> Welcome <B><%=objCAS.netID%></B>, you have successfully authenticated with CAS </span> </P> Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}<%{span} |
' '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
Wiki Markup |
---|
{span:id=hl|style=background-color:yellow}%>{span} |
</BODY> </HTML>
|
Panel |
---|
borderColor | #cccccc |
---|
bgColor | #ffffff |
---|
titleBGColor | #eeeeee |
---|
borderStyle | dashed |
---|
title | clsString |
---|
|
'***************************************************************** ' clsString : A String Class helper to aid in string concatenation '***************************************************************** Class clsString
Private m_intLength Private m_intCounter Private m_arrString()
Private Sub Class_Initialize() 'Dim an array and set position counter m_intCounter = 1 m_intLength = 100 Redim m_arrString(m_intLength) End Sub
Public Sub Reset 'Erase current array and recreate Erase m_arrString Call Class_Initialize() End Sub
Public Property Get Value 'Use Join function to create final string Value = Join(m_arrString,"") End Property
Public Property Get Delim(ByVal delimeter) 'Use Join function to create final string Redim Preserve m_arrString(Limit) Delim = Join(m_arrString,delimeter) End Property
Public Property Get Element(ByVal j) 'Use Join function to create final string Element = m_arrString(j) End Property
Public Property Get Limit 'Use Join function to create final string Limit = m_intCounter - 1 End Property
Public Sub Add(byval strValue) Dim intArrLen 'Add value to string array intArrLen = Ubound(m_arrString) If m_intCounter >intArrLen Then _ Redim Preserve m_arrString(intArrLen + m_intLength) m_arrString(m_intCounter) = Cstr(strValue) 'Increment position counter m_intCounter = m_intCounter + 1 End Sub
Public Sub RemoveLast(byval intLastIndex) 'Remove the last intLastIndex elements from string array Redim Preserve m_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 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 |
---|
borderStyle | dashed |
---|
title | CAS Authenticator |
---|
|
'*************************************************************************** ' CAS_Authenticator : A Class wrapper that encapsulates authentication via CAS '*************************************************************************** Class CAS_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 Private Sub Class_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 Sub Class_Terminate() Set objXML = Nothing Set srvXMLHttp = Nothing Set proxyArgHash = Nothing Set queryCollection = Nothing End Sub Public Sub AddProxyArgument(ByVal name, ByVal value) If Not proxyArgHash.Exists(name) Then proxyArgHash.Add name,value End If End Sub Public Property Get ProxyGrantingTicketIOU ProxyGrantingTicketIOU = Application("pgtIou") End Property
Public Property Get Proxies Proxies = m_Proxies End Property Public Property Get netID netID = m_netID End Property
Public Property Get LastError LastError = m_ErrorText End Property Public Property Let CASpgtUrl(ByVal vNewValue) m_pgtUrl = vNewValue End Property
Public Property Let CASURL(ByValvNewValue) m_CASURL = vNewValue End Property Public Sub LogOut(ByVal url) Session.Abandon() Response.Redirect m_CASURL & "/logout?url=" & url End Sub Public Function MakeWebRequest(ByVal requestUrl, ByVal httpMethod) On Error Resume Next Err.Clear Select Case httpMethod Case "GET" 'request method is a GET
'Check the proxy arguments If proxyArgHash.Count > 0 Then requestUrl = requestUrl & "?"& CollectionToQueryString(proxyArgHash) End If
srvXmlHttp.open "GET", requestUrl, false srvXmlHttp.send() Case "POST" 'request method is a POST srvXmlHttp.open "POST", requestUrl, false srvXmlHttp.setRequestHeader "Content-Type","application/x-www-form-urlencoded"
'Check the proxy arguments If proxyArgHash.Count > 0 Then srvXmlHttp.send CollectionToQueryString(proxyArgHash) Else srvXmlHttp.send() End If Case Else 'Return error m_ErrorText = "Expecting either 'GET' OR 'POST' http method!" 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 Function CollectionToQueryString(ByVal srcCollection) Dim srcKey Dim strCollItems Set strCollItems = New clsString For Each srcKey in srcCollection.Keys strCollItems.Add srcKey & "="& srcCollection.Item(srcKey) Next CollectionToQueryString = Mid(strCollItems.Delim("&"), 2) Set strCollItems = Nothing End Function
Private Function CASRequest(ByVal validateurl) Dim proxyNodeText Dim objCASResponse Dim objCASAuthenticationNode Dim objCASUser Dim objCASProxies Dim objCASpgt Dim objCASProxy Dim objCASproxyTicket Dim objProxyList Dim htmlResponse Dim 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_ErrorText = "cas:serviceResponse XML Node is Empty!" CASRequest = False Exit Function End If Set objCASAuthenticationNode = objCASResponse.item(0).firstChild Select Case objCASAuthenticationNode.nodeName Case "cas:authenticationSuccess" 'Get CAS user nodeText Set objCASUser = objCASAuthenticationNode.getElementsByTagName("cas:user") If objCASUser.length = 0 then m_ErrorText = "cas:user element NOT present in source CAS XML!" CASRequest = False Exit Function End If m_netID = objCASUser.item(0).nodeTypedValue
'Get CAS proxies nodeText Set objCASProxy = objCASAuthenticationNode.getElementsByTagName("cas:proxies") If objCASProxy.length > 0 then Set objProxyList = New clsString Set objCASProxies = objCASProxy.item(0).childNodes For j = 0 To objCASProxies.length - 1 objProxyList.Add(objCASProxies.item(j).nodeTypedValue) Next m_Proxies = objProxyList.Delim(vbcrlf) Set objProxyList = Nothing End If
'Get CAS proxyGT nodeText Set objCASpgt = objCASAuthenticationNode.getElementsByTagName("cas:proxyGrantingTicket") If objCASpgt.length > 0 Then Application.Lock Application("pgtIou") = objCASpgt.item(0).nodeTypedValue Application.UnLock End If
Case "cas:authenticationFailure" m_ErrorText = objCASAuthenticationNode.nodeTypedValue 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_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_ErrorText = objCASAuthenticationNode.nodeTypedValue CASRequest = False Exit Function Case Else m_ErrorText = "Undefined CAS error!" 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 Function ServiceValidate(ByVal serviceUrl) Dim tkt Dim URLToValidate tkt = Request.QueryString.Item("ticket") URLToValidate = m_CASURL & "/serviceValidate"
If IsEmpty(tkt) Then 'if no ticket in URL then send user to CAS to get one 'send the user back to CAS 'set-up to avoid endless loop to CAS Response.Redirect m_CASURL & "/login?service="& serviceUrl 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.RemoveAll queryCollection.Add"ticket", tkt queryCollection.Add "service", serviceUrl
If NOT IsEmpty(m_pgtUrl) Then queryCollection.Add "pgtUrl", m_pgtUrl End If
If NOT CASRequest(URLToValidate) Then 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 = False Exit Function End If ServiceValidate = True End Function Public Function RequestProxyTicket(ByVal pgtId, ByVal proxyAppUrl) Dim validateurl
validateurl = m_CASURL & "/proxy"
queryCollection.RemoveAll queryCollection.Add "targetService", proxyAppUrl queryCollection.Add "pgt", pgtId If Not CASRequest(validateurl) Then RequestProxyTicket = Empty Exit Function End If
RequestProxyTicket = m_pgtId End Function Public Function ValidateProxy(ByVal proxyTicket) Dim validateurl ' Validate proxy ticket validateurl = m_CASURL & "/proxyValidate" queryCollection.RemoveAll queryCollection.Add "ticket", proxyTicket queryCollection.Add "service", GetRequestUrl()
If Not CASRequest(validateurl) Then ValidateProxy = False Exit Function 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." ValidateProxy = False Exit Function End If ValidateProxy = True End Function End Class |