Removing OpenShift integration and moving to separate extension
closes #20496 Co-authored-by: mposolda <mposolda@gmail.com>
This commit is contained in:
parent
9ad295a3bc
commit
0832992e59
51 changed files with 293 additions and 3590 deletions
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
5
dependencies/server-all/pom.xml
vendored
5
dependencies/server-all/pom.xml
vendored
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
21
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
org.keycloak.protocol.openshift.OpenShiftTokenReviewEndpointFactory
|
||||
org.keycloak.protocol.oidc.grants.ciba.endpoints.CibaRootEndpoint
|
||||
org.keycloak.protocol.oidc.par.endpoints.ParRootEndpoint
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"kind": "APIVersions",
|
||||
"versions": [
|
||||
"v1"
|
||||
],
|
||||
"serverAddressByClientCIDRs": [
|
||||
{
|
||||
"clientCIDR": "0.0.0.0/0",
|
||||
"serverAddress": "localhost:8880"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"kind": "APIVersions",
|
||||
"versions": [
|
||||
"v1"
|
||||
],
|
||||
"serverAddressByClientCIDRs": [
|
||||
{
|
||||
"clientCIDR": "0.0.0.0/0",
|
||||
"serverAddress": "192.168.121.194:8443"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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": ""
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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\"}}"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -28,7 +28,6 @@ migration,4
|
|||
model,6
|
||||
oauth,6
|
||||
oidc,6
|
||||
openshift,6
|
||||
policy,6
|
||||
providers,4
|
||||
runonserver,6
|
||||
|
|
Loading…
Reference in a new issue