Browse Source

Prototype

pull/1/head
Hiroyuki Wada 6 years ago
parent
commit
4dfb74b5a8
  1. 4
      .gitignore
  2. 47
      ear/pom.xml
  3. 15
      ear/src/main/application/META-INF/jboss-deployment-structure.xml
  4. 52
      ejb/pom.xml
  5. 95
      ejb/src/main/java/org/keycloak/social/discord/DiscordIdentityProvider.java
  6. 46
      ejb/src/main/java/org/keycloak/social/discord/DiscordIdentityProviderFactory.java
  7. 6
      ejb/src/main/resources/META-INF/keycloak-themes.json
  8. 1
      ejb/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory
  9. 127
      ejb/src/main/resources/theme/discord/admin/resources/partials/realm-identity-provider-discord.html
  10. 2
      ejb/src/main/resources/theme/discord/admin/theme.properties
  11. 67
      pom.xml

4
.gitignore

@ -0,0 +1,4 @@
target
.project
.classpath
.settings

47
ear/pom.xml

@ -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>

15
ear/src/main/application/META-INF/jboss-deployment-structure.xml

@ -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>

52
ejb/pom.xml

@ -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>

95
ejb/src/main/java/org/keycloak/social/discord/DiscordIdentityProvider.java

@ -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;
}
}

46
ejb/src/main/java/org/keycloak/social/discord/DiscordIdentityProviderFactory.java

@ -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;
}
}

6
ejb/src/main/resources/META-INF/keycloak-themes.json

@ -0,0 +1,6 @@
{
"themes": [{
"name" : "discord",
"types": [ "admin" ]
}]
}

1
ejb/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory

@ -0,0 +1 @@
org.keycloak.social.discord.DiscordIdentityProviderFactory

127
ejb/src/main/resources/theme/discord/admin/resources/partials/realm-identity-provider-discord.html

@ -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>

2
ejb/src/main/resources/theme/discord/admin/theme.properties

@ -0,0 +1,2 @@
parent=keycloak

67
pom.xml

@ -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>
Loading…
Cancel
Save