Friday, February 24, 2006

On the use of the "final" keyword

The CAS codebase makes aggressive use of the final keyword. Some developers attempting to extend CAS by subclassing are annoyed and inconvenienced by this. Why do we use the final keyword?



If the CAS framework is to be extended by subclassing, then support for extension by subclassing needs to be deliberately designed into the classes in order to provide maintanable APIs and a maintainable project. The effort required to do this design is greater than zero work but it is important for everyone involved -- it retains some explicitness about what APIs we're exposing so we can be careful that implementation changes don't damage exposed APIs. It provides additional sanity for the person doing the local subclassing, exposing deliberate and documented template methods and documenting what is being guaranteed as opposed to what happens to be true.

The final keywords pretect the project from frozen implementing code via all of the implementation having been inadvertantly exposed as APIs to potential subclasses. The final keywords protect you, the local CAS implementer, from making assumptions the project had not intended to guarantee.

Therefore, wherever a class has not been designed for subclassing, it should be marked final. This protects everyone from following bad expectations -- implementing extension by subclassing and thereby coding against assumptions that aren't recognized as something to contnue to support without understanding this risk.

Wherever a class is not marked final, any method that isn't designed to be replaced by a subclass should be marked final. Any method that isn't designed to be invoked by a subclass should be marked private (balanced against the advantages of package-scoping internal implementation methods for application of unit testing of the internal methods). This is annoying for a would-be subclasser, but it protects the project against exposing APIs and having to contnue to support them when the implications of this haven't been thought through.

No code is perfect. All code ages. The CAS3 design decisions aren't perfect and no design decision is without tradeoffs. Nonetheless, aggressive use of the final keyword is a technique for slowing project cruft-accumulation of APIs we've exposed that it turns out shouldn't have been exposed.



So, wherever it is that you're annoyed that we've marked something final, that probably means that there's a specific opportunity to recognize an extension point and design it in, or to better document what the preferred alternative to doing that subclassing is, or possibly that you've really found an extension point that's out of
scope of the CAS project because though you need it your need is so esoteric that the complexity cost of adding the extension point is greater than its value. (This latter is unlikely but I'd like to manage expectations: CAS cannot become the union of all
possible features, and it can't even support the union of all possible extension points, but it can and should and wants to support many many common extension points.)

We hope you'll voice your needs on the discussion lists, engage the CAS developers and join the process of identifying, advancing, and implementing support for CAS extension points, and share your code. The final keyword isn't intended to communicate that we think CAS is perfect as is and no one would ever want to change our code (we don't think this). It is intended to focus effort on identifying and supporting the extension points you can productively use in a way that is maintainable for the project and benefits all involved.

Our use of the final keyword enables the developers to respond to a newly recognized extension point need by adding the extension point, confident that this does not break existing subclasses of the object to be enhanced, since those subclasses were discouraged by the final declaration.

Friday, February 10, 2006

CASFilter authorizedProxy parameter

Question:
When would I need/want to configure CASFilter or CASValidateFilter's authorizedProxy parameter?

Answer:
Any service that authenticates users via CAS can obtain a proxy ticket
under the CAS protcol (you can, of course, locally restrict this with
fairly trivial local hacks).

This is okay because at validation any proxy ticket resolves to two
pieces of information: the userid of the end user, and the chain of
proxies. In the case of uPortal, this chain is the https: proxy ticket
callback URL of your uPortal server. If your uPortal proxied to a mail
servlet, which in turn proxied to an IMAP server, then the proxy ticket
the IMAP server validates will resolve to a chain including both uPortal
and the mail servlet.

CAS represents this chain in its proxy ticket validation response.
However, your application absolutely must examine this chain to see if
it includes services you actually want to accept proxying from. Your
IMAP server might accept proxying from uPortal but not from Sakai, say.
If you do not examine the proxying service's identity, then any service
could proxy through your filter.

The authorizedProxy init param is the way you configure CASFilter or
CASValidateFilter as to which services are authorized to proxy through
it. The filter by default accepts service tickets. It will also accept
proxy tickets whose most recent proxying service is listed among the
authorizedProxies.

In the case of configuring uPortal itself for CAS authentication, it
would almost never be necessary to configure this parameter (only end
users, not other services, would be authenticating to the portal itself).