Add ability to configure OAuth type in Multi User Che (#9640)

6.19.x
Mykhailo Kuznietsov 2018-05-14 17:37:14 +03:00 committed by GitHub
parent 5c4fd8b698
commit cd99cf4e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 421 additions and 288 deletions

View File

@ -86,6 +86,8 @@ import org.eclipse.che.multiuser.resource.api.ResourceModule;
import org.eclipse.che.plugin.github.factory.resolver.GithubFactoryParametersResolver;
import org.eclipse.che.security.PBKDF2PasswordEncryptor;
import org.eclipse.che.security.PasswordEncryptor;
import org.eclipse.che.security.oauth.EmbeddedOAuthAPI;
import org.eclipse.che.security.oauth.OAuthAPI;
import org.eclipse.che.workspace.infrastructure.docker.DockerInfraModule;
import org.eclipse.che.workspace.infrastructure.docker.local.LocalDockerModule;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfraModule;
@ -137,6 +139,7 @@ public class WsMasterModule extends AbstractModule {
bind(org.eclipse.che.api.user.server.UserService.class);
bind(org.eclipse.che.api.user.server.ProfileService.class);
bind(org.eclipse.che.api.user.server.PreferencesService.class);
bind(org.eclipse.che.security.oauth.OAuthAuthenticationService.class);
MapBinder<String, String> stacks =
MapBinder.newMapBinder(
@ -278,7 +281,7 @@ public class WsMasterModule extends AbstractModule {
bind(org.eclipse.che.security.oauth.shared.OAuthTokenProvider.class)
.to(org.eclipse.che.security.oauth.OAuthAuthenticatorTokenProvider.class);
bind(org.eclipse.che.security.oauth.OAuthAuthenticationService.class);
bind(OAuthAPI.class).to(EmbeddedOAuthAPI.class);
bind(RemoteSubscriptionStorage.class).to(InmemoryRemoteSubscriptionStorage.class);
install(new org.eclipse.che.api.workspace.activity.inject.WorkspaceActivityModule());

View File

@ -136,3 +136,9 @@ che.keycloak.js_adapter_url=NULL
# a discovery endpoint as detailed in the following specification
# https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
che.keycloak.oidc_provider=NULL
# Configuration of OAuth Authentication Service that can be used in "embedded" or "delegated" mode.
# If set to "embedded", then the service work as a wrapper to Che's OAuthAuthenticator ( as in Single User mode).
# If set to "delegated", then the service will use Keycloak IdentityProvider mechanism.
# Runtime Exception wii be thrown, in case if this property is not set properly.
che.oauth.service_mode=delegated

View File

@ -77,6 +77,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-account</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-shared</artifactId>

View File

@ -19,7 +19,7 @@ import org.eclipse.che.multiuser.keycloak.server.KeycloakConfigurationService;
import org.eclipse.che.multiuser.keycloak.server.KeycloakTokenValidator;
import org.eclipse.che.multiuser.keycloak.server.KeycloakUserManager;
import org.eclipse.che.multiuser.keycloak.server.dao.KeycloakProfileDao;
import org.eclipse.che.multiuser.keycloak.server.oauth2.KeycloakOAuthAuthenticationService;
import org.eclipse.che.security.oauth.OAuthAPI;
public class KeycloakModule extends AbstractModule {
@Override
@ -29,9 +29,10 @@ public class KeycloakModule extends AbstractModule {
.to(org.eclipse.che.multiuser.keycloak.server.KeycloakHttpJsonRequestFactory.class);
bind(TokenValidator.class).to(KeycloakTokenValidator.class);
bind(KeycloakConfigurationService.class);
bind(KeycloakOAuthAuthenticationService.class);
bind(ProfileDao.class).to(KeycloakProfileDao.class);
bind(PersonalAccountUserManager.class).to(KeycloakUserManager.class);
bind(OAuthAPI.class).toProvider(OAuthAPIProvider.class);
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.keycloak.server.deploy;
import com.google.inject.Injector;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.multiuser.keycloak.server.oauth2.DelegatedOAuthAPI;
import org.eclipse.che.security.oauth.EmbeddedOAuthAPI;
import org.eclipse.che.security.oauth.OAuthAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides appropriate OAuth Authentication API depending on configuration.
*
* @author Mykhailo Kuznietsov.
*/
public class OAuthAPIProvider implements Provider<OAuthAPI> {
private static final Logger LOG = LoggerFactory.getLogger(OAuthAPIProvider.class);
private String oauthType;
private Injector injector;
@Inject
public OAuthAPIProvider(
@Nullable @Named("che.oauth.service_mode") String oauthType, Injector injector) {
this.oauthType = oauthType;
this.injector = injector;
}
@Override
public OAuthAPI get() {
switch (oauthType) {
case "embedded":
return injector.getInstance(EmbeddedOAuthAPI.class);
case "delegated":
return injector.getInstance(DelegatedOAuthAPI.class);
default:
throw new RuntimeException(
"Unknown value configured for \"che.oauth.service_mode\", must be either \"embedded\", or \"delegated\"");
}
}
}

View File

@ -13,54 +13,48 @@ package org.eclipse.che.multiuser.keycloak.server.oauth2;
import io.jsonwebtoken.Jwt;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.UnauthorizedException;
import org.eclipse.che.api.core.rest.annotations.Required;
import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.multiuser.keycloak.server.KeycloakServiceClient;
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakTokenResponse;
import org.eclipse.che.security.oauth.OAuthAPI;
import org.eclipse.che.security.oauth.OAuthAuthenticationService;
import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor;
@Path("/oauth")
public class KeycloakOAuthAuthenticationService {
@Context UriInfo uriInfo;
@Context SecurityContext security;
/**
* Implementation of functional API component for {@link OAuthAuthenticationService}, that uses
* {@link KeycloakServiceClient} for authenticating users through Keycloak Identity providers.
*
* @author Mykhailo Kuznietsov
*/
public class DelegatedOAuthAPI implements OAuthAPI {
private final KeycloakServiceClient keycloakServiceClient;
@Inject
public KeycloakOAuthAuthenticationService(KeycloakServiceClient keycloakServiceClient) {
public DelegatedOAuthAPI(KeycloakServiceClient keycloakServiceClient) {
this.keycloakServiceClient = keycloakServiceClient;
}
/**
* Performs local and Keycloak accounts linking
*
* @return typically Response that redirect user for OAuth provider site
*/
@GET
@Path("authenticate")
@Override
public Response authenticate(
@Required @QueryParam("oauth_provider") String oauthProvider,
@Required @QueryParam("redirect_after_login") String redirectAfterLogin,
@Context HttpServletRequest request)
throws ForbiddenException, BadRequestException {
UriInfo uriInfo,
String oauthProvider,
List<String> scopes,
String redirectAfterLogin,
HttpServletRequest request)
throws BadRequestException {
Jwt jwtToken = (Jwt) request.getAttribute("token");
if (jwtToken == null) {
@ -71,19 +65,10 @@ public class KeycloakOAuthAuthenticationService {
return Response.temporaryRedirect(URI.create(accountLinkUrl)).build();
}
/**
* Gets OAuth token for user from Keycloak.
*
* @param oauthProvider OAuth provider name
* @return OAuthToken
* @throws ServerException
*/
@GET
@Path("token")
@Produces(MediaType.APPLICATION_JSON)
public OAuthToken token(@Required @QueryParam("oauth_provider") String oauthProvider)
throws ForbiddenException, BadRequestException, ConflictException, NotFoundException,
ServerException, UnauthorizedException {
@Override
public OAuthToken getToken(String oauthProvider)
throws ForbiddenException, BadRequestException, NotFoundException, ServerException,
UnauthorizedException {
try {
KeycloakTokenResponse response =
keycloakServiceClient.getIdentityProviderToken(oauthProvider);
@ -94,4 +79,20 @@ public class KeycloakOAuthAuthenticationService {
throw new ServerException(e.getMessage());
}
}
@Override
public void invalidateToken(String oauthProvider) throws ForbiddenException {
throw new ForbiddenException("Method is not supported in this implementation of OAuth API");
}
@Override
public Response callback(UriInfo uriInfo, List<String> errorValues) throws ForbiddenException {
throw new ForbiddenException("Method is not supported in this implementation of OAuth API");
}
@Override
public Set<OAuthAuthenticatorDescriptor> getRegisteredAuthenticators(UriInfo uriInfo)
throws ForbiddenException {
throw new ForbiddenException("Method is not supported in this implementation of OAuth API");
}
}

View File

@ -0,0 +1,163 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.security.oauth;
import static java.util.Collections.emptyList;
import static org.eclipse.che.commons.lang.UrlUtils.*;
import static org.eclipse.che.commons.lang.UrlUtils.getParameter;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.*;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.UnauthorizedException;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.rest.shared.dto.LinkParameter;
import org.eclipse.che.api.core.util.LinksHelper;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of functional API component for {@link OAuthAuthenticationService}, that uses
* {@link OAuthAuthenticator}.
*
* @author Mykhailo Kuznietsov
*/
public class EmbeddedOAuthAPI implements OAuthAPI {
private static final Logger LOG = LoggerFactory.getLogger(EmbeddedOAuthAPI.class);
@Inject
@Named("che.auth.access_denied_error_page")
protected String errorPage;
@Inject protected OAuthAuthenticatorProvider providers;
@Override
public Response authenticate(
UriInfo uriInfo,
String oauthProvider,
List<String> scopes,
String redirectAfterLogin,
HttpServletRequest request)
throws NotFoundException, OAuthAuthenticationException {
OAuthAuthenticator oauth = getAuthenticator(oauthProvider);
final String authUrl =
oauth.getAuthenticateUrl(getRequestUrl(uriInfo), scopes == null ? emptyList() : scopes);
return Response.temporaryRedirect(URI.create(authUrl)).build();
}
@Override
public Response callback(UriInfo uriInfo, List<String> errorValues)
throws NotFoundException, OAuthAuthenticationException {
URL requestUrl = getRequestUrl(uriInfo);
Map<String, List<String>> params = getQueryParametersFromState(getState(requestUrl));
if (errorValues != null && errorValues.contains("access_denied")) {
return Response.temporaryRedirect(
uriInfo.getRequestUriBuilder().replacePath(errorPage).replaceQuery(null).build())
.build();
}
final String providerName = getParameter(params, "oauth_provider");
OAuthAuthenticator oauth = getAuthenticator(providerName);
final List<String> scopes = params.get("scope");
oauth.callback(requestUrl, scopes == null ? Collections.<String>emptyList() : scopes);
final String redirectAfterLogin = getParameter(params, "redirect_after_login");
return Response.temporaryRedirect(URI.create(redirectAfterLogin)).build();
}
@Override
public Set<OAuthAuthenticatorDescriptor> getRegisteredAuthenticators(UriInfo uriInfo) {
Set<OAuthAuthenticatorDescriptor> result = new HashSet<>();
final UriBuilder uriBuilder =
uriInfo.getBaseUriBuilder().clone().path(OAuthAuthenticationService.class);
for (String name : providers.getRegisteredProviderNames()) {
final List<Link> links = new LinkedList<>();
links.add(
LinksHelper.createLink(
HttpMethod.GET,
uriBuilder
.clone()
.path(OAuthAuthenticationService.class, "authenticate")
.build()
.toString(),
null,
null,
"Authenticate URL",
newDto(LinkParameter.class)
.withName("oauth_provider")
.withRequired(true)
.withDefaultValue(name),
newDto(LinkParameter.class)
.withName("mode")
.withRequired(true)
.withDefaultValue("federated_login")));
result.add(newDto(OAuthAuthenticatorDescriptor.class).withName(name).withLinks(links));
}
return result;
}
@Override
public OAuthToken getToken(String oauthProvider)
throws NotFoundException, UnauthorizedException, ServerException {
OAuthAuthenticator provider = getAuthenticator(oauthProvider);
final Subject subject = EnvironmentContext.getCurrent().getSubject();
try {
OAuthToken token = provider.getToken(subject.getUserId());
if (token == null) {
token = provider.getToken(subject.getUserName());
}
if (token != null) {
return token;
}
throw new UnauthorizedException(
"OAuth token for user " + subject.getUserId() + " was not found");
} catch (IOException e) {
throw new ServerException(e.getLocalizedMessage(), e);
}
}
@Override
public void invalidateToken(String oauthProvider)
throws NotFoundException, UnauthorizedException, ServerException {
OAuthAuthenticator oauth = getAuthenticator(oauthProvider);
final Subject subject = EnvironmentContext.getCurrent().getSubject();
try {
if (!oauth.invalidateToken(subject.getUserId())) {
throw new UnauthorizedException(
"OAuth token for user " + subject.getUserId() + " was not found");
}
} catch (IOException e) {
throw new ServerException(e.getMessage());
}
}
protected OAuthAuthenticator getAuthenticator(String oauthProviderName) throws NotFoundException {
OAuthAuthenticator oauth = providers.getAuthenticator(oauthProviderName);
if (oauth == null) {
LOG.warn("Unsupported OAuth provider {} ", oauthProviderName);
throw new NotFoundException("Unsupported OAuth provider " + oauthProviderName);
}
return oauth;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.security.oauth;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
import org.eclipse.che.api.core.*;
import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor;
/**
* Interface of OAuth authentication service API component, that is used for.
*
* @author Mykhailo Kuznietsov
*/
public interface OAuthAPI {
/**
* Implementation of method {@link OAuthAuthenticationService#authenticate(String, String, List,
* HttpServletRequest)}
*/
Response authenticate(
UriInfo uriInfo,
String oauthProvider,
List<String> scopes,
String redirectAfterLogin,
HttpServletRequest request)
throws NotFoundException, OAuthAuthenticationException, ForbiddenException,
BadRequestException;
/** Implementation of method {@link OAuthAuthenticationService#callback(List)} */
Response callback(UriInfo uriInfo, List<String> errorValues)
throws NotFoundException, OAuthAuthenticationException, ForbiddenException;
/** Implementation of method {@link OAuthAuthenticationService#getRegisteredAuthenticators()} */
Set<OAuthAuthenticatorDescriptor> getRegisteredAuthenticators(UriInfo uriInfo)
throws ForbiddenException;
/** Implementation of method {@link OAuthAuthenticationService#token(String)} */
OAuthToken getToken(String oauthProvider)
throws NotFoundException, UnauthorizedException, ServerException, ForbiddenException,
BadRequestException, ConflictException;
/** Implementation of method {@link OAuthAuthenticationService#invalidate(String)}} */
void invalidateToken(String oauthProvider)
throws NotFoundException, UnauthorizedException, ServerException, ForbiddenException;
}

View File

@ -10,27 +10,12 @@
*/
package org.eclipse.che.security.oauth;
import static java.util.Collections.emptyList;
import static org.eclipse.che.commons.lang.UrlUtils.getParameter;
import static org.eclipse.che.commons.lang.UrlUtils.getQueryParametersFromState;
import static org.eclipse.che.commons.lang.UrlUtils.getRequestUrl;
import static org.eclipse.che.commons.lang.UrlUtils.getState;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
@ -38,89 +23,49 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.UnauthorizedException;
import org.eclipse.che.api.core.*;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.core.rest.annotations.Required;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.rest.shared.dto.LinkParameter;
import org.eclipse.che.api.core.util.LinksHelper;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** RESTful wrapper for OAuthAuthenticator. */
@Path("oauth")
public class OAuthAuthenticationService {
private static final Logger LOG = LoggerFactory.getLogger(OAuthAuthenticationService.class);
@Inject
@Named("che.auth.access_denied_error_page")
protected String errorPage;
@Inject protected OAuthAuthenticatorProvider providers;
public class OAuthAuthenticationService extends Service {
@Context protected UriInfo uriInfo;
@Context protected SecurityContext security;
@Inject private OAuthAPI oAuthAPI;
/**
* Redirect request to OAuth provider site for authentication|authorization. Client request must
* contains set of required query parameters:
*
* <table>
* <tr><th>Name</th><th>Description</th><th>Mandatory</th><th>Default value</th></tr>
* <tr><td>oauth_provider</td><td>Name of OAuth provider. At the moment <tt>google</tt> and <tt>github</tt>
* supported</td><td>yes</td><td>none</td></tr>
* <tr><td>scope</td><td>Specify exactly what type of access needed. List of scopes dependents to OAuth provider.
* Requested scopes displayed at user authorization page at OAuth provider site. Check docs about scopes
* supported by
* suitable OAuth provider.</td><td>no</td><td>Empty list</td></tr>
* <tr><td>mode</td><td>Authentication mode. May be <tt>federated_login</tt> or <tt>token</tt>. If <tt>mode</tt>
* set
* as <tt>federated_login</tt> that parameters 'username' and 'password' added to redirect URL after successful
* user
* authentication. (see next parameter) In this case 'password' is temporary generated password. This password will
* be validated by FederatedLoginModule.</td><td>no</td><td>token</td></tr>
* <tr><td>redirect_after_login</td><td>URL for user redirection after successful
* authentication</td><td>yes</td><td>none</td></tr>
* </table>
* Redirect request to OAuth provider site for authentication|authorization. Client must provide
* query parameters, that may or may not be required, depending on the active implementation of
* {@link OAuthAPI}.
*
* @param oauthProvider -
* @param redirectAfterLogin
* @param scopes - list
* @return typically Response that redirect user for OAuth provider site
*/
@GET
@Path("authenticate")
public Response authenticate(
@Required @QueryParam("oauth_provider") String oauthProvider,
@QueryParam("scope") List<String> scopes)
throws ForbiddenException, NotFoundException, OAuthAuthenticationException {
OAuthAuthenticator oauth = getAuthenticator(oauthProvider);
final String authUrl =
oauth.getAuthenticateUrl(getRequestUrl(uriInfo), scopes == null ? emptyList() : scopes);
return Response.temporaryRedirect(URI.create(authUrl)).build();
@QueryParam("oauth_provider") String oauthProvider,
@QueryParam("redirect_after_login") String redirectAfterLogin,
@QueryParam("scope") List<String> scopes,
@Context HttpServletRequest request)
throws NotFoundException, OAuthAuthenticationException, BadRequestException,
ForbiddenException {
return oAuthAPI.authenticate(uriInfo, oauthProvider, scopes, redirectAfterLogin, request);
}
@GET
@Path("callback")
/** Process OAuth callback */
public Response callback(@QueryParam("errorValues") List<String> errorValues)
throws OAuthAuthenticationException, NotFoundException {
URL requestUrl = getRequestUrl(uriInfo);
Map<String, List<String>> params = getQueryParametersFromState(getState(requestUrl));
if (errorValues != null && errorValues.contains("access_denied")) {
return Response.temporaryRedirect(
uriInfo.getRequestUriBuilder().replacePath(errorPage).replaceQuery(null).build())
.build();
}
final String providerName = getParameter(params, "oauth_provider");
OAuthAuthenticator oauth = getAuthenticator(providerName);
final List<String> scopes = params.get("scope");
oauth.callback(requestUrl, scopes == null ? Collections.<String>emptyList() : scopes);
final String redirectAfterLogin = getParameter(params, "redirect_after_login");
return Response.temporaryRedirect(URI.create(redirectAfterLogin)).build();
throws OAuthAuthenticationException, NotFoundException, ForbiddenException {
return oAuthAPI.callback(uriInfo, errorValues);
}
/**
@ -130,29 +75,8 @@ public class OAuthAuthenticationService {
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Set<OAuthAuthenticatorDescriptor> getRegisteredAuthenticators() {
Set<OAuthAuthenticatorDescriptor> result = new HashSet<>();
final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder().clone().path(getClass());
for (String name : providers.getRegisteredProviderNames()) {
final List<Link> links = new LinkedList<>();
links.add(
LinksHelper.createLink(
HttpMethod.GET,
uriBuilder.clone().path(getClass(), "authenticate").build().toString(),
null,
null,
"Authenticate URL",
newDto(LinkParameter.class)
.withName("oauth_provider")
.withRequired(true)
.withDefaultValue(name),
newDto(LinkParameter.class)
.withName("mode")
.withRequired(true)
.withDefaultValue("federated_login")));
result.add(newDto(OAuthAuthenticatorDescriptor.class).withName(name).withLinks(links));
}
return result;
public Set<OAuthAuthenticatorDescriptor> getRegisteredAuthenticators() throws ForbiddenException {
return oAuthAPI.getRegisteredAuthenticators(uriInfo);
}
/**
@ -166,47 +90,20 @@ public class OAuthAuthenticationService {
@Path("token")
@Produces(MediaType.APPLICATION_JSON)
public OAuthToken token(@Required @QueryParam("oauth_provider") String oauthProvider)
throws ServerException, UnauthorizedException, NotFoundException, ForbiddenException {
OAuthAuthenticator provider = getAuthenticator(oauthProvider);
final Subject subject = EnvironmentContext.getCurrent().getSubject();
try {
OAuthToken token = provider.getToken(subject.getUserId());
if (token == null) {
token = provider.getToken(subject.getUserName());
}
if (token != null) {
return token;
}
throw new UnauthorizedException(
"OAuth token for user " + subject.getUserId() + " was not found");
} catch (IOException e) {
throw new ServerException(e.getLocalizedMessage(), e);
}
throws ServerException, UnauthorizedException, NotFoundException, ForbiddenException,
BadRequestException, ConflictException {
return oAuthAPI.getToken(oauthProvider);
}
/**
* Invalidate OAuth token for user.
*
* @param oauthProvider OAuth provider name
*/
@DELETE
@Path("token")
public void invalidate(@Required @QueryParam("oauth_provider") String oauthProvider)
throws UnauthorizedException, NotFoundException, ServerException, ForbiddenException {
OAuthAuthenticator oauth = getAuthenticator(oauthProvider);
final Subject subject = EnvironmentContext.getCurrent().getSubject();
try {
if (!oauth.invalidateToken(subject.getUserId())) {
throw new UnauthorizedException(
"OAuth token for user " + subject.getUserId() + " was not found");
}
} catch (IOException e) {
throw new ServerException(e.getMessage());
}
}
protected OAuthAuthenticator getAuthenticator(String oauthProviderName) throws NotFoundException {
OAuthAuthenticator oauth = providers.getAuthenticator(oauthProviderName);
if (oauth == null) {
LOG.warn("Unsupported OAuth provider {} ", oauthProviderName);
throw new NotFoundException("Unsupported OAuth provider " + oauthProviderName);
}
return oauth;
oAuthAPI.invalidateToken(oauthProvider);
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.security.oauth;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
import org.eclipse.che.api.core.NotFoundException;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/** @author Mykhailo Kuznietsov */
@Listeners(value = MockitoTestNGListener.class)
public class EmbeddedOAuthAPITest {
@Mock OAuthAuthenticatorProvider providers;
@InjectMocks EmbeddedOAuthAPI embeddedOAuthAPI;
@Test(
expectedExceptions = NotFoundException.class,
expectedExceptionsMessageRegExp = "Unsupported OAuth provider unknown"
)
public void shouldThrowExceptionIfNoSuchProviderFound() throws Exception {
embeddedOAuthAPI.getToken("unknown");
}
@Test
public void shouldBeAbleToGetUserToken() throws Exception {
String provider = "myprovider";
String token = "token123";
OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class);
when(providers.getAuthenticator(eq(provider))).thenReturn(authenticator);
when(authenticator.getToken(anyString())).thenReturn(newDto(OAuthToken.class).withToken(token));
OAuthToken result = embeddedOAuthAPI.getToken(provider);
assertEquals(result.getToken(), token);
}
}

View File

@ -1,110 +0,0 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.security.oauth;
import static com.jayway.restassured.RestAssured.given;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME;
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD;
import static org.everrest.assured.JettyHttpServer.SECURE_PATH;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import com.jayway.restassured.response.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
import org.eclipse.che.api.core.rest.ApiExceptionMapper;
import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.eclipse.che.dto.server.DtoFactory;
import org.everrest.assured.EverrestJetty;
import org.everrest.assured.JettyHttpServer;
import org.everrest.core.Filter;
import org.everrest.core.GenericContainerRequest;
import org.everrest.core.RequestFilter;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/** @author Max Shaposhnik */
@Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class})
public class OAuthAuthenticationServiceTest {
@SuppressWarnings("unused")
private EnvironmentFilter filter = new EnvironmentFilter();
@SuppressWarnings("unused")
private final ApiExceptionMapper exceptionMapper = new ApiExceptionMapper();
@Mock protected OAuthAuthenticatorProvider providers;
@Mock protected UriInfo uriInfo;
@Mock protected SecurityContext security;
@InjectMocks OAuthAuthenticationService service;
@Filter
public static class EnvironmentFilter implements RequestFilter {
public void doFilter(GenericContainerRequest request) {
EnvironmentContext context = EnvironmentContext.getCurrent();
context.setSubject(
new SubjectImpl(JettyHttpServer.ADMIN_USER_NAME, "id-2314", "token-2323", false));
}
}
@Test
public void shouldThrowExceptionIfNoSuchProviderFound() throws Exception {
final Response response =
given()
.auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.contentType("application/json")
.when()
.queryParam("oauth_provider", "unknown")
.get(SECURE_PATH + "/oauth/token");
assertEquals(response.getStatusCode(), 404);
assertEquals(
DtoFactory.getInstance()
.createDtoFromJson(response.getBody().asInputStream(), ServiceError.class)
.getMessage(),
"Unsupported OAuth provider unknown");
}
@Test
public void shouldBeAbleToGetUserToken() throws Exception {
String provider = "myprovider";
String token = "token123";
OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class);
when(providers.getAuthenticator(eq(provider))).thenReturn(authenticator);
when(authenticator.getToken(anyString())).thenReturn(newDto(OAuthToken.class).withToken(token));
final Response response =
given()
.auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.contentType("application/json")
.when()
.queryParam("oauth_provider", provider)
.get(SECURE_PATH + "/oauth/token");
assertEquals(response.getStatusCode(), 200);
assertEquals(
DtoFactory.getInstance()
.createDtoFromJson(response.getBody().asInputStream(), OAuthToken.class)
.getToken(),
token);
}
}