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:
Neon Ngo 2022-04-06 07:45:11 -04:00 committed by GitHub
parent fa7a2b6de1
commit f11573eeb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 160 additions and 16 deletions

View file

@ -35,18 +35,79 @@ 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_SCOPE = "user:email";
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 GitHubIdentityProvider(KeycloakSession session, OAuth2IdentityProviderConfig config) {
super(session, config);
config.setAuthorizationUrl(AUTH_URL);
config.setTokenUrl(TOKEN_URL);
config.setUserInfoUrl(PROFILE_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);
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
protected boolean supportsExternalExchange() {
@ -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()) {

View file

@ -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));
}
}

View file

@ -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

View file

@ -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>