Remove deprecated LinkedIn social provider

Closes #23127

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-04-15 12:10:48 +02:00 committed by Marek Posolda
parent d807093f63
commit ddacfbdefd
8 changed files with 7 additions and 249 deletions

View file

@ -95,9 +95,6 @@ public class Profile {
DPOP("OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer", Type.PREVIEW), DPOP("OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer", Type.PREVIEW),
@Deprecated
LINKEDIN_OAUTH("LinkedIn Social Identity Provider based on OAuth", Type.DEPRECATED),
DEVICE_FLOW("OAuth 2.0 Device Authorization Grant", Type.DEFAULT), DEVICE_FLOW("OAuth 2.0 Device Authorization Grant", Type.DEFAULT),
TRANSIENT_USERS("Transient users for brokering", Type.EXPERIMENTAL), TRANSIENT_USERS("Transient users for brokering", Type.EXPERIMENTAL),

View file

@ -27,7 +27,7 @@ public class ProfileTest {
private static final Profile.Feature DISABLED_BY_DEFAULT_FEATURE = Profile.Feature.DOCKER; private static final Profile.Feature DISABLED_BY_DEFAULT_FEATURE = Profile.Feature.DOCKER;
private static final Profile.Feature PREVIEW_FEATURE = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ; private static final Profile.Feature PREVIEW_FEATURE = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ;
private static final Profile.Feature EXPERIMENTAL_FEATURE = Profile.Feature.DYNAMIC_SCOPES; private static final Profile.Feature EXPERIMENTAL_FEATURE = Profile.Feature.DYNAMIC_SCOPES;
private static Profile.Feature DEPRECATED_FEATURE = Profile.Feature.LINKEDIN_OAUTH; private static Profile.Feature DEPRECATED_FEATURE = Profile.Feature.HOSTNAME_V1;
@Rule @Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder(); public TemporaryFolder temporaryFolder = new TemporaryFolder();
@ -132,7 +132,7 @@ public class ProfileTest {
public void configWithCommaSeparatedList() { public void configWithCommaSeparatedList() {
String enabledFeatures = DISABLED_BY_DEFAULT_FEATURE.getKey() + "," + PREVIEW_FEATURE.getKey() + "," + EXPERIMENTAL_FEATURE.getKey(); String enabledFeatures = DISABLED_BY_DEFAULT_FEATURE.getKey() + "," + PREVIEW_FEATURE.getKey() + "," + EXPERIMENTAL_FEATURE.getKey();
if (DEPRECATED_FEATURE != null) { if (DEPRECATED_FEATURE != null) {
enabledFeatures += "," + DEPRECATED_FEATURE.getKey(); enabledFeatures += "," + DEPRECATED_FEATURE.getVersionedKey();
} }
String disabledFeatures = DEFAULT_FEATURE.getKey(); String disabledFeatures = DEFAULT_FEATURE.getKey();

View file

@ -247,3 +247,7 @@ bin/kc.[sh|bat] start --spi-group-jpa-escape-slashes-in-group-path=true
The escape char is the tilde character `~`. The previous example results in the path `/top/group~/slash`. The escape marks the last slash is part of the name and not a hierarchy separator. The escape char is the tilde character `~`. The previous example results in the path `/top/group~/slash`. The escape marks the last slash is part of the name and not a hierarchy separator.
The escaping is currently disabled by default because it represents a change in behavior. Nevertheless enabling escaping is recommended and it can be the default in future versions. The escaping is currently disabled by default because it represents a change in behavior. Nevertheless enabling escaping is recommended and it can be the default in future versions.
= Removal of the deprecated LinkedIn provider
In version 22.0.2 the OAuh 2.0 social provider for LinkedIn was replaced by a new OpenId Connect implementation. The legacy provider was deprecated but not removed, just in case it was still functional in some existing realms. {project_name} 25.0.0 is definitely removing the old provider and its associated `linkedin-oauth` feature. From now on, the default `LinkedIn` social provider is the only option available.

View file

@ -34,7 +34,6 @@ import org.keycloak.social.github.GitHubIdentityProviderFactory;
import org.keycloak.social.gitlab.GitLabIdentityProviderFactory; import org.keycloak.social.gitlab.GitLabIdentityProviderFactory;
import org.keycloak.social.google.GoogleIdentityProviderFactory; import org.keycloak.social.google.GoogleIdentityProviderFactory;
import org.keycloak.social.instagram.InstagramIdentityProviderFactory; import org.keycloak.social.instagram.InstagramIdentityProviderFactory;
import org.keycloak.social.linkedin.LinkedInIdentityProviderFactory;
import org.keycloak.social.linkedin.LinkedInOIDCIdentityProviderFactory; import org.keycloak.social.linkedin.LinkedInOIDCIdentityProviderFactory;
import org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory; import org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory;
import org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory; import org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory;
@ -73,7 +72,6 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
GitLabIdentityProviderFactory.PROVIDER_ID, GitLabIdentityProviderFactory.PROVIDER_ID,
GoogleIdentityProviderFactory.PROVIDER_ID, GoogleIdentityProviderFactory.PROVIDER_ID,
InstagramIdentityProviderFactory.PROVIDER_ID, InstagramIdentityProviderFactory.PROVIDER_ID,
LinkedInIdentityProviderFactory.PROVIDER_ID,
LinkedInOIDCIdentityProviderFactory.PROVIDER_ID, LinkedInOIDCIdentityProviderFactory.PROVIDER_ID,
MicrosoftIdentityProviderFactory.PROVIDER_ID, MicrosoftIdentityProviderFactory.PROVIDER_ID,
OpenshiftV3IdentityProviderFactory.PROVIDER_ID, OpenshiftV3IdentityProviderFactory.PROVIDER_ID,

View file

@ -1,167 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.social.linkedin;
import com.fasterxml.jackson.databind.JsonNode;
import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
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.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import java.io.IOException;
import java.util.Iterator;
/**
* LinkedIn social provider. See https://developer.linkedin.com/docs/oauth2
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
@Deprecated
public class LinkedInIdentityProvider extends AbstractOAuth2IdentityProvider<OAuth2IdentityProviderConfig> implements SocialIdentityProvider<OAuth2IdentityProviderConfig> {
private static final Logger log = Logger.getLogger(LinkedInIdentityProvider.class);
public static final String AUTH_URL = "https://www.linkedin.com/oauth/v2/authorization";
public static final String TOKEN_URL = "https://www.linkedin.com/oauth/v2/accessToken";
public static final String PROFILE_URL = "https://api.linkedin.com/v2/me";
public static final String EMAIL_URL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))";
public static final String EMAIL_SCOPE = "r_emailaddress";
public static final String DEFAULT_SCOPE = "r_liteprofile " + EMAIL_SCOPE;
private static final String PROFILE_PROJECTION = "profileProjection";
public LinkedInIdentityProvider(KeycloakSession session, OAuth2IdentityProviderConfig config) {
super(session, config);
config.setAuthorizationUrl(AUTH_URL);
config.setTokenUrl(TOKEN_URL);
config.setUserInfoUrl(getUserInfoUrl(config.getConfig().get(PROFILE_PROJECTION)));
// email scope is mandatory in order to resolve the username using the email address
if (!config.getDefaultScope().contains(EMAIL_SCOPE)) {
config.setDefaultScope(config.getDefaultScope() + " " + EMAIL_SCOPE);
}
}
@Override
protected boolean supportsExternalExchange() {
return true;
}
@Override
protected String getProfileEndpointForValidation(EventBuilder event) {
return getConfig().getUserInfoUrl();
}
@Override
protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode profile) {
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"));
user.setFirstName(getFirstMultiLocaleString(profile, "firstName"));
user.setLastName(getFirstMultiLocaleString(profile, "lastName"));
user.setIdpConfig(getConfig());
user.setIdp(this);
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
return user;
}
@Override
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
log.debug("doGetFederatedIdentity()");
try {
BrokeredIdentityContext identity = extractIdentityFromProfile(null, doHttpGet(getConfig().getUserInfoUrl(), accessToken));
identity.setEmail(fetchEmailAddress(accessToken, identity));
if (identity.getUsername() == null) {
identity.setUsername(identity.getEmail());
}
return identity;
} catch (Exception e) {
throw new IdentityBrokerException("Could not obtain user profile from linkedIn.", e);
}
}
@Override
protected String getDefaultScopes() {
return DEFAULT_SCOPE;
}
private String fetchEmailAddress(String accessToken, BrokeredIdentityContext identity) {
if (identity.getEmail() == null && getConfig().getDefaultScope() != null && getConfig().getDefaultScope().contains(EMAIL_SCOPE)) {
try {
JsonNode emailAddressNode = doHttpGet(EMAIL_URL, accessToken).findPath("emailAddress");
if (emailAddressNode != null) {
return emailAddressNode.asText();
}
} catch (IOException cause) {
throw new RuntimeException("Failed to retrieve user email", cause);
}
}
return null;
}
private JsonNode doHttpGet(String url, String bearerToken) throws IOException {
JsonNode response = SimpleHttp.doGet(url, session).header("Authorization", "Bearer " + bearerToken).asJson();
if (response.hasNonNull("serviceErrorCode")) {
throw new IdentityBrokerException("Could not obtain response from [" + url + "]. Response from server: " + response);
}
return response;
}
private String getFirstMultiLocaleString(JsonNode node, String name) {
JsonNode claim = node.get(name);
if (claim != null) {
JsonNode localized = claim.get("localized");
if (localized != null) {
Iterator<JsonNode> iterator = localized.iterator();
if (iterator.hasNext()) {
return iterator.next().asText();
}
}
}
return null;
}
/**
* append profileProjection to profile URL if exists
*
* @param projection parameter
* @return Profile URL
*/
private String getUserInfoUrl(String projection) {
return projection == null || projection.isEmpty()
? PROFILE_URL
: PROFILE_URL + "?projection=" + projection;
}
}

View file

@ -1,73 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.social.linkedin;
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.common.Profile;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import java.util.List;
/**
* @author Vlastimil Elias (velias at redhat dot com)
*/
@Deprecated
public class LinkedInIdentityProviderFactory extends AbstractIdentityProviderFactory<LinkedInIdentityProvider>
implements SocialIdentityProviderFactory<LinkedInIdentityProvider>, EnvironmentDependentProviderFactory {
public static final String PROVIDER_ID = "linkedin";
@Override
public String getName() {
return "LinkedIn (deprecated)";
}
@Override
public LinkedInIdentityProvider create(KeycloakSession session, IdentityProviderModel model) {
return new LinkedInIdentityProvider(session, new OAuth2IdentityProviderConfig(model));
}
@Override
public OAuth2IdentityProviderConfig createConfig() {
return new OAuth2IdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return ProviderConfigurationBuilder.create()
.property().name("profileProjection")
.label("Profile projection")
.helpText("Projection parameter for profile request. Leave empty for default projection.")
.add().build();
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.LINKEDIN_OAUTH);
}
}

View file

@ -25,7 +25,7 @@ import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
*/ */
public class LinkedInUserAttributeMapper extends AbstractJsonUserAttributeMapper { public class LinkedInUserAttributeMapper extends AbstractJsonUserAttributeMapper {
private static final String[] cp = new String[] { LinkedInIdentityProviderFactory.PROVIDER_ID, LinkedInOIDCIdentityProviderFactory.PROVIDER_ID }; private static final String[] cp = new String[] { LinkedInOIDCIdentityProviderFactory.PROVIDER_ID };
@Override @Override
public String[] getCompatibleProviders() { public String[] getCompatibleProviders() {

View file

@ -19,7 +19,6 @@ org.keycloak.social.facebook.FacebookIdentityProviderFactory
org.keycloak.social.paypal.PayPalIdentityProviderFactory org.keycloak.social.paypal.PayPalIdentityProviderFactory
org.keycloak.social.github.GitHubIdentityProviderFactory org.keycloak.social.github.GitHubIdentityProviderFactory
org.keycloak.social.google.GoogleIdentityProviderFactory org.keycloak.social.google.GoogleIdentityProviderFactory
org.keycloak.social.linkedin.LinkedInIdentityProviderFactory
org.keycloak.social.linkedin.LinkedInOIDCIdentityProviderFactory org.keycloak.social.linkedin.LinkedInOIDCIdentityProviderFactory
org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory
org.keycloak.social.twitter.TwitterIdentityProviderFactory org.keycloak.social.twitter.TwitterIdentityProviderFactory