Remove deprecated LinkedIn social provider
Closes #23127 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
d807093f63
commit
ddacfbdefd
8 changed files with 7 additions and 249 deletions
|
@ -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),
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue