[KECLOAK-8237] - Openshift Client Storage
This commit is contained in:
parent
99a5656f0f
commit
0c39eda8d2
35 changed files with 2673 additions and 32 deletions
4
dependencies/server-all/pom.xml
vendored
4
dependencies/server-all/pom.xml
vendored
|
@ -446,6 +446,10 @@
|
|||
<artifactId>guice</artifactId>
|
||||
<classifier>no_aop</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.openshift</groupId>
|
||||
<artifactId>openshift-restclient-java</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -784,6 +784,10 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.openshift</groupId>
|
||||
<artifactId>openshift-restclient-java</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ * 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.
|
||||
-->
|
||||
<module xmlns="urn:jboss:module:1.3" name="com.openshift.openshift-restclient-java">
|
||||
<properties>
|
||||
<property name="jboss.api" value="private"/>
|
||||
</properties>
|
||||
|
||||
<resources>
|
||||
<artifact name="${com.openshift:openshift-restclient-java}"/>
|
||||
</resources>
|
||||
|
||||
<dependencies>
|
||||
<module name="com.squareup.okhttp3"/>
|
||||
<module name="org.apache.commons.lang"/>
|
||||
<module name="org.jboss.dmr"/>
|
||||
<module name="org.apache.log4j"/>
|
||||
<module name="org.slf4j"/>
|
||||
</dependencies>
|
||||
</module>
|
|
@ -44,6 +44,9 @@
|
|||
<module name="org.keycloak.keycloak-authz-policy-common" services="import"/>
|
||||
<module name="org.keycloak.keycloak-authz-policy-drools" services="import"/>
|
||||
|
||||
<!-- Openshift Client Storage -->
|
||||
<module name="com.openshift.openshift-restclient-java" services="import"/>
|
||||
|
||||
<module name="com.googlecode.owasp-java-html-sanitizer"/>
|
||||
<module name="com.google.guava"/>
|
||||
<module name="org.freemarker"/>
|
||||
|
|
|
@ -718,7 +718,7 @@ public class UserCacheSession implements UserCache {
|
|||
consentModel.setLastUpdatedDate(cachedConsent.getLastUpdatedDate());
|
||||
|
||||
for (String clientScopeId : cachedConsent.getClientScopeIds()) {
|
||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(realm, clientScopeId);
|
||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(realm, client, clientScopeId);
|
||||
if (clientScope != null) {
|
||||
consentModel.addGrantedClientScope(clientScope);
|
||||
}
|
||||
|
|
|
@ -305,7 +305,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
|||
Collection<UserConsentClientScopeEntity> grantedClientScopeEntities = entity.getGrantedClientScopes();
|
||||
if (grantedClientScopeEntities != null) {
|
||||
for (UserConsentClientScopeEntity grantedClientScope : grantedClientScopeEntities) {
|
||||
ClientScopeModel grantedClientScopeModel = KeycloakModelUtils.findClientScopeById(realm, grantedClientScope.getScopeId());
|
||||
ClientScopeModel grantedClientScopeModel = KeycloakModelUtils.findClientScopeById(realm, client, grantedClientScope.getScopeId());
|
||||
if (grantedClientScopeModel != null) {
|
||||
model.addGrantedClientScope(grantedClientScopeModel);
|
||||
}
|
||||
|
|
10
pom.xml
10
pom.xml
|
@ -92,6 +92,9 @@
|
|||
<!-- Authorization Drools Policy Provider -->
|
||||
<version.org.drools>7.11.0.Final</version.org.drools>
|
||||
|
||||
<!-- Openshift -->
|
||||
<version.com.openshift.openshift-restclient-java>6.1.3.Final</version.com.openshift.openshift-restclient-java>
|
||||
|
||||
<!-- Others -->
|
||||
<apacheds.version>2.0.0-M21</apacheds.version>
|
||||
<apacheds.codec.version>1.0.0-M33</apacheds.codec.version>
|
||||
|
@ -1172,6 +1175,13 @@
|
|||
<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>
|
||||
|
|
|
@ -628,9 +628,14 @@ public final class KeycloakModelUtils {
|
|||
* Lookup clientScope OR client by id. Method is useful if you know just ID, but you don't know
|
||||
* if underlying model is clientScope or client
|
||||
*/
|
||||
public static ClientScopeModel findClientScopeById(RealmModel realm, String clientScopeId) {
|
||||
public static ClientScopeModel findClientScopeById(RealmModel realm, ClientModel client, String clientScopeId) {
|
||||
ClientScopeModel clientScope = realm.getClientScopeById(clientScopeId);
|
||||
|
||||
if (clientScope == null) {
|
||||
// as fallback we try to resolve dynamic scopes
|
||||
clientScope = client.getDynamicClientScope(clientScopeId);
|
||||
}
|
||||
|
||||
if (clientScope != null) {
|
||||
return clientScope;
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.client;
|
||||
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
|
||||
public abstract class AbstractReadOnlyClientScopeAdapter implements ClientScopeModel {
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocol(String protocol) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProtocolMapper(ProtocolMapperModel mapping) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProtocolMapper(ProtocolMapperModel mapping) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(RoleModel role) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteScopeMapping(RoleModel role) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof ClientScopeModel)) return false;
|
||||
|
||||
ClientScopeModel that = (ClientScopeModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
}
|
|
@ -177,6 +177,17 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
|
|||
*/
|
||||
Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol);
|
||||
|
||||
/**
|
||||
* <p>Returns a {@link ClientScopeModel} associated with this client.
|
||||
*
|
||||
* <p>This method is used as a fallback in order to let clients to resolve a {@code scope} dynamically which is not listed as default or optional scope when calling {@link #getClientScopes(boolean, boolean)}.
|
||||
*
|
||||
* @param scope the scope name
|
||||
* @return the client scope
|
||||
*/
|
||||
default ClientScopeModel getDynamicClientScope(String scope) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time in seconds since epoc
|
||||
|
|
|
@ -92,6 +92,4 @@ public interface ClientScopeModel extends ProtocolMapperContainerModel, ScopeCon
|
|||
default void setIncludeInTokenScope(boolean includeInTokenScope) {
|
||||
setAttribute(INCLUDE_IN_TOKEN_SCOPE, String.valueOf(includeInTokenScope));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -178,6 +178,10 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.openshift</groupId>
|
||||
<artifactId>openshift-restclient-java</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
||||
import org.keycloak.storage.StorageId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
@ -52,28 +53,18 @@ public class ApplicationsBean {
|
|||
|
||||
Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
|
||||
|
||||
List<ClientModel> realmClients = realm.getClients();
|
||||
for (ClientModel client : realmClients) {
|
||||
// Don't show bearerOnly clients
|
||||
if (client.isBearerOnly()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (ClientModel client : getApplications(session, realm, user)) {
|
||||
Set<RoleModel> availableRoles = new HashSet<>();
|
||||
if (client.getClientId().equals(Constants.ADMIN_CLI_CLIENT_ID)
|
||||
|| client.getClientId().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) {
|
||||
if (!AdminPermissions.realms(session, realm, user).isAdmin()) continue;
|
||||
|
||||
} else {
|
||||
// Construct scope parameter with all optional scopes to see all potentially available roles
|
||||
Set<ClientScopeModel> allClientScopes = new HashSet<>(client.getClientScopes(true, true).values());
|
||||
allClientScopes.addAll(client.getClientScopes(false, true).values());
|
||||
allClientScopes.add(client);
|
||||
// Construct scope parameter with all optional scopes to see all potentially available roles
|
||||
Set<ClientScopeModel> allClientScopes = new HashSet<>(client.getClientScopes(true, true).values());
|
||||
allClientScopes.addAll(client.getClientScopes(false, true).values());
|
||||
allClientScopes.add(client);
|
||||
|
||||
availableRoles = TokenManager.getAccess(user, client, allClientScopes);
|
||||
}
|
||||
List<RoleModel> realmRolesAvailable = new LinkedList<RoleModel>();
|
||||
MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable = new MultivaluedHashMap<String, ClientRoleEntry>();
|
||||
availableRoles = TokenManager.getAccess(user, client, allClientScopes);
|
||||
|
||||
List<RoleModel> realmRolesAvailable = new LinkedList<>();
|
||||
MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable = new MultivaluedHashMap<>();
|
||||
processRoles(availableRoles, realmRolesAvailable, resourceRolesAvailable);
|
||||
|
||||
List<ClientScopeModel> orderedScopes = new ArrayList<>();
|
||||
|
@ -94,12 +85,39 @@ public class ApplicationsBean {
|
|||
additionalGrants.add("${offlineToken}");
|
||||
}
|
||||
|
||||
ApplicationEntry appEntry = new ApplicationEntry(realmRolesAvailable, resourceRolesAvailable, client,
|
||||
clientScopesGranted, additionalGrants);
|
||||
applications.add(appEntry);
|
||||
applications.add(new ApplicationEntry(realmRolesAvailable, resourceRolesAvailable, client, clientScopesGranted, additionalGrants));
|
||||
}
|
||||
}
|
||||
|
||||
private Set<ClientModel> getApplications(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
Set<ClientModel> clients = new HashSet<>();
|
||||
|
||||
for (ClientModel client : realm.getClients()) {
|
||||
// Don't show bearerOnly clients
|
||||
if (client.isBearerOnly()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (client.getClientId().equals(Constants.ADMIN_CLI_CLIENT_ID)
|
||||
|| client.getClientId().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) {
|
||||
if (!AdminPermissions.realms(session, realm, user).isAdmin()) continue;
|
||||
}
|
||||
|
||||
clients.add(client);
|
||||
}
|
||||
|
||||
List<UserConsentModel> consents = session.users().getConsents(realm, user.getId());
|
||||
|
||||
for (UserConsentModel consent : consents) {
|
||||
ClientModel client = consent.getClient();
|
||||
|
||||
if (!new StorageId(client.getId()).isLocal()) {
|
||||
clients.add(client);
|
||||
}
|
||||
}
|
||||
return clients;
|
||||
}
|
||||
|
||||
private void processRoles(Set<RoleModel> inputRoles, List<RoleModel> realmRoles, MultivaluedHashMap<String, ClientRoleEntry> clientRoles) {
|
||||
for (RoleModel role : inputRoles) {
|
||||
if (role.getContainer() instanceof RealmModel) {
|
||||
|
|
|
@ -988,7 +988,7 @@ public class AuthenticationManager {
|
|||
List<ClientScopeModel> clientScopesToDisplay = new LinkedList<>();
|
||||
|
||||
for (String clientScopeId : authSession.getClientScopes()) {
|
||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(realm, clientScopeId);
|
||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(realm, authSession.getClient(), clientScopeId);
|
||||
|
||||
if (clientScope == null || !clientScope.isDisplayOnConsentScreen()) {
|
||||
continue;
|
||||
|
|
|
@ -845,8 +845,8 @@ public class LoginActionsService {
|
|||
boolean updateConsentRequired = false;
|
||||
|
||||
for (String clientScopeId : authSession.getClientScopes()) {
|
||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(realm, clientScopeId);
|
||||
if (clientScope != null) {
|
||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(realm, client, clientScopeId);
|
||||
if (clientScope != null && clientScope.isDisplayOnConsentScreen()) {
|
||||
if (!grantedConsent.isClientScopeGranted(clientScope)) {
|
||||
grantedConsent.addGrantedClientScope(clientScope);
|
||||
updateConsentRequired = true;
|
||||
|
|
|
@ -199,7 +199,7 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
|||
private Set<ClientScopeModel> loadClientScopes() {
|
||||
Set<ClientScopeModel> clientScopes = new HashSet<>();
|
||||
for (String scopeId : clientScopeIds) {
|
||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(clientSession.getClient().getRealm(), scopeId);
|
||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(clientSession.getClient().getRealm(), getClientSession().getClient(), scopeId);
|
||||
if (clientScope != null) {
|
||||
if (isClientScopePermittedForUser(clientScope)) {
|
||||
clientScopes.add(clientScope);
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.util.regex.Matcher;
|
||||
|
||||
import com.openshift.restclient.IClient;
|
||||
import com.openshift.restclient.NotFoundException;
|
||||
import com.openshift.restclient.model.IResource;
|
||||
import org.keycloak.models.ClientModel;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @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(String id, RealmModel realm) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
if (!storageId.getProviderId().equals(providerModel.getId())) return null;
|
||||
String clientId = storageId.getExternalId();
|
||||
return getClientByClientId(clientId, realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientByClientId(String clientId, RealmModel realm) {
|
||||
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 void close() {
|
||||
|
||||
}
|
||||
|
||||
private IResource getServiceAccount(String name, String namespace) {
|
||||
try {
|
||||
return client.get("ServiceAccount", name, namespace);
|
||||
} catch (NotFoundException nfe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
/*
|
||||
* 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.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 final Set<ProtocolMapperModel> DEFAULT_PROTOCOL_MAPPERS = createDefaultProtocolMappers();
|
||||
|
||||
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 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, boolean filterByProtocol) {
|
||||
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 Set<ProtocolMapperModel> getProtocolMappers() {
|
||||
return getConfigOrDefault(() -> {
|
||||
List<ProtocolMapperRepresentation> mappers = defaultConfig.getProtocolMappers();
|
||||
|
||||
if (mappers == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<ProtocolMapperModel> model = new HashSet<>();
|
||||
|
||||
for (ProtocolMapperRepresentation mapper : mappers) {
|
||||
model.add(RepresentationToModel.toModel(mapper));
|
||||
}
|
||||
|
||||
return model;
|
||||
}, (Consumer<Set<ProtocolMapperModel>>) mappers -> {
|
||||
defaultConfig.setProtocolMappers(mappers.stream().map(ModelToRepresentation::toRepresentation).collect(Collectors.toList()));
|
||||
}, (Supplier<Set<ProtocolMapperModel>>) () -> DEFAULT_PROTOCOL_MAPPERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||
return getProtocolMappers().stream().filter(protocolMapperModel -> id.equals(protocolMapperModel.getId())).findAny().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
|
||||
return getProtocolMappers().stream().filter(protocolMapperModel -> name.equals(protocolMapperModel.getName())).findAny().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getScopeMappings() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmScopeMappings() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@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.getClientScopes().stream().filter(scopeModel -> scopeModel.getName().equals(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 Set<ProtocolMapperModel> getProtocolMappers() {
|
||||
return DEFAULT_PROTOCOL_MAPPERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getScopeMappings() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmScopeMappings() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScope(RoleModel role) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# * 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
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* 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 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;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
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.admin.client.resource.UserResource;
|
||||
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.admin.ApiUtil;
|
||||
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.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
/**
|
||||
* Test that clients can override auth flows
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
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;
|
||||
|
||||
@Deployment
|
||||
public static WebArchive deploy() {
|
||||
return RunOnServerDeployment.create(UserResource.class)
|
||||
.addPackages(true, "org.keycloak.testsuite");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@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() {
|
||||
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, "https://myapp.org/callback", () -> assertSuccessfulResponseWithoutConsent(clientId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failCodeGrantFlowWithServiceAccountUsingOAuthRedirectReference() throws Exception {
|
||||
testCodeGrantFlow("system:serviceaccount:default:sa-oauth-redirect-reference", "http://myapp.org/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://myapp.org/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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,495 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"kind": "APIVersions",
|
||||
"versions": [
|
||||
"v1"
|
||||
],
|
||||
"serverAddressByClientCIDRs": [
|
||||
{
|
||||
"clientCIDR": "0.0.0.0/0",
|
||||
"serverAddress": "localhost:8880"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"kind": "APIVersions",
|
||||
"versions": [
|
||||
"v1"
|
||||
],
|
||||
"serverAddressByClientCIDRs": [
|
||||
{
|
||||
"clientCIDR": "0.0.0.0/0",
|
||||
"serverAddress": "192.168.121.194:8443"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,732 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"major": "3",
|
||||
"minor": "10+",
|
||||
"gitVersion": "v3.10.0+2084755-68",
|
||||
"gitCommit": "2084755",
|
||||
"gitTreeState": "",
|
||||
"buildDate": "2018-10-30T09:01:17Z",
|
||||
"goVersion": "",
|
||||
"compiler": "",
|
||||
"platform": ""
|
||||
}ssss
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"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": "myapp.org",
|
||||
"to": {
|
||||
"kind": "Service",
|
||||
"name": "proxy",
|
||||
"weight": 100
|
||||
},
|
||||
"tls": {
|
||||
"termination": "reencrypt",
|
||||
"destinationCACertificate": "-----BEGIN COMMENT-----\nThis is an empty PEM file created to provide backwards compatibility\nfor reencrypt routes that have no destinationCACertificate. This \ncontent will only appear for routes accessed via /oapi/v1/routes.\n-----END COMMENT-----\n"
|
||||
},
|
||||
"wildcardPolicy": "None"
|
||||
},
|
||||
"status": {
|
||||
"ingress": [
|
||||
{
|
||||
"host": "myapp.org",
|
||||
"routerName": "router",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Admitted",
|
||||
"status": "True",
|
||||
"lastTransitionTime": "2018-10-16T13:49:00Z"
|
||||
}
|
||||
],
|
||||
"wildcardPolicy": "None"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"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\"}}"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -346,3 +346,9 @@ addTeam=Add team to share your resource with
|
|||
myPermissions=My Permissions
|
||||
waitingforApproval=Waiting for approval
|
||||
anyPermission=Any Permission
|
||||
|
||||
# Openshift messages
|
||||
openshift.scope.user_info=User information
|
||||
openshift.scope.user_check-access=User access information
|
||||
openshift.scope.user_full=Full Access
|
||||
openshift.scope.list-projects=List projects
|
|
@ -312,3 +312,9 @@ console-verify-email=You are required to verify your email address. An email ha
|
|||
console-email-code=Email Code:
|
||||
console-accept-terms=Accept Terms? [y/n]:
|
||||
console-accept=y
|
||||
|
||||
# Openshift messages
|
||||
openshift.scope.user_info=User information
|
||||
openshift.scope.user_check-access=User access information
|
||||
openshift.scope.user_full=Full Access
|
||||
openshift.scope.list-projects=List projects
|
Loading…
Reference in a new issue