...
What is less obvious is the best way to leverage this feature, in particular to make it more agile. The configuration of the interceptor is, like all flow configuration, not reloadable. You have to restart the IdP to apply changes. This is obviously not ideal. Since both the attribute resolver service, metadata, and the views are generally reloadable, it follows that it is advantageous to design the use the interceptor in such a way that all of the actual rules are located in one of those places. There are many ways to do this; this page just illustrates a few some of them.
A really interesting extension of all this would be expressing attribute names and values to check for in the metadata itself using a tag.
Use of the Attribute Resolver
...
If I had used the resolver to actually generate the events to signal, I wouldn’t bother with the condition at all and just write a script that checks for a value in a candidate attribute and sets event to that value.
Code Block | ||
---|---|---|
| ||
var event = "proceed";
var rpctx = input.getSubcontext("net.shibboleth.idp.profile.context.RelyingPartyContext");
var attributeCtx = rpCtx.getSubcontext("net.shibboleth.idp.attribute.context.AttributeContext");
if (attributeCtx != null) {
var attribute = attributeCtx.getUnfilteredIdPAttributes().get("contextCheckEvent");
if (attribute != null && !attribute.getValues().isEmpty()) {
event = attribute.getValues().get(0).getValue();
}
}
event; |
Error Handling
The last piece is error handling. In most cases, my goal is to force system owners that insist on the IdP doing this to provide a page to send people to rather than requiring the IdP to host something. This is obviously not always successful but most people prefer to control that content anyway.
In the simple case where a single event is used, any specific logic pertaining to an SP is going to be handled by the error view itself, so again is reloadable.
The part that is baked into the IdP is to get it to handle the custom event by routing it to a local error view template. In my case, the “ContextCheckDenied” event is actually already known to the IdP, so I don’t have to define it per se, only handle the dispatching via conf/errors.xml:
Code Block | ||
---|---|---|
| ||
<!-- Map local events to alternate view templates. -->
<util:map id="shibboleth.EventViewMap">
...
<entry key="ContextCheckDenied" value="access-denied" />
...
</util:map>
<util:map id="shibboleth.LocalEventMap">
...
<entry key="ContextCheckDenied" value="true" />
...
</util:map> |
Both of these maps may contain many entries. The “true” flag in the second one signifies that the IdP should record an audit log entry for any requests that finish with that event.
The views/access-denied.vm template is something like this (I am skipping a lot of the local look and feel material to just focus on the important parts):
Code Block |
---|
## ## Velocity Template for error end-state ## ## Velocity context will contain the following properties ## flowRequestContext - the Spring Web Flow RequestContext ## profileRequestContext - root of context tree ## encoder - HTMLEncoder class ## request - HttpServletRequest ## response - HttpServletResponse ## environment - Spring Environment object for property resolution ## custom - arbitrary object injected by deployer ## #set ($rpContext = $profileRequestContext.getSubcontext('net.shibboleth.idp.profile.context.RelyingPartyContext')) #set ($rpid = $rpContext.getRelyingPartyId()) $response.setStatus(403) ## <!DOCTYPE html> <html lang="en"> <head> #set ($title = "Web Login Service") #set ($description = "OSU Web Login Service error handling page") #if ($rpid == "https://ohio-state.slack.com") <meta http-equiv="refresh" content="0;url=http://ohio-state-slack.it.ohio-state.edu/denial.html"> #elseif ($rpid == "https://osu.zoom.us") <meta http-equiv="refresh" content="0;url=https://it.osu.edu/zoom-access/"> #elseif ($rpid == "https://app.biorender.com") <meta http-equiv="refresh" content="0;url=https://ccc.osu.edu/biorenderredirect/"> #end </head> <div id="page"> <div id="header"> <div class="container"> <h1>Web Login Service</h1> </div> </div> <div id="content" tabindex="-1" role="main" style="background: #FFFFFF"> <div class="container"> #if ($rpid == "https://ohio-state.slack.com") <p>You do not have access to the Ohio State internal Slack instance.</p> #elseif ($rpid == "https://osu.zoom.us") <p>You do not have access to CarmenZoom.</p> #elseif ($rpid == "https://app.biorender.com") <p>You do not have access to BioRender.</p> #end </div> </div> </div> </body> </html> |
Another enhancement to this would be to resolve an attribute containing the proper redirection page or message (or both) and simply extract that attribute’s value to encode into the page.