Skip to content

Commit d855e59

Browse files
committed
commit
1 parent bb09ce9 commit d855e59

11 files changed

Lines changed: 209 additions & 31 deletions

File tree

infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
package org.eclipse.che.api.factory.server.scm.kubernetes;
1313

1414
import static com.google.common.base.Strings.isNullOrEmpty;
15+
import static java.lang.Long.parseLong;
1516
import static org.eclipse.che.commons.lang.StringUtils.trimEnd;
1617

1718
import com.google.common.collect.ImmutableMap;
@@ -75,6 +76,8 @@ public class KubernetesPersonalAccessTokenManager implements PersonalAccessToken
7576
"che.eclipse.org/scm-personal-access-token-name";
7677
public static final String ANNOTATION_SCM_URL = "che.eclipse.org/scm-url";
7778
public static final String TOKEN_DATA_FIELD = "token";
79+
public static final String REFRESH_TOKEN_DATA_FIELD = "refresh-token";
80+
public static final String EXPIRES_IN_DATA_FIELD = "expires-in";
7881

7982
private final KubernetesNamespaceFactory namespaceFactory;
8083
private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory;
@@ -119,15 +122,29 @@ public void store(PersonalAccessToken personalAccessToken)
119122
.withLabels(SECRET_LABELS)
120123
.build();
121124

125+
String tokenEncoded =
126+
Base64.getEncoder()
127+
.encodeToString(personalAccessToken.getToken().getBytes(StandardCharsets.UTF_8));
128+
String refreshTokenEncoded =
129+
Base64.getEncoder()
130+
.encodeToString(
131+
personalAccessToken.getRefreshToken().getBytes(StandardCharsets.UTF_8));
132+
String expiresInEncoded =
133+
Base64.getEncoder()
134+
.encodeToString(
135+
String.valueOf(personalAccessToken.getExpiresIn())
136+
.getBytes(StandardCharsets.UTF_8));
122137
Secret secret =
123138
new SecretBuilder()
124139
.withMetadata(meta)
125140
.withData(
126141
Map.of(
127142
TOKEN_DATA_FIELD,
128-
Base64.getEncoder()
129-
.encodeToString(
130-
personalAccessToken.getToken().getBytes(StandardCharsets.UTF_8))))
143+
tokenEncoded,
144+
REFRESH_TOKEN_DATA_FIELD,
145+
refreshTokenEncoded,
146+
EXPIRES_IN_DATA_FIELD,
147+
expiresInEncoded))
131148
.build();
132149

133150
cheServerKubernetesClientFactory
@@ -262,7 +279,9 @@ private List<PersonalAccessToken> doGetPersonalAccessTokens(
262279
scmUsername.get(),
263280
personalAccessTokenParams.getScmTokenName(),
264281
personalAccessTokenParams.getScmTokenId(),
265-
personalAccessTokenParams.getToken());
282+
personalAccessTokenParams.getToken(),
283+
personalAccessTokenParams.getRefreshToken(),
284+
personalAccessTokenParams.getExpiresIn());
266285
result.add(personalAccessToken);
267286
continue;
268287
}
@@ -370,8 +389,18 @@ private boolean deleteSecretIfMisconfigured(Secret secret) throws Infrastructure
370389

371390
private PersonalAccessTokenParams secret2PersonalAccessTokenParams(Secret secret) {
372391
Map<String, String> secretAnnotations = secret.getMetadata().getAnnotations();
392+
String refreshTokenData = secret.getData().get("refresh-token");
393+
String expiresInData = secret.getData().get("expires-in");
373394

374395
String token = new String(Base64.getDecoder().decode(secret.getData().get("token"))).trim();
396+
String refreshToken =
397+
isNullOrEmpty(refreshTokenData)
398+
? null
399+
: new String(Base64.getDecoder().decode(refreshTokenData)).trim();
400+
long expiresIn =
401+
isNullOrEmpty(expiresInData)
402+
? 0
403+
: parseLong(new String(Base64.getDecoder().decode(expiresInData)));
375404
String configuredOAuthTokenName =
376405
secretAnnotations.get(ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME);
377406
String configuredTokenId = secretAnnotations.get(ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID);
@@ -385,7 +414,9 @@ private PersonalAccessTokenParams secret2PersonalAccessTokenParams(Secret secret
385414
configuredOAuthTokenName,
386415
configuredTokenId,
387416
token,
388-
configuredScmOrganization);
417+
configuredScmOrganization,
418+
refreshToken,
419+
expiresIn);
389420
}
390421

391422
private boolean isSecretMatchesSearchCriteria(
@@ -396,8 +427,7 @@ private boolean isSecretMatchesSearchCriteria(
396427
Map<String, String> secretAnnotations = secret.getMetadata().getAnnotations();
397428
String configuredScmServerUrl = secretAnnotations.get(ANNOTATION_SCM_URL);
398429
String configuredCheUserId = secretAnnotations.get(ANNOTATION_CHE_USERID);
399-
String configuredOAuthProviderName =
400-
secretAnnotations.get(ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME);
430+
String configuredOAuthProviderName = secretAnnotations.get(ANNOTATION_SCM_PROVIDER_NAME);
401431

402432
return (configuredCheUserId.equals(cheUser.getUserId()))
403433
&& (oAuthProviderName == null || oAuthProviderName.equals(configuredOAuthProviderName))

infrastructures/kubernetes/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@
113113
<groupId>jakarta.ws.rs</groupId>
114114
<artifactId>jakarta.ws.rs-api</artifactId>
115115
</dependency>
116+
<dependency>
117+
<groupId>org.eclipse.che.core</groupId>
118+
<artifactId>che-core-api-auth</artifactId>
119+
</dependency>
116120
<dependency>
117121
<groupId>org.eclipse.che.core</groupId>
118122
<artifactId>che-core-api-core</artifactId>

wsmaster/che-core-api-auth-shared/src/main/java/org/eclipse/che/api/auth/shared/dto/OAuthToken.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,16 @@ public interface OAuthToken {
3535
void setScope(String scope);
3636

3737
OAuthToken withScope(String scope);
38+
39+
String getRefreshToken();
40+
41+
void setRefreshToken(String refreshToken);
42+
43+
OAuthToken withRefreshToken(String refreshToken);
44+
45+
long getExpiresIn();
46+
47+
void setExpiresIn(long expiresIn);
48+
49+
OAuthToken withExpiresIn(long expiresIn);
3850
}

wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.eclipse.che.security.oauth.OAuthAuthenticator.SSL_ERROR_CODE;
2222
import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME;
2323

24+
import com.google.api.client.auth.oauth2.TokenResponse;
2425
import jakarta.servlet.http.HttpServletRequest;
2526
import jakarta.ws.rs.HttpMethod;
2627
import jakarta.ws.rs.core.Response;
@@ -62,7 +63,7 @@
6263
* @author Mykhailo Kuznietsov
6364
*/
6465
@Singleton
65-
public class EmbeddedOAuthAPI implements OAuthAPI {
66+
public class EmbeddedOAuthAPI implements OAuthAPI {
6667
private static final Logger LOG = LoggerFactory.getLogger(EmbeddedOAuthAPI.class);
6768

6869
@Inject
@@ -106,7 +107,8 @@ public Response callback(UriInfo uriInfo, @Nullable List<String> errorValues)
106107
OAuthAuthenticator oauth = getAuthenticator(providerName);
107108
final List<String> scopes = params.get("scope");
108109
try {
109-
String token = oauth.callback(requestUrl, scopes == null ? emptyList() : scopes);
110+
TokenResponse tokenResponse =
111+
oauth.callback(requestUrl, scopes == null ? emptyList() : scopes);
110112
personalAccessTokenManager.store(
111113
new PersonalAccessToken(
112114
oauth.getEndpointUrl(),
@@ -116,7 +118,9 @@ public Response callback(UriInfo uriInfo, @Nullable List<String> errorValues)
116118
null,
117119
NameGenerator.generate(OAUTH_2_PREFIX, 5),
118120
NameGenerator.generate("id-", 5),
119-
token));
121+
tokenResponse.getAccessToken(),
122+
tokenResponse.getRefreshToken(),
123+
tokenResponse.getExpiresInSeconds()));
120124
} catch (OAuthAuthenticationException e) {
121125
return Response.temporaryRedirect(
122126
URI.create(
@@ -260,23 +264,44 @@ public OAuthToken refreshToken(String oauthProvider)
260264
throws NotFoundException, UnauthorizedException, ServerException {
261265
OAuthAuthenticator provider = getAuthenticator(oauthProvider);
262266
Subject subject = EnvironmentContext.getCurrent().getSubject();
267+
String userId = subject.getUserId();
268+
String userName = subject.getUserName();
263269
try {
264-
OAuthToken token = provider.refreshToken(subject.getUserId());
265-
if (token == null) {
266-
token = provider.refreshToken(subject.getUserName());
270+
OAuthToken storedToken = provider.refreshToken(userId);
271+
if (storedToken == null) {
272+
storedToken = provider.refreshToken(userName);
267273
}
268274

269-
if (token != null) {
270-
return token;
275+
if (storedToken != null) {
276+
return storedToken;
271277
} else {
272-
throw new UnauthorizedException(
273-
"OAuth token for user " + subject.getUserId() + " was not found");
278+
Optional<PersonalAccessToken> tokenOptional =
279+
personalAccessTokenManager.get(subject, oauthProvider, null, null);
280+
if (tokenOptional.isPresent()) {
281+
PersonalAccessToken token = tokenOptional.get();
282+
if (isNullOrEmpty(token.getRefreshToken())) {
283+
throw getUnauthorizedException(userId);
284+
}
285+
TokenResponse tokenResponse =
286+
new TokenResponse()
287+
.setAccessToken(token.getToken())
288+
.setRefreshToken(token.getRefreshToken())
289+
.setExpiresInSeconds(token.getExpiresIn());
290+
provider.flow.createAndStoreCredential(tokenResponse, userId);
291+
return provider.refreshToken(userId);
292+
} else {
293+
throw getUnauthorizedException(userId);
294+
}
274295
}
275-
} catch (IOException e) {
296+
} catch (IOException | ScmConfigurationPersistenceException | ScmCommunicationException e) {
276297
throw new ServerException(e.getLocalizedMessage(), e);
277298
}
278299
}
279300

301+
private UnauthorizedException getUnauthorizedException(String userId) {
302+
return new UnauthorizedException("OAuth token for user " + userId + " was not found");
303+
}
304+
280305
@Override
281306
public void invalidateToken(String oauthProvider)
282307
throws NotFoundException, UnauthorizedException, ServerException {

wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAPI.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ OAuthToken getOrRefreshToken(String oauthProvider)
6363
* Refreshes the token for the given OAuth provider.
6464
*
6565
* @param oauthProvider - the OAuth provider name
66-
* @return the refreshed token
66+
* @return the refreshed token or {@code null} if the given token is
6767
*/
6868
OAuthToken refreshToken(String oauthProvider)
6969
throws NotFoundException, UnauthorizedException, ServerException, ForbiddenException;

wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticationService.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import jakarta.servlet.http.HttpServletRequest;
1515
import jakarta.ws.rs.DELETE;
1616
import jakarta.ws.rs.GET;
17+
import jakarta.ws.rs.POST;
1718
import jakarta.ws.rs.Path;
1819
import jakarta.ws.rs.Produces;
1920
import jakarta.ws.rs.QueryParam;
@@ -30,6 +31,14 @@
3031
import org.eclipse.che.api.core.rest.Service;
3132
import org.eclipse.che.api.core.rest.annotations.Required;
3233
import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager;
34+
import org.eclipse.che.api.factory.server.scm.GitCredentialManager;
35+
import org.eclipse.che.api.factory.server.scm.PersonalAccessToken;
36+
import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
37+
import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException;
38+
import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException;
39+
import org.eclipse.che.commons.env.EnvironmentContext;
40+
import org.eclipse.che.commons.lang.NameGenerator;
41+
import org.eclipse.che.commons.subject.Subject;
3342
import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor;
3443

3544
/** RESTful wrapper for OAuthAuthenticator. */
@@ -40,6 +49,8 @@ public class OAuthAuthenticationService extends Service {
4049

4150
@Inject private OAuthAPI oAuthAPI;
4251
@Inject private AuthorisationRequestManager authorisationRequestManager;
52+
@Inject private PersonalAccessTokenManager personalAccessTokenManager;
53+
@Inject private GitCredentialManager gitCredentialManager;
4354

4455
/**
4556
* Redirect request to OAuth provider site for authentication|authorization. Client must provide
@@ -105,6 +116,35 @@ public OAuthToken token(@Required @QueryParam("oauth_provider") String oauthProv
105116
return oAuthAPI.getOrRefreshToken(oauthProvider);
106117
}
107118

119+
@POST
120+
@Path("refresh")
121+
public void refresh(
122+
@Required @QueryParam("oauth_provider") String oauthProvider,
123+
@Required @QueryParam("provider_url") String providerUrl)
124+
throws ServerException,
125+
UnauthorizedException,
126+
NotFoundException,
127+
ForbiddenException,
128+
UnsatisfiedScmPreconditionException,
129+
ScmConfigurationPersistenceException {
130+
OAuthToken token = oAuthAPI.refreshToken(oauthProvider);
131+
Subject subject = EnvironmentContext.getCurrent().getSubject();
132+
PersonalAccessToken personalAccessToken =
133+
new PersonalAccessToken(
134+
providerUrl,
135+
oauthProvider,
136+
subject.getUserId(),
137+
null,
138+
subject.getUserName(),
139+
NameGenerator.generate("oauth2-", 5),
140+
NameGenerator.generate("id-", 5),
141+
token.getToken(),
142+
token.getRefreshToken(),
143+
token.getExpiresIn());
144+
personalAccessTokenManager.store(personalAccessToken);
145+
gitCredentialManager.createOrReplace(personalAccessToken);
146+
}
147+
108148
/**
109149
* Invalidate OAuth token for user.
110150
*

wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public abstract class OAuthAuthenticator {
5353
protected AuthorizationCodeFlow flow;
5454
private String clientId;
5555
protected Map<Pattern, String> redirectUrisMap;
56+
private String clientSecret;
5657

5758
/**
5859
* @see {@link #configure(String, String, String[], String, String, MemoryDataStoreFactory, List)}
@@ -65,6 +66,7 @@ protected void configure(
6566
String tokenUri,
6667
MemoryDataStoreFactory dataStoreFactory)
6768
throws IOException {
69+
this.clientSecret = clientSecret;
6870
configure(
6971
clientId,
7072
clientSecret,
@@ -179,12 +181,12 @@ protected String findRedirectUrl(URL requestUrl) {
179181
* server
180182
* @param scopes specify exactly what type of access needed. This list must be exactly the same as
181183
* list passed to the method {@link #getAuthenticateUrl(URL, java.util.List)}
182-
* @return access token
184+
* @return oauth token object
183185
* @throws OAuthAuthenticationException if authentication failed or <code>requestUrl</code> does
184186
* not contain required parameters, e.g. 'code'
185187
* @throws ScmCommunicationException if communication with SCM failed
186188
*/
187-
public String callback(URL requestUrl, List<String> scopes)
189+
public TokenResponse callback(URL requestUrl, List<String> scopes)
188190
throws OAuthAuthenticationException, ScmCommunicationException {
189191
if (!isConfigured()) {
190192
throw new OAuthAuthenticationException(AUTHENTICATOR_IS_NOT_CONFIGURED);
@@ -209,7 +211,7 @@ public String callback(URL requestUrl, List<String> scopes)
209211
userId = EnvironmentContext.getCurrent().getSubject().getUserId();
210212
}
211213
flow.createAndStoreCredential(tokenResponse, userId);
212-
return tokenResponse.getAccessToken();
214+
return tokenResponse;
213215
} catch (IOException ioe) {
214216
if (ioe instanceof SSLHandshakeException) {
215217
throw new ScmCommunicationException(
@@ -366,7 +368,10 @@ public OAuthToken refreshToken(String userId) throws IOException {
366368
}
367369
return null;
368370
}
369-
return newDto(OAuthToken.class).withToken(credential.getAccessToken());
371+
return newDto(OAuthToken.class)
372+
.withToken(credential.getAccessToken())
373+
.withRefreshToken(credential.getRefreshToken())
374+
.withExpiresIn(credential.getExpiresInSeconds());
370375
}
371376

372377
/**

wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPITest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import static org.testng.Assert.assertNull;
3030
import static org.testng.Assert.assertTrue;
3131

32+
import com.google.api.client.auth.oauth2.TokenResponse;
3233
import jakarta.ws.rs.core.Response;
3334
import jakarta.ws.rs.core.UriBuilder;
3435
import jakarta.ws.rs.core.UriInfo;
@@ -150,8 +151,10 @@ public void shouldStoreTokenOnCallback() throws Exception {
150151
// given
151152
UriInfo uriInfo = mock(UriInfo.class);
152153
OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class);
154+
TokenResponse tokenResponse = mock(TokenResponse.class);
153155
when(authenticator.getEndpointUrl()).thenReturn("http://eclipse.che");
154-
when(authenticator.callback(any(URL.class), anyList())).thenReturn("token");
156+
when(tokenResponse.getAccessToken()).thenReturn("token");
157+
when(authenticator.callback(any(URL.class), anyList())).thenReturn(tokenResponse);
155158
when(uriInfo.getRequestUri())
156159
.thenReturn(
157160
new URI(

wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ private PersonalAccessToken fetchOrRefreshPersonalAccessToken(
112112
scmServerUrl,
113113
OAUTH_PROVIDER_NAME,
114114
EnvironmentContext.getCurrent().getSubject().getUserId(),
115-
null,
116115
user.getSlug(),
117116
token.getName(),
118117
valueOf(token.getId()),

0 commit comments

Comments
 (0)