Removing OpenShift integration and moving to separate extension

closes #20496

Co-authored-by: mposolda <mposolda@gmail.com>
This commit is contained in:
stianst 2023-03-16 12:00:04 +01:00 committed by Marek Posolda
parent 9ad295a3bc
commit 0832992e59
51 changed files with 293 additions and 3590 deletions

View file

@ -57,8 +57,6 @@ public class Profile {
IMPERSONATION("Ability for admins to impersonate users", Type.DEFAULT),
OPENSHIFT_INTEGRATION("Extension to enable securing OpenShift", Type.PREVIEW),
SCRIPTS("Write custom authenticators using JavaScript", Type.PREVIEW),
TOKEN_EXCHANGE("Token Exchange Service", Type.PREVIEW),

View file

@ -79,7 +79,6 @@ public class ProfileTest {
Profile.Feature.RECOVERY_CODES,
Profile.Feature.SCRIPTS,
Profile.Feature.TOKEN_EXCHANGE,
Profile.Feature.OPENSHIFT_INTEGRATION,
Profile.Feature.MAP_STORAGE,
Profile.Feature.DECLARATIVE_USER_PROFILE,
Profile.Feature.CLIENT_SECRET_ROTATION,
@ -91,7 +90,7 @@ public class ProfileTest {
disabledFeatures.add(Profile.Feature.KERBEROS);
}
assertEquals(profile.getDisabledFeatures(), disabledFeatures);
assertEquals(profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL);
assertEquals(profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL);
}
@Test

View file

@ -112,11 +112,6 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-policy-common</artifactId>
</dependency>
<dependency>
<groupId>com.openshift</groupId>
<artifactId>openshift-restclient-java</artifactId>
</dependency>
</dependencies>
</project>

View file

@ -55,3 +55,9 @@ For example, let's assume we want to overwrite the https://github.com/keycloak/k
Installations which use Keycloak's `--proxy` configuration setting with mode *passthrough* should review the documentation as the behavior of this mode has changed.
See the migration guide for more details.
= Removed openshift-integration feature and related providers
The `openshift-integration` preview feature was removed from Keycloak codebase into separate extension project.
See the migration guide for more details.

View file

@ -226,3 +226,20 @@ This change is already in our documentation and in our quickstart repository. Fo
If you cannot migrate your applications to Jakarta, you can still use the "legacy" SAML JEE adapter and still
be able to integrate with future releases of the server. However, consider upgrading your applications as soon as possible
because we are no longer providing support to JEE.
= Changes for openshift-integration feature
The preview feature `openshift-integration` was removed from Keycloak codebase and moved into separate extension. This includes
moving of related providers such as custom client storage provider and token review endpoint for Openshift integration.
If you used this feature, you should not use the `openshift-integration` feature anymore when starting Keycloak server and instead you need to deploy
the JAR file from custom extension. You can check the https://github.com/keycloak-extensions/keycloak-openshift-ext/[Openshift extension] and the instructions
in it's README file for how to deploy the extension to your Keycloak server.
= Removing thirdparty dependencies
The removal of openshift-integration allows us to remove few thirdparty dependencies from Keycloak distribution. This includes
`openshift-rest-client`, `okio-jvm`, `okhttp`, `commons-lang`, `commons-compress`, `jboss-dmr` and `kotlin-stdlib`. This means that if you use
any of these libraries as dependencies of your own providers deployed to Keycloak server, you may also need to copy those `jar` files
explicitly to the Keycloak distribution `providers` directory as well.

View file

@ -84,6 +84,11 @@
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>

View file

@ -0,0 +1,206 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.test.builders;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.utils.HmacOTP;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class UserBuilder {
private final UserRepresentation rep;
public static UserBuilder create() {
UserRepresentation rep = new UserRepresentation();
rep.setEnabled(Boolean.TRUE);
return new UserBuilder(rep);
}
public static UserBuilder edit(UserRepresentation rep) {
return new UserBuilder(rep);
}
private UserBuilder(UserRepresentation rep) {
this.rep = rep;
}
public UserBuilder id(String id) {
rep.setId(id);
return this;
}
public UserBuilder username(String username) {
rep.setUsername(username);
return this;
}
public UserBuilder firstName(String firstName) {
rep.setFirstName(firstName);
return this;
}
public UserBuilder lastName(String lastName) {
rep.setLastName(lastName);
return this;
}
/**
* This method adds additional passwords to the user.
*/
public UserBuilder addPassword(String password) {
if (rep.getCredentials() == null) {
rep.setCredentials(new LinkedList<>());
}
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType(CredentialRepresentation.PASSWORD);
credential.setValue(password);
rep.getCredentials().add(credential);
return this;
}
public UserBuilder addAttribute(String name, String... values) {
if (rep.getAttributes() == null) {
rep.setAttributes(new HashMap<>());
}
rep.getAttributes().put(name, Arrays.asList(values));
return this;
}
/**
* This method makes sure that there is one single password for the user.
*/
public UserBuilder password(String password) {
rep.setCredentials(null);
return addPassword(password);
}
public UserBuilder email(String email) {
rep.setEmail(email);
return this;
}
public UserBuilder emailVerified(boolean emailVerified) {
rep.setEmailVerified(emailVerified);
return this;
}
public UserBuilder enabled(boolean enabled) {
rep.setEnabled(enabled);
return this;
}
public UserBuilder addRoles(String... roles) {
if (rep.getRealmRoles() == null) {
rep.setRealmRoles(new ArrayList<>());
}
rep.getRealmRoles().addAll(Arrays.asList(roles));
return this;
}
public UserBuilder role(String client, String role) {
if (rep.getClientRoles() == null) {
rep.setClientRoles(new HashMap<>());
}
if (rep.getClientRoles().get(client) == null) {
rep.getClientRoles().put(client, new LinkedList<>());
}
rep.getClientRoles().get(client).add(role);
return this;
}
public UserBuilder requiredAction(String requiredAction) {
if (rep.getRequiredActions() == null) {
rep.setRequiredActions(new LinkedList<>());
}
rep.getRequiredActions().add(requiredAction);
return this;
}
public UserBuilder serviceAccountId(String serviceAccountId) {
rep.setServiceAccountClientId(serviceAccountId);
return this;
}
public UserBuilder secret(CredentialRepresentation credential) {
if (rep.getCredentials() == null) {
rep.setCredentials(new LinkedList<>());
}
rep.getCredentials().add(credential);
rep.setTotp(true);
return this;
}
public UserBuilder totpSecret(String totpSecret) {
CredentialRepresentation credential = ModelToRepresentation.toRepresentation(
OTPCredentialModel.createTOTP(totpSecret, 6, 30, HmacOTP.HMAC_SHA1));
return secret(credential);
}
public UserBuilder hotpSecret(String hotpSecret) {
CredentialRepresentation credential = ModelToRepresentation.toRepresentation(
OTPCredentialModel.createHOTP(hotpSecret, 6, 0, HmacOTP.HMAC_SHA1));
return secret(credential);
}
public UserBuilder otpEnabled() {
rep.setTotp(Boolean.TRUE);
return this;
}
public UserBuilder addGroups(String... group) {
if (rep.getGroups() == null) {
rep.setGroups(new ArrayList<>());
}
rep.getGroups().addAll(Arrays.asList(group));
return this;
}
public UserBuilder federatedLink(String identityProvider, String federatedUserId) {
if (rep.getFederatedIdentities() == null) {
rep.setFederatedIdentities(new LinkedList<>());
}
FederatedIdentityRepresentation federatedIdentity = new FederatedIdentityRepresentation();
federatedIdentity.setUserId(federatedUserId);
federatedIdentity.setUserName(rep.getUsername());
federatedIdentity.setIdentityProvider(identityProvider);
rep.getFederatedIdentities().add(federatedIdentity);
return this;
}
public UserRepresentation build() {
return rep;
}
}

View file

@ -43,10 +43,6 @@
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.openshift</groupId>
<artifactId>openshift-restclient-java</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-core</artifactId>

View file

@ -1,111 +0,0 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.storage.openshift;
import com.openshift.restclient.IClient;
import com.openshift.restclient.NotFoundException;
import com.openshift.restclient.model.IResource;
import java.util.Collections;
import java.util.Map;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.client.ClientStorageProviderModel;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.stream.Stream;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class OpenshiftClientStorageProvider implements ClientStorageProvider {
private final KeycloakSession session;
private final ClientStorageProviderModel providerModel;
private final IClient client;
OpenshiftClientStorageProvider(KeycloakSession session, ClientStorageProviderModel providerModel, IClient client) {
this.session = session;
this.providerModel = providerModel;
this.client = client;
}
@Override
public ClientModel getClientById(RealmModel realm, String id) {
StorageId storageId = new StorageId(id);
if (!storageId.getProviderId().equals(providerModel.getId())) return null;
String clientId = storageId.getExternalId();
return getClientByClientId(realm, clientId);
}
@Override
public ClientModel getClientByClientId(RealmModel realm, String clientId) {
Matcher matcher = OpenshiftClientStorageProviderFactory.SERVICE_ACCOUNT_PATTERN.matcher(clientId);
IResource resource = null;
if (matcher.matches()) {
resource = getServiceAccount(matcher.group(2), matcher.group(1));
} else {
String defaultNamespace = providerModel.get(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_DEFAULT_NAMESPACE);
if (defaultNamespace != null) {
resource = getServiceAccount(clientId, defaultNamespace);
}
}
if (resource == null) {
return null;
}
return new OpenshiftSAClientAdapter(clientId, resource, client, session, realm, providerModel);
}
@Override
public Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) {
// TODO not sure about this, but I don't see this implementation using the search now
return Stream.of(getClientByClientId(realm, clientId));
}
@Override
public Stream<ClientModel> searchClientsByAttributes(RealmModel realm, Map<String, String> attributes, Integer firstResult, Integer maxResults) {
// TODO not sure if we support searching clients for this provider
return Stream.empty();
}
@Override
public void close() {
}
private IResource getServiceAccount(String name, String namespace) {
try {
return client.get("ServiceAccount", name, namespace);
} catch (NotFoundException nfe) {
return null;
}
}
@Override
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) {
// TODO not sure about this, this implementation doesn't use it now
return Collections.EMPTY_MAP;
}
}

View file

@ -1,147 +0,0 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.storage.openshift;
import static org.keycloak.storage.CacheableStorageProviderModel.CACHE_POLICY;
import java.util.List;
import java.util.regex.Pattern;
import com.openshift.restclient.ClientBuilder;
import com.openshift.restclient.IClient;
import org.keycloak.common.Profile;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.CacheableStorageProviderModel;
import org.keycloak.storage.client.ClientStorageProviderFactory;
import org.keycloak.storage.client.ClientStorageProviderModel;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class OpenshiftClientStorageProviderFactory implements ClientStorageProviderFactory<OpenshiftClientStorageProvider>, EnvironmentDependentProviderFactory {
public static final String PROVIDER_ID = "openshift-oauth-client";
static final Pattern SERVICE_ACCOUNT_PATTERN = Pattern.compile("system:serviceaccount:([^:]+):([^:]+)");
public static final String CONFIG_PROPERTY_ACCESS_TOKEN = "openshift.access_token";
public static final String CONFIG_PROPERTY_OPENSHIFT_URI = "openshift.uri";
public static final String CONFIG_PROPERTY_DEFAULT_NAMESPACE = "openshift.namespace.default";
public static final String CONFIG_PROPERTY_REQUIRE_USER_CONSENT = "user.consent.require";
public static final String CONFIG_PROPERTY_DISPLAY_SCOPE_CONSENT_TEXT= "user.consent.scope.consent.text";
private final List<ProviderConfigProperty> CONFIG_PROPERTIES;
private IClient client;
public OpenshiftClientStorageProviderFactory() {
CONFIG_PROPERTIES = ProviderConfigurationBuilder.create()
.property().name(CONFIG_PROPERTY_ACCESS_TOKEN)
.type(ProviderConfigProperty.STRING_TYPE)
.label("Access Token")
.helpText("Bearer token that will be used to invoke on Openshift api server. Must have privilege to lookup oauth clients, service accounts, and invoke on token review interface")
.add()
.property().name(CONFIG_PROPERTY_OPENSHIFT_URI)
.type(ProviderConfigProperty.STRING_TYPE)
.label("Openshift URL")
.helpText("Openshift api server URL base endpoint.")
.add()
.property().name(CONFIG_PROPERTY_DEFAULT_NAMESPACE)
.type(ProviderConfigProperty.STRING_TYPE)
.label("Default Namespace")
.helpText("The default namespace to use when the server is not able to resolve the namespace from the client identifier. Useful when clients in Openshift don't have names with the following pattern: " + SERVICE_ACCOUNT_PATTERN.pattern())
.add()
.property().name(CONFIG_PROPERTY_REQUIRE_USER_CONSENT)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.label("Require User Consent")
.helpText("If set to true, clients from this storage will ask the end-user for any scope requested during the authorization flow")
.add()
.property().name(CONFIG_PROPERTY_DISPLAY_SCOPE_CONSENT_TEXT)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.label("Display Scopes Consent Text")
.helpText("If set to true, the consent page will display texts from the message bundle for scopes. Otherwise, the scope name will be displayed.")
.add()
.build();
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public OpenshiftClientStorageProvider create(KeycloakSession session, ComponentModel model) {
ClientStorageProviderModel providerModel = createProviderModel(model);
IClient client = getClient(providerModel);
if (client != null) {
return new OpenshiftClientStorageProvider(session, providerModel, client);
}
client.getAuthorizationContext().setToken(providerModel.get(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_ACCESS_TOKEN));
return new OpenshiftClientStorageProvider(session, providerModel, client);
}
@Override
public String getHelpText() {
return "Openshift OAuth Client Adapter";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
config.getConfig().putSingle(CACHE_POLICY, CacheableStorageProviderModel.CachePolicy.NO_CACHE.name());
}
@Override
public void onUpdate(KeycloakSession session, RealmModel realm, ComponentModel oldModel, ComponentModel newModel) {
if (!oldModel.get(CONFIG_PROPERTY_OPENSHIFT_URI).equals(newModel.get(CONFIG_PROPERTY_OPENSHIFT_URI))) {
client = null;
} else {
getClient(createProviderModel(newModel)).getAuthorizationContext().setToken(newModel.get(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_ACCESS_TOKEN));
}
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION);
}
private IClient getClient(ClientStorageProviderModel providerModel) {
synchronized (this) {
if (client == null) {
client = new ClientBuilder(providerModel.get(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_OPENSHIFT_URI)).build();
}
}
return client;
}
private ClientStorageProviderModel createProviderModel(ComponentModel model) {
return new ClientStorageProviderModel(model);
}
}

View file

@ -1,483 +0,0 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.storage.openshift;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.openshift.restclient.IClient;
import com.openshift.restclient.model.IResource;
import com.openshift.restclient.model.route.IRoute;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.storage.client.AbstractReadOnlyClientScopeAdapter;
import org.keycloak.storage.client.AbstractReadOnlyClientStorageAdapter;
import org.keycloak.storage.client.ClientStorageProviderModel;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public final class OpenshiftSAClientAdapter extends AbstractReadOnlyClientStorageAdapter {
private static final String ANNOTATION_OAUTH_REDIRECT_URI = "serviceaccounts.openshift.io/oauth-redirecturi";
private static final String ANNOTATION_OAUTH_REDIRECT_REFERENCE = "serviceaccounts.openshift.io/oauth-redirectreference";
private static final Pattern ROLE_SCOPE_PATTERN = Pattern.compile("role:([^:]+):([^:!]+)(:[!])?");
private static final Set<String> OPTIONAL_SCOPES = Stream.of("user:info", "user:check-access").collect(Collectors.toSet());
private static Set<ProtocolMapperModel> createDefaultProtocolMappers() {
Set<ProtocolMapperModel> mappers = new HashSet<>();
ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper("username", "username",
"preferred_username", "string", true, true, UserPropertyMapper.PROVIDER_ID);
mapper.setId(KeycloakModelUtils.generateId());
mappers.add(mapper);
return mappers;
}
private final IResource resource;
private final String clientId;
private final IClient client;
private final ClientRepresentation defaultConfig = new ClientRepresentation();
public OpenshiftSAClientAdapter(String clientId, IResource resource, IClient client, KeycloakSession session, RealmModel realm, ClientStorageProviderModel component) {
super(session, realm, component);
this.resource = resource;
this.clientId = clientId;
this.client = client;
}
@Override
public String getClientId() {
return clientId;
}
@Override
public String getName() {
return resource.getName();
}
@Override
public String getDescription() {
return getConfigOrDefault(() -> defaultConfig.getDescription(), defaultConfig::setDescription, new StringBuilder().append(resource.getKind()).append(" ").append(resource.getName()).append(" from namespace ").append(resource.getNamespace().getName()).toString());
}
@Override
public boolean isEnabled() {
return getConfigOrDefault(() -> defaultConfig.isEnabled(), defaultConfig::setEnabled, true);
}
@Override
public boolean isAlwaysDisplayInConsole() {
return getConfigOrDefault(() -> defaultConfig.isAlwaysDisplayInConsole(), defaultConfig::setAlwaysDisplayInConsole, false);
}
@Override
public Set<String> getWebOrigins() {
return new HashSet<>(getConfigOrDefault(() -> defaultConfig.getWebOrigins(), defaultConfig::setWebOrigins, Collections.emptyList()));
}
@Override
public Set<String> getRedirectUris() {
return new HashSet<>(getConfigOrDefault((Supplier<List<String>>) () -> defaultConfig.getRedirectUris(),
uris -> defaultConfig.setRedirectUris(uris),
(Supplier<List<String>>) () -> resource.getAnnotations().entrySet().stream()
.filter((entry) -> entry.getKey().startsWith(ANNOTATION_OAUTH_REDIRECT_URI) || entry.getKey().startsWith(ANNOTATION_OAUTH_REDIRECT_REFERENCE))
.map(entry -> {
if (entry.getKey().startsWith(ANNOTATION_OAUTH_REDIRECT_URI)) {
return entry.getValue();
} else {
Map values;
try {
values = JsonSerialization.readValue(entry.getValue(), Map.class);
} catch (IOException e) {
throw new RuntimeException("Failed to parse annotation [" + ANNOTATION_OAUTH_REDIRECT_REFERENCE + "]", e);
}
Map<String, String> reference = (Map<String, String>) values.get("reference");
String kind = (String) reference.get("kind");
if (!"Route".equals(kind)) {
throw new IllegalArgumentException("Only route references are supported for " + ANNOTATION_OAUTH_REDIRECT_REFERENCE);
}
String name = (String) reference.get("name");
IRoute route = client.get(kind, name, resource.getNamespace().getName());
StringBuilder url = new StringBuilder(route.getURL());
if (url.charAt(url.length() - 1) != '/') {
url.append('/');
}
return url.append('*').toString();
}
}).collect(Collectors.toList())));
}
@Override
public String getManagementUrl() {
return null;
}
@Override
public String getRootUrl() {
return null;
}
@Override
public String getBaseUrl() {
return null;
}
@Override
public boolean isBearerOnly() {
return false;
}
@Override
public int getNodeReRegistrationTimeout() {
return 0;
}
@Override
public String getClientAuthenticatorType() {
return null;
}
@Override
public boolean validateSecret(String secret) {
//TODO: do we want SAs as confidential clients and enable client credentials grant and resource owner grant ?
return false;
}
@Override
public String getSecret() {
//TODO: check if validate secret is enough, don't see a reason to return SAs secret
return null;
}
@Override
public String getRegistrationToken() {
return null;
}
@Override
public String getProtocol() {
//TODO: set login protocol, always oidc
return OIDCLoginProtocol.LOGIN_PROTOCOL;
}
@Override
public String getAttribute(String name) {
return null;
}
@Override
public Map<String, String> getAttributes() {
return Collections.emptyMap();
}
@Override
public String getAuthenticationFlowBindingOverride(String binding) {
return null;
}
@Override
public Map<String, String> getAuthenticationFlowBindingOverrides() {
return Collections.emptyMap();
}
@Override
public boolean isFrontchannelLogout() {
return false;
}
@Override
public boolean isFullScopeAllowed() {
return false;
}
@Override
public boolean isPublicClient() {
return true;
}
@Override
public boolean isConsentRequired() {
return component.get(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_REQUIRE_USER_CONSENT, true);
}
@Override
public boolean isDisplayOnConsentScreen() {
return false;
}
@Override
public boolean isStandardFlowEnabled() {
return true;
}
@Override
public boolean isImplicitFlowEnabled() {
return false;
}
@Override
public boolean isDirectAccessGrantsEnabled() {
return false;
}
@Override
public boolean isServiceAccountsEnabled() {
return false;
}
@Override
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope) {
if (defaultScope) {
return Collections.emptyMap();
}
Map<String, ClientScopeModel> scopes = new HashMap<>();
for (String scope : OPTIONAL_SCOPES) {
scopes.put(scope, createClientScope(scope));
}
return scopes;
}
@Override
public ClientScopeModel getDynamicClientScope(String scope) {
if (OPTIONAL_SCOPES.contains(scope)) {
return createClientScope(scope);
}
Matcher matcher = ROLE_SCOPE_PATTERN.matcher(scope);
if (matcher.matches()) {
String namespace = matcher.group(2);
if (resource.getNamespace().getName().equals(namespace)) {
return createClientScope(scope);
}
}
return null;
}
@Override
public int getNotBefore() {
return 0;
}
@Override
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
List<ProtocolMapperRepresentation> mappers = defaultConfig.getProtocolMappers();
if (mappers == null) {
Set<ProtocolMapperModel> defaultProtocolMappers = createDefaultProtocolMappers();
defaultConfig.setProtocolMappers(defaultProtocolMappers.stream()
.map(ModelToRepresentation::toRepresentation).collect(Collectors.toList()));
return defaultProtocolMappers.stream();
}
return mappers.stream().map(RepresentationToModel::toModel);
}
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
return getProtocolMappersStream()
.filter(protocolMapperModel -> Objects.equals(id, protocolMapperModel.getId()))
.findAny()
.orElse(null);
}
@Override
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
return getProtocolMappersStream()
.filter(protocolMapperModel -> Objects.equals(name, protocolMapperModel.getName()))
.findAny()
.orElse(null);
}
@Override
public Stream<RoleModel> getScopeMappingsStream() {
return Stream.empty();
}
@Override
public Stream<RoleModel> getRealmScopeMappingsStream() {
return Stream.empty();
}
@Override
public boolean hasScope(RoleModel role) {
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof ClientModel)) return false;
ClientModel that = (ClientModel) o;
return that.getId().equals(getId());
}
private <V> V getConfigOrDefault(Supplier<V> valueSupplier, Consumer<V> valueConsumer, Supplier<V> defaultValue) {
V value = valueSupplier.get();
if (value != null) {
return value;
}
value = defaultValue.get();
if (valueConsumer != null) {
valueConsumer.accept(value);
}
return value;
}
private <V> V getConfigOrDefault(Supplier<V> valueSupplier, Consumer<V> valueConsumer, V defaultValue) {
return getConfigOrDefault(valueSupplier, valueConsumer, (Supplier<V>) () -> defaultValue);
}
private ClientScopeModel createClientScope(String scope) {
ClientScopeModel managedScope = realm.getClientScopesStream()
.filter(scopeModel -> Objects.equals(scopeModel.getName(), scope))
.findAny()
.orElse(null);
if (managedScope != null) {
return managedScope;
}
Map<String, String> attributes = new HashMap<>();
attributes.put(ClientScopeModel.DISPLAY_ON_CONSENT_SCREEN, Boolean.valueOf(isConsentRequired()).toString());
if (component.get(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_DISPLAY_SCOPE_CONSENT_TEXT, Boolean.TRUE)) {
StringBuilder consentText = new StringBuilder("${openshift.scope.");
if (scope.indexOf(':') != -1) {
consentText.append(scope.replaceFirst(":", "_"));
}
attributes.put(ClientScopeModel.CONSENT_SCREEN_TEXT, consentText.append("}").toString());
} else {
attributes.put(ClientScopeModel.CONSENT_SCREEN_TEXT, scope);
}
return new AbstractReadOnlyClientScopeAdapter() {
@Override
public String getId() {
return scope;
}
@Override
public String getName() {
return scope;
}
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public String getDescription() {
return scope;
}
@Override
public String getProtocol() {
return OIDCLoginProtocol.LOGIN_PROTOCOL;
}
@Override
public String getAttribute(String name) {
return attributes.get(name);
}
@Override
public Map<String, String> getAttributes() {
return attributes;
}
@Override
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
return createDefaultProtocolMappers().stream();
}
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
return null;
}
@Override
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
return null;
}
@Override
public Stream<RoleModel> getScopeMappingsStream() {
return Stream.empty();
}
@Override
public Stream<RoleModel> getRealmScopeMappingsStream() {
return Stream.empty();
}
@Override
public boolean hasScope(RoleModel role) {
return false;
}
};
}
}

View file

@ -1,18 +0,0 @@
#
# * Copyright 2018 Red Hat, Inc. and/or its affiliates
# * and other contributors as indicated by the @author tags.
# *
# * Licensed under the Apache License, Version 2.0 (the "License");
# * you may not use this file except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * http://www.apache.org/licenses/LICENSE-2.0
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an "AS IS" BASIS,
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
#
org.keycloak.storage.openshift.OpenshiftClientStorageProviderFactory

View file

@ -43,10 +43,6 @@
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.openshift</groupId>
<artifactId>openshift-restclient-java</artifactId>
</dependency>
</dependencies>
</project>

21
pom.xml
View file

@ -138,12 +138,8 @@
<picketbox.version>5.0.3.Final</picketbox.version>
<google.guava.version>30.1-jre</google.guava.version>
<xstream.version>1.4.20</xstream.version>
<okhttp.version>4.10.0</okhttp.version>
<org.snakeyaml.snakeyaml-engine.version>2.6</org.snakeyaml.snakeyaml-engine.version>
<!-- Openshift -->
<version.com.openshift.openshift-restclient-java>9.0.5.Final</version.com.openshift.openshift-restclient-java>
<!-- Others -->
<commons-lang.version>2.6</commons-lang.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
@ -323,16 +319,6 @@
<artifactId>snakeyaml-engine</artifactId>
<version>${org.snakeyaml.snakeyaml-engine.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jboss-dmr</artifactId>
@ -1589,13 +1575,6 @@
<version>${project.version}</version>
</dependency>
<!-- Openshift -->
<dependency>
<groupId>com.openshift</groupId>
<artifactId>openshift-restclient-java</artifactId>
<version>${version.com.openshift.openshift-restclient-java}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-as7-modules</artifactId>

View file

@ -667,23 +667,6 @@
<groupId>org.twitter4j</groupId>
<artifactId>twitter4j-core</artifactId>
</dependency>
<!-- Openshift -->
<dependency>
<groupId>com.openshift</groupId>
<artifactId>openshift-restclient-java</artifactId>
<version>${version.com.openshift.openshift-restclient-java}</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>

View file

@ -28,7 +28,7 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTI
@LegacyStore
public class FeaturesDistTest {
private static final String PREVIEW_FEATURES_EXPECTED_LOG = "Preview features enabled: admin-fine-grained-authz, client-secret-rotation, declarative-user-profile, openshift-integration, recovery-codes, scripts, token-exchange, update-email";
private static final String PREVIEW_FEATURES_EXPECTED_LOG = "Preview features enabled: admin-fine-grained-authz, client-secret-rotation, declarative-user-profile, recovery-codes, scripts, token-exchange, update-email";
@Test
public void testEnableOnBuild(KeycloakDistribution dist) {

View file

@ -47,17 +47,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
HTTP/TLS:

View file

@ -47,17 +47,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
HTTP/TLS:

View file

@ -57,17 +57,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Config:

View file

@ -120,17 +120,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Config:

View file

@ -57,17 +57,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Config:

View file

@ -120,17 +120,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Config:

View file

@ -72,17 +72,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Hostname:

View file

@ -72,17 +72,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Hostname:

View file

@ -135,17 +135,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Hostname:

View file

@ -135,17 +135,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Hostname:

View file

@ -78,17 +78,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Hostname:

View file

@ -78,17 +78,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Hostname:

View file

@ -141,17 +141,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Hostname:

View file

@ -141,17 +141,15 @@ Feature:
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, docker, dynamic-scopes, fips, impersonation,
js-adapter, kerberos, map-storage, openshift-integration, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
update-email, web-authn.
js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, update-email, web-authn.
Hostname:

View file

@ -1,179 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.openshift;
import org.keycloak.TokenVerifier;
import org.keycloak.common.Profile;
import org.keycloak.common.VerificationException;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.SignatureVerifierContext;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.ext.OIDCExtProvider;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.Urls;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OpenShiftTokenReviewEndpoint implements OIDCExtProvider, EnvironmentDependentProviderFactory {
private KeycloakSession session;
private TokenManager tokenManager;
private EventBuilder event;
public OpenShiftTokenReviewEndpoint(KeycloakSession session) {
this.session = session;
this.tokenManager = new TokenManager();
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
}
@Path("/")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response tokenReview(OpenShiftTokenReviewRequestRepresentation reviewRequest) throws Exception {
return tokenReview(null, reviewRequest);
}
@Path("/{client_id}")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response tokenReview(@PathParam("client_id") String clientId, OpenShiftTokenReviewRequestRepresentation reviewRequest) throws Exception {
event.event(EventType.INTROSPECT_TOKEN);
if (clientId != null) {
session.setAttribute("client_id", clientId);
}
checkSsl();
checkRealm();
authorizeClient();
RealmModel realm = session.getContext().getRealm();
AccessToken token = null;
try {
TokenVerifier<AccessToken> verifier = TokenVerifier.create(reviewRequest.getSpec().getToken(), AccessToken.class)
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()))
.audience(reviewRequest.getSpec().getAudiences());
SignatureVerifierContext verifierContext = session.getProvider(SignatureProvider.class, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
verifier.verifierContext(verifierContext);
verifier.verify();
token = verifier.getToken();
} catch (VerificationException e) {
error(401, Errors.INVALID_TOKEN, "Token verification failure");
}
if (!tokenManager.checkTokenValidForIntrospection(session, realm, token, true)) {
error(401, Errors.INVALID_TOKEN, "Token verification failure");
}
OpenShiftTokenReviewResponseRepresentation response = new OpenShiftTokenReviewResponseRepresentation();
response.getStatus().setAuthenticated(true);
response.getStatus().setUser(new OpenShiftTokenReviewResponseRepresentation.User());
OpenShiftTokenReviewResponseRepresentation.User userRep = response.getStatus().getUser();
userRep.setUid(token.getSubject());
userRep.setUsername(token.getPreferredUsername());
if (token.getScope() != null && !token.getScope().isEmpty()) {
OpenShiftTokenReviewResponseRepresentation.Extra extra = new OpenShiftTokenReviewResponseRepresentation.Extra();
extra.setScopes(token.getScope().split(" "));
userRep.setExtra(extra);
}
if (token.getOtherClaims() != null && token.getOtherClaims().get("groups") != null) {
List<String> groups = (List<String>) token.getOtherClaims().get("groups");
userRep.setGroups(groups);
}
event.success();
return Response.ok(response, MediaType.APPLICATION_JSON).build();
}
private void checkSsl() {
if (!session.getContext().getUri().getBaseUri().getScheme().equals("https") && session.getContext().getRealm().getSslRequired().isRequired(session.getContext().getConnection())) {
error(401, Errors.SSL_REQUIRED, null);
}
}
private void checkRealm() {
if (!session.getContext().getRealm().isEnabled()) {
error(401, Errors.REALM_DISABLED,null);
}
}
private void authorizeClient() {
try {
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, null).getClient();
event.client(client);
if (client == null || client.isPublicClient()) {
error(401, Errors.INVALID_CLIENT, "Public client is not permitted to invoke token review endpoint");
}
} catch (ErrorResponseException ere) {
error(401, Errors.INVALID_CLIENT_CREDENTIALS, ere.getErrorDescription());
} catch (Exception e) {
error(401, Errors.INVALID_CLIENT_CREDENTIALS, null);
}
}
private void error(int statusCode, String error, String description) {
OpenShiftTokenReviewResponseRepresentation rep = new OpenShiftTokenReviewResponseRepresentation();
rep.getStatus().setAuthenticated(false);
Response response = Response.status(statusCode).entity(rep).type(MediaType.APPLICATION_JSON_TYPE).build();
event.error(error);
event.detail(Details.REASON, description);
throw new ErrorResponseException(response);
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION);
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.openshift;
import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.ext.OIDCExtProvider;
import org.keycloak.protocol.oidc.ext.OIDCExtProviderFactory;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OpenShiftTokenReviewEndpointFactory implements OIDCExtProviderFactory, EnvironmentDependentProviderFactory {
@Override
public OIDCExtProvider create(KeycloakSession session) {
return new OpenShiftTokenReviewEndpoint(session);
}
@Override
public String getId() {
return "openshift-token-review";
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION);
}
}

View file

@ -1,91 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.openshift;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class OpenShiftTokenReviewRequestRepresentation implements Serializable {
@JsonProperty("apiVersion")
private String apiVersion = "authentication.k8s.io/v1beta1";
@JsonProperty("kind")
private String kind = "TokenReview";
@JsonProperty("spec")
private Spec spec;
public String getApiVersion() {
return apiVersion;
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public Spec getSpec() {
return spec;
}
public void setSpec(Spec spec) {
this.spec = spec;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Spec implements Serializable {
@JsonProperty("token")
private String token;
@JsonProperty("audiences")
private String[] audiences;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String[] getAudiences() {
return audiences;
}
public void setAudiences(String[] audiences) {
this.audiences = audiences;
}
}
}

View file

@ -1,153 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.openshift;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OpenShiftTokenReviewResponseRepresentation implements Serializable {
@JsonProperty("apiVersion")
private String apiVersion = "authentication.k8s.io/v1beta1";
@JsonProperty("kind")
private String kind = "TokenReview";
@JsonProperty("status")
private Status status = new Status();
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public String getApiVersion() {
return apiVersion;
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public static class Status implements Serializable {
@JsonProperty("authenticated")
private boolean authenticated;
@JsonProperty("user")
protected User user;
public boolean isAuthenticated() {
return authenticated;
}
public void setAuthenticated(boolean authenticated) {
this.authenticated = authenticated;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
public static class User implements Serializable {
@JsonProperty("username")
protected String username;
@JsonProperty("uid")
protected String uid;
@JsonProperty("groups")
protected List<String> groups = new LinkedList<>();
@JsonProperty("extra")
protected Extra extra;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public List<String> getGroups() {
return groups;
}
public void setGroups(List<String> groups) {
this.groups = groups;
}
public Extra getExtra() {
return extra;
}
public void setExtra(Extra extra) {
this.extra = extra;
}
}
public static class Extra implements Serializable {
@JsonProperty("scopes.authorization.openshift.io")
private String[] scopes;
public String[] getScopes() {
return scopes;
}
public void setScopes(String[] scopes) {
this.scopes = scopes;
}
}
}

View file

@ -1,3 +1,2 @@
org.keycloak.protocol.openshift.OpenShiftTokenReviewEndpointFactory
org.keycloak.protocol.oidc.grants.ciba.endpoints.CibaRootEndpoint
org.keycloak.protocol.oidc.par.endpoints.ParRootEndpoint

View file

@ -16,7 +16,6 @@
*/
package org.keycloak.testsuite.federation;
import org.apache.commons.lang.BooleanUtils;
import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
@ -61,7 +60,7 @@ public class HardcodedGroupStorageProvider implements GroupStorageProvider {
Logger.getLogger(HardcodedGroupStorageProvider.class).warn(ex.getCause());
return Stream.empty();
}
if(BooleanUtils.isTrue(exact)){
if(exact != null && exact){
if (search != null && this.groupName.equals(search)) {
return Stream.of(new HardcodedGroupAdapter(realm));
}

View file

@ -1,502 +0,0 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.openshift;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.Algorithm;
import org.keycloak.events.Details;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.protocol.openshift.OpenShiftTokenReviewRequestRepresentation;
import org.keycloak.protocol.openshift.OpenShiftTokenReviewResponseRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.util.JsonSerialization;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.keycloak.common.Profile.Feature.OPENSHIFT_INTEGRATION;
import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
import static org.keycloak.utils.MediaType.APPLICATION_JSON;
@EnableFeature(value = OPENSHIFT_INTEGRATION, skipRestart = true)
public class OpenShiftTokenReviewEndpointTest extends AbstractTestRealmKeycloakTest {
private static boolean flowConfigured;
@Rule
public AssertEvents events = new AssertEvents(this);
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
ClientRepresentation client = testRealm.getClients().stream().filter(r -> r.getClientId().equals("test-app")).findFirst().get();
List<ProtocolMapperRepresentation> mappers = new LinkedList<>();
ProtocolMapperRepresentation mapper = new ProtocolMapperRepresentation();
mapper.setName("groups");
mapper.setProtocolMapper(GroupMembershipMapper.PROVIDER_ID);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
Map<String, String> config = new HashMap<>();
config.put("full.path", "false");
config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "groups");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
mapper.setConfig(config);
mappers.add(mapper);
client.setProtocolMappers(mappers);
client.setPublicClient(false);
client.setClientAuthenticatorType("testsuite-client-dummy");
testRealm.getUsers().add(
UserBuilder.create()
.username("groups-user")
.password("password")
.addGroups("/topGroup", "/topGroup/level2group")
.role("account", "view-profile")
.build());
testRealm.getUsers().add(
UserBuilder.create()
.username("empty-audience")
.password("password")
.build());
}
@Before
public void enablePassthroughAuthenticator() {
if (!flowConfigured) {
HashMap<String, String> data = new HashMap<>();
data.put("newName", "testsuite-client-dummy");
Response response = testRealm().flows().copy("clients", data);
assertEquals(201, response.getStatus());
response.close();
data = new HashMap<>();
data.put("provider", "testsuite-client-dummy");
data.put("requirement", "ALTERNATIVE");
testRealm().flows().addExecution("testsuite-client-dummy", data);
RealmRepresentation realmRep = testRealm().toRepresentation();
realmRep.setClientAuthenticationFlow("testsuite-client-dummy");
testRealm().update(realmRep);
List<AuthenticationExecutionInfoRepresentation> executions = testRealm().flows().getExecutions("testsuite-client-dummy");
for (AuthenticationExecutionInfoRepresentation e : executions) {
if (e.getProviderId().equals("testsuite-client-dummy")) {
e.setRequirement("ALTERNATIVE");
testRealm().flows().updateExecutions("testsuite-client-dummy", e);
}
}
flowConfigured = true;
}
}
@Test
public void basicTest() {
Review r = new Review().invoke();
String userId = testRealm().users().search(r.username).get(0).getId();
OpenShiftTokenReviewResponseRepresentation.User user = r.response.getStatus().getUser();
assertEquals(userId, user.getUid());
assertEquals("test-user@localhost", user.getUsername());
assertNotNull(user.getExtra());
r.assertScope("openid", "email", "profile");
}
@Test
public void longExpiration() {
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
ClientRepresentation clientRep = client.toRepresentation();
try {
clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_LIFESPAN, "-1");
client.update(clientRep);
// Set time offset just before SSO idle, to get session last refresh updated
setTimeOffset(1500);
Review review = new Review();
review.invoke().assertSuccess();
// Bump last refresh updated again
setTimeOffset(3000);
review.invoke().assertSuccess();
// And, again
setTimeOffset(4500);
// Token should still be valid as session last refresh should have been updated
review.invoke().assertSuccess();
} finally {
clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_LIFESPAN, null);
client.update(clientRep);
}
}
@Test
public void hs256() {
RealmResource realm = adminClient.realm("test");
RealmRepresentation rep = realm.toRepresentation();
try {
rep.setDefaultSignatureAlgorithm(Algorithm.HS256);
realm.update(rep);
Review r = new Review().algorithm(Algorithm.HS256).invoke()
.assertSuccess();
String userId = testRealm().users().search(r.username).get(0).getId();
OpenShiftTokenReviewResponseRepresentation.User user = r.response.getStatus().getUser();
assertEquals(userId, user.getUid());
assertEquals("test-user@localhost", user.getUsername());
assertNotNull(user.getExtra());
r.assertScope("openid", "email", "profile");
} finally {
rep.setDefaultSignatureAlgorithm(null);
realm.update(rep);
}
}
@Test
public void groups() {
new Review().username("groups-user")
.invoke()
.assertSuccess().assertGroups("topGroup", "level2group");
}
@Test
public void customScopes() {
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
clientScope.setProtocol("openid-connect");
clientScope.setName("user:info");
String id;
try (Response r = testRealm().clientScopes().create(clientScope)) {
id = ApiUtil.getCreatedId(r);
}
ClientRepresentation clientRep = testRealm().clients().findByClientId("test-app").get(0);
testRealm().clients().get(clientRep.getId()).addOptionalClientScope(id);
try {
oauth.scope("user:info");
new Review()
.invoke()
.assertSuccess().assertScope("openid", "user:info", "profile", "email");
} finally {
testRealm().clients().get(clientRep.getId()).removeOptionalClientScope(id);
}
}
@Test
public void emptyAudience() {
new Review().username("empty-audience")
.invoke()
.assertError(401, "Token verification failure");
}
@Test
public void expiredToken() {
try {
new Review()
.runAfterTokenRequest(i -> setTimeOffset(testRealm().toRepresentation().getAccessTokenLifespan() + 10))
.invoke()
.assertError(401, "Token verification failure");
} finally {
resetTimeOffset();
}
}
@Test
public void invalidPublicKey() {
new Review()
.runAfterTokenRequest(i -> {
String header = i.token.split("\\.")[0];
String s = new String(Base64Url.decode(header));
s = s.replace(",\"kid\" : \"", ",\"kid\" : \"x");
String newHeader = Base64Url.encode(s.getBytes());
i.token = i.token.replaceFirst(header, newHeader);
})
.invoke()
.assertError(401, "Token verification failure");
}
@Test
public void noUserSession() {
new Review()
.runAfterTokenRequest(i -> {
String userId = testRealm().users().search(i.username).get(0).getId();
testRealm().users().get(userId).logout();
})
.invoke()
.assertError(401, "Token verification failure");
}
@Test
public void invalidTokenSignature() {
new Review()
.runAfterTokenRequest(i -> i.token += "x")
.invoke()
.assertError(401, "Token verification failure");
}
@Test
public void realmDisabled() {
RealmRepresentation r = testRealm().toRepresentation();
try {
new Review().runAfterTokenRequest(i -> {
r.setEnabled(false);
testRealm().update(r);
}).invoke().assertError(401, null);
} finally {
r.setEnabled(true);
testRealm().update(r);
}
}
@Test
public void publicClientNotPermitted() {
ClientRepresentation clientRep = testRealm().clients().findByClientId("test-app").get(0);
clientRep.setPublicClient(true);
testRealm().clients().get(clientRep.getId()).update(clientRep);
try {
new Review()
.clientAuthMethod(ClientIdAndSecretAuthenticator.PROVIDER_ID)
.invoke().assertError(401, "Public client is not permitted to invoke token review endpoint");
} finally {
clientRep.setPublicClient(false);
clientRep.setSecret("password");
testRealm().clients().get(clientRep.getId()).update(clientRep);
}
}
@Test
public void checkPropertyValidation() throws IOException {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
String url = getAuthServerContextRoot() + "/auth/realms/" + "test" + "/protocol/openid-connect/ext/openshift-token-review/";
HttpPost post = new HttpPost(url);
post.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
post.setHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
post.setEntity(new StringEntity("{\"<img src=alert(1)>\":1}"));
try (CloseableHttpResponse response = client.execute(post)) {
Header header = response.getFirstHeader("Content-Type");
assertThat(header, notNullValue());
// Verify the Content-Type is not text/html
assertThat(Arrays.stream(header.getElements())
.map(HeaderElement::getName)
.filter(Objects::nonNull)
.anyMatch(f -> f.equals(APPLICATION_JSON)), is(true));
// OpenShiftTokenReviewRequestRepresentation ignore unknown attributes and is returned default representation
assertThat(EntityUtils.toString(response.getEntity()).contains("Unrecognized field \\\"<img src=alert(1)>\\\""), is(false));
}
}
}
private class Review {
private String realm = "test";
private String clientId = "test-app";
private String username = "test-user@localhost";
private String password = "password";
private String algorithm = Algorithm.RS256;
private InvokeRunnable runAfterTokenRequest;
private String token;
private String clientAuthMethod = "testsuite-client-dummy";
private int responseStatus;
private OpenShiftTokenReviewResponseRepresentation response;
public Review username(String username) {
this.username = username;
return this;
}
public Review algorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
public Review clientAuthMethod(String clientAuthMethod) {
this.clientAuthMethod = clientAuthMethod;
return this;
}
public Review runAfterTokenRequest(InvokeRunnable runnable) {
this.runAfterTokenRequest = runnable;
return this;
}
public Review invoke() {
try {
if (token == null) {
String userId = testRealm().users().search(username).get(0).getId();
oauth.doLogin(username, password);
EventRepresentation loginEvent = events.expectLogin().user(userId).assertEvent();
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()).detail("client_auth_method", this.clientAuthMethod).user(userId).assertEvent();
token = accessTokenResponse.getAccessToken();
}
assertEquals(algorithm, new JWSInput(token).getHeader().getAlgorithm().name());
if (runAfterTokenRequest != null) {
runAfterTokenRequest.run(this);
}
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
String url = getAuthServerContextRoot() + "/auth/realms/" + realm + "/protocol/openid-connect/ext/openshift-token-review/" + clientId;
OpenShiftTokenReviewRequestRepresentation request = new OpenShiftTokenReviewRequestRepresentation();
OpenShiftTokenReviewRequestRepresentation.Spec spec = new OpenShiftTokenReviewRequestRepresentation.Spec();
spec.setToken(token);
spec.setAudiences(new String[]{"account"});
request.setSpec(spec);
HttpPost post = new HttpPost(url);
post.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
post.setHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
post.setEntity(new StringEntity(JsonSerialization.writeValueAsString(request)));
try (CloseableHttpResponse resp = client.execute(post)) {
responseStatus = resp.getStatusLine().getStatusCode();
response = JsonSerialization.readValue(resp.getEntity().getContent(), OpenShiftTokenReviewResponseRepresentation.class);
}
assertEquals("authentication.k8s.io/v1beta1", response.getApiVersion());
assertEquals("TokenReview", response.getKind());
}
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Review assertSuccess() {
assertEquals(200, responseStatus);
assertTrue(response.getStatus().isAuthenticated());
assertNotNull(response.getStatus().getUser());
return this;
}
private Review assertError(int expectedStatus, String expectedReason) {
assertEquals(expectedStatus, responseStatus);
assertFalse(response.getStatus().isAuthenticated());
assertNull(response.getStatus().getUser());
if (expectedReason != null) {
EventRepresentation poll = events.poll();
assertEquals(expectedReason, poll.getDetails().get(Details.REASON));
}
return this;
}
private void assertScope(String... expectedScope) {
List<String> actualScopes = Arrays.asList(response.getStatus().getUser().getExtra().getScopes());
assertEquals(expectedScope.length, actualScopes.size());
assertThat(actualScopes, containsInAnyOrder(expectedScope));
}
private void assertEmptyScope() {
assertNull(response.getStatus().getUser().getExtra());
}
private void assertGroups(String... expectedGroups) {
List<String> actualGroups = new LinkedList<>(response.getStatus().getUser().getGroups());
assertEquals(expectedGroups.length, actualGroups.size());
assertThat(actualGroups, containsInAnyOrder(expectedGroups));
}
}
private interface InvokeRunnable {
void run(Review i);
}
}

View file

@ -1,288 +0,0 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.openshift;
import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.admin.client.resource.ComponentResource;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.events.Details;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.openshift.OpenshiftClientStorageProviderFactory;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ConsentPage;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.OAuthClient;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.keycloak.common.Profile.Feature.OPENSHIFT_INTEGRATION;
import static org.keycloak.testsuite.ProfileAssume.assumeFeatureEnabled;
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
/**
* Test that clients can override auth flows
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@EnableFeature(value = OPENSHIFT_INTEGRATION, skipRestart = true)
public final class OpenshiftClientStorageTest extends AbstractTestRealmKeycloakTest {
private static Undertow OPENSHIFT_API_SERVER;
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
private LoginPage loginPage;
@Page
private AppPage appPage;
@Page
private ConsentPage consentPage;
@Page
private ErrorPage errorPage;
private String userId;
private String clientStorageId;
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
}
@BeforeClass
public static void checkNotMapStorage() {
ProfileAssume.assumeFeatureDisabled(Feature.MAP_STORAGE);
}
@BeforeClass
public static void onBeforeClass() {
OPENSHIFT_API_SERVER = Undertow.builder().addHttpListener(8880, "localhost", new HttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
String uri = exchange.getRequestURI();
if (uri.endsWith("/version/openshift") || uri.endsWith("/version")) {
writeResponse("openshift-version.json", exchange);
} else if (uri.endsWith("/oapi")) {
writeResponse("oapi-response.json", exchange);
} else if (uri.endsWith("/apis")) {
writeResponse("apis-response.json", exchange);
} else if (uri.endsWith("/api")) {
writeResponse("api.json", exchange);
} else if (uri.endsWith("/api/v1")) {
writeResponse("api-v1.json", exchange);
} else if (uri.endsWith("/oapi/v1")) {
writeResponse("oapi-v1.json", exchange);
} else if (uri.contains("/apis/route.openshift.io/v1")) {
writeResponse("apis-route-v1.json", exchange);
} else if (uri.endsWith("/api/v1/namespaces/default")) {
writeResponse("namespace-default.json", exchange);
} else if (uri.endsWith("/oapi/v1/namespaces/default/routes/proxy")) {
writeResponse("route-response.json", exchange);
} else if (uri.contains("/serviceaccounts/system")) {
writeResponse("sa-system.json", exchange);
} else if (uri.contains("/serviceaccounts/")) {
writeResponse(uri.substring(uri.lastIndexOf('/') + 1) + ".json", exchange);
}
}
private void writeResponse(String file, HttpServerExchange exchange) throws IOException {
exchange.getResponseSender().send(StreamUtil.readString(getClass().getResourceAsStream("/openshift/client-storage/" + file)));
}
}).build();
OPENSHIFT_API_SERVER.start();
}
@AfterClass
public static void onAfterClass() {
if (OPENSHIFT_API_SERVER != null) {
OPENSHIFT_API_SERVER.stop();
}
}
@Before
public void onBefore() {
assumeFeatureEnabled(OPENSHIFT_INTEGRATION);
ComponentRepresentation provider = new ComponentRepresentation();
provider.setName("openshift-client-storage");
provider.setProviderId(OpenshiftClientStorageProviderFactory.PROVIDER_ID);
provider.setProviderType(ClientStorageProvider.class.getName());
provider.setConfig(new MultivaluedHashMap<>());
provider.getConfig().putSingle(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_OPENSHIFT_URI, "http://localhost:8880");
provider.getConfig().putSingle(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_ACCESS_TOKEN, "token");
provider.getConfig().putSingle(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_DEFAULT_NAMESPACE, "default");
provider.getConfig().putSingle(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_REQUIRE_USER_CONSENT, "true");
Response resp = adminClient.realm("test").components().add(provider);
resp.close();
clientStorageId = ApiUtil.getCreatedId(resp);
getCleanup().addComponentId(clientStorageId);
}
@Before
public void clientConfiguration() {
userId = findUserByUsername(adminClient.realm("test"), "test-user@localhost").getId();
}
@Test
public void testCodeGrantFlowWithServiceAccountUsingOAuthRedirectReference() {
String clientId = "system:serviceaccount:default:sa-oauth-redirect-reference";
testCodeGrantFlow(clientId, "http://127.0.0.1:8180/callback", () -> assertSuccessfulResponseWithoutConsent(clientId));
}
@Test
public void failCodeGrantFlowWithServiceAccountUsingOAuthRedirectReference() throws Exception {
testCodeGrantFlow("system:serviceaccount:default:sa-oauth-redirect-reference", "http://invalid/callback", () -> assertEquals(OAuthErrorException.INVALID_REDIRECT_URI, events.poll().getError()));
}
@Test
public void testCodeGrantFlowWithServiceAccountUsingOAuthRedirectUri() {
String clientId = "system:serviceaccount:default:sa-oauth-redirect-uri";
testCodeGrantFlow(clientId, "http://localhost:8180/auth/realms/master/app/auth", () -> assertSuccessfulResponseWithoutConsent(clientId));
testCodeGrantFlow(clientId, "http://localhost:8180/auth/realms/master/app/auth/second", () -> assertSuccessfulResponseWithoutConsent(clientId));
testCodeGrantFlow(clientId, "http://localhost:8180/auth/realms/master/app/auth/third", () -> assertSuccessfulResponseWithoutConsent(clientId));
}
@Test
public void testCodeGrantFlowWithUserConsent() {
String clientId = "system:serviceaccount:default:sa-oauth-redirect-uri";
testCodeGrantFlow(clientId, "http://localhost:8180/auth/realms/master/app/auth", () -> assertSuccessfulResponseWithConsent(clientId), "user:info user:check-access");
ComponentResource component = testRealm().components().component(clientStorageId);
ComponentRepresentation representation = component.toRepresentation();
representation.getConfig().put(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_REQUIRE_USER_CONSENT, Arrays.asList("false"));
component.update(representation);
testCodeGrantFlow(clientId, "http://localhost:8180/auth/realms/master/app/auth", () -> assertSuccessfulResponseWithoutConsent(clientId), "user:info user:check-access");
representation.getConfig().put(OpenshiftClientStorageProviderFactory.CONFIG_PROPERTY_REQUIRE_USER_CONSENT, Arrays.asList("true"));
component.update(representation);
testCodeGrantFlow(clientId, "http://localhost:8180/auth/realms/master/app/auth", () -> assertSuccessfulResponseWithoutConsent(clientId, Details.CONSENT_VALUE_PERSISTED_CONSENT), "user:info user:check-access");
testRealm().users().get(userId).revokeConsent(clientId);
testCodeGrantFlow(clientId, "http://localhost:8180/auth/realms/master/app/auth", () -> assertSuccessfulResponseWithConsent(clientId), "user:info user:check-access");
}
@Test
public void failCodeGrantFlowWithServiceAccountUsingOAuthRedirectUri() throws Exception {
testCodeGrantFlow("system:serviceaccount:default:sa-oauth-redirect-uri", "http://invalid/callback", () -> assertEquals(OAuthErrorException.INVALID_REDIRECT_URI, events.poll().getError()));
}
private void testCodeGrantFlow(String clientId, String expectedRedirectUri, Runnable assertThat) {
testCodeGrantFlow(clientId, expectedRedirectUri, assertThat, null);
}
private void testCodeGrantFlow(String clientId, String expectedRedirectUri, Runnable assertThat, String scope) {
if (scope != null) {
oauth.scope(scope);
}
oauth.clientId(clientId);
oauth.redirectUri(expectedRedirectUri);
driver.navigate().to(oauth.getLoginFormUrl());
loginPage.assertCurrent();
try {
// Fill username+password. I am successfully authenticated
oauth.fillLoginForm("test-user@localhost", "password");
} catch (Exception ignore) {
}
assertThat.run();
}
private void assertSuccessfulResponseWithoutConsent(String clientId) {
assertSuccessfulResponseWithoutConsent(clientId, null);
}
private void assertSuccessfulResponseWithoutConsent(String clientId, String consentDetail) {
AssertEvents.ExpectedEvent expectedEvent = events.expectLogin().client(clientId).detail(Details.REDIRECT_URI, oauth.getRedirectUri()).detail(Details.USERNAME, "test-user@localhost");
if (consentDetail != null) {
expectedEvent.detail(Details.CONSENT, Details.CONSENT_VALUE_PERSISTED_CONSENT);
}
expectedEvent.assertEvent();
assertSuccessfulRedirect();
}
private void assertSuccessfulResponseWithConsent(String clientId) {
consentPage.assertCurrent();
driver.getPageSource().contains("user:info");
driver.getPageSource().contains("user:check-access");
consentPage.confirm();
events.expectLogin().client(clientId).detail(Details.REDIRECT_URI, oauth.getRedirectUri()).detail(Details.USERNAME, "test-user@localhost").detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED).assertEvent();
assertSuccessfulRedirect("user:info", "user:check-access");
}
private void assertSuccessfulRedirect(String... expectedScopes) {
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, null);
String accessToken = tokenResponse.getAccessToken();
Assert.assertNotNull(accessToken);
try {
AccessToken token = new JWSInput(accessToken).readJsonContent(AccessToken.class);
for (String expectedScope : expectedScopes) {
token.getScope().contains(expectedScope);
}
} catch (Exception e) {
fail("Failed to parse access token");
e.printStackTrace();
}
Assert.assertNotNull(tokenResponse.getRefreshToken());
oauth.doLogout(tokenResponse.getRefreshToken(), null);
events.clear();
}
}

View file

@ -1,495 +0,0 @@
{
"kind": "APIResourceList",
"groupVersion": "v1",
"resources": [
{
"name": "bindings",
"singularName": "",
"namespaced": true,
"kind": "Binding",
"verbs": [
"create"
]
},
{
"name": "componentstatuses",
"singularName": "",
"namespaced": false,
"kind": "ComponentStatus",
"verbs": [
"get",
"list"
],
"shortNames": [
"cs"
]
},
{
"name": "configmaps",
"singularName": "",
"namespaced": true,
"kind": "ConfigMap",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"cm"
]
},
{
"name": "endpoints",
"singularName": "",
"namespaced": true,
"kind": "Endpoints",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"ep"
]
},
{
"name": "events",
"singularName": "",
"namespaced": true,
"kind": "Event",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"ev"
]
},
{
"name": "limitranges",
"singularName": "",
"namespaced": true,
"kind": "LimitRange",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"limits"
]
},
{
"name": "namespaces",
"singularName": "",
"namespaced": false,
"kind": "Namespace",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"ns"
]
},
{
"name": "namespaces/finalize",
"singularName": "",
"namespaced": false,
"kind": "Namespace",
"verbs": [
"update"
]
},
{
"name": "namespaces/status",
"singularName": "",
"namespaced": false,
"kind": "Namespace",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "nodes",
"singularName": "",
"namespaced": false,
"kind": "Node",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"no"
]
},
{
"name": "nodes/proxy",
"singularName": "",
"namespaced": false,
"kind": "Node",
"verbs": []
},
{
"name": "nodes/status",
"singularName": "",
"namespaced": false,
"kind": "Node",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "persistentvolumeclaims",
"singularName": "",
"namespaced": true,
"kind": "PersistentVolumeClaim",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"pvc"
]
},
{
"name": "persistentvolumeclaims/status",
"singularName": "",
"namespaced": true,
"kind": "PersistentVolumeClaim",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "persistentvolumes",
"singularName": "",
"namespaced": false,
"kind": "PersistentVolume",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"pv"
]
},
{
"name": "persistentvolumes/status",
"singularName": "",
"namespaced": false,
"kind": "PersistentVolume",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "pods",
"singularName": "",
"namespaced": true,
"kind": "Pod",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"po"
],
"categories": [
"all"
]
},
{
"name": "pods/attach",
"singularName": "",
"namespaced": true,
"kind": "Pod",
"verbs": []
},
{
"name": "pods/binding",
"singularName": "",
"namespaced": true,
"kind": "Binding",
"verbs": [
"create"
]
},
{
"name": "pods/eviction",
"singularName": "",
"namespaced": true,
"group": "policy",
"version": "v1beta1",
"kind": "Eviction",
"verbs": [
"create"
]
},
{
"name": "pods/exec",
"singularName": "",
"namespaced": true,
"kind": "Pod",
"verbs": []
},
{
"name": "pods/log",
"singularName": "",
"namespaced": true,
"kind": "Pod",
"verbs": [
"get"
]
},
{
"name": "pods/portforward",
"singularName": "",
"namespaced": true,
"kind": "Pod",
"verbs": []
},
{
"name": "pods/proxy",
"singularName": "",
"namespaced": true,
"kind": "Pod",
"verbs": []
},
{
"name": "pods/status",
"singularName": "",
"namespaced": true,
"kind": "Pod",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "podtemplates",
"singularName": "",
"namespaced": true,
"kind": "PodTemplate",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "replicationcontrollers",
"singularName": "",
"namespaced": true,
"kind": "ReplicationController",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"rc"
],
"categories": [
"all"
]
},
{
"name": "replicationcontrollers/scale",
"singularName": "",
"namespaced": true,
"group": "autoscaling",
"version": "v1",
"kind": "Scale",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "replicationcontrollers/status",
"singularName": "",
"namespaced": true,
"kind": "ReplicationController",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "resourcequotas",
"singularName": "",
"namespaced": true,
"kind": "ResourceQuota",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"quota"
]
},
{
"name": "resourcequotas/status",
"singularName": "",
"namespaced": true,
"kind": "ResourceQuota",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "secrets",
"singularName": "",
"namespaced": true,
"kind": "Secret",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "serviceaccounts",
"singularName": "",
"namespaced": true,
"kind": "ServiceAccount",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"sa"
]
},
{
"name": "services",
"singularName": "",
"namespaced": true,
"kind": "Service",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"svc"
],
"categories": [
"all"
]
},
{
"name": "services/proxy",
"singularName": "",
"namespaced": true,
"kind": "Service",
"verbs": []
},
{
"name": "services/status",
"singularName": "",
"namespaced": true,
"kind": "Service",
"verbs": [
"get",
"patch",
"update"
]
}
]
}

View file

@ -1,12 +0,0 @@
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "localhost:8880"
}
]
}

View file

@ -1,20 +0,0 @@
{
"kind": "APIGroupList",
"apiVersion": "v1",
"groups": [
{
"name": "route.openshift.io",
"versions": [
{
"groupVersion": "route.openshift.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "route.openshift.io/v1",
"version": "v1"
},
"serverAddressByClientCIDRs": null
}
]
}

View file

@ -1,37 +0,0 @@
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "route.openshift.io/v1",
"resources": [
{
"name": "routes",
"singularName": "",
"namespaced": true,
"kind": "Route",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"categories": [
"all"
]
},
{
"name": "routes/status",
"singularName": "",
"namespaced": true,
"kind": "Route",
"verbs": [
"get",
"patch",
"update"
]
}
]
}

View file

@ -1,25 +0,0 @@
{
"kind": "Namespace",
"apiVersion": "v1",
"metadata": {
"name": "default",
"selfLink": "/api/v1/namespaces/default",
"uid": "cb37acb7-d084-11e8-aea9-5254001e7d16",
"resourceVersion": "977",
"creationTimestamp": "2018-10-15T14:15:39Z",
"annotations": {
"openshift.io/sa.scc.mcs": "s0:c1,c0",
"openshift.io/sa.scc.supplemental-groups": "1000000000/10000",
"openshift.io/sa.scc.uid-range": "1000000000/10000"
}
},
"spec": {
"finalizers": [
"kubernetes",
"openshift.io/origin"
]
},
"status": {
"phase": "Active"
}
}

View file

@ -1,12 +0,0 @@
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "192.168.121.194:8443"
}
]
}

View file

@ -1,732 +0,0 @@
{
"kind": "APIResourceList",
"groupVersion": "v1",
"resources": [
{
"name": "appliedclusterresourcequotas",
"singularName": "",
"namespaced": true,
"kind": "AppliedClusterResourceQuota",
"verbs": [
"get",
"list"
]
},
{
"name": "buildconfigs",
"singularName": "",
"namespaced": true,
"kind": "BuildConfig",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"bc"
]
},
{
"name": "buildconfigs/instantiate",
"singularName": "",
"namespaced": true,
"kind": "BuildRequest",
"verbs": [
"create"
]
},
{
"name": "buildconfigs/instantiatebinary",
"singularName": "",
"namespaced": true,
"kind": "BinaryBuildRequestOptions",
"verbs": []
},
{
"name": "buildconfigs/webhooks",
"singularName": "",
"namespaced": true,
"kind": "Build",
"verbs": []
},
{
"name": "builds",
"singularName": "",
"namespaced": true,
"kind": "Build",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "builds/clone",
"singularName": "",
"namespaced": true,
"kind": "BuildRequest",
"verbs": [
"create"
]
},
{
"name": "builds/details",
"singularName": "",
"namespaced": true,
"kind": "Build",
"verbs": [
"update"
]
},
{
"name": "builds/log",
"singularName": "",
"namespaced": true,
"kind": "BuildLog",
"verbs": [
"get"
]
},
{
"name": "clusternetworks",
"singularName": "",
"namespaced": false,
"kind": "ClusterNetwork",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "clusterresourcequotas",
"singularName": "",
"namespaced": false,
"kind": "ClusterResourceQuota",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"clusterquota"
]
},
{
"name": "clusterresourcequotas/status",
"singularName": "",
"namespaced": false,
"kind": "ClusterResourceQuota",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "clusterrolebindings",
"singularName": "",
"namespaced": false,
"kind": "ClusterRoleBinding",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update"
]
},
{
"name": "clusterroles",
"singularName": "",
"namespaced": false,
"kind": "ClusterRole",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update"
]
},
{
"name": "deploymentconfigs",
"singularName": "",
"namespaced": true,
"kind": "DeploymentConfig",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"dc"
]
},
{
"name": "deploymentconfigs/instantiate",
"singularName": "",
"namespaced": true,
"kind": "DeploymentRequest",
"verbs": [
"create"
]
},
{
"name": "deploymentconfigs/log",
"singularName": "",
"namespaced": true,
"kind": "DeploymentLog",
"verbs": [
"get"
]
},
{
"name": "deploymentconfigs/rollback",
"singularName": "",
"namespaced": true,
"kind": "DeploymentConfigRollback",
"verbs": [
"create"
]
},
{
"name": "deploymentconfigs/scale",
"singularName": "",
"namespaced": true,
"group": "extensions",
"version": "v1beta1",
"kind": "Scale",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "deploymentconfigs/status",
"singularName": "",
"namespaced": true,
"kind": "DeploymentConfig",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "egressnetworkpolicies",
"singularName": "",
"namespaced": true,
"kind": "EgressNetworkPolicy",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "groups",
"singularName": "",
"namespaced": false,
"kind": "Group",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "hostsubnets",
"singularName": "",
"namespaced": false,
"kind": "HostSubnet",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "identities",
"singularName": "",
"namespaced": false,
"kind": "Identity",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "images",
"singularName": "",
"namespaced": false,
"kind": "Image",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "imagesignatures",
"singularName": "",
"namespaced": false,
"kind": "ImageSignature",
"verbs": [
"create",
"delete"
]
},
{
"name": "imagestreamimages",
"singularName": "",
"namespaced": true,
"kind": "ImageStreamImage",
"verbs": [
"get"
],
"shortNames": [
"isimage"
]
},
{
"name": "imagestreamimports",
"singularName": "",
"namespaced": true,
"kind": "ImageStreamImport",
"verbs": [
"create"
]
},
{
"name": "imagestreammappings",
"singularName": "",
"namespaced": true,
"kind": "ImageStreamMapping",
"verbs": [
"create"
]
},
{
"name": "imagestreams",
"singularName": "",
"namespaced": true,
"kind": "ImageStream",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"is"
]
},
{
"name": "imagestreams/secrets",
"singularName": "",
"namespaced": true,
"kind": "SecretList",
"verbs": [
"get"
]
},
{
"name": "imagestreams/status",
"singularName": "",
"namespaced": true,
"kind": "ImageStream",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "imagestreamtags",
"singularName": "",
"namespaced": true,
"kind": "ImageStreamTag",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update"
],
"shortNames": [
"istag"
]
},
{
"name": "localresourceaccessreviews",
"singularName": "",
"namespaced": true,
"kind": "LocalResourceAccessReview",
"verbs": [
"create"
]
},
{
"name": "localsubjectaccessreviews",
"singularName": "",
"namespaced": true,
"kind": "LocalSubjectAccessReview",
"verbs": [
"create"
]
},
{
"name": "netnamespaces",
"singularName": "",
"namespaced": false,
"kind": "NetNamespace",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "oauthaccesstokens",
"singularName": "",
"namespaced": false,
"kind": "OAuthAccessToken",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "oauthauthorizetokens",
"singularName": "",
"namespaced": false,
"kind": "OAuthAuthorizeToken",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "oauthclientauthorizations",
"singularName": "",
"namespaced": false,
"kind": "OAuthClientAuthorization",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "oauthclients",
"singularName": "",
"namespaced": false,
"kind": "OAuthClient",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "podsecuritypolicyreviews",
"singularName": "",
"namespaced": true,
"kind": "PodSecurityPolicyReview",
"verbs": [
"create"
]
},
{
"name": "podsecuritypolicyselfsubjectreviews",
"singularName": "",
"namespaced": true,
"kind": "PodSecurityPolicySelfSubjectReview",
"verbs": [
"create"
]
},
{
"name": "podsecuritypolicysubjectreviews",
"singularName": "",
"namespaced": true,
"kind": "PodSecurityPolicySubjectReview",
"verbs": [
"create"
]
},
{
"name": "processedtemplates",
"singularName": "",
"namespaced": true,
"kind": "Template",
"verbs": [
"create"
]
},
{
"name": "projectrequests",
"singularName": "",
"namespaced": false,
"kind": "ProjectRequest",
"verbs": [
"create",
"list"
]
},
{
"name": "projects",
"singularName": "",
"namespaced": false,
"kind": "Project",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "resourceaccessreviews",
"singularName": "",
"namespaced": true,
"kind": "ResourceAccessReview",
"verbs": [
"create"
]
},
{
"name": "rolebindingrestrictions",
"singularName": "",
"namespaced": true,
"kind": "RoleBindingRestriction",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "rolebindings",
"singularName": "",
"namespaced": true,
"kind": "RoleBinding",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update"
]
},
{
"name": "roles",
"singularName": "",
"namespaced": true,
"kind": "Role",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update"
]
},
{
"name": "routes",
"singularName": "",
"namespaced": true,
"kind": "Route",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "routes/status",
"singularName": "",
"namespaced": true,
"kind": "Route",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "selfsubjectrulesreviews",
"singularName": "",
"namespaced": true,
"kind": "SelfSubjectRulesReview",
"verbs": [
"create"
]
},
{
"name": "subjectaccessreviews",
"singularName": "",
"namespaced": true,
"kind": "SubjectAccessReview",
"verbs": [
"create"
]
},
{
"name": "subjectrulesreviews",
"singularName": "",
"namespaced": true,
"kind": "SubjectRulesReview",
"verbs": [
"create"
]
},
{
"name": "templates",
"singularName": "",
"namespaced": true,
"kind": "Template",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
},
{
"name": "useridentitymappings",
"singularName": "",
"namespaced": false,
"kind": "UserIdentityMapping",
"verbs": [
"create",
"delete",
"get",
"patch",
"update"
]
},
{
"name": "users",
"singularName": "",
"namespaced": false,
"kind": "User",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
}
]
}

View file

@ -1,11 +0,0 @@
{
"major": "3",
"minor": "10+",
"gitVersion": "v3.10.0+2084755-68",
"gitCommit": "2084755",
"gitTreeState": "",
"buildDate": "2018-10-30T09:01:17Z",
"goVersion": "",
"compiler": "",
"platform": ""
}

View file

@ -1,40 +0,0 @@
{
"kind": "Route",
"apiVersion": "v1",
"metadata": {
"name": "proxy",
"namespace": "default",
"selfLink": "/oapi/v1/namespaces/default/routes/proxy",
"uid": "3bf12cd8-d14a-11e8-82c2-5254001e7d16",
"resourceVersion": "45934",
"creationTimestamp": "2018-10-16T13:48:59Z",
"annotations": {
"openshift.io/host.generated": "true"
}
},
"spec": {
"host": "127.0.0.1",
"to": {
"kind": "Service",
"name": "proxy",
"weight": 100
},
"wildcardPolicy": "None"
},
"status": {
"ingress": [
{
"host": "127.0.0.1",
"routerName": "router",
"conditions": [
{
"type": "Admitted",
"status": "True",
"lastTransitionTime": "2018-10-16T13:49:00Z"
}
],
"wildcardPolicy": "None"
}
]
}
}

View file

@ -1,15 +0,0 @@
{
"kind": "ServiceAccount",
"apiVersion": "v1",
"metadata": {
"name": "proxy-with-redirect-reference",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/serviceaccounts/proxy",
"uid": "3befc25c-d14a-11e8-8666-5254001e7d16",
"resourceVersion": "205225",
"creationTimestamp": "2018-10-16T13: 48:59Z",
"annotations": {
"serviceaccounts.openshift.io/oauth-redirectreference.primary": "{\"kind\":\"OAuthRedirectReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"Route\",\"name\":\"proxy\"}}"
}
}
}

View file

@ -1,17 +0,0 @@
{
"kind": "ServiceAccount",
"apiVersion": "v1",
"metadata": {
"name": "proxy-with-redirect-uri",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/serviceaccounts/proxy",
"uid": "3befc25c-d14a-11e8-8666-5254001e7d16",
"resourceVersion": "205225",
"creationTimestamp": "2018-10-16T13: 48:59Z",
"annotations": {
"serviceaccounts.openshift.io/oauth-redirecturi.first": "http://localhost:8180/auth/realms/master/app/auth",
"serviceaccounts.openshift.io/oauth-redirecturi.second": "http://localhost:8180/auth/realms/master/app/auth/second",
"serviceaccounts.openshift.io/oauth-redirecturi.third": "http://localhost:8180/auth/realms/master/app/auth/third"
}
}
}

View file

@ -1,12 +0,0 @@
{
"kind": "ServiceAccount",
"apiVersion": "v1",
"metadata": {
"name": "system",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/serviceaccounts/proxy",
"uid": "3befc25c-d14a-11e8-8666-5254001e7d16",
"resourceVersion": "205225",
"creationTimestamp": "2018-10-16T13: 48:59Z"
}
}

View file

@ -28,7 +28,6 @@ migration,4
model,6
oauth,6
oidc,6
openshift,6
policy,6
providers,4
runonserver,6