001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 */
012package org.nuxeo.ecm.webengine.jaxrs.login;
013
014import java.io.IOException;
015import java.security.Principal;
016import java.util.Set;
017
018import javax.security.auth.login.LoginContext;
019import javax.security.auth.login.LoginException;
020import javax.servlet.FilterChain;
021import javax.servlet.FilterConfig;
022import javax.servlet.ServletException;
023import javax.servlet.http.HttpServletRequest;
024import javax.servlet.http.HttpServletRequestWrapper;
025import javax.servlet.http.HttpServletResponse;
026
027import org.nuxeo.common.utils.Base64;
028import org.nuxeo.common.utils.StringUtils;
029import org.nuxeo.ecm.core.api.local.ClientLoginModule;
030import org.nuxeo.ecm.webengine.jaxrs.HttpFilter;
031import org.nuxeo.runtime.api.Framework;
032
033/**
034 * Filter using the {@link SimpleLoginModule} to authenticate a request.
035 *
036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
037 */
038public class AuthenticationFilter extends HttpFilter {
039
040    public static final String DEFAULT_SECURITY_DOMAIN = "nuxeo-client-login";
041
042    protected String domain = DEFAULT_SECURITY_DOMAIN;
043
044    protected boolean autoPrompt = true;
045
046    protected String realmName = "Nuxeo";
047
048    @Override
049    public void init(FilterConfig filterConfig) throws ServletException {
050        String v = filterConfig.getInitParameter("securityDomain");
051        if (v != null) {
052            domain = v;
053        }
054        v = filterConfig.getInitParameter("realmName");
055        if (v != null) {
056            realmName = v;
057        }
058    }
059
060    @Override
061    public void run(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException,
062            ServletException {
063
064        LoginContext lc = null;
065        if (request.getUserPrincipal() == null) {
066            try {
067                lc = doLogin(request, response);
068                request = wrapRequest(request, lc);
069            } catch (LoginException e) {
070                // login failed
071                handleLoginFailure(request, response, e);
072                return;
073            }
074        }
075
076        try {
077            chain.doFilter(request, response);
078        } finally {
079            ClientLoginModule.getThreadLocalLogin().clear();
080            if (lc != null) {
081                // a null lc may indicate an anonymous login
082                try {
083                    lc.logout();
084                } catch (LoginException e) {
085                    throw new RuntimeException(e);
086                }
087            }
088        }
089    }
090
091    @Override
092    public void destroy() {
093    }
094
095    protected String[] retrieveBasicLogin(HttpServletRequest httpRequest) {
096        String auth = httpRequest.getHeader("authorization");
097        if (auth != null && auth.toLowerCase().startsWith("basic")) {
098            int idx = auth.indexOf(' ');
099            String b64userpassword = auth.substring(idx + 1);
100            byte[] clearUp = Base64.decode(b64userpassword);
101            String userpassword = new String(clearUp);
102            String[] up = StringUtils.split(userpassword, ':', false);
103            if (up.length != 2) {
104                return null;
105            }
106            return up;
107        }
108        return null;
109    }
110
111    protected LoginContext doLogin(HttpServletRequest request, HttpServletResponse response) throws LoginException {
112        String[] login = retrieveBasicLogin(request);
113        if (login != null) {
114            return Framework.login(login[0], login[1]);
115        }
116        // TODO no login provided - use anonymous ?
117        // for now no anonymous user supported - we require a login
118        throw new LoginException("User must login");
119        // return null;
120    }
121
122    protected void handleLoginFailure(HttpServletRequest request, HttpServletResponse response, LoginException e) {
123        String s = "Basic realm=\"" + realmName + "\"";
124        response.setHeader("WWW-Authenticate", s);
125        response.setStatus(401);
126    }
127
128    protected HttpServletRequest wrapRequest(HttpServletRequest request, LoginContext lc) {
129        Set<Principal> set = lc.getSubject().getPrincipals();
130        if (!set.isEmpty()) {
131            final Principal principal = set.iterator().next();
132            return new HttpServletRequestWrapper(request) {
133                @Override
134                public Principal getUserPrincipal() {
135                    return principal;
136                }
137            };
138        }
139        return request;
140    }
141}