From ddacfbdefd93694c7adbae19d37c706ca4d18011 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 15 Apr 2024 12:10:48 +0200 Subject: [PATCH] Remove deprecated LinkedIn social provider Closes #23127 Signed-off-by: rmartinc --- .../java/org/keycloak/common/Profile.java | 3 - .../java/org/keycloak/common/ProfileTest.java | 4 +- .../topics/changes/changes-25_0_0.adoc | 4 + .../oidc/mappers/UsernameTemplateMapper.java | 2 - .../linkedin/LinkedInIdentityProvider.java | 167 ------------------ .../LinkedInIdentityProviderFactory.java | 73 -------- .../linkedin/LinkedInUserAttributeMapper.java | 2 +- ...roker.social.SocialIdentityProviderFactory | 1 - 8 files changed, 7 insertions(+), 249 deletions(-) delete mode 100755 services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java delete mode 100755 services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java index 697829b568..0ba99c8b9e 100755 --- a/common/src/main/java/org/keycloak/common/Profile.java +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -95,9 +95,6 @@ public class Profile { 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), TRANSIENT_USERS("Transient users for brokering", Type.EXPERIMENTAL), diff --git a/common/src/test/java/org/keycloak/common/ProfileTest.java b/common/src/test/java/org/keycloak/common/ProfileTest.java index d42c28a9ab..f6d39afe89 100644 --- a/common/src/test/java/org/keycloak/common/ProfileTest.java +++ b/common/src/test/java/org/keycloak/common/ProfileTest.java @@ -27,7 +27,7 @@ public class ProfileTest { 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 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 public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -132,7 +132,7 @@ public class ProfileTest { public void configWithCommaSeparatedList() { String enabledFeatures = DISABLED_BY_DEFAULT_FEATURE.getKey() + "," + PREVIEW_FEATURE.getKey() + "," + EXPERIMENTAL_FEATURE.getKey(); if (DEPRECATED_FEATURE != null) { - enabledFeatures += "," + DEPRECATED_FEATURE.getKey(); + enabledFeatures += "," + DEPRECATED_FEATURE.getVersionedKey(); } String disabledFeatures = DEFAULT_FEATURE.getKey(); diff --git a/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc b/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc index 04181b2e67..4c18c74825 100644 --- a/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc @@ -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 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. diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/UsernameTemplateMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/UsernameTemplateMapper.java index 8129eb9f4c..f9b3759d8f 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/mappers/UsernameTemplateMapper.java +++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/UsernameTemplateMapper.java @@ -34,7 +34,6 @@ import org.keycloak.social.github.GitHubIdentityProviderFactory; import org.keycloak.social.gitlab.GitLabIdentityProviderFactory; import org.keycloak.social.google.GoogleIdentityProviderFactory; import org.keycloak.social.instagram.InstagramIdentityProviderFactory; -import org.keycloak.social.linkedin.LinkedInIdentityProviderFactory; import org.keycloak.social.linkedin.LinkedInOIDCIdentityProviderFactory; import org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory; import org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory; @@ -73,7 +72,6 @@ public class UsernameTemplateMapper extends AbstractClaimMapper { GitLabIdentityProviderFactory.PROVIDER_ID, GoogleIdentityProviderFactory.PROVIDER_ID, InstagramIdentityProviderFactory.PROVIDER_ID, - LinkedInIdentityProviderFactory.PROVIDER_ID, LinkedInOIDCIdentityProviderFactory.PROVIDER_ID, MicrosoftIdentityProviderFactory.PROVIDER_ID, OpenshiftV3IdentityProviderFactory.PROVIDER_ID, diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java deleted file mode 100755 index e744050eec..0000000000 --- a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java +++ /dev/null @@ -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 implements SocialIdentityProvider { - - 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 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; - } -} diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java deleted file mode 100755 index a03845d9a5..0000000000 --- a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java +++ /dev/null @@ -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 - implements SocialIdentityProviderFactory, 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 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); - } -} diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java index 35a695b372..d022349931 100644 --- a/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java @@ -25,7 +25,7 @@ import org.keycloak.broker.oidc.mappers.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 public String[] getCompatibleProviders() { diff --git a/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory index 8bcc24617a..a7d7abd648 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory @@ -19,7 +19,6 @@ org.keycloak.social.facebook.FacebookIdentityProviderFactory org.keycloak.social.paypal.PayPalIdentityProviderFactory org.keycloak.social.github.GitHubIdentityProviderFactory org.keycloak.social.google.GoogleIdentityProviderFactory -org.keycloak.social.linkedin.LinkedInIdentityProviderFactory org.keycloak.social.linkedin.LinkedInOIDCIdentityProviderFactory org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory org.keycloak.social.twitter.TwitterIdentityProviderFactory