AnsweredAssumed Answered

External OAuth

Question asked by vikpr on Jan 27, 2016
Hi, we'd like to use Alfresco with our own identity provider and the idea is to develop an extension for Alfresco that would rely on an external OAuth server for the authentication and authorization.
In the Alfresco documentation I found a reference to this Share extension https://github.com/gdepourtales/share-oauth-sso which was a great help but I took a slightly different approach. The general idea is the following:
<ol>
<li>Share authentication filter checks if the user is authenticated and starts the OpenIDConnect authentication procedure if she's not;</li>
<li>If the authentication procedure succeeds then the user is logged in via AuthenticationUtil.login and the user details are stored in a (custom) global JNDI resource factory;</li>
<li>The Share connector checks if the user is authenticated and adds a bearer authentication header if she is;</li>
<li>The authentication component on the repository side uses a RemoteUserMapper to check if the bearer token is present and if the user with such token exists in JNDI.</li>
</ol>
The Share authentication filter has the following code in the end of doFilter method:

if (!AuthenticationUtil.isAuthenticated(req)) {
    if (req.getMethod().equals("GET")) {
        // Starts the OAuth procedure and redirects to the identity provider
    } else {
        logger.debug("doFilter: not HTTP GET, skipping");
        redirectToLoginPage(req, res);
    }
} else {
    String userId = AuthenticationUtil.getUserId(req);
    challengeOrPassThrough(chain, req, res, session, userId);
}

The OAuth callback is done on a separate filter that is mapped to /share/callback , the important part of the callback filter looks like the following:

// Acquired the authentication token and made the call to the user_info endpoint.
// resourceResponse contains a response from the user_info endpoint of the OAuth server
if (resourceResponse.getResponseCode() == 200) {
    Map<String, String> map = (Map)JSONUtils.parseJSON(resourceResponse.getBody());
    if (map.containsKey("email")) {
        String email = map.get("email");
        AuthenticationUtil.login(req, email);
        // puts the user info in JNDI here
    }
}
// redirects back to the original URL that caused the OAuth procedure to be started

The connector has the following piece of code (at the moment it uses the plain user ID as the token but it's only for the testing purposes):

if(AuthenticationUtil.isAuthenticated(req)){
    String userId=AuthenticationUtil.getUserId(req);
    if(userId!=null&&JNDIRepository.getUser(userId)!=null){
        headers.put("Authorization","Bearer "+userId);
        logger.debug("Adding bearer header "+userId);
    }
}else{
    logger.debug("Not authenticated - adding no bearer");
}

And the remote user mapper looks like the following:

    public String getRemoteUser(HttpServletRequest request) {
        String auth = request.getHeader("Authorization");
        if (auth != null && auth.startsWith("Bearer ")) {
            String token = auth.substring("Bearer ".length() - 1).trim();
            logger.debug("Bearer token: " + token);
            User user=JNDIRepository.getUser(token);
            Date now = new Date(System.currentTimeMillis());
            if (user != null && user.getExpires().after(now)) {
                logger.debug("External user: " + user.getName() + ", expires " + user.getExpires());
                return user.getName();
            } else {
                if (user == null) {
                    logger.debug("User is null");
                } else if (user.getExpires().before(now)) {
                    logger.debug("User is expired: " + user.getExpires());
                }
            }
        }
        return null;
    }

This all works. The only question I have about this part is whether I used the AuthenticationUtil in the correct way to login the user on the Share side.

Now, the question that made we write this post is how to propagate the user information and the authorization information from OAuth claims to the Alfresco repository in a proper way. At this moment when the callback filter logs the user in it uses only the "email" claim, but there are others like the first and the last name, the user groups, etc. which I'd like to add to the user account in Alfresco.
So far I came up with two possibilities:
<ol><li>Use the REST api from the callback filter to create/update the person information and modify the user groups, pretty much like it's done in the Share oauth extension</li>
<li>Put a custom web script in the alfresco web app which would be called from the callback filter to create/update the person and the groups</li>
</ol>
I don't like the first approach because I don't like the idea of storing the admin password for one web app in the configuration of another web app, but it does seem to be simpler than developing a custom web script as I never done that before and not sure how to change the users with the Java API.

What do you think? Or maybe there's another possibility that I'm not aware of?

Thanks.

Outcomes