KEYCLOAK-10169 OpenShift 4 Identity Provider
This commit is contained in:
parent
2f9d875840
commit
69d6613ab6
13 changed files with 471 additions and 1 deletions
|
@ -35,6 +35,7 @@ import org.keycloak.social.instagram.InstagramIdentityProviderFactory;
|
||||||
import org.keycloak.social.linkedin.LinkedInIdentityProviderFactory;
|
import org.keycloak.social.linkedin.LinkedInIdentityProviderFactory;
|
||||||
import org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory;
|
import org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory;
|
||||||
import org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory;
|
import org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory;
|
||||||
|
import org.keycloak.social.openshift.OpenshiftV4IdentityProviderFactory;
|
||||||
import org.keycloak.social.paypal.PayPalIdentityProviderFactory;
|
import org.keycloak.social.paypal.PayPalIdentityProviderFactory;
|
||||||
import org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory;
|
import org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory;
|
||||||
import org.keycloak.social.twitter.TwitterIdentityProviderFactory;
|
import org.keycloak.social.twitter.TwitterIdentityProviderFactory;
|
||||||
|
@ -62,6 +63,7 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
|
||||||
LinkedInIdentityProviderFactory.PROVIDER_ID,
|
LinkedInIdentityProviderFactory.PROVIDER_ID,
|
||||||
MicrosoftIdentityProviderFactory.PROVIDER_ID,
|
MicrosoftIdentityProviderFactory.PROVIDER_ID,
|
||||||
OpenshiftV3IdentityProviderFactory.PROVIDER_ID,
|
OpenshiftV3IdentityProviderFactory.PROVIDER_ID,
|
||||||
|
OpenshiftV4IdentityProviderFactory.PROVIDER_ID,
|
||||||
PayPalIdentityProviderFactory.PROVIDER_ID,
|
PayPalIdentityProviderFactory.PROVIDER_ID,
|
||||||
StackoverflowIdentityProviderFactory.PROVIDER_ID,
|
StackoverflowIdentityProviderFactory.PROVIDER_ID,
|
||||||
TwitterIdentityProviderFactory.PROVIDER_ID
|
TwitterIdentityProviderFactory.PROVIDER_ID
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
package org.keycloak.social.openshift;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
|
||||||
|
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
|
||||||
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
|
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||||
|
import org.keycloak.connections.httpclient.HttpClientProvider;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identity provider for Openshift V4.
|
||||||
|
*
|
||||||
|
* @author David Festal and Sebastian Łaskawiec
|
||||||
|
*/
|
||||||
|
public class OpenshiftV4IdentityProvider extends AbstractOAuth2IdentityProvider<OpenshiftV4IdentityProviderConfig> implements SocialIdentityProvider<OpenshiftV4IdentityProviderConfig> {
|
||||||
|
|
||||||
|
public static final String BASE_URL = "https://api.preview.openshift.com";
|
||||||
|
public static final String OPENSHIFT_OAUTH_METADATA_ENDPOINT = "/.well-known/oauth-authorization-server";
|
||||||
|
public static final String PROFILE_RESOURCE = "/apis/user.openshift.io/v1/users/~";
|
||||||
|
public static final String DEFAULT_SCOPE = "user:info";
|
||||||
|
|
||||||
|
public OpenshiftV4IdentityProvider(KeycloakSession session, OpenshiftV4IdentityProviderConfig config) {
|
||||||
|
super(session, config);
|
||||||
|
final String baseUrl = Optional.ofNullable(config.getBaseUrl()).orElse(BASE_URL);
|
||||||
|
Map<String, Object> oauthDescriptor = getAuthJson(session, config.getBaseUrl());
|
||||||
|
logger.debugv("Openshift v4 OAuth descriptor: {0}", oauthDescriptor);
|
||||||
|
config.setAuthorizationUrl((String) oauthDescriptor.get("authorization_endpoint"));
|
||||||
|
config.setTokenUrl((String) oauthDescriptor.get("token_endpoint"));
|
||||||
|
config.setUserInfoUrl(baseUrl + PROFILE_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> getAuthJson(KeycloakSession session, String baseUrl) {
|
||||||
|
try {
|
||||||
|
InputStream response = getOauthMetadataInputStream(session, baseUrl);
|
||||||
|
Map<String, Object> map = mapMetadata(response);
|
||||||
|
return map;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IdentityBrokerException("Could not initialize oAuth metadata", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream getOauthMetadataInputStream(KeycloakSession session, String baseUrl) throws IOException {
|
||||||
|
HttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient();
|
||||||
|
HttpGet getRequest = new HttpGet(baseUrl + OPENSHIFT_OAUTH_METADATA_ENDPOINT);
|
||||||
|
getRequest.addHeader("accept", "application/json");
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(getRequest);
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() != 200) {
|
||||||
|
throw new RuntimeException("Failed : HTTP error code : " + response.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
return response.getEntity().getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map mapMetadata(InputStream response) throws IOException {
|
||||||
|
return new ObjectMapper().readValue(response, Map.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDefaultScopes() {
|
||||||
|
return DEFAULT_SCOPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
|
||||||
|
try {
|
||||||
|
final JsonNode profile = fetchProfile(accessToken);
|
||||||
|
final BrokeredIdentityContext user = extractUserContext(profile);
|
||||||
|
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
|
||||||
|
return user;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IdentityBrokerException("Could not obtain user profile from Openshift.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BrokeredIdentityContext extractUserContext(JsonNode profile) {
|
||||||
|
JsonNode metadata = profile.get("metadata");
|
||||||
|
logger.debugv("extractUserContext: metadata = {0}", metadata);
|
||||||
|
final BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(metadata, "uid"));
|
||||||
|
user.setUsername(getJsonProperty(metadata, "name"));
|
||||||
|
user.setName(getJsonProperty(profile, "fullName"));
|
||||||
|
user.setIdpConfig(getConfig());
|
||||||
|
user.setIdp(this);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode fetchProfile(String accessToken) throws IOException {
|
||||||
|
return SimpleHttp.doGet(getConfig().getUserInfoUrl(), this.session)
|
||||||
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
|
.asJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean supportsExternalExchange() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProfileEndpointForValidation(EventBuilder event) {
|
||||||
|
return getConfig().getUserInfoUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode profile) {
|
||||||
|
final BrokeredIdentityContext user = extractUserContext(profile);
|
||||||
|
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.keycloak.social.openshift;
|
||||||
|
|
||||||
|
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenShift 4 Identity Provider configuration class.
|
||||||
|
*
|
||||||
|
* @author David Festal and Sebastian Łaskawiec
|
||||||
|
*/
|
||||||
|
public class OpenshiftV4IdentityProviderConfig extends OAuth2IdentityProviderConfig {
|
||||||
|
|
||||||
|
private static final String BASE_URL = "baseUrl";
|
||||||
|
|
||||||
|
public OpenshiftV4IdentityProviderConfig(IdentityProviderModel identityProviderModel) {
|
||||||
|
super(identityProviderModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimTrailingSlash(String baseUrl) {
|
||||||
|
if (baseUrl != null && baseUrl.endsWith("/")) {
|
||||||
|
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
||||||
|
}
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return getConfig().get(BASE_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseUrl(String baseUrl) {
|
||||||
|
getConfig().put(BASE_URL, trimTrailingSlash(baseUrl));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.keycloak.social.openshift;
|
||||||
|
|
||||||
|
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||||
|
import org.keycloak.broker.social.SocialIdentityProviderFactory;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenShift 4 Identity Provider factory class.
|
||||||
|
*
|
||||||
|
* @author David Festal and Sebastian Łaskawiec
|
||||||
|
*/
|
||||||
|
public class OpenshiftV4IdentityProviderFactory extends AbstractIdentityProviderFactory<OpenshiftV4IdentityProvider> implements SocialIdentityProviderFactory<OpenshiftV4IdentityProvider> {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "openshift-v4";
|
||||||
|
public static final String NAME = "Openshift v4";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenshiftV4IdentityProvider create(KeycloakSession keycloakSession, IdentityProviderModel identityProviderModel) {
|
||||||
|
return new OpenshiftV4IdentityProvider(keycloakSession, new OpenshiftV4IdentityProviderConfig(identityProviderModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory
|
||||||
org.keycloak.social.twitter.TwitterIdentityProviderFactory
|
org.keycloak.social.twitter.TwitterIdentityProviderFactory
|
||||||
org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory
|
org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory
|
||||||
org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory
|
org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory
|
||||||
|
org.keycloak.social.openshift.OpenshiftV4IdentityProviderFactory
|
||||||
org.keycloak.social.gitlab.GitLabIdentityProviderFactory
|
org.keycloak.social.gitlab.GitLabIdentityProviderFactory
|
||||||
org.keycloak.social.bitbucket.BitbucketIdentityProviderFactory
|
org.keycloak.social.bitbucket.BitbucketIdentityProviderFactory
|
||||||
org.keycloak.social.instagram.InstagramIdentityProviderFactory
|
org.keycloak.social.instagram.InstagramIdentityProviderFactory
|
|
@ -0,0 +1,76 @@
|
||||||
|
package org.keycloak.social.openshift;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.apache.commons.io.Charsets;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class OpenshiftV4IdentityProviderTest {
|
||||||
|
|
||||||
|
private final String TEST_OAUTH_METADATA_FILE = "/org/keycloak/test/social/openshift/OpenshiftV4-oauth-metadata.json";
|
||||||
|
|
||||||
|
private URL oauthMetadataFile;
|
||||||
|
private String authMetadata;
|
||||||
|
private Map<String, String> oauthMetadataMap;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() throws Exception {
|
||||||
|
oauthMetadataFile = OpenshiftV4IdentityProviderTest.class.getResource(TEST_OAUTH_METADATA_FILE);
|
||||||
|
authMetadata = IOUtils.toString(oauthMetadataFile, Charsets.toCharset("UTF-8"));
|
||||||
|
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
oauthMetadataMap = objectMapper.readValue(authMetadata, HashMap.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractingConfigProperties() throws IOException {
|
||||||
|
//given
|
||||||
|
OpenshiftV4IdentityProviderConfig config = new OpenshiftV4IdentityProviderConfig(new IdentityProviderModel());
|
||||||
|
|
||||||
|
//when
|
||||||
|
new OpenshiftV4IdentityProvider(null, config) {
|
||||||
|
@Override
|
||||||
|
InputStream getOauthMetadataInputStream(KeycloakSession session, String baseUrl) {
|
||||||
|
return new ByteArrayInputStream(authMetadata.getBytes());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//then
|
||||||
|
Assert.assertEquals(OpenshiftV4IdentityProvider.BASE_URL + OpenshiftV4IdentityProvider.PROFILE_RESOURCE, config.getUserInfoUrl());
|
||||||
|
Assert.assertEquals(oauthMetadataMap.get("token_endpoint"), config.getTokenUrl());
|
||||||
|
Assert.assertEquals(oauthMetadataMap.get("authorization_endpoint"), config.getAuthorizationUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHttpClientErrors() throws IOException {
|
||||||
|
//given
|
||||||
|
OpenshiftV4IdentityProviderConfig config = new OpenshiftV4IdentityProviderConfig(new IdentityProviderModel());
|
||||||
|
|
||||||
|
//when
|
||||||
|
try {
|
||||||
|
new OpenshiftV4IdentityProvider(null, config) {
|
||||||
|
@Override
|
||||||
|
InputStream getOauthMetadataInputStream(KeycloakSession session, String baseUrl) {
|
||||||
|
throw new RuntimeException("Failed : HTTP error code : 500");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Assert.fail();
|
||||||
|
} catch (IdentityBrokerException e) {
|
||||||
|
//then
|
||||||
|
//OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"issuer": "https://127.0.0.1",
|
||||||
|
"authorization_endpoint": "https://127.0.0.1:6443/oauth/authorize",
|
||||||
|
"token_endpoint": "https://127.0.0.1:6443/oauth/token",
|
||||||
|
"scopes_supported": [
|
||||||
|
"user:full",
|
||||||
|
"user:info",
|
||||||
|
"user:check-access",
|
||||||
|
"user:list-scoped-projects",
|
||||||
|
"user:list-projects"
|
||||||
|
],
|
||||||
|
"response_types_supported": [
|
||||||
|
"code",
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"grant_types_supported": [
|
||||||
|
"authorization_code",
|
||||||
|
"implicit"
|
||||||
|
],
|
||||||
|
"code_challenge_methods_supported": [
|
||||||
|
"plain",
|
||||||
|
"S256"
|
||||||
|
]
|
||||||
|
}
|
|
@ -80,6 +80,7 @@ import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.GOOGLE_NON_
|
||||||
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.LINKEDIN;
|
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.LINKEDIN;
|
||||||
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.MICROSOFT;
|
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.MICROSOFT;
|
||||||
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.OPENSHIFT;
|
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.OPENSHIFT;
|
||||||
|
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.OPENSHIFT4;
|
||||||
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.PAYPAL;
|
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.PAYPAL;
|
||||||
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.STACKOVERFLOW;
|
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.STACKOVERFLOW;
|
||||||
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.TWITTER;
|
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.TWITTER;
|
||||||
|
@ -115,6 +116,7 @@ public class SocialLoginTest extends AbstractKeycloakTest {
|
||||||
PAYPAL("paypal", PayPalLoginPage.class),
|
PAYPAL("paypal", PayPalLoginPage.class),
|
||||||
STACKOVERFLOW("stackoverflow", StackOverflowLoginPage.class),
|
STACKOVERFLOW("stackoverflow", StackOverflowLoginPage.class),
|
||||||
OPENSHIFT("openshift-v3", OpenShiftLoginPage.class),
|
OPENSHIFT("openshift-v3", OpenShiftLoginPage.class),
|
||||||
|
OPENSHIFT4("openshift-v4", OpenShiftLoginPage.class),
|
||||||
GITLAB("gitlab", GitLabLoginPage.class),
|
GITLAB("gitlab", GitLabLoginPage.class),
|
||||||
BITBUCKET("bitbucket", BitbucketLoginPage.class),
|
BITBUCKET("bitbucket", BitbucketLoginPage.class),
|
||||||
INSTAGRAM("instagram", InstagramLoginPage.class);
|
INSTAGRAM("instagram", InstagramLoginPage.class);
|
||||||
|
@ -235,6 +237,15 @@ public class SocialLoginTest extends AbstractKeycloakTest {
|
||||||
assertAccount();
|
assertAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openshift4Login() {
|
||||||
|
setTestProvider(OPENSHIFT4);
|
||||||
|
performLogin();
|
||||||
|
assertUpdateProfile(false, false, true);
|
||||||
|
assertAccount();
|
||||||
|
testTokenExchange();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@UncaughtServerErrorExpected
|
@UncaughtServerErrorExpected
|
||||||
public void googleLogin() throws InterruptedException {
|
public void googleLogin() throws InterruptedException {
|
||||||
|
@ -382,7 +393,7 @@ public class SocialLoginTest extends AbstractKeycloakTest {
|
||||||
if (provider == STACKOVERFLOW) {
|
if (provider == STACKOVERFLOW) {
|
||||||
idp.getConfig().put("key", getConfig(provider, "clientKey"));
|
idp.getConfig().put("key", getConfig(provider, "clientKey"));
|
||||||
}
|
}
|
||||||
if (provider == OPENSHIFT) {
|
if (provider == OPENSHIFT || provider == OPENSHIFT4) {
|
||||||
idp.getConfig().put("baseUrl", getConfig(provider, "baseUrl"));
|
idp.getConfig().put("baseUrl", getConfig(provider, "baseUrl"));
|
||||||
}
|
}
|
||||||
if (provider == PAYPAL) {
|
if (provider == PAYPAL) {
|
||||||
|
|
|
@ -618,6 +618,8 @@ key=Key
|
||||||
#stackoverflow.key.tooltip=The Key obtained from Stack Overflow client registration.
|
#stackoverflow.key.tooltip=The Key obtained from Stack Overflow client registration.
|
||||||
#openshift.base-url=Base Url
|
#openshift.base-url=Base Url
|
||||||
#openshift.base-url.tooltip=Base Url to OpenShift Online API
|
#openshift.base-url.tooltip=Base Url to OpenShift Online API
|
||||||
|
#openshift4.base-url=Base Url
|
||||||
|
#openshift4.base-url.tooltip=Base Url to OpenShift Online API
|
||||||
#gitlab-application-id=Application Id
|
#gitlab-application-id=Application Id
|
||||||
#gitlab-application-secret=Application Secret
|
#gitlab-application-secret=Application Secret
|
||||||
#gitlab.application-id.tooltip=Application Id for the application you created in your GitLab Applications account menu
|
#gitlab.application-id.tooltip=Application Id for the application you created in your GitLab Applications account menu
|
||||||
|
|
|
@ -606,6 +606,8 @@ key=Key
|
||||||
stackoverflow.key.tooltip=Stack Overflow のクライアント登録で取得した Key を設定します。
|
stackoverflow.key.tooltip=Stack Overflow のクライアント登録で取得した Key を設定します。
|
||||||
openshift.base-url=ベースURL
|
openshift.base-url=ベースURL
|
||||||
openshift.base-url.tooltip=OpenShift Online APIのベースURL
|
openshift.base-url.tooltip=OpenShift Online APIのベースURL
|
||||||
|
openshift4.base-url=ベースURL
|
||||||
|
openshift4.base-url.tooltip=OpenShift Online APIのベースURL
|
||||||
gitlab-application-id=アプリケーションID
|
gitlab-application-id=アプリケーションID
|
||||||
gitlab-application-secret=アプリケーションシークレット
|
gitlab-application-secret=アプリケーションシークレット
|
||||||
gitlab.application-id.tooltip=GitLabアプリケーションのアカウントメニューで作成したアプリケーションのアプリケーションID
|
gitlab.application-id.tooltip=GitLabアプリケーションのアカウントメニューで作成したアプリケーションのアプリケーションID
|
||||||
|
|
|
@ -651,6 +651,8 @@ key=Key
|
||||||
stackoverflow.key.tooltip=The Key obtained from Stack Overflow client registration.
|
stackoverflow.key.tooltip=The Key obtained from Stack Overflow client registration.
|
||||||
openshift.base-url=Base Url
|
openshift.base-url=Base Url
|
||||||
openshift.base-url.tooltip=Base Url to OpenShift Online API
|
openshift.base-url.tooltip=Base Url to OpenShift Online API
|
||||||
|
openshift4.base-url=Base Url
|
||||||
|
openshift4.base-url.tooltip=Base Url to OpenShift Online API
|
||||||
gitlab-application-id=Application Id
|
gitlab-application-id=Application Id
|
||||||
gitlab-application-secret=Application Secret
|
gitlab-application-secret=Application Secret
|
||||||
gitlab.application-id.tooltip=Application Id for the application you created in your GitLab Applications account menu
|
gitlab.application-id.tooltip=Application Id for the application you created in your GitLab Applications account menu
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
<label class="col-md-2 control-label" for="baseUrl"><span class="required">*</span> {{:: 'openshift4.base-url' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" id="baseUrl" type="text" ng-model="identityProvider.config.baseUrl" required>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'openshift4.base-url.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
|
@ -0,0 +1,149 @@
|
||||||
|
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li>
|
||||||
|
<li data-ng-hide="newIdentityProvider">{{provider.name}}</li>
|
||||||
|
<li data-ng-show="newIdentityProvider">{{:: 'add-identity-provider' | translate}}</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<kc-tabs-identity-provider></kc-tabs-identity-provider>
|
||||||
|
|
||||||
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageIdentityProviders">
|
||||||
|
<fieldset>
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
<label class="col-md-2 control-label" for="redirectUri">{{:: 'redirect-uri' | translate}}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input class="form-control" id="redirectUri" type="text" value="{{callbackUrl}}{{identityProvider.alias}}/endpoint" readonly kc-select-action="click">
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'redirect-uri.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
<label class="col-md-2 control-label" for="identifier"><span class="required">*</span> {{:: 'alias' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" id="identifier" type="text" ng-model="identityProvider.alias" data-ng-readonly="!newIdentityProvider" required>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'identity-provider.alias.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
<label class="col-md-2 control-label" for="displayName"> {{:: 'display-name' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" id="displayName" type="text" ng-model="identityProvider.displayName">
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'identity-provider.display-name.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
<label class="col-md-2 control-label" for="clientId"><span class="required">*</span> {{:: 'client-id' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" id="clientId" type="text" ng-model="identityProvider.config.clientId" required>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'social.client-id.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
<label class="col-md-2 control-label" for="clientSecret"><span class="required">*</span> {{:: 'client-secret' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" id="clientSecret" kc-password ng-model="identityProvider.config.clientSecret" required>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'social.client-secret.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-' + identityProvider.providerId + '-ext.html'"></div>
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
<label class="col-md-2 control-label" for="defaultScope">{{:: 'default-scopes' | translate}} </label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" id="defaultScope" type="text" ng-model="identityProvider.config.defaultScope">
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'social.default-scopes.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="storeToken">{{:: 'store-tokens' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'identity-provider.store-tokens.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="storedTokensReadable">{{:: 'stored-tokens-readable' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="identityProvider.addReadTokenRoleOnCreate" id="storedTokensReadable" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="enabled">{{:: 'enabled' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="identityProvider.enabled" id="enabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="disableUserInfo">{{:: 'disableUserInfo' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="identityProvider.config.disableUserInfo" id="disableUserInfo" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'identity-provider.disableUserInfo.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="identityProvider.trustEmail" name="identityProvider.trustEmail" id="trustEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'trust-email.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="linkOnly">{{:: 'link-only' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="identityProvider.linkOnly" name="identityProvider.trustEmail" id="linkOnly" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'linkOnly.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="hideOnLoginPage">{{:: 'hide-on-login-page' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="identityProvider.config.hideOnLoginPage" name="identityProvider.config.hideOnLoginPage" id="hideOnLoginPage" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'hide-on-login-page.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="guiOrder">{{:: 'gui-order' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" id="guiOrder" type="text" ng-model="identityProvider.config.guiOrder">
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div>
|
||||||
|
<select class="form-control" id="firstBrokerLoginFlowAlias"
|
||||||
|
ng-model="identityProvider.firstBrokerLoginFlowAlias"
|
||||||
|
ng-options="flow.alias as flow.alias for flow in authFlows"
|
||||||
|
required>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="postBrokerLoginFlowAlias">{{:: 'post-broker-login-flow' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div>
|
||||||
|
<select class="form-control" id="postBrokerLoginFlowAlias"
|
||||||
|
ng-model="identityProvider.postBrokerLoginFlowAlias"
|
||||||
|
ng-options="flow.alias as flow.alias for flow in postBrokerAuthFlows">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-10 col-md-offset-2">
|
||||||
|
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
|
<button kc-cancel data-ng-click="cancel()" data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kc-menu></kc-menu>
|
Loading…
Reference in a new issue