KEYCLOAK-13828 Allow override of baseUrl and apiUrl in GitHub identity provider (#7021)
Allow override of baseUrl & apiUrl in GitHub identity provider Closes #11144
This commit is contained in:
parent
fa7a2b6de1
commit
f11573eeb2
4 changed files with 160 additions and 16 deletions
|
@ -35,17 +35,78 @@ import org.keycloak.models.KeycloakSession;
|
|||
*/
|
||||
public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
|
||||
|
||||
public static final String AUTH_URL = "https://github.com/login/oauth/authorize";
|
||||
public static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
|
||||
public static final String PROFILE_URL = "https://api.github.com/user";
|
||||
public static final String EMAIL_URL = "https://api.github.com/user/emails";
|
||||
public static final String DEFAULT_BASE_URL = "https://github.com";
|
||||
public static final String AUTH_FRAGMENT = "/login/oauth/authorize";
|
||||
public static final String TOKEN_FRAGMENT = "/login/oauth/access_token";
|
||||
public static final String DEFAULT_AUTH_URL = DEFAULT_BASE_URL + AUTH_FRAGMENT;
|
||||
public static final String DEFAULT_TOKEN_URL = DEFAULT_BASE_URL + TOKEN_FRAGMENT;
|
||||
/** @deprecated Use {@link #DEFAULT_AUTH_URL} instead. */
|
||||
@Deprecated
|
||||
public static final String AUTH_URL = DEFAULT_AUTH_URL;
|
||||
/** @deprecated Use {@link #DEFAULT_TOKEN_URL} instead. */
|
||||
@Deprecated
|
||||
public static final String TOKEN_URL = DEFAULT_TOKEN_URL;
|
||||
|
||||
public static final String DEFAULT_API_URL = "https://api.github.com";
|
||||
public static final String PROFILE_FRAGMENT = "/user";
|
||||
public static final String EMAIL_FRAGMENT = "/user/emails";
|
||||
public static final String DEFAULT_PROFILE_URL = DEFAULT_API_URL + PROFILE_FRAGMENT;
|
||||
public static final String DEFAULT_EMAIL_URL = DEFAULT_API_URL + EMAIL_FRAGMENT;
|
||||
/** @deprecated Use {@link #DEFAULT_PROFILE_URL} instead. */
|
||||
@Deprecated
|
||||
public static final String PROFILE_URL = DEFAULT_PROFILE_URL;
|
||||
/** @deprecated Use {@link #DEFAULT_EMAIL_URL} instead. */
|
||||
@Deprecated
|
||||
public static final String EMAIL_URL = DEFAULT_EMAIL_URL;
|
||||
|
||||
public static final String DEFAULT_SCOPE = "user:email";
|
||||
|
||||
/** Base URL key in config map. */
|
||||
protected static final String BASE_URL_KEY = "baseUrl";
|
||||
/** API URL key in config map. */
|
||||
protected static final String API_URL_KEY = "apiUrl";
|
||||
/** Email URL key in config map. */
|
||||
protected static final String EMAIL_URL_KEY = "emailUrl";
|
||||
|
||||
private final String authUrl;
|
||||
private final String tokenUrl;
|
||||
private final String profileUrl;
|
||||
private final String emailUrl;
|
||||
|
||||
public GitHubIdentityProvider(KeycloakSession session, OAuth2IdentityProviderConfig config) {
|
||||
super(session, config);
|
||||
config.setAuthorizationUrl(AUTH_URL);
|
||||
config.setTokenUrl(TOKEN_URL);
|
||||
config.setUserInfoUrl(PROFILE_URL);
|
||||
|
||||
String baseUrl = getUrlFromConfig(config, BASE_URL_KEY, DEFAULT_BASE_URL);
|
||||
String apiUrl = getUrlFromConfig(config, API_URL_KEY, DEFAULT_API_URL);
|
||||
|
||||
authUrl = baseUrl + AUTH_FRAGMENT;
|
||||
tokenUrl = baseUrl + TOKEN_FRAGMENT;
|
||||
profileUrl = apiUrl + PROFILE_FRAGMENT;
|
||||
emailUrl = apiUrl + EMAIL_FRAGMENT;
|
||||
|
||||
config.setAuthorizationUrl(authUrl);
|
||||
config.setTokenUrl(tokenUrl);
|
||||
config.setUserInfoUrl(profileUrl);
|
||||
config.getConfig().put(EMAIL_URL_KEY, emailUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL from config with default value fallback.
|
||||
*
|
||||
* @param config Identity provider configuration.
|
||||
* @param key Key to look for value in config's config map.
|
||||
* @param defaultValue Default value if value at key is null or empty string.
|
||||
* @return URL for specified key in the configuration with default value fallback.
|
||||
*/
|
||||
protected static String getUrlFromConfig(OAuth2IdentityProviderConfig config, String key, String defaultValue) {
|
||||
String url = config.getConfig().get(key);
|
||||
if (url == null || url.trim().isEmpty()) {
|
||||
url = defaultValue;
|
||||
}
|
||||
if (url.endsWith("/")) {
|
||||
url = url.substring(0, url.length() - 1);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -55,7 +116,7 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
|
|||
|
||||
@Override
|
||||
protected String getProfileEndpointForValidation(EventBuilder event) {
|
||||
return PROFILE_URL;
|
||||
return profileUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,14 +133,12 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
|
|||
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
|
||||
|
||||
return user;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
|
||||
try {
|
||||
JsonNode profile = SimpleHttp.doGet(PROFILE_URL, session).header("Authorization", "Bearer " + accessToken).asJson();
|
||||
JsonNode profile = SimpleHttp.doGet(profileUrl, session).header("Authorization", "Bearer " + accessToken).asJson();
|
||||
|
||||
BrokeredIdentityContext user = extractIdentityFromProfile(null, profile);
|
||||
|
||||
|
@ -95,7 +154,7 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
|
|||
|
||||
private String searchEmail(String accessToken) {
|
||||
try {
|
||||
ArrayNode emails = (ArrayNode) SimpleHttp.doGet(EMAIL_URL, session).header("Authorization", "Bearer " + accessToken).asJson();
|
||||
ArrayNode emails = (ArrayNode) SimpleHttp.doGet(emailUrl, session).header("Authorization", "Bearer " + accessToken).asJson();
|
||||
|
||||
Iterator<JsonNode> loop = emails.elements();
|
||||
while (loop.hasNext()) {
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright 2020 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.social.github;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
|
||||
|
||||
/**
|
||||
* Unit test for {@link org.keycloak.social.github.GitHubIdentityProvider}.
|
||||
*
|
||||
* @author Neon Ngo
|
||||
*/
|
||||
public class GitHubIdentityProviderTest {
|
||||
|
||||
/**
|
||||
* Test constructor with empty config (i.e. to use default values).
|
||||
* This also tests GitHubIdentityProvider.getProfileEndpointForValidation(null).
|
||||
*/
|
||||
@Test
|
||||
public void testGitHubIdentityProvider() {
|
||||
OAuth2IdentityProviderConfig config = new OAuth2IdentityProviderConfig();
|
||||
GitHubIdentityProvider idp = new GitHubIdentityProvider(null, config);
|
||||
|
||||
validateUrls(idp, GitHubIdentityProvider.DEFAULT_BASE_URL, GitHubIdentityProvider.DEFAULT_API_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test constructor with config overrides of default base URL and API URL.
|
||||
*/
|
||||
@Test
|
||||
public void testGitHubIdentityProviderOverrides() {
|
||||
OAuth2IdentityProviderConfig config = new OAuth2IdentityProviderConfig();
|
||||
String baseUrl = "https://test.com";
|
||||
String apiUrl = "https://api.test.com";
|
||||
config.getConfig().put(GitHubIdentityProvider.BASE_URL_KEY, baseUrl);
|
||||
config.getConfig().put(GitHubIdentityProvider.API_URL_KEY, apiUrl);
|
||||
GitHubIdentityProvider idp = new GitHubIdentityProvider(null, config);
|
||||
|
||||
validateUrls(idp, baseUrl, apiUrl);
|
||||
}
|
||||
|
||||
protected void validateUrls(GitHubIdentityProvider idp, String baseUrl, String apiUrl) {
|
||||
OAuth2IdentityProviderConfig config = idp.getConfig();
|
||||
assertEquals(baseUrl + GitHubIdentityProvider.AUTH_FRAGMENT, config.getAuthorizationUrl());
|
||||
assertEquals(baseUrl + GitHubIdentityProvider.TOKEN_FRAGMENT, config.getTokenUrl());
|
||||
assertEquals(apiUrl + GitHubIdentityProvider.EMAIL_FRAGMENT, config.getConfig().get(GitHubIdentityProvider.EMAIL_URL_KEY));
|
||||
assertEquals(apiUrl + GitHubIdentityProvider.PROFILE_FRAGMENT, config.getUserInfoUrl());
|
||||
assertEquals(apiUrl + GitHubIdentityProvider.PROFILE_FRAGMENT, idp.getProfileEndpointForValidation(null));
|
||||
}
|
||||
|
||||
}
|
|
@ -668,6 +668,10 @@ redirect-uri.tooltip=The redirect uri to use when configuring the identity provi
|
|||
alias=Alias
|
||||
display-name=Display Name
|
||||
identity-provider.alias.tooltip=The alias uniquely identifies an identity provider and it is also used to build the redirect uri.
|
||||
identity-provider.api-url=API URL
|
||||
identity-provider.api-url.tooltip=Override the default API URL for this identity provider.
|
||||
identity-provider.base-url=Base URL
|
||||
identity-provider.base-url.tooltip=Override the default Base URL for this identity provider.
|
||||
identity-provider.display-name.tooltip=Friendly name for Identity Providers.
|
||||
identity-provider.enabled.tooltip=Enable/disable this identity provider.
|
||||
authenticate-by-default=Authenticate by Default
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<div class="form-group clearfix block">
|
||||
<label class="col-md-2 control-label" for="baseUrl">{{:: 'identity-provider.base-url' | translate}}</label>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" id="baseUrl" type="text" ng-model="identityProvider.config.baseUrl">
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'identity-provider.base-url.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group clearfix block">
|
||||
<label class="col-md-2 control-label" for="apiUrl">{{:: 'identity-provider.api-url' | translate}}</label>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" id="apiUrl" type="text" ng-model="identityProvider.config.apiUrl">
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'identity-provider.api-url.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
Loading…
Reference in a new issue