Purpose
Applications need to programmatically access CAS. Generally, proxying works for this. However, there are cases where an application needs to access a resource as itself, in which case proxying doesn't make any sense.
At Rutgers, we've implemented a relatively "heavyweight" SOAP based service via Axis. We're now looking at complementing that with a lightweight resource-driven architecture. This page details that proposed work.
This API works to expose a way to RESTfully obtain a Ticket Granting Ticket resource and then use that to obtain a Service Ticket.
Protocol
The RESTful API follows the same basic protocol as the original CAS2 protocol, augmented with some additional well-defined resource urls (though the protocol doesn't change so it should be just as secure).
Ticket Granting Ticket
The Ticket Granting Ticket is an exposed resource. It has a unique URI.
Request for a Ticket Granting Ticket Resource
POST /cas/tickets HTTP/1.0
username=battags&password=password&additionalParam1=paramvalue
Response for a Ticket Granting Ticket Resource
Successful Response
201 Created
Location: http:
Unsuccessful Responses
If incorrect credentials are sent, CAS will respond with a 400 Bad Request error (will also respond for missing parameters, etc.). If you send a media type it does not understand, it will send the 415 Unsupported Media Type
Request for a Service Ticket
POST /cas/tickets/{TGT id} HTTP/1.0
service={form encoded parameter for the service url}
Response for Service Ticket
Successful Response
200 OK
ST-1-FFDFHDSJKHSDFJKSDHFJKRUEYREWUIFSD2132
Unsuccessful Responses
If parameters are missing, etc. CAS will send a 400 Bad Request. If you send a media type it does not understand, it will send the 415 Unsupported Media Type.
Logout of the Service
To log out, you merely need to delete the ticket.
DELETE /cas/tickets/TGT-fdsjfsdfjkalfewrihfdhfaie HTTP/1.0
Configuration
By default the CAS RESTful API is configured in the restlet-servlet.xml, which contains the routing for the tickets. It also defines the resources that will resolve the URLs. The TicketResource defined by default (which can be extended) accepts username/password.
To turn on the RESTful API, add the following to the web.xml:
<servlet>
<servlet-name>restlet</servlet-name>
<servlet-class>com.noelios.restlet.ext.spring.RestletFrameworkServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>restlet</servlet-name>
<url-pattern>/v1/*</url-pattern>
</servlet-mapping>
Note, that in the above configuration example, we are explicitly versioning the RESTful API, so things would be accessed via /cas/v1/tickets/*, etc.
In the pom.xml file include the following:
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-integration-restlet</artifactId>
<version>3.3-RC3</version>
<type>jar</type>
</dependency>
where 3.3-RC3 is the version of CAS you are currently using (3.3 or higher).
Python REST Client Example
#!/usr/bin/python
import os.path
import httplib, urllib, urllib2, cookielib
# 1. Grab the Ticket Granting Ticket (TGT)
params = urllib.urlencode({'username': 'battags', 'password': 'password'})
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
conn = httplib.HTTPSConnection("cas.acme.com")
conn.request("POST", "/cas/v1/tickets/", params, headers)
response = conn.getresponse()
print response.status, response.reason
data = response.read()
location = response.getheader('location')
# Pull off the TGT from the end of the location, this works for CAS 3.3-FINAL
tgt = location[location.rfind('/') + 1:]
conn.close()
print location
print tgt
print "***"
# 2. Grab a service ticket (ST) for a CAS protected service
document = '/secure/blah.txt'
service = 'http:
params = urllib.urlencode({'service': service })
conn = httplib.HTTPSConnection("cas.acme.com")
conn.request("POST", "/cas/v1/tickets/%s" % ( tgt ), params, headers)
response = conn.getresponse()
print response.status, response.reason
st = response.read()
conn.close()
print "service: %s" % (service)
print "st : %s" % (st)
print "***"
# 3. Grab the protected document
#httplib.HTTPConnection.debuglevel = 1
url = "%s?ticket=%s" % ( service, st ) # Use &ticket if service already has query parameters
print "url : %s" % (url)
cj = cookielib.CookieJar()
# no proxies please
no_proxy_support = urllib2.ProxyHandler({})
# we need to handle session cookies AND redirects
cookie_handler = urllib2.HTTPCookieProcessor(cj)
opener = urllib2.build_opener(no_proxy_support, cookie_handler)
urllib2.install_opener(opener)
protected_data = urllib2.urlopen(url).read()
print protected_data