keycloak-aplcache

Details

diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index 45183c3..a5304ba 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -75,7 +75,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
         String defaultScope = config.getDefaultScope();
 
         if (!defaultScope.contains(SCOPE_OPENID)) {
-            config.setDefaultScope(SCOPE_OPENID + " " + defaultScope);
+            config.setDefaultScope((SCOPE_OPENID + " " + defaultScope).trim());
         }
     }
 
@@ -232,48 +232,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
         JsonWebToken idToken = validateToken(encodedIdToken);
 
         try {
-            String id = idToken.getSubject();
-            BrokeredIdentityContext identity = new BrokeredIdentityContext(id);
-            String name = (String)idToken.getOtherClaims().get(IDToken.NAME);
-            String preferredUsername = (String)idToken.getOtherClaims().get(IDToken.PREFERRED_USERNAME);
-            String email = (String)idToken.getOtherClaims().get(IDToken.EMAIL);
-
-            if (!getConfig().isDisableUserInfoService()) {
-                String userInfoUrl = getUserInfoUrl();
-                if (userInfoUrl != null && !userInfoUrl.isEmpty() && (id == null || name == null || preferredUsername == null || email == null)) {
-                    SimpleHttp request = JsonSimpleHttp.doGet(userInfoUrl, session)
-                            .header("Authorization", "Bearer " + accessToken);
-                    JsonNode userInfo = JsonSimpleHttp.asJson(request);
-
-                    id = getJsonProperty(userInfo, "sub");
-                    name = getJsonProperty(userInfo, "name");
-                    preferredUsername = getJsonProperty(userInfo, "preferred_username");
-                    email = getJsonProperty(userInfo, "email");
-                    AbstractJsonUserAttributeMapper.storeUserProfileForMapper(identity, userInfo, getConfig().getAlias());
-                }
-            }
-            identity.getContextData().put(FEDERATED_ACCESS_TOKEN_RESPONSE, tokenResponse);
-            identity.getContextData().put(VALIDATED_ID_TOKEN, idToken);
-            processAccessTokenResponse(identity, tokenResponse);
-
-            identity.setId(id);
-            identity.setName(name);
-            identity.setEmail(email);
-
-            identity.setBrokerUserId(getConfig().getAlias() + "." + id);
-            if (tokenResponse.getSessionState() != null) {
-                identity.setBrokerSessionId(getConfig().getAlias() + "." + tokenResponse.getSessionState());
-            }
-
-            if (preferredUsername == null) {
-                preferredUsername = email;
-            }
-
-            if (preferredUsername == null) {
-                preferredUsername = id;
-            }
-
-            identity.setUsername(preferredUsername);
+            BrokeredIdentityContext identity = extractIdentity(tokenResponse, accessToken, idToken);
 
             if (getConfig().isStoreToken()) {
                 identity.setToken(response);
@@ -285,6 +244,56 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
         }
     }
 
+    protected BrokeredIdentityContext extractIdentity(AccessTokenResponse tokenResponse, String accessToken, JsonWebToken idToken) throws IOException {
+        String id = idToken.getSubject();
+        BrokeredIdentityContext identity = new BrokeredIdentityContext(id);
+        String name = (String)idToken.getOtherClaims().get(IDToken.NAME);
+        String preferredUsername = (String)idToken.getOtherClaims().get(getUsernameClaimName());
+        String email = (String)idToken.getOtherClaims().get(IDToken.EMAIL);
+
+        if (!getConfig().isDisableUserInfoService()) {
+            String userInfoUrl = getUserInfoUrl();
+            if (userInfoUrl != null && !userInfoUrl.isEmpty() && (id == null || name == null || preferredUsername == null || email == null)) {
+                SimpleHttp request = JsonSimpleHttp.doGet(userInfoUrl, session)
+                        .header("Authorization", "Bearer " + accessToken);
+                JsonNode userInfo = JsonSimpleHttp.asJson(request);
+
+                id = getJsonProperty(userInfo, "sub");
+                name = getJsonProperty(userInfo, "name");
+                preferredUsername = getJsonProperty(userInfo, "preferred_username");
+                email = getJsonProperty(userInfo, "email");
+                AbstractJsonUserAttributeMapper.storeUserProfileForMapper(identity, userInfo, getConfig().getAlias());
+            }
+        }
+        identity.getContextData().put(FEDERATED_ACCESS_TOKEN_RESPONSE, tokenResponse);
+        identity.getContextData().put(VALIDATED_ID_TOKEN, idToken);
+        processAccessTokenResponse(identity, tokenResponse);
+
+        identity.setId(id);
+        identity.setName(name);
+        identity.setEmail(email);
+
+        identity.setBrokerUserId(getConfig().getAlias() + "." + id);
+        if (tokenResponse.getSessionState() != null) {
+            identity.setBrokerSessionId(getConfig().getAlias() + "." + tokenResponse.getSessionState());
+        }
+
+        if (preferredUsername == null) {
+            preferredUsername = email;
+        }
+
+        if (preferredUsername == null) {
+            preferredUsername = id;
+        }
+
+        identity.setUsername(preferredUsername);
+        return identity;
+    }
+
+    protected String getUsernameClaimName() {
+        return IDToken.PREFERRED_USERNAME;
+    }
+
     protected String getUserInfoUrl() {
         return getConfig().getUserInfoUrl();
     }
diff --git a/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProvider.java b/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProvider.java
new file mode 100755
index 0000000..a57704f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProvider.java
@@ -0,0 +1,109 @@
+/*
+ * 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.gitlab;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.oidc.OIDCIdentityProvider;
+import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+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.models.KeycloakSession;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.IDToken;
+import org.keycloak.representations.JsonWebToken;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class GitLabIdentityProvider extends OIDCIdentityProvider  implements SocialIdentityProvider<OIDCIdentityProviderConfig> {
+
+	public static final String AUTH_URL = "https://gitlab.com/oauth/authorize";
+	public static final String TOKEN_URL = "https://gitlab.com/oauth/token";
+	public static final String USER_INFO = "https://gitlab.com/api/v4/user";
+	public static final String API_SCOPE = "api";
+
+	public GitLabIdentityProvider(KeycloakSession session, OIDCIdentityProviderConfig config) {
+		super(session, config);
+		config.setAuthorizationUrl(AUTH_URL);
+		config.setTokenUrl(TOKEN_URL);
+		config.setUserInfoUrl(USER_INFO);
+
+		String defaultScope = config.getDefaultScope();
+
+		if (defaultScope.equals(SCOPE_OPENID)) {
+			config.setDefaultScope((API_SCOPE + " " + defaultScope).trim());
+		}
+	}
+
+	protected BrokeredIdentityContext extractIdentity(AccessTokenResponse tokenResponse, String accessToken, JsonWebToken idToken) throws IOException {
+		String id = idToken.getSubject();
+		BrokeredIdentityContext identity = new BrokeredIdentityContext(id);
+		String name = (String)idToken.getOtherClaims().get(IDToken.NAME);
+		String preferredUsername = (String)idToken.getOtherClaims().get(IDToken.NICKNAME);
+		String email = (String)idToken.getOtherClaims().get(IDToken.EMAIL);
+
+		if (getConfig().getDefaultScope().contains(API_SCOPE)) {
+			String userInfoUrl = getUserInfoUrl();
+			if (userInfoUrl != null && !userInfoUrl.isEmpty() && (id == null || name == null || preferredUsername == null || email == null)) {
+				SimpleHttp request = JsonSimpleHttp.doGet(userInfoUrl, session)
+						.header("Authorization", "Bearer " + accessToken);
+				JsonNode userInfo = JsonSimpleHttp.asJson(request);
+
+				name = getJsonProperty(userInfo, "name");
+				preferredUsername = getJsonProperty(userInfo, "username");
+				email = getJsonProperty(userInfo, "email");
+				AbstractJsonUserAttributeMapper.storeUserProfileForMapper(identity, userInfo, getConfig().getAlias());
+			}
+		}
+		identity.getContextData().put(FEDERATED_ACCESS_TOKEN_RESPONSE, tokenResponse);
+		identity.getContextData().put(VALIDATED_ID_TOKEN, idToken);
+		processAccessTokenResponse(identity, tokenResponse);
+
+		identity.setId(id);
+		identity.setName(name);
+		identity.setEmail(email);
+
+		identity.setBrokerUserId(getConfig().getAlias() + "." + id);
+		if (tokenResponse.getSessionState() != null) {
+			identity.setBrokerSessionId(getConfig().getAlias() + "." + tokenResponse.getSessionState());
+		}
+
+		if (preferredUsername == null) {
+			preferredUsername = email;
+		}
+
+		if (preferredUsername == null) {
+			preferredUsername = id;
+		}
+
+		identity.setUsername(preferredUsername);
+		return identity;
+	}
+
+
+
+
+}
diff --git a/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProviderFactory.java
new file mode 100755
index 0000000..35e7a5e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProviderFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.gitlab;
+
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+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 Pedro Igor
+ */
+public class GitLabIdentityProviderFactory extends AbstractIdentityProviderFactory<GitLabIdentityProvider> implements SocialIdentityProviderFactory<GitLabIdentityProvider> {
+
+    public static final String PROVIDER_ID = "gitlab";
+
+    @Override
+    public String getName() {
+        return "GitLab";
+    }
+
+    @Override
+    public GitLabIdentityProvider create(KeycloakSession session, IdentityProviderModel model) {
+        return new GitLabIdentityProvider(session, new OIDCIdentityProviderConfig(model));
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+}
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 00a5e51..561970a 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
@@ -23,3 +23,4 @@ org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory
 org.keycloak.social.twitter.TwitterIdentityProviderFactory
 org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory
 org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory
+org.keycloak.social.gitlab.GitLabIdentityProviderFactory
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 73412e9..7a6ac2e 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -574,6 +574,11 @@ key=Key
 stackoverflow.key.tooltip=The Key obtained from Stack Overflow client registration.
 openshift.base-url=Base Url
 openshift.base-url.tooltip=Base Url to Openshift Online API
+gitlab-application-id=Application Id
+gitlab-application-secret=Application Secret
+gitlab.application-id.tooltip=Application Id for the application you created in your GitLab Applications account menu
+gitlab.application-secret.tooltip=Secret for the application that you created in your GitLab Applications account menu
+gitlab.default-scopes.tooltip=Scopes to ask for on login.  Will always ask for openid.  Additionally adds api if you do not specify anything.
 
 # User federation
 sync-ldap-roles-to-keycloak=Sync LDAP Roles To Keycloak
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-gitlab.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-gitlab.html
new file mode 100755
index 0000000..152d1f1
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-gitlab.html
@@ -0,0 +1,130 @@
+<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">
+        <input type="text" readonly value="this is not a login form" style="display: none;">
+        <input type="password" readonly value="this is not a login form" style="display: none;">
+
+        <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> {{:: 'gitlab-application-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>{{:: 'gitlab.application-id.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="clientSecret"><span class="required">*</span> {{:: 'gitlab-application-secret' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="clientSecret" type="password" ng-model="identityProvider.config.clientSecret" required>
+                </div>
+                <kc-tooltip>{{:: 'gitlab.application-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>{{:: 'gitlab.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>
\ No newline at end of file