Hiroyuki Wada
6 years ago
11 changed files with 462 additions and 0 deletions
-
4.gitignore
-
47ear/pom.xml
-
15ear/src/main/application/META-INF/jboss-deployment-structure.xml
-
52ejb/pom.xml
-
95ejb/src/main/java/org/keycloak/social/discord/DiscordIdentityProvider.java
-
46ejb/src/main/java/org/keycloak/social/discord/DiscordIdentityProviderFactory.java
-
6ejb/src/main/resources/META-INF/keycloak-themes.json
-
1ejb/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory
-
127ejb/src/main/resources/theme/discord/admin/resources/partials/realm-identity-provider-discord.html
-
2ejb/src/main/resources/theme/discord/admin/theme.properties
-
67pom.xml
@ -0,0 +1,4 @@ |
|||
target |
|||
.project |
|||
.classpath |
|||
.settings |
@ -0,0 +1,47 @@ |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
|||
<parent> |
|||
<groupId>org.keycloak.extensions</groupId> |
|||
<artifactId>keycloak-discord-parent</artifactId> |
|||
<version>1.0.0-SNAPSHOT</version> |
|||
</parent> |
|||
|
|||
<name>Keycloak Discord EAR</name> |
|||
<description/> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
|
|||
<artifactId>keycloak-discord-ear</artifactId> |
|||
<packaging>ear</packaging> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>org.keycloak.extensions</groupId> |
|||
<artifactId>keycloak-discord-ejb</artifactId> |
|||
<type>ejb</type> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<finalName>keycloak-discord</finalName> |
|||
|
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-ear-plugin</artifactId> |
|||
<version>2.10</version> |
|||
<configuration> |
|||
<version>7</version> |
|||
<defaultLibBundleDir>lib</defaultLibBundleDir> |
|||
<fileNameMapping>no-version</fileNameMapping> |
|||
</configuration> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.wildfly.plugins</groupId> |
|||
<artifactId>wildfly-maven-plugin</artifactId> |
|||
<configuration> |
|||
<skip>false</skip> |
|||
</configuration> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
</project> |
@ -0,0 +1,15 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<jboss-deployment-structure> |
|||
<deployment> |
|||
<dependencies> |
|||
<module name="org.keycloak.keycloak-core" export="true" /> |
|||
<module name="org.keycloak.keycloak-server-spi" export="true" /> |
|||
<module name="org.keycloak.keycloak-server-spi-private" export="true" /> |
|||
<module name="org.keycloak.keycloak-services" export="true" /> |
|||
<module name="org.keycloak.keycloak-saml-core-public" export="true" /> |
|||
<module name="org.bouncycastle" export="true" /> |
|||
<module name="com.google.guava" export="true" /> |
|||
<module name="org.jboss.logging" export="true" /> |
|||
</dependencies> |
|||
</deployment> |
|||
</jboss-deployment-structure> |
@ -0,0 +1,52 @@ |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
|||
<parent> |
|||
<groupId>org.keycloak.extensions</groupId> |
|||
<artifactId>keycloak-discord-parent</artifactId> |
|||
<version>1.0.0-SNAPSHOT</version> |
|||
</parent> |
|||
|
|||
<name>Keycloak Discord EJB</name> |
|||
<description/> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
|
|||
<artifactId>keycloak-discord-ejb</artifactId> |
|||
<packaging>jar</packaging> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>org.keycloak</groupId> |
|||
<artifactId>keycloak-core</artifactId> |
|||
<scope>provided</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.keycloak</groupId> |
|||
<artifactId>keycloak-server-spi</artifactId> |
|||
<scope>provided</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.keycloak</groupId> |
|||
<artifactId>keycloak-server-spi-private</artifactId> |
|||
<scope>provided</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.keycloak</groupId> |
|||
<artifactId>keycloak-services</artifactId> |
|||
<scope>provided</scope> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-compiler-plugin</artifactId> |
|||
<version>2.3.2</version> |
|||
<configuration> |
|||
<source>1.8</source> |
|||
<target>1.8</target> |
|||
</configuration> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
</project> |
@ -0,0 +1,95 @@ |
|||
/* |
|||
* Copyright 2018 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.discord; |
|||
|
|||
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 org.keycloak.social.linkedin.LinkedInIdentityProvider; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
|
|||
/** |
|||
* @author <a href="mailto:wadahiro@gmail.com">Hiroyuki Wada</a> |
|||
*/ |
|||
public class DiscordIdentityProvider extends AbstractOAuth2IdentityProvider<OAuth2IdentityProviderConfig> |
|||
implements SocialIdentityProvider<OAuth2IdentityProviderConfig> { |
|||
|
|||
private static final Logger log = Logger.getLogger(LinkedInIdentityProvider.class); |
|||
|
|||
public static final String AUTH_URL = "https://discordapp.com/api/oauth2/authorize"; |
|||
public static final String TOKEN_URL = "https://discordapp.com/api/oauth2/token"; |
|||
public static final String PROFILE_URL = "https://discordapp.com/api/users/@me"; |
|||
public static final String DEFAULT_SCOPE = "identify email"; |
|||
|
|||
public DiscordIdentityProvider(KeycloakSession session, OAuth2IdentityProviderConfig config) { |
|||
super(session, config); |
|||
config.setAuthorizationUrl(AUTH_URL); |
|||
config.setTokenUrl(TOKEN_URL); |
|||
config.setUserInfoUrl(PROFILE_URL); |
|||
} |
|||
|
|||
@Override |
|||
protected boolean supportsExternalExchange() { |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
protected String getProfileEndpointForValidation(EventBuilder event) { |
|||
return PROFILE_URL; |
|||
} |
|||
|
|||
@Override |
|||
protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode profile) { |
|||
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id")); |
|||
|
|||
user.setUsername(getJsonProperty(profile, "username")); |
|||
// user.setName(getJsonProperty(profile, "username")); |
|||
user.setEmail(getJsonProperty(profile, "email")); |
|||
user.setIdpConfig(getConfig()); |
|||
user.setIdp(this); |
|||
|
|||
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias()); |
|||
|
|||
return user; |
|||
} |
|||
|
|||
@Override |
|||
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) { |
|||
log.debug("doGetFederatedIdentity()"); |
|||
try { |
|||
JsonNode profile = SimpleHttp.doGet(PROFILE_URL, session).header("Authorization", "Bearer " + accessToken).asJson(); |
|||
// System.out.println(profile.toString()); |
|||
return extractIdentityFromProfile(null, profile); |
|||
} catch (Exception e) { |
|||
throw new IdentityBrokerException("Could not obtain user profile from discord.", e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected String getDefaultScopes() { |
|||
return DEFAULT_SCOPE; |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
/* |
|||
* Copyright 2018 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.discord; |
|||
|
|||
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; |
|||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory; |
|||
import org.keycloak.broker.social.SocialIdentityProviderFactory; |
|||
import org.keycloak.models.IdentityProviderModel; |
|||
import org.keycloak.models.KeycloakSession; |
|||
|
|||
/** |
|||
* @author <a href="mailto:wadahiro@gmail.com">Hiroyuki Wada</a> |
|||
*/ |
|||
public class DiscordIdentityProviderFactory extends AbstractIdentityProviderFactory<DiscordIdentityProvider> implements SocialIdentityProviderFactory<DiscordIdentityProvider> { |
|||
|
|||
public static final String PROVIDER_ID = "discord"; |
|||
|
|||
@Override |
|||
public String getName() { |
|||
return "Discord"; |
|||
} |
|||
|
|||
@Override |
|||
public DiscordIdentityProvider create(KeycloakSession session, IdentityProviderModel model) { |
|||
return new DiscordIdentityProvider(session, new OIDCIdentityProviderConfig(model)); |
|||
} |
|||
|
|||
@Override |
|||
public String getId() { |
|||
return PROVIDER_ID; |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"themes": [{ |
|||
"name" : "discord", |
|||
"types": [ "admin" ] |
|||
}] |
|||
} |
@ -0,0 +1 @@ |
|||
org.keycloak.social.discord.DiscordIdentityProviderFactory |
@ -0,0 +1,127 @@ |
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2"> |
|||
<ol class="breadcrumb"> |
|||
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li> |
|||
<li data-ng-hide="newIdentityProvider">{{provider.name}}</li> |
|||
<li data-ng-show="newIdentityProvider">{{:: 'add-identity-provider' | translate}}</li> |
|||
</ol> |
|||
|
|||
<kc-tabs-identity-provider></kc-tabs-identity-provider> |
|||
|
|||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageIdentityProviders"> |
|||
<fieldset> |
|||
<div class="form-group clearfix"> |
|||
<label class="col-md-2 control-label" for="redirectUri">{{:: 'redirect-uri' | translate}}</label> |
|||
<div class="col-sm-6"> |
|||
<input class="form-control" id="redirectUri" type="text" value="{{callbackUrl}}{{identityProvider.alias}}/endpoint" readonly kc-select-action="click"> |
|||
</div> |
|||
<kc-tooltip>{{:: 'redirect-uri.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
</fieldset> |
|||
<fieldset> |
|||
<div class="form-group clearfix"> |
|||
<label class="col-md-2 control-label" for="clientId"><span class="required">*</span> {{:: 'discord-client-id' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<input class="form-control" id="clientId" type="text" ng-model="identityProvider.config.clientId" required> |
|||
</div> |
|||
<kc-tooltip>{{:: 'discord.client-id.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group clearfix"> |
|||
<label class="col-md-2 control-label" for="clientSecret"><span class="required">*</span> {{:: 'discord-client-secret' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<input class="form-control" id="clientSecret" kc-password ng-model="identityProvider.config.clientSecret" required> |
|||
</div> |
|||
<kc-tooltip>{{:: 'discord.client-secret.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group clearfix"> |
|||
<label class="col-md-2 control-label" for="defaultScope">{{:: 'default-scopes' | translate}} </label> |
|||
<div class="col-md-6"> |
|||
<input class="form-control" id="defaultScope" type="text" ng-model="identityProvider.config.defaultScope"> |
|||
</div> |
|||
<kc-tooltip>{{:: 'discord.default-scopes.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-md-2 control-label" for="enabled">{{:: 'store-tokens' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" /> |
|||
</div> |
|||
<kc-tooltip>{{:: 'identity-provider.store-tokens.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-md-2 control-label" for="storedTokensReadable">{{:: 'stored-tokens-readable' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<input ng-model="identityProvider.addReadTokenRoleOnCreate" id="storedTokensReadable" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" /> |
|||
</div> |
|||
<kc-tooltip>{{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-md-2 control-label" for="enabled">{{:: 'enabled' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<input ng-model="identityProvider.enabled" id="enabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" /> |
|||
</div> |
|||
<kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<input ng-model="identityProvider.trustEmail" name="identityProvider.trustEmail" id="trustEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" /> |
|||
</div> |
|||
<kc-tooltip>{{:: 'trust-email.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-md-2 control-label" for="linkOnly">{{:: 'link-only' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<input ng-model="identityProvider.linkOnly" name="identityProvider.trustEmail" id="linkOnly" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" /> |
|||
</div> |
|||
<kc-tooltip>{{:: 'link-only.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-md-2 control-label" for="hideOnLoginPage">{{:: 'hide-on-login-page' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<input ng-model="identityProvider.config.hideOnLoginPage" name="identityProvider.config.hideOnLoginPage" id="hideOnLoginPage" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" /> |
|||
</div> |
|||
<kc-tooltip>{{:: 'hide-on-login-page.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-md-2 control-label" for="guiOrder">{{:: 'gui-order' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<input class="form-control" id="guiOrder" type="text" ng-model="identityProvider.config.guiOrder"> |
|||
</div> |
|||
<kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<div> |
|||
<select class="form-control" id="firstBrokerLoginFlowAlias" |
|||
ng-model="identityProvider.firstBrokerLoginFlowAlias" |
|||
ng-options="flow.alias as flow.alias for flow in authFlows" |
|||
required> |
|||
</select> |
|||
</div> |
|||
</div> |
|||
<kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-md-2 control-label" for="postBrokerLoginFlowAlias">{{:: 'post-broker-login-flow' | translate}}</label> |
|||
<div class="col-md-6"> |
|||
<div> |
|||
<select class="form-control" id="postBrokerLoginFlowAlias" |
|||
ng-model="identityProvider.postBrokerLoginFlowAlias" |
|||
ng-options="flow.alias as flow.alias for flow in postBrokerAuthFlows"> |
|||
</select> |
|||
</div> |
|||
</div> |
|||
<kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip> |
|||
</div> |
|||
</fieldset> |
|||
|
|||
<div class="form-group"> |
|||
<div class="col-md-10 col-md-offset-2"> |
|||
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button> |
|||
<button kc-cancel data-ng-click="cancel()" data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button> |
|||
</div> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
|
|||
<kc-menu></kc-menu> |
@ -0,0 +1,2 @@ |
|||
parent=keycloak |
|||
|
@ -0,0 +1,67 @@ |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
|||
|
|||
<name>Keycloak Discord</name> |
|||
<description/> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
|
|||
<groupId>org.keycloak.extensions</groupId> |
|||
<artifactId>keycloak-discord-parent</artifactId> |
|||
<version>1.0.0-SNAPSHOT</version> |
|||
<packaging>pom</packaging> |
|||
|
|||
<properties> |
|||
<version.keycloak>4.5.0.Final</version.keycloak> |
|||
<version.wildfly.maven.plugin>1.1.0.Final</version.wildfly.maven.plugin> |
|||
</properties> |
|||
|
|||
<dependencyManagement> |
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>org.keycloak.bom</groupId> |
|||
<artifactId>keycloak-spi-bom</artifactId> |
|||
<version>${version.keycloak}</version> |
|||
<type>pom</type> |
|||
<scope>import</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.keycloak.extensions</groupId> |
|||
<artifactId>keycloak-discord-ejb</artifactId> |
|||
<version>${project.version}</version> |
|||
<type>ejb</type> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.keycloak</groupId> |
|||
<artifactId>keycloak-server-spi-private</artifactId> |
|||
<scope>provided</scope> |
|||
<version>${version.keycloak}</version> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.keycloak</groupId> |
|||
<artifactId>keycloak-services</artifactId> |
|||
<scope>provided</scope> |
|||
<version>${version.keycloak}</version> |
|||
</dependency> |
|||
</dependencies> |
|||
</dependencyManagement> |
|||
|
|||
<modules> |
|||
<module>ejb</module> |
|||
<module>ear</module> |
|||
</modules> |
|||
|
|||
<build> |
|||
<pluginManagement> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.wildfly.plugins</groupId> |
|||
<artifactId>wildfly-maven-plugin</artifactId> |
|||
<version>${version.wildfly.maven.plugin}</version> |
|||
<configuration> |
|||
<skip>true</skip> |
|||
</configuration> |
|||
</plugin> |
|||
</plugins> |
|||
</pluginManagement> |
|||
</build> |
|||
</project> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue