001/*
002 * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nelson Silva <nelson.silva@inevo.pt> - initial API and implementation
016 *     Nuxeo
017 */
018
019package org.nuxeo.ecm.platform.oauth2.openid.auth;
020
021import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_ERROR;
022
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Map;
026
027import javax.servlet.http.HttpServletRequest;
028import javax.servlet.http.HttpServletResponse;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.ecm.core.api.NuxeoException;
033import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo;
034import org.nuxeo.ecm.platform.oauth2.openid.OpenIDConnectProvider;
035import org.nuxeo.ecm.platform.oauth2.openid.OpenIDConnectProviderRegistry;
036import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin;
037import org.nuxeo.runtime.api.Framework;
038
039/**
040 * Authenticator using OpenID to retrieve user identity.
041 *
042 * @author Nelson Silva <nelson.silva@inevo.pt>
043 */
044public class OpenIDConnectAuthenticator implements NuxeoAuthenticationPlugin {
045
046    private static final Log log = LogFactory.getLog(OpenIDConnectAuthenticator.class);
047
048    public static final String STATE_URL_PARAM_NAME = "state";
049
050    public static final String STATE_SESSION_ATTRIBUTE = STATE_URL_PARAM_NAME;
051
052    public static final String CODE_URL_PARAM_NAME = "code";
053
054    public static final String ERROR_URL_PARAM_NAME = "error";
055
056    public static final String PROVIDER_URL_PARAM_NAME = "provider";
057
058    public static final String USERINFO_KEY = "OPENID_USERINFO";
059
060    public static final String PROPERTY_OAUTH_CREATE_USER = "nuxeo.oauth.auth.create.user";
061
062    public static final String PROPERTY_SKIP_OAUTH_TOKEN = "nuxeo.skip.oauth.token.state.check";
063
064    protected void sendError(HttpServletRequest req, String msg) {
065        req.setAttribute(LOGIN_ERROR, msg);
066    }
067
068    public UserIdentificationInfo retrieveIdentityFromOAuth(HttpServletRequest req, HttpServletResponse resp) {
069
070        // Getting the "error" URL parameter
071        String error = req.getParameter(ERROR_URL_PARAM_NAME);
072
073        // / Checking if there was an error such as the user denied access
074        if (error != null && error.length() > 0) {
075            sendError(req, "There was an error: \"" + error + "\".");
076            return null;
077        }
078
079        // Getting the "code" URL parameter
080        String code = req.getParameter(CODE_URL_PARAM_NAME);
081
082        // Checking conditions on the "code" URL parameter
083        if (code == null || code.isEmpty()) {
084            sendError(req, "There was an error: \"" + code + "\".");
085            return null;
086        }
087
088        // Getting the "provider" URL parameter
089        String serviceProviderName = req.getParameter(PROVIDER_URL_PARAM_NAME);
090
091        // Checking conditions on the "provider" URL parameter
092        if (serviceProviderName == null || serviceProviderName.isEmpty()) {
093            sendError(req, "Missing OpenID Connect Provider ID.");
094            return null;
095        }
096
097        try {
098            OpenIDConnectProviderRegistry registry = Framework.getLocalService(OpenIDConnectProviderRegistry.class);
099            OpenIDConnectProvider provider = registry.getProvider(serviceProviderName);
100
101            if (provider == null) {
102                sendError(req, "No service provider called: \"" + serviceProviderName + "\".");
103                return null;
104            }
105
106            // Check the state token
107
108            if (!Framework.isBooleanPropertyTrue(PROPERTY_SKIP_OAUTH_TOKEN) && !provider.verifyStateToken(req)) {
109                sendError(req, "Invalid state parameter.");
110            }
111
112            // Validate the token
113            String accessToken = provider.getAccessToken(req, code);
114
115            if (accessToken == null) {
116                return null;
117            }
118
119            OpenIDUserInfo info = provider.getUserInfo(accessToken);
120
121            // Store the user info as a key in the request so apps can use it
122            // later in the chain
123            req.setAttribute(USERINFO_KEY, info);
124
125            UserResolver userResolver = provider.getUserResolver();
126
127            String userId;
128            if (Framework.isBooleanPropertyTrue(PROPERTY_OAUTH_CREATE_USER)) {
129                userId = userResolver.findOrCreateNuxeoUser(info);
130            } else {
131                userId = userResolver.findNuxeoUser(info);
132            }
133
134            if (userId == null) {
135
136                sendError(req, "No user found with email: \"" + info.getEmail() + "\".");
137                return null;
138            }
139
140            return new UserIdentificationInfo(userId, userId);
141
142        } catch (NuxeoException e) {
143            log.error("Error while retrieve Identity From OAuth", e);
144        }
145
146        return null;
147    }
148
149    @Override
150    public List<String> getUnAuthenticatedURLPrefix() {
151        return new ArrayList<String>();
152    }
153
154    @Override
155    public UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest httpRequest,
156            HttpServletResponse httpResponse) {
157        String error = httpRequest.getParameter(ERROR_URL_PARAM_NAME);
158        String code = httpRequest.getParameter(CODE_URL_PARAM_NAME);
159        String serviceProviderName = httpRequest.getParameter(PROVIDER_URL_PARAM_NAME);
160        if (serviceProviderName == null) {
161            return null;
162        }
163        if (code == null && error == null) {
164            return null;
165        }
166        UserIdentificationInfo userIdent = retrieveIdentityFromOAuth(httpRequest, httpResponse);
167        if (userIdent != null) {
168            userIdent.setAuthPluginName("TRUSTED_LM");
169        }
170        return userIdent;
171    }
172
173    @Override
174    public Boolean handleLoginPrompt(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String baseURL) {
175        return false;
176    }
177
178    @Override
179    public Boolean needLoginPrompt(HttpServletRequest httpRequest) {
180        return false;
181    }
182
183    @Override
184    public void initPlugin(Map<String, String> parameters) {
185    }
186}