Browse Source

feat: Add OIDC compliant 'picture' attribute mapping

pull/64/head
Kevin Paul 3 weeks ago
parent
commit
5e4e84558c
  1. 36
      src/main/java/org/keycloak/social/discord/DiscordIdentityProvider.java

36
src/main/java/org/keycloak/social/discord/DiscordIdentityProvider.java

@ -18,6 +18,7 @@
package org.keycloak.social.discord;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.ws.rs.core.Response;
import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
@ -32,6 +33,7 @@ import org.keycloak.services.ErrorPageException;
import org.keycloak.services.messages.Messages;
import java.util.Set;
import java.util.regex.Pattern;
/**
* @author <a href="mailto:wadahiro@gmail.com">Hiroyuki Wada</a>
@ -45,9 +47,13 @@ public class DiscordIdentityProvider extends AbstractOAuth2IdentityProvider<Disc
public static final String TOKEN_URL = "https://discord.com/api/oauth2/token";
public static final String PROFILE_URL = "https://discord.com/api/users/@me";
public static final String GROUP_URL = "https://discord.com/api/users/@me/guilds";
public static final String USER_PICTURE_URL = "https://cdn.discordapp.com/avatars/%s/%s.%s";
public static final String DEFAULT_SCOPE = "identify email";
public static final String GUILDS_SCOPE = "guilds";
private static final Pattern AVATAR_HASH_PATTERN = Pattern.compile("^(a_)?[0-9a-f]{32}$");
private static final Pattern DISCORD_ID_PATTERN = Pattern.compile("^\\d+$");
public DiscordIdentityProvider(KeycloakSession session, DiscordIdentityProviderConfig config) {
super(session, config);
config.setAuthorizationUrl(AUTH_URL);
@ -78,6 +84,8 @@ public class DiscordIdentityProvider extends AbstractOAuth2IdentityProvider<Disc
user.setUsername(username);
user.setEmail(getJsonProperty(profile, "email"));
setUserPicture(user, profile);
user.setIdp(this);
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
@ -85,6 +93,34 @@ public class DiscordIdentityProvider extends AbstractOAuth2IdentityProvider<Disc
return user;
}
/**
* Constructs the OIDC 'picture' URL based on the Discord avatar hash and sets it
* in the user context to ensure OIDC compliance.
* <p>Discord returns an avatar hash (or null), but OIDC expects a direct URL
* to the image in the 'picture' claim.</p>
* @param user The context where the 'picture' attribute will be set
* @param profile The raw Discord user profile JSON containing the 'avatar' hash
*/
private void setUserPicture(BrokeredIdentityContext user, JsonNode profile) {
if (user.getId() == null || !DISCORD_ID_PATTERN.matcher(user.getId()).matches()) {
return;
}
String avatarHash = getJsonProperty(profile, "avatar");
if (avatarHash == null || avatarHash.isEmpty() || !AVATAR_HASH_PATTERN.matcher(avatarHash).matches()) {
return;
}
String extension = "png";
if (avatarHash.startsWith("a_")) {
extension = "gif";
}
String finalURL = String.format(USER_PICTURE_URL, user.getId(), avatarHash, extension);
user.setUserAttribute("picture", finalURL);
if (profile instanceof ObjectNode objectNodeProfile) {
objectNodeProfile.put("picture", finalURL);
}
}
@Override
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
log.debug("doGetFederatedIdentity()");

Loading…
Cancel
Save