fixup! Fetch oauth tokens from kubernetes secrets

pull/652/head
ivinokur 2024-02-07 11:10:24 +02:00
parent 71b21e37f5
commit ab873284e6
8 changed files with 85 additions and 19 deletions

View File

@ -14,7 +14,6 @@ package org.eclipse.che.api.factory.server.scm.kubernetes;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.eclipse.che.commons.lang.StringUtils.trimEnd;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.LabelSelectorBuilder;
@ -87,8 +86,8 @@ public class KubernetesPersonalAccessTokenManager implements PersonalAccessToken
this.gitCredentialManager = gitCredentialManager;
}
@VisibleForTesting
void save(PersonalAccessToken personalAccessToken)
@Override
public void store(PersonalAccessToken personalAccessToken)
throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException {
try {
String namespace = getFirstNamespace();
@ -136,7 +135,7 @@ public class KubernetesPersonalAccessTokenManager implements PersonalAccessToken
ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException {
PersonalAccessToken personalAccessToken =
scmPersonalAccessTokenFetcher.fetchPersonalAccessToken(cheUser, scmServerUrl);
save(personalAccessToken);
store(personalAccessToken);
return personalAccessToken;
}
@ -291,7 +290,7 @@ public class KubernetesPersonalAccessTokenManager implements PersonalAccessToken
}
@Override
public void store(String scmServerUrl)
public void storeGitCredentials(String scmServerUrl)
throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException,
ScmCommunicationException, ScmUnauthorizedException {
Subject subject = EnvironmentContext.getCurrent().getSubject();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2023 Red Hat, Inc.
* Copyright (c) 2012-2024 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
@ -142,7 +142,7 @@ public class KubernetesPersonalAccessTokenManagerTest {
"https://bitbucket.com", "cheUser", "username", "token-name", "tid-24", "token123");
// when
personalAccessTokenManager.save(token);
personalAccessTokenManager.store(token);
// then
verify(nonNamespaceOperation).createOrReplace(captor.capture());

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2023 Red Hat, Inc.
* Copyright (c) 2012-2024 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
@ -73,7 +73,7 @@ public class CredentialsSecretConfigurator implements NamespaceConfigurator {
.forEach(
s -> {
try {
personalAccessTokenManager.store(
personalAccessTokenManager.storeGitCredentials(
s.getMetadata().getAnnotations().get(ANNOTATION_SCM_URL));
} catch (ScmCommunicationException
| ScmConfigurationPersistenceException

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2023 Red Hat, Inc.
* Copyright (c) 2012-2024 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
@ -91,7 +91,7 @@ public class CredentialsSecretConfiguratorTest {
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then
verify(personalAccessTokenManager).store(eq("test-url"));
verify(personalAccessTokenManager).storeGitCredentials(eq("test-url"));
}
@Test
@ -129,6 +129,6 @@ public class CredentialsSecretConfiguratorTest {
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then
verify(personalAccessTokenManager, never()).store(anyString());
verify(personalAccessTokenManager, never()).storeGitCredentials(anyString());
}
}

View File

@ -14,6 +14,7 @@ package org.eclipse.che.security.oauth;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyList;
import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX;
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;
@ -43,7 +44,10 @@ import org.eclipse.che.api.core.util.LinksHelper;
import org.eclipse.che.api.factory.server.scm.PersonalAccessToken;
import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException;
import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor;
import org.slf4j.Logger;
@ -84,7 +88,8 @@ public class EmbeddedOAuthAPI implements OAuthAPI {
}
@Override
public Response callback(UriInfo uriInfo, List<String> errorValues) throws NotFoundException {
public Response callback(UriInfo uriInfo, @Nullable List<String> errorValues)
throws NotFoundException {
URL requestUrl = getRequestUrl(uriInfo);
Map<String, List<String>> params = getQueryParametersFromState(getState(requestUrl));
errorValues = errorValues == null ? uriInfo.getQueryParameters().get("error") : errorValues;
@ -97,13 +102,25 @@ public class EmbeddedOAuthAPI implements OAuthAPI {
OAuthAuthenticator oauth = getAuthenticator(providerName);
final List<String> scopes = params.get("scope");
try {
oauth.callback(requestUrl, scopes == null ? emptyList() : scopes);
String token = oauth.callback(requestUrl, scopes == null ? emptyList() : scopes);
personalAccessTokenManager.store(
new PersonalAccessToken(
oauth.getEndpointUrl(),
EnvironmentContext.getCurrent().getSubject().getUserId(),
null,
null,
NameGenerator.generate(OAUTH_2_PREFIX, 5),
NameGenerator.generate("id-", 5),
token));
} catch (OAuthAuthenticationException e) {
return Response.temporaryRedirect(
URI.create(
getParameter(params, "redirect_after_login")
+ String.format("&%s=access_denied", ERROR_QUERY_NAME)))
.build();
} catch (UnsatisfiedScmPreconditionException | ScmConfigurationPersistenceException e) {
// Skip exception, the token will be stored in the next request.
LOG.error(e.getMessage(), e);
}
final String redirectAfterLogin = getParameter(params, "redirect_after_login");
return Response.temporaryRedirect(URI.create(redirectAfterLogin)).build();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2023 Red Hat, Inc.
* Copyright (c) 2012-2024 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
@ -174,7 +174,7 @@ public abstract class OAuthAuthenticator {
* server
* @param scopes specify exactly what type of access needed. This list must be exactly the same as
* list passed to the method {@link #getAuthenticateUrl(URL, java.util.List)}
* @return id of authenticated user
* @return access token
* @throws OAuthAuthenticationException if authentication failed or <code>requestUrl</code> does
* not contain required parameters, e.g. 'code'
*/
@ -202,7 +202,7 @@ public abstract class OAuthAuthenticator {
userId = EnvironmentContext.getCurrent().getSubject().getUserId();
}
flow.createAndStoreCredential(tokenResponse, userId);
return userId;
return tokenResponse.getAccessToken();
} catch (IOException ioe) {
throw new OAuthAuthenticationException(ioe.getMessage());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2023 Red Hat, Inc.
* Copyright (c) 2012-2024 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
@ -11,23 +11,33 @@
*/
package org.eclipse.che.security.oauth;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.core.UriInfo;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.util.Set;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.factory.server.scm.PersonalAccessToken;
import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
@ -40,6 +50,7 @@ public class EmbeddedOAuthAPITest {
@Mock OAuthAuthenticatorProvider oauth2Providers;
@Mock org.eclipse.che.security.oauth1.OAuthAuthenticatorProvider oauth1Providers;
@Mock PersonalAccessTokenManager personalAccessTokenManager;
@InjectMocks EmbeddedOAuthAPI embeddedOAuthAPI;
@ -101,4 +112,32 @@ public class EmbeddedOAuthAPITest {
callback.getLocation().toString(),
"http://eclipse.che?quary%3Dparam%26error_code%3Daccess_denied");
}
@Test
public void shouldStoreTokenOnCallback() throws Exception {
// given
UriInfo uriInfo = mock(UriInfo.class);
OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class);
when(authenticator.getEndpointUrl()).thenReturn("http://eclipse.che");
when(authenticator.callback(any(URL.class), anyList())).thenReturn("token");
when(uriInfo.getRequestUri())
.thenReturn(
new URI(
"http://eclipse.che?state=oauth_provider%3Dgithub%26redirect_after_login%3DredirectUrl"));
when(oauth2Providers.getAuthenticator("github")).thenReturn(authenticator);
ArgumentCaptor<PersonalAccessToken> tokenCapture =
ArgumentCaptor.forClass(PersonalAccessToken.class);
// when
embeddedOAuthAPI.callback(uriInfo, emptyList());
// then
verify(personalAccessTokenManager).store(tokenCapture.capture());
PersonalAccessToken token = tokenCapture.getValue();
assertEquals(token.getScmProviderUrl(), "http://eclipse.che");
assertEquals(token.getCheUserId(), "0000-00-0000");
assertTrue(token.getScmTokenId().startsWith("id-"));
assertTrue(token.getScmTokenName().startsWith(OAUTH_2_PREFIX));
assertEquals(token.getToken(), "token");
}
}

View File

@ -105,7 +105,18 @@ public interface PersonalAccessTokenManager {
* @throws ScmCommunicationException - problem occurred during communication with scm provider.
* @throws ScmUnauthorizedException - scm authorization required.
*/
void store(String scmServerUrl)
void storeGitCredentials(String scmServerUrl)
throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException,
ScmCommunicationException, ScmUnauthorizedException;
/**
* Store {@link PersonalAccessToken} in permanent storage.
*
* @param token personal access token
* @throws UnsatisfiedScmPreconditionException - storage preconditions aren't met.
* @throws ScmConfigurationPersistenceException - problem occurred during communication with
* permanent storage.
*/
void store(PersonalAccessToken token)
throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException;
}