Yesterday I was hit by a problem. I was looking to set the login page for a suite of applications to an XPage. Some years ago I had a project and, with much less XPages expertise than now, just added some hacky code to redirect to a login XPage based on a sessionScope variable, so even if the user was authenticated, everything still went through that login page. But this was slightly different. I wanted to override the server-wide login page.
So first it requires a bit of explanatory background on XPages processing, which I’ve covered in a variety of User Group sessions. When you first hit a server via a browser, the HTTP task kicks in and checks things like whether the “.nsf” location exists (if not you get an HTTP 404 error). Then it checks access to the NSF and, based on that redirects to the server’s HTTP login form. Then it checks the resource within the NSF (Form, View, XPage etc) exists, and if not it throws another HTTP 404 error. Then it may do some check on access to the relevant resource, for example if the resource is a Form it checks Author access or if the resource is public access, it checks whether youo can read / create public access documents. If that check fails, it redirects to the server’s HTTP login form. Then, for XPages, it checks the signer and, if invalid, throws an HTTP 403 error. Only after all these checks does the XPages Command Manager kick in and do anything.
Custom login forms can be created in the domcfg.nsf on the server and configured at server level. But they’re Notes Forms only. Not XPages.
So I started asking the community if anyone had tried and succeeded with it. Thanks to input from Declan Lynch, David Leedy, Howard Greenberg, Jesse Gallagher and John Dalsgaard we identified that it’s not possible to redirect to an XPage and embedding an XPage in the custom login form didn’t seem to work either. I also hypothesised on redirecting to an OSGi plugin, which again doesn’t look feasible. But the suggestion of code in beforePageLoad event and mention of the ACL setting on an XPage got me thinking. All either code is doing is re-evaluating access to the current XPage and redirecting. Indeed the ACL setting is just using in-built Domino code to perform that check, code that redirects but doesn’t give you control over where it redirects to. A comment that the ACL setting would be faster because it doesn’t have to load the XPage into memory got me thinking. Because I’ve already been using a version of Jesse Gallagher’s Scaffolding Framework for some time, which uses a ControllingViewHandler class to override some functionality as any XPage is loaded into memory. The ControllingViewHandler class actually calls the
com.ibm.xsp.application.ViewHandlerExImpl class which is the one which creates the initial component tree of the XPage into memory. So I can already pre-empt the code earlier than beforePageLoad.
This prompted some investigation and an XSnippet to cover the scenarios. This blog post is to give a bit more background on what the code is doing, for anyone who wants to fix it or enhance it. Thanks to everyone who was involved in the conversation for prompting ideas and helping make this a pretty comprehensive solution, for any XPages application on the server.
First of all, we can’t rely on standard HTTP access queries, because they only redirect to the HTTP server logon page. So the application needs to allow Anonymous to have Reader access. That means you’ll want to secure all Forms, Views and Agents so they can’t be accessed from the web. XPages access all such resources server-side, so hiding them from the web doesn’t affect your application.
This may initially sound insecure, but it’s just taking control of authentication and restriction via code rather than configuration. It’s the kind of thing I’ve had to do with other web technologies and other back-ends where document-level security is not enabled. So while it may seem anathema to anyone who’s only ever done Domino development, controlling access via code is not insecure if the coding closes the back doors into the application.
The benefit of setting Anonymous to have Reader access is that you don’t need to jump through the hoops involved and the limitations for using Public Access settings. If you have an application that doesn’t want to log in via an XPage, you can still use standard ACL settings for those. And for more advanced settings like role-based restrictions, I’m sure many developers are, like myself, redirecting based on the role access.
So onto the code. The key chunk is the ControllingViewHandler Java class. If you are already using a class like this, the relevant code can just be inserted, as I did by inserting it in the class Jesse had created.
The first element is an enum that defines all XPages that do allow anonymous access. For those not au fait with Java, enums are extremely useful for ensuring you only have to type the String variable once and you can’t use options unless they’ve been added to the enum. So if you want to see all anonymous pages, you don’t have to trawl code, just look at the enum class. Here I’ve got a home page (“/index.xsp”) and of course the error page (“/Error.xsp”). Obviously if we don’t include the error page, the standard redirection to the Error page if an error occurs in our code or an anonymous page will fail!
The createView method is the one that is triggered to load the component tree into memory when the page is first hit. The first big chunk is commented out, and would be triggered if loading a document directly rather than doing e.g. “/myDoc.xsp?documentId=25FE3A”. When hitting the document directly, it uses “$$OpenDominoDocument” URL call. If this is used, we check the Form and amend the pageName accordingly to the actual XPage it’s set to open in. Thanks also to Jesse Gallagher for mentioning there’s an in-built DominoUtils.getXPagesForDocument() method and also that the relevant XPage can be defined in the xsp.properties file rather than the Form, if required. (The commented out code shows I tend to use an intermediary method to the get the relevant application’s database.)
The code then ensures the pageName is in a consistent format, so always is prefixed with “/” and suffixed with “.xsp”. Then we have a boolean to identify whether to continue loading the XPage. If the effectiveUserName is not “Anonymous”, we continue. Otherwise we check the AnonymousPageNames enum to see whether the current page allows anonymous access. If it does, we set the boolean to true. (A similar check against a different enum could be used to restrict access to admin areas based on role access.)
If the boolean is not true, we know we need to redirect. What happens then depends on where you’re login XPage is located. If it’s in the current NSF or you want to force the user to a homepage, you can use context.redirectToPage(). That’s surrounded by a try/catch block that does usual logging, but skipping the RedirectSignal error that I think can occur if you call context.redirectToPage(). To be honest, the in-built error handling function I have in all my applications handles that check for me, so I’ve forgotten!
If the login page is in a different NSF, it gets a little trickier. FacesContext.getExternalContext().redirect() doesn’t work this early in the lifecycle, I think because there’s nothing here to print it back to the server – I also got a NullPointerException when trying to access the ResponseWriter here. So we create a requestScope variable and change the pageName to the Error page. I chose the Error page because it’s relatively lightweight and needs to be accessible to anonymous anyway. You then need to add code to the beforeRenderResponse of the Error page to check whether the requestScope variable has been set, in which case redirect accordingly.
The final step is to ensure the ControllingViewHandler is registered to the application. So in the faces-config.xml, it needs registering, and the code for this is in the XSnippet.
There are plenty of examples of how to do an AJAX authentication out on the web. But this code could even be moved into an OSGi plugin. A couple of changes would be required – allowing the login page to be picked up from the database’s xsp.properties (with a default), and the same for the anonymous pages. And of course this won’t work for traditional Domino web design elements, like Forms, Views or mail databases. But it could prove of benefit for providing a more modern, flexible login page for all XPages applications on the server.