Skip to content

Commit 81b8de7

Browse files
authored
feat: Support OpenShift external IDP (#952)
* feat: Support OpenShift external IDP Signed-off-by: Anatolii Bazko <abazko@redhat.com> * feat: Support OpenShift external IDP Signed-off-by: Anatolii Bazko <abazko@redhat.com> * feat: Support OpenShift external IDP Signed-off-by: Anatolii Bazko <abazko@redhat.com> * feat: Support OpenShift external IDP Signed-off-by: Anatolii Bazko <abazko@redhat.com> * feat: Support OpenShift external IDP Signed-off-by: Anatolii Bazko <abazko@redhat.com> * feat: Support OpenShift external IDP Signed-off-by: Anatolii Bazko <abazko@redhat.com> * feat: Support OpenShift external IDP Signed-off-by: Anatolii Bazko <abazko@redhat.com> --------- Signed-off-by: Anatolii Bazko <abazko@redhat.com>
1 parent 9dffc0e commit 81b8de7

54 files changed

Lines changed: 606 additions & 319 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

assembly/assembly-root-war/src/test/java/org/eclipse/che/DashboardRedirectionFilterTest.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2025 Red Hat, Inc.
2+
* Copyright (c) 2012-2026 Red Hat, Inc.
33
* This program and the accompanying materials are made
44
* available under the terms of the Eclipse Public License 2.0
55
* which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -22,6 +22,7 @@
2222
import jakarta.servlet.ServletResponse;
2323
import jakarta.servlet.http.HttpServletRequest;
2424
import jakarta.servlet.http.HttpServletResponse;
25+
import java.util.Collections;
2526
import org.eclipse.che.commons.env.EnvironmentContext;
2627
import org.eclipse.che.commons.subject.SubjectImpl;
2728
import org.mockito.InjectMocks;
@@ -50,7 +51,8 @@ public void shouldRedirectIfGetRequestIsNotNamespaceWorkspaceName(String uri) th
5051
when(request.getMethod()).thenReturn("GET");
5152
when(request.getRequestURI()).thenReturn(uri);
5253
EnvironmentContext context = new EnvironmentContext();
53-
context.setSubject(new SubjectImpl("id123", "name", "token123", false));
54+
context.setSubject(
55+
new SubjectImpl("id123", Collections.emptyList(), "name", "token123", false));
5456
EnvironmentContext.setCurrent(context);
5557

5658
// when
@@ -66,7 +68,8 @@ public void shouldRedirectIfHEADRequestIsNotNamespaceWorkspaceName(String uri) t
6668
when(request.getMethod()).thenReturn("HEAD");
6769
when(request.getRequestURI()).thenReturn(uri);
6870
EnvironmentContext context = new EnvironmentContext();
69-
context.setSubject(new SubjectImpl("id123", "name", "token123", false));
71+
context.setSubject(
72+
new SubjectImpl("id123", Collections.emptyList(), "name", "token123", false));
7073
EnvironmentContext.setCurrent(context);
7174

7275
// when

assembly/assembly-wsmaster-war/pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,6 @@
259259
<groupId>org.eclipse.che.multiuser</groupId>
260260
<artifactId>che-multiuser-api-workspace-activity</artifactId>
261261
</dependency>
262-
<dependency>
263-
<groupId>org.eclipse.che.multiuser</groupId>
264-
<artifactId>che-multiuser-keycloak-server</artifactId>
265-
</dependency>
266262
<dependency>
267263
<groupId>org.eclipse.che.multiuser</groupId>
268264
<artifactId>che-multiuser-keycloak-token-provider</artifactId>

assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2025 Red Hat, Inc.
2+
* Copyright (c) 2012-2026 Red Hat, Inc.
33
* This program and the accompanying materials are made
44
* available under the terms of the Eclipse Public License 2.0
55
* which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -11,6 +11,7 @@
1111
*/
1212
package org.eclipse.che.api.deploy;
1313

14+
import static com.google.common.base.Strings.isNullOrEmpty;
1415
import static com.google.inject.matcher.Matchers.subclassesOf;
1516
import static org.eclipse.che.inject.Matchers.names;
1617
import static org.eclipse.che.multiuser.api.permission.server.SystemDomain.SYSTEM_DOMAIN_ACTIONS;
@@ -26,7 +27,6 @@
2627
import io.jsonwebtoken.SigningKeyResolver;
2728
import java.util.HashMap;
2829
import java.util.Map;
29-
import org.eclipse.che.api.core.notification.RemoteSubscriptionStorage;
3030
import org.eclipse.che.api.core.rest.CheJsonProvider;
3131
import org.eclipse.che.api.core.rest.MessageBodyAdapter;
3232
import org.eclipse.che.api.core.rest.MessageBodyAdapterInterceptor;
@@ -63,8 +63,6 @@
6363
import org.eclipse.che.api.user.server.spi.ProfileDao;
6464
import org.eclipse.che.api.user.server.spi.UserDao;
6565
import org.eclipse.che.api.workspace.server.WorkspaceEntityProvider;
66-
import org.eclipse.che.api.workspace.server.WorkspaceLockService;
67-
import org.eclipse.che.api.workspace.server.WorkspaceStatusCache;
6866
import org.eclipse.che.api.workspace.server.devfile.DevfileModule;
6967
import org.eclipse.che.api.workspace.server.hc.ServersCheckerFactory;
7068
import org.eclipse.che.api.workspace.server.spi.provision.InternalEnvironmentProvisioner;
@@ -105,6 +103,8 @@
105103
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientConfigFactory;
106104
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfraModule;
107105
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructure;
106+
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker;
107+
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.KubernetesOIDCAuthorizationCheckerImpl;
108108
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
109109
import org.eclipse.che.workspace.infrastructure.kubernetes.multiuser.oauth.KubernetesOidcProviderConfigFactory;
110110
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer;
@@ -118,8 +118,8 @@
118118
import org.eclipse.che.workspace.infrastructure.metrics.InfrastructureMetricsModule;
119119
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftInfraModule;
120120
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftInfrastructure;
121+
import org.eclipse.che.workspace.infrastructure.openshift.authorization.OpenShiftAuthorizationCheckerImpl;
121122
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment;
122-
import org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth.KeycloakProviderConfigFactory;
123123
import org.eclipse.persistence.config.PersistenceUnitProperties;
124124

125125
/**
@@ -319,27 +319,13 @@ protected void configure() {
319319

320320
private void configureMultiUserMode(
321321
Map<String, String> persistenceProperties, String infrastructure) {
322-
if (OpenShiftInfrastructure.NAME.equals(infrastructure)
323-
|| KubernetesInfrastructure.NAME.equals(infrastructure)) {
324-
install(new ReplicationModule(persistenceProperties));
325-
bind(
326-
org.eclipse.che.multiuser.permission.workspace.infra.kubernetes
327-
.BrokerServicePermissionFilter.class);
328-
configureJwtProxySecureProvisioner(infrastructure);
329-
} else {
330-
bind(RemoteSubscriptionStorage.class)
331-
.to(org.eclipse.che.api.core.notification.InmemoryRemoteSubscriptionStorage.class);
332-
bind(WorkspaceLockService.class)
333-
.to(org.eclipse.che.api.workspace.server.DefaultWorkspaceLockService.class);
334-
bind(WorkspaceStatusCache.class)
335-
.to(org.eclipse.che.api.workspace.server.DefaultWorkspaceStatusCache.class);
336-
}
322+
install(new ReplicationModule(persistenceProperties));
323+
bind(
324+
org.eclipse.che.multiuser.permission.workspace.infra.kubernetes
325+
.BrokerServicePermissionFilter.class);
326+
configureJwtProxySecureProvisioner(infrastructure);
337327

338-
if (Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) {
339-
bind(KubernetesClientConfigFactory.class).to(KubernetesOidcProviderConfigFactory.class);
340-
} else if (OpenShiftInfrastructure.NAME.equals(infrastructure)) {
341-
bind(KubernetesClientConfigFactory.class).to(KeycloakProviderConfigFactory.class);
342-
}
328+
bind(KubernetesClientConfigFactory.class).to(KubernetesOidcProviderConfigFactory.class);
343329

344330
persistenceProperties.put(
345331
PersistenceUnitProperties.EXCEPTION_HANDLER_CLASS,
@@ -369,18 +355,19 @@ private void configureMultiUserMode(
369355

370356
bind(org.eclipse.che.multiuser.permission.workspace.activity.ActivityPermissionsFilter.class);
371357

372-
if (Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) {
373-
bind(RequestTokenExtractor.class).to(HeaderRequestTokenExtractor.class);
374-
if (KubernetesInfrastructure.NAME.equals(infrastructure)) {
375-
bind(OIDCInfo.class).toProvider(OIDCInfoProvider.class).asEagerSingleton();
376-
bind(SigningKeyResolver.class).to(OIDCSigningKeyResolver.class);
377-
bind(JwtParser.class).toProvider(OIDCJwtParserProvider.class);
378-
bind(JwkProvider.class).toProvider(OIDCJwkProvider.class);
379-
}
380-
bind(TokenValidator.class).to(NotImplementedTokenValidator.class);
381-
bind(ProfileDao.class).to(JpaProfileDao.class);
382-
bind(OAuthAPI.class).to(EmbeddedOAuthAPI.class).asEagerSingleton();
358+
bind(RequestTokenExtractor.class).to(HeaderRequestTokenExtractor.class);
359+
if (isOpenShiftOAuthEnabled()) {
360+
bind(AuthorizationChecker.class).to(OpenShiftAuthorizationCheckerImpl.class);
361+
} else {
362+
bind(OIDCInfo.class).toProvider(OIDCInfoProvider.class).asEagerSingleton();
363+
bind(SigningKeyResolver.class).to(OIDCSigningKeyResolver.class);
364+
bind(JwtParser.class).toProvider(OIDCJwtParserProvider.class);
365+
bind(JwkProvider.class).toProvider(OIDCJwkProvider.class);
366+
bind(AuthorizationChecker.class).to(KubernetesOIDCAuthorizationCheckerImpl.class);
383367
}
368+
bind(TokenValidator.class).to(NotImplementedTokenValidator.class);
369+
bind(ProfileDao.class).to(JpaProfileDao.class);
370+
bind(OAuthAPI.class).to(EmbeddedOAuthAPI.class).asEagerSingleton();
384371

385372
install(new MachineAuthModule());
386373

@@ -483,4 +470,15 @@ private void installDefaultSecureServerExposer(String infrastructure) {
483470
PassThroughProxySecureServerExposerFactory<OpenShiftEnvironment>>() {});
484471
}
485472
}
473+
474+
private boolean isOpenShiftOAuthEnabled() {
475+
String openShiftOAuthEnabled = System.getenv("CHE_INFRA_OPENSHIFT_OAUTH__ENABLED");
476+
477+
if (!isNullOrEmpty(openShiftOAuthEnabled)) {
478+
return Boolean.valueOf(openShiftOAuthEnabled);
479+
}
480+
481+
String infrastructure = System.getenv("CHE_INFRASTRUCTURE_ACTIVE");
482+
return OpenShiftInfrastructure.NAME.equals(infrastructure);
483+
}
486484
}

assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterServletModule.java

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2025 Red Hat, Inc.
2+
* Copyright (c) 2012-2026 Red Hat, Inc.
33
* This program and the accompanying materials are made
44
* available under the terms of the Eclipse Public License 2.0
55
* which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -11,16 +11,14 @@
1111
*/
1212
package org.eclipse.che.api.deploy;
1313

14+
import static com.google.common.base.Strings.isNullOrEmpty;
15+
1416
import com.google.common.collect.ImmutableMap;
1517
import com.google.inject.servlet.ServletModule;
1618
import org.eclipse.che.api.core.cors.CheCorsFilter;
1719
import org.eclipse.che.commons.logback.filter.RequestIdLoggerFilter;
18-
import org.eclipse.che.inject.ConfigurationException;
1920
import org.eclipse.che.inject.DynaModule;
20-
import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakServletModule;
21-
import org.eclipse.che.multiuser.machine.authentication.server.MachineLoginFilter;
2221
import org.eclipse.che.multiuser.oidc.filter.OidcTokenInitializationFilter;
23-
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructure;
2422
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftInfrastructure;
2523
import org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth.OpenshiftTokenInitializationFilter;
2624
import org.everrest.guice.servlet.GuiceEverrestServlet;
@@ -50,13 +48,8 @@ protected void configureServlets() {
5048
serveRegex("^(?!/websocket.?)(.*)")
5149
.with(GuiceEverrestServlet.class, ImmutableMap.of("openapi.context.id", "org.eclipse.che"));
5250

53-
if (Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) {
54-
LOG.info("Running in native-user mode ...");
55-
configureNativeUserMode();
56-
} else {
57-
LOG.info("Running in classic multi-user mode ...");
58-
configureMultiUserMode();
59-
}
51+
LOG.info("Running in native-user mode ...");
52+
configureNativeUserMode();
6053

6154
if (Boolean.valueOf(System.getenv("CHE_METRICS_ENABLED"))) {
6255
install(new org.eclipse.che.core.metrics.MetricsServletModule());
@@ -73,19 +66,22 @@ private boolean isCheCorsEnabled() {
7366
}
7467
}
7568

76-
private void configureMultiUserMode() {
77-
filterRegex(".*").through(MachineLoginFilter.class);
78-
install(new KeycloakServletModule());
79-
}
80-
8169
private void configureNativeUserMode() {
82-
final String infrastructure = System.getenv("CHE_INFRASTRUCTURE_ACTIVE");
83-
if (OpenShiftInfrastructure.NAME.equals(infrastructure)) {
70+
if (isOpenShiftOAuthEnabled()) {
8471
filter("/*").through(OpenshiftTokenInitializationFilter.class);
85-
} else if (KubernetesInfrastructure.NAME.equals(infrastructure)) {
86-
filter("/*").through(OidcTokenInitializationFilter.class);
8772
} else {
88-
throw new ConfigurationException("Native user mode is currently supported on on OpenShift.");
73+
filter("/*").through(OidcTokenInitializationFilter.class);
74+
}
75+
}
76+
77+
private boolean isOpenShiftOAuthEnabled() {
78+
String openShiftOAuthEnabled = System.getenv("CHE_INFRA_OPENSHIFT_OAUTH__ENABLED");
79+
80+
if (!isNullOrEmpty(openShiftOAuthEnabled)) {
81+
return Boolean.valueOf(openShiftOAuthEnabled);
8982
}
83+
84+
String infrastructure = System.getenv("CHE_INFRASTRUCTURE_ACTIVE");
85+
return OpenShiftInfrastructure.NAME.equals(infrastructure);
9086
}
9187
}

assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/multiuser.properties

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2012-2023 Red Hat, Inc.
2+
# Copyright (c) 2012-2026 Red Hat, Inc.
33
# This program and the accompanying materials are made
44
# available under the terms of the Eclipse Public License 2.0
55
# which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -92,6 +92,18 @@ che.oidc.allowed_clock_skew_sec=3
9292
# `name` in Dex installations.
9393
che.oidc.username_claim=NULL
9494

95+
# Prefix to be prepended to username values extracted from the OIDC token.
96+
# If not defined, no prefix will be added.
97+
che.oidc.username_prefix=NULL
98+
99+
# Claim to be used for extracting user group membership from the JWT token.
100+
# If not defined, group information will not be extracted from the token.
101+
che.oidc.groups_claim=NULL
102+
103+
# Prefix to be prepended to group names extracted from the OIDC token.
104+
# If not defined, no prefix will be added.
105+
che.oidc.group_prefix=NULL
106+
95107
# Email claim to be used when parsing JWT token.
96108
# If not defined, the fallback value is 'email'.
97109
che.oidc.email_claim=NULL

core/che-core-api-core/src/main/java/org/eclipse/che/commons/subject/Subject.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2025 Red Hat, Inc.
2+
* Copyright (c) 2012-2026 Red Hat, Inc.
33
* This program and the accompanying materials are made
44
* available under the terms of the Eclipse Public License 2.0
55
* which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -11,6 +11,7 @@
1111
*/
1212
package org.eclipse.che.commons.subject;
1313

14+
import java.util.List;
1415
import org.eclipse.che.api.core.ForbiddenException;
1516

1617
/**
@@ -33,6 +34,11 @@ public String getUserName() {
3334
return "Anonymous";
3435
}
3536

37+
@Override
38+
public List<String> getGroups() {
39+
return java.util.Collections.emptyList();
40+
}
41+
3642
@Override
3743
public boolean hasPermission(String domain, String instance, String action) {
3844
return false;
@@ -81,6 +87,11 @@ public boolean isTemporary() {
8187
*/
8288
String getUserName();
8389

90+
/**
91+
* @return list of groups the user belongs to
92+
*/
93+
List<String> getGroups();
94+
8495
/**
8596
* Checks does subject have specified permission.
8697
*

core/che-core-api-core/src/main/java/org/eclipse/che/commons/subject/SubjectImpl.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2018 Red Hat, Inc.
2+
* Copyright (c) 2012-2026 Red Hat, Inc.
33
* This program and the accompanying materials are made
44
* available under the terms of the Eclipse Public License 2.0
55
* which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -11,6 +11,8 @@
1111
*/
1212
package org.eclipse.che.commons.subject;
1313

14+
import java.util.Collections;
15+
import java.util.List;
1416
import java.util.Objects;
1517
import org.eclipse.che.api.core.ForbiddenException;
1618

@@ -22,11 +24,14 @@
2224
public class SubjectImpl implements Subject {
2325
private final String id;
2426
private final String name;
27+
private final List<String> groups;
2528
private final String token;
2629
private final boolean isTemporary;
2730

28-
public SubjectImpl(String name, String id, String token, boolean isTemporary) {
31+
public SubjectImpl(
32+
String name, List<String> groups, String id, String token, boolean isTemporary) {
2933
this.name = name;
34+
this.groups = Collections.unmodifiableList(groups);
3035
this.id = id;
3136
this.token = token;
3237
this.isTemporary = isTemporary;
@@ -37,6 +42,11 @@ public String getUserName() {
3742
return name;
3843
}
3944

45+
@Override
46+
public List<String> getGroups() {
47+
return groups;
48+
}
49+
4050
@Override
4151
public boolean hasPermission(String domain, String instance, String action) {
4252
return false;
@@ -73,6 +83,7 @@ public boolean equals(Object obj) {
7383
return Objects.equals(id, other.id)
7484
&& Objects.equals(name, other.name)
7585
&& Objects.equals(token, other.token)
86+
&& Objects.equals(groups, other.groups)
7687
&& isTemporary == other.isTemporary;
7788
}
7889

@@ -83,18 +94,21 @@ public int hashCode() {
8394
hash = 31 * hash + Objects.hashCode(name);
8495
hash = 31 * hash + Objects.hashCode(token);
8596
hash = 31 * hash + Boolean.hashCode(isTemporary);
97+
hash = 31 * hash + Objects.hashCode(groups);
8698
return hash;
8799
}
88100

89101
@Override
90102
public String toString() {
91-
return "UserImpl{"
103+
return "SubjectImpl{"
92104
+ "id='"
93105
+ id
94106
+ '\''
95107
+ ", name='"
96108
+ name
97109
+ '\''
110+
+ ", groups="
111+
+ groups
98112
+ ", token='"
99113
+ token
100114
+ '\''

0 commit comments

Comments
 (0)