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