001/*
002 * (C) Copyright 2016 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 *     Gabriel Barata <gbarata@nuxeo.com>
018 */
019package org.nuxeo.ecm.restapi.server.jaxrs;
020
021import com.google.api.client.auth.oauth2.Credential;
022import org.codehaus.jackson.map.ObjectMapper;
023
024import javax.servlet.http.HttpServletResponse;
025import javax.ws.rs.core.Response.Status;
026import javax.ws.rs.core.Response.StatusType;
027
028import org.nuxeo.ecm.automation.server.jaxrs.RestOperationException;
029import org.nuxeo.ecm.core.api.DocumentModel;
030import org.nuxeo.ecm.core.api.NuxeoException;
031import org.nuxeo.ecm.platform.oauth2.providers.AbstractOAuth2UserEmailProvider;
032import org.nuxeo.ecm.platform.oauth2.providers.NuxeoOAuth2ServiceProvider;
033import org.nuxeo.ecm.platform.oauth2.providers.OAuth2ServiceProvider;
034import org.nuxeo.ecm.platform.oauth2.providers.OAuth2ServiceProviderRegistry;
035import org.nuxeo.ecm.platform.oauth2.tokens.NuxeoOAuth2Token;
036import org.nuxeo.ecm.webengine.model.WebObject;
037import org.nuxeo.ecm.webengine.model.impl.AbstractResource;
038import org.nuxeo.ecm.webengine.model.impl.ResourceTypeImpl;
039import org.nuxeo.runtime.api.Framework;
040
041import javax.servlet.http.HttpServletRequest;
042import javax.ws.rs.GET;
043import javax.ws.rs.Path;
044import javax.ws.rs.PathParam;
045import javax.ws.rs.core.Context;
046import javax.ws.rs.core.MediaType;
047import javax.ws.rs.core.Response;
048import java.io.IOException;
049import java.io.Serializable;
050import java.util.HashMap;
051import java.util.List;
052import java.util.Map;
053
054/**
055 * Endpoint to retrieve OAuth2 authentication data
056 * @since 8.4
057 */
058@WebObject(type = "oauth2")
059public class OAuth2Object extends AbstractResource<ResourceTypeImpl> {
060
061    /**
062     * Retrieves oauth2 data for a given provider.
063     */
064    @GET
065    @Path("provider/{providerId}")
066    public Response getProvider(@PathParam("providerId") String providerId,
067                                @Context HttpServletRequest request) throws IOException, RestOperationException {
068
069        NuxeoOAuth2ServiceProvider provider = getProvider(providerId);
070
071        Map<String,Object> result = new HashMap<>();
072        result.put("serviceName", provider.getServiceName());
073        result.put("isAvailable", provider.isProviderAvailable());
074        result.put("clientId", provider.getClientId());
075        result.put("authorizationURL", provider.getClientId() == null ? null : provider.getAuthorizationUrl(request));
076
077        String username = request.getUserPrincipal().getName();
078        NuxeoOAuth2Token token = getToken(provider, username);
079        boolean isAuthorized = (token != null);
080        String login = isAuthorized ? token.getServiceLogin() : null;
081        result.put("isAuthorized", isAuthorized);
082        result.put("userId", login);
083
084        return buildResponse(Status.OK, result);
085    }
086
087    /**
088     * Retrieves a valid access token for a given provider and the current user.
089     * If expired, the token will be refreshed.
090     */
091    @GET
092    @Path("provider/{providerId}/token")
093    public Response getToken(@PathParam("providerId") String providerId,
094                             @Context HttpServletRequest request) throws IOException, RestOperationException {
095
096        NuxeoOAuth2ServiceProvider provider = getProvider(providerId);
097
098        String username = request.getUserPrincipal().getName();
099        NuxeoOAuth2Token token = getToken(provider, username);
100        if (token == null) {
101            return Response.status(Status.NOT_FOUND).build();
102        }
103        Credential credential = getCredential(provider, token);
104
105        if (credential == null) {
106            return Response.status(Status.NOT_FOUND).build();
107        }
108        Long expiresInSeconds = credential.getExpiresInSeconds();
109        if (expiresInSeconds != null && expiresInSeconds <= 0) {
110            credential.refreshToken();
111        }
112        Map<String,Object> result = new HashMap<>();
113        result.put("token", credential.getAccessToken());
114        return buildResponse(Status.OK, result);
115    }
116
117    private NuxeoOAuth2Token getToken(NuxeoOAuth2ServiceProvider provider, String nxuser) {
118        Map<String, Serializable> filter = new HashMap<>();
119        filter.put("serviceName", provider.getId());
120        filter.put(NuxeoOAuth2Token.KEY_NUXEO_LOGIN, nxuser);
121        return Framework.doPrivileged(() -> {
122            List<DocumentModel> entries = provider.getCredentialDataStore().query(filter);
123            if (entries != null) {
124                if (entries.size() > 1) {
125                    throw new NuxeoException("Found multiple " + provider.getId() + " accounts for " + nxuser);
126                } else if (entries.size() == 1) {
127                    return new NuxeoOAuth2Token(entries.get(0));
128                }
129            }
130            return null;
131        });
132    }
133
134    private Credential getCredential(NuxeoOAuth2ServiceProvider provider, NuxeoOAuth2Token token) {
135        return provider.loadCredential(
136            (provider instanceof AbstractOAuth2UserEmailProvider) ? token.getServiceLogin() : token.getNuxeoLogin());
137    }
138
139    private NuxeoOAuth2ServiceProvider getProvider(String providerId) throws RestOperationException {
140        OAuth2ServiceProvider provider = Framework.getService(OAuth2ServiceProviderRegistry.class)
141            .getProvider(providerId);
142        if (provider == null || !(provider instanceof NuxeoOAuth2ServiceProvider)) {
143            RestOperationException err = new RestOperationException("Invalid provider: " + providerId);
144            err.setStatus(HttpServletResponse.SC_BAD_REQUEST);
145            throw err;
146        }
147        return (NuxeoOAuth2ServiceProvider) provider;
148    }
149
150    private Response buildResponse(StatusType status, Object obj) throws IOException {
151        ObjectMapper mapper = new ObjectMapper();
152        String message = mapper.writeValueAsString(obj);
153
154        return Response.status(status)
155            .header("Content-Length", message.getBytes("UTF-8").length)
156            .type(MediaType.APPLICATION_JSON + "; charset=UTF-8")
157            .entity(message)
158            .build();
159    }
160
161}