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