diff --git a/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProvider.java b/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProvider.java new file mode 100755 index 0000000000..c7583b738e --- /dev/null +++ b/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProvider.java @@ -0,0 +1,98 @@ +/* + * 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.bitbucket; + +import com.fasterxml.jackson.databind.JsonNode; +import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; +import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; +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; + +/** + * @author Stian Thorgersen + */ +public class BitbucketIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider { + + public static final String AUTH_URL = "https://bitbucket.org/site/oauth2/authorize"; + public static final String TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token"; + public static final String USER_URL = "https://api.bitbucket.org/2.0/user"; + public static final String EMAIL_SCOPE = "email"; + public static final String ACCOUNT_SCOPE = "account"; + public static final String DEFAULT_SCOPE = ACCOUNT_SCOPE; + + public BitbucketIdentityProvider(KeycloakSession session, OAuth2IdentityProviderConfig config) { + super(session, config); + config.setAuthorizationUrl(AUTH_URL); + config.setTokenUrl(TOKEN_URL); + String defaultScope = config.getDefaultScope(); + + if (defaultScope == null || defaultScope.trim().equals("")) { + config.setDefaultScope(ACCOUNT_SCOPE); + } + } + + @Override + protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) { + try { + JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(USER_URL, session).header("Authorization", "Bearer " + accessToken)); + + String type = getJsonProperty(profile, "type"); + if (type == null) { + throw new IdentityBrokerException("Could not obtain account information from bitbucket."); + + } + if (type.equals("error")) { + JsonNode errorNode = profile.get("error"); + if (errorNode != null) { + String errorMsg = getJsonProperty(errorNode, "message"); + throw new IdentityBrokerException("Could not obtain account information from bitbucket. Error: " + errorMsg); + } else { + throw new IdentityBrokerException("Could not obtain account information from bitbucket."); + } + } + if (!type.equals("user")) { + logger.debug("Unknown object type: " + type); + throw new IdentityBrokerException("Could not obtain account information from bitbucket."); + + } + BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "account_id")); + + String username = getJsonProperty(profile, "username"); + user.setUsername(username); + user.setIdpConfig(getConfig()); + user.setIdp(this); + + AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias()); + + return user; + } catch (Exception e) { + if (e instanceof IdentityBrokerException) throw (IdentityBrokerException)e; + throw new IdentityBrokerException("Could not obtain user profile from github.", e); + } + } + + @Override + protected String getDefaultScopes() { + return DEFAULT_SCOPE; + } +} diff --git a/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProviderFactory.java new file mode 100755 index 0000000000..b736d74181 --- /dev/null +++ b/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProviderFactory.java @@ -0,0 +1,46 @@ +/* + * 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.bitbucket; + +import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; +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 BitbucketIdentityProviderFactory extends AbstractIdentityProviderFactory implements SocialIdentityProviderFactory { + + public static final String PROVIDER_ID = "bitbucket"; + + @Override + public String getName() { + return "BitBucket"; + } + + @Override + public BitbucketIdentityProvider create(KeycloakSession session, IdentityProviderModel model) { + return new BitbucketIdentityProvider(session, new OAuth2IdentityProviderConfig(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 561970ad26..f2861abcdf 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 @@ -24,3 +24,4 @@ org.keycloak.social.twitter.TwitterIdentityProviderFactory org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory org.keycloak.social.gitlab.GitLabIdentityProviderFactory +org.keycloak.social.bitbucket.BitbucketIdentityProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index 5e9a47322b..2ca5a7050f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -378,7 +378,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { return true; } - //@Test + @Test public void testDemo() throws Exception { testingClient.server().run(FineGrainAdminUnitTest::setupDemo); Thread.sleep(1000000000); 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 7a6ac2e810..a2c8d5bbb9 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 @@ -580,6 +580,12 @@ gitlab.application-id.tooltip=Application Id for the application you created in 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. +bitbucket-consumer-key=Consumer Key +bitbucket-consumer-secret=Consumer Secret +bitbucket.key.tooltip=Bitbucket OAuth Consumer Key +bitbucket.secret.tooltip=Bitbucket OAuth Consumer Secret +bitbucket.default-scopes.tooltip=Scopes to ask for on login. If you do not specify anything, scope defaults to 'email'. + # User federation sync-ldap-roles-to-keycloak=Sync LDAP Roles To Keycloak sync-keycloak-roles-to-ldap=Sync Keycloak Roles To LDAP diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-bitbucket.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-bitbucket.html new file mode 100755 index 0000000000..90d5c1fbd4 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-bitbucket.html @@ -0,0 +1,130 @@ +
+ + + + +
+ + + +
+
+ +
+ +
+ {{:: 'redirect-uri.tooltip' | translate}} +
+
+
+
+ +
+ +
+ {{:: 'bitbucket.key.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'bitbucket.secret.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'bitbucket.default-scopes.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'identity-provider.store-tokens.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'identity-provider.enabled.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'trust-email.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'link-only.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'hide-on-login-page.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'gui-order.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'first-broker-login-flow.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'post-broker-login-flow.tooltip' | translate}} +
+
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file