001/*
002 * (C) Copyright 2018 Nuxeo (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 *     Florent Guillaume
018 */
019package org.nuxeo.ecm.platform.ui.web.auth.oauth;
020
021import static java.lang.Boolean.FALSE;
022import static javax.ws.rs.core.HttpHeaders.AUTHORIZATION;
023import static net.oauth.OAuth.OAUTH_SIGNATURE;
024
025import java.io.IOException;
026import java.net.URISyntaxException;
027import java.util.List;
028import java.util.Map;
029
030import javax.servlet.http.HttpServletRequest;
031import javax.servlet.http.HttpServletResponse;
032
033import org.apache.logging.log4j.LogManager;
034import org.apache.logging.log4j.Logger;
035import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo;
036import org.nuxeo.ecm.platform.oauth.consumers.NuxeoOAuthConsumer;
037import org.nuxeo.ecm.platform.oauth.consumers.OAuthConsumerRegistry;
038import org.nuxeo.ecm.platform.oauth.keys.OAuthServerKeyManager;
039import org.nuxeo.ecm.platform.oauth.tokens.OAuthToken;
040import org.nuxeo.ecm.platform.oauth.tokens.OAuthTokenStore;
041import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin;
042import org.nuxeo.runtime.api.Framework;
043import org.nuxeo.runtime.transaction.TransactionHelper;
044
045import net.oauth.OAuthAccessor;
046import net.oauth.OAuthException;
047import net.oauth.OAuthMessage;
048import net.oauth.OAuthValidator;
049import net.oauth.SimpleOAuthValidator;
050import net.oauth.server.OAuthServlet;
051
052/**
053 * OAuth 1 Authentication Plugin.
054 *
055 * @since 10.3
056 */
057public class NuxeoOAuth1Authenticator implements NuxeoAuthenticationPlugin {
058
059    private static final Logger log = LogManager.getLogger(NuxeoOAuth1Authenticator.class);
060
061    @Override
062    public void initPlugin(Map<String, String> parameters) {
063        // nothing to init
064    }
065
066    @Override
067    public List<String> getUnAuthenticatedURLPrefix() {
068        return null; // NOSONAR
069    }
070
071    @Override
072    public Boolean needLoginPrompt(HttpServletRequest httpRequest) {
073        return FALSE;
074    }
075
076    @Override
077    public Boolean handleLoginPrompt(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String baseURL) {
078        return FALSE;
079    }
080
081    @Override
082    public UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest request, HttpServletResponse response) {
083        if (!isOAuth1SignedRequest(request)) {
084            log.trace("Not an OAuth 1 signed request");
085            return null;
086        }
087        String username = getIdentity(request);
088        if (username == null) {
089            log.trace("OAuth 1 auth failed");
090            return null;
091        }
092        log.trace("OAuth 1 auth for user: {}", username);
093        return new UserIdentificationInfo(username);
094    }
095
096    protected boolean isOAuth1SignedRequest(HttpServletRequest request) {
097        String auth = request.getHeader(AUTHORIZATION);
098        if (auth != null && auth.contains("OAuth")) {
099            return true;
100        }
101        if (request.getParameter(OAUTH_SIGNATURE) != null) {
102            return true;
103        }
104        return false;
105    }
106
107    protected String getIdentity(HttpServletRequest request) {
108        return TransactionHelper.runInTransaction(() -> {
109            try {
110                return getOAuth1Identity(request);
111            } catch (IOException e) {
112                log.debug(e, e);
113                return null;
114            }
115        });
116    }
117
118    /**
119     * Verifies OAuth information and returns identity.
120     */
121    protected String getOAuth1Identity(HttpServletRequest request) throws IOException {
122        String url = getRequestURL(request);
123        OAuthMessage message = OAuthServlet.getMessage(request, url);
124
125        String consumerKey = message.getConsumerKey();
126        String signatureMethod = message.getSignatureMethod();
127        OAuthConsumerRegistry consumerRegistry = Framework.getService(OAuthConsumerRegistry.class);
128        NuxeoOAuthConsumer consumer = consumerRegistry.getConsumer(consumerKey, signatureMethod);
129
130        if (consumer == null && consumerKey != null) {
131            OAuthServerKeyManager okm = Framework.getService(OAuthServerKeyManager.class);
132            if (consumerKey.equals(okm.getInternalKey())) {
133                consumer = okm.getInternalConsumer();
134            }
135        }
136        if (consumer == null) {
137            return null;
138        }
139
140        OAuthAccessor accessor = new OAuthAccessor(consumer);
141        OAuthValidator validator = new SimpleOAuthValidator();
142        OAuthTokenStore tokenStore = Framework.getService(OAuthTokenStore.class);
143        OAuthToken aToken = tokenStore.getAccessToken(message.getToken());
144        String username;
145        if (aToken != null) {
146            // three-legged auth
147            accessor.accessToken = aToken.getToken();
148            accessor.tokenSecret = aToken.getTokenSecret();
149            username = aToken.getNuxeoLogin();
150        } else {
151            // two-legged auth
152            if (!consumer.allowSignedFetch()) {
153                return null;
154            }
155            username = consumer.getSignedFetchUser();
156            if (NuxeoOAuthConsumer.SIGNEDFETCH_OPENSOCIAL_VIEWER.equals(username)) {
157                username = message.getParameter("opensocial_viewer_id");
158            } else if (NuxeoOAuthConsumer.SIGNEDFETCH_OPENSOCIAL_OWNER.equals(username)) {
159                username = message.getParameter("opensocial_owner_id");
160            }
161        }
162        try {
163            validator.validateMessage(message, accessor);
164            return username;
165        } catch (OAuthException | URISyntaxException e) {
166            log.debug("Invalid OAuth signature", e);
167            return null;
168        }
169    }
170
171    /**
172     * Gets the URL used for this request by checking the X-Forwarded-Proto header used in the request.
173     */
174    // public for tests
175    public static String getRequestURL(HttpServletRequest request) {
176        String url = request.getRequestURL().toString();
177        String forwardedProto = request.getHeader("X-Forwarded-Proto");
178        if (forwardedProto != null && !url.startsWith(forwardedProto)) {
179            url = forwardedProto + url.substring(url.indexOf("://"));
180        }
181        return url;
182    }
183
184}