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