aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml46
-rw-r--r--server/sonar-auth-common/build.gradle1
-rw-r--r--server/sonar-auth-github/build.gradle1
-rw-r--r--server/sonar-auth-gitlab/build.gradle1
-rw-r--r--server/sonar-auth-saml/build.gradle26
-rw-r--r--server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlIdentityProvider.java191
-rw-r--r--server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlModule.java37
-rw-r--r--server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java181
-rw-r--r--server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/package-info.java23
-rw-r--r--server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlIdentityProviderTest.java337
-rw-r--r--server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlModuleTest.java36
-rw-r--r--server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlSettingsTest.java227
-rw-r--r--server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_full_response.txt1
-rw-r--r--server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_minimal_response.txt1
-rw-r--r--server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_response_without_login.txt1
-rw-r--r--server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_response_without_name.txt1
-rw-r--r--server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/how_to_generate_test_response.txt6
-rw-r--r--server/sonar-docs/src/pages/instance-administration/delegated-auth.md4
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java2
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java12
-rw-r--r--server/sonar-web/public/images/saml.pngbin0 -> 15683 bytes
-rw-r--r--server/sonar-webserver/build.gradle1
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java4
-rw-r--r--settings.gradle1
-rw-r--r--sonar-application/build.gradle1
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
26 files changed, 1136 insertions, 8 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 2b8e20f6981..fca399b5ecb 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -166,11 +166,57 @@ qa_task:
path: "**/test-results/**/*.xml"
format: junit
+# SAML QA is executed in a dedicated task in order to not slow down the pipeline, as a Keycloak server docker image is required.
+qa_saml_task:
+ depends_on: build
+ # Comment the following line and commit with message "DO NOT MERGE" in order to run
+ # this task on your branch
+ only_if: $CIRRUS_BRANCH == "branch-nightly-build"
+ gke_container:
+ dockerfile: private/docker/Dockerfile-build
+ builder_image_project: ci-cd-215716
+ builder_image_name: docker-builder-v1
+ cluster_name: cirrus-uscentral1a-cluster
+ zone: us-central1-a
+ namespace: default
+ cpu: 2.4
+ memory: 10Gb
+ additional_containers:
+ - name: keycloak
+ image: jboss/keycloak:7.0.0
+ port: 8080
+ cpu: 1
+ memory: 1Gb
+ env:
+ KEYCLOAK_USER: admin
+ KEYCLOAK_PASSWORD: admin
+ env:
+ # No need to clone the full history.
+ # Depth of 1 is not enough because it would fail the build in case of consecutive pushes
+ # (example of error: "Hard resetting to c968ecaf7a1942dacecd78480b3751ac74d53c33...Failed to force reset to c968ecaf7a1942dacecd78480b3751ac74d53c33: object not found!")
+ CIRRUS_CLONE_DEPTH: 50
+ QA_CATEGORY: SAML
+ gradle_cache:
+ folder: ~/.gradle/caches
+ script:
+ - ./private/cirrus/cirrus-qa.sh h2
+ cleanup_before_cache_script:
+ - ./private/cirrus/cleanup-gradle-cache.sh
+ on_failure:
+ reports_artifacts:
+ path: "**/build/reports/**/*"
+ screenshots_artifacts:
+ path: "**/build/screenshots/**/*"
+ junit_artifacts:
+ path: "**/test-results/**/*.xml"
+ format: junit
+
promote_task:
depends_on:
- build
- validate
- qa
+ - qa_saml
only_if: $CIRRUS_BRANCH !=~ "dogfood/.*" && $CIRRUS_BRANCH != "public_master" && $CIRRUS_BRANCH != "branch-nightly-build"
gke_container:
dockerfile: private/docker/Dockerfile-build
diff --git a/server/sonar-auth-common/build.gradle b/server/sonar-auth-common/build.gradle
index 9a669541dac..8adf3d9591e 100644
--- a/server/sonar-auth-common/build.gradle
+++ b/server/sonar-auth-common/build.gradle
@@ -14,7 +14,6 @@ dependencies {
testCompile 'commons-lang:commons-lang'
testCompile 'com.squareup.okhttp3:mockwebserver'
- testCompile 'com.squareup.okhttp3:okhttp'
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
testCompile 'org.mockito:mockito-core'
diff --git a/server/sonar-auth-github/build.gradle b/server/sonar-auth-github/build.gradle
index 3bc4be0a024..b8911ac7943 100644
--- a/server/sonar-auth-github/build.gradle
+++ b/server/sonar-auth-github/build.gradle
@@ -16,7 +16,6 @@ dependencies {
compileOnly 'com.squareup.okhttp3:okhttp'
compileOnly 'javax.servlet:javax.servlet-api'
compileOnly project(':sonar-core')
- compileOnly project(':sonar-ws')
testCompile 'com.squareup.okhttp3:mockwebserver'
testCompile 'com.squareup.okhttp3:okhttp'
diff --git a/server/sonar-auth-gitlab/build.gradle b/server/sonar-auth-gitlab/build.gradle
index 54f97687849..f4c43ca8769 100644
--- a/server/sonar-auth-gitlab/build.gradle
+++ b/server/sonar-auth-gitlab/build.gradle
@@ -16,7 +16,6 @@ dependencies {
compileOnly 'com.squareup.okhttp3:okhttp'
compileOnly 'javax.servlet:javax.servlet-api'
compileOnly project(':sonar-core')
- compileOnly project(':sonar-ws')
testCompile 'com.squareup.okhttp3:mockwebserver'
testCompile 'com.squareup.okhttp3:okhttp'
diff --git a/server/sonar-auth-saml/build.gradle b/server/sonar-auth-saml/build.gradle
new file mode 100644
index 00000000000..5651516b7b6
--- /dev/null
+++ b/server/sonar-auth-saml/build.gradle
@@ -0,0 +1,26 @@
+description = 'SonarQube :: Authentication :: SAML'
+
+configurations {
+ testCompile.extendsFrom compileOnly
+}
+
+ext {
+ oneLoginVersion = '2.5.0'
+}
+
+dependencies {
+ // please keep the list ordered
+
+ compile "com.onelogin:java-saml:${oneLoginVersion}"
+ compile "com.onelogin:java-saml-core:${oneLoginVersion}"
+
+ compileOnly 'com.google.code.findbugs:jsr305'
+ compileOnly 'com.squareup.okhttp3:okhttp'
+ compileOnly 'javax.servlet:javax.servlet-api'
+ compileOnly project(':sonar-core')
+
+ testCompile 'com.tngtech.java:junit-dataprovider'
+ testCompile 'junit:junit'
+ testCompile 'org.assertj:assertj-core'
+ testCompile 'org.mockito:mockito-core'
+}
diff --git a/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlIdentityProvider.java b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlIdentityProvider.java
new file mode 100644
index 00000000000..1107d4f6acd
--- /dev/null
+++ b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlIdentityProvider.java
@@ -0,0 +1,191 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.auth.saml;
+
+import com.onelogin.saml2.Auth;
+import com.onelogin.saml2.exception.SettingsException;
+import com.onelogin.saml2.settings.Saml2Settings;
+import com.onelogin.saml2.settings.SettingsBuilder;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.server.authentication.Display;
+import org.sonar.api.server.authentication.OAuth2IdentityProvider;
+import org.sonar.api.server.authentication.UnauthorizedException;
+import org.sonar.api.server.authentication.UserIdentity;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static java.util.Collections.emptySet;
+import static java.util.Objects.requireNonNull;
+
+@ServerSide
+public class SamlIdentityProvider implements OAuth2IdentityProvider {
+
+ private static final String KEY = "saml";
+
+ private static final Logger LOGGER = Loggers.get(SamlIdentityProvider.class);
+
+ private static final String ANY_URL = "http://anyurl";
+ private static final String STATE_REQUEST_PARAMETER = "RelayState";
+
+ private final SamlSettings samlSettings;
+
+ public SamlIdentityProvider(SamlSettings samlSettings) {
+ this.samlSettings = samlSettings;
+ }
+
+ @Override
+ public String getKey() {
+ return KEY;
+ }
+
+ @Override
+ public String getName() {
+ return samlSettings.getProviderName();
+ }
+
+ @Override
+ public Display getDisplay() {
+ return Display.builder()
+ .setIconPath("/images/saml.png")
+ .setBackgroundColor("#444444")
+ .build();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return samlSettings.isEnabled();
+ }
+
+ @Override
+ public boolean allowsUsersToSignUp() {
+ return true;
+ }
+
+ @Override
+ public void init(InitContext context) {
+ try {
+ Auth auth = newAuth(initSettings(context.getCallbackUrl()), context.getRequest(), context.getResponse());
+ auth.login(context.generateCsrfState());
+ } catch (IOException | SettingsException e) {
+ throw new IllegalStateException("Fail to intialize SAML authentication plugin", e);
+ }
+ }
+
+ @Override
+ public void callback(CallbackContext context) {
+ Auth auth = newAuth(initSettings(null), context.getRequest(), context.getResponse());
+ processResponse(auth);
+ context.verifyCsrfState(STATE_REQUEST_PARAMETER);
+
+ LOGGER.trace("Name ID : {}", auth.getNameId());
+ checkAuthentication(auth);
+
+ LOGGER.trace("Attributes received : {}", auth.getAttributes());
+ String login = getNonNullFirstAttribute(auth, samlSettings.getUserLogin());
+ UserIdentity.Builder userIdentityBuilder = UserIdentity.builder()
+ .setLogin(login)
+ .setProviderLogin(login)
+ .setName(getNonNullFirstAttribute(auth, samlSettings.getUserName()));
+ samlSettings.getUserEmail().ifPresent(
+ email -> userIdentityBuilder.setEmail(getFirstAttribute(auth, email)));
+ samlSettings.getGroupName().ifPresent(
+ group -> userIdentityBuilder.setGroups(getGroups(auth, group)));
+ context.authenticate(userIdentityBuilder.build());
+ context.redirectToRequestedPage();
+ }
+
+ private static Auth newAuth(Saml2Settings saml2Settings, HttpServletRequest request, HttpServletResponse response) {
+ try {
+ return new Auth(saml2Settings, request, response);
+ } catch (SettingsException e) {
+ throw new IllegalStateException("Fail to create Auth", e);
+ }
+ }
+
+ private static void processResponse(Auth auth) {
+ try {
+ auth.processResponse();
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to process response", e);
+ }
+ }
+
+ private static void checkAuthentication(Auth auth) {
+ List<String> errors = auth.getErrors();
+ if (auth.isAuthenticated() && errors.isEmpty()) {
+ return;
+ }
+ String errorReason = auth.getLastErrorReason();
+ throw new UnauthorizedException(errorReason != null && !errorReason.isEmpty() ? errorReason : "Unknown error reason");
+ }
+
+ private static String getNonNullFirstAttribute(Auth auth, String key) {
+ String attribute = getFirstAttribute(auth, key);
+ requireNonNull(attribute, String.format("%s is missing", key));
+ return attribute;
+ }
+
+ @CheckForNull
+ private static String getFirstAttribute(Auth auth, String key) {
+ Collection<String> attribute = auth.getAttribute(key);
+ if (attribute == null || attribute.isEmpty()) {
+ return null;
+ }
+ return attribute.iterator().next();
+ }
+
+ private static Set<String> getGroups(Auth auth, String groupAttribute) {
+ Collection<String> attribute = auth.getAttribute(groupAttribute);
+ if (attribute == null || attribute.isEmpty()) {
+ return emptySet();
+ }
+ return new HashSet<>(attribute);
+ }
+
+ private Saml2Settings initSettings(@Nullable String callbackUrl) {
+ Map<String, Object> samlData = new HashMap<>();
+ // TODO strict mode is unfortunately not compatible with HTTPS configuration on reverse proxy =>
+ // https://jira.sonarsource.com/browse/SQAUTHSAML-8
+ samlData.put("onelogin.saml2.strict", false);
+
+ samlData.put("onelogin.saml2.idp.entityid", samlSettings.getProviderId());
+ samlData.put("onelogin.saml2.idp.single_sign_on_service.url", samlSettings.getLoginUrl());
+ samlData.put("onelogin.saml2.idp.x509cert", samlSettings.getCertificate());
+
+ samlData.put("onelogin.saml2.sp.entityid", samlSettings.getApplicationId());
+ // During callback, the callback URL is by definition not needed, but the Saml2Settings does never allow this setting to be empty...
+ samlData.put("onelogin.saml2.sp.assertion_consumer_service.url", callbackUrl != null ? callbackUrl : ANY_URL);
+ SettingsBuilder builder = new SettingsBuilder();
+ return builder
+ .fromValues(samlData)
+ .build();
+ }
+}
diff --git a/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlModule.java b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlModule.java
new file mode 100644
index 00000000000..ca7cc79c4fc
--- /dev/null
+++ b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlModule.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.auth.saml;
+
+import java.util.List;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.core.platform.Module;
+
+public class SamlModule extends Module {
+
+ @Override
+ protected void configureModule() {
+ add(
+ SamlIdentityProvider.class,
+ SamlSettings.class);
+ List<PropertyDefinition> definitions = SamlSettings.definitions();
+ add(definitions.toArray(new Object[definitions.size()]));
+ }
+
+}
diff --git a/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java
new file mode 100644
index 00000000000..188c806dd2d
--- /dev/null
+++ b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java
@@ -0,0 +1,181 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.auth.saml;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.server.ServerSide;
+
+import static java.lang.String.valueOf;
+import static org.sonar.api.PropertyType.BOOLEAN;
+
+@ServerSide
+public class SamlSettings {
+
+ private static final String ENABLED = "sonar.auth.saml.enabled";
+ private static final String PROVIDER_ID = "sonar.auth.saml.providerId";
+ private static final String PROVIDER_NAME = "sonar.auth.saml.providerName";
+
+ private static final String APPLICATION_ID = "sonar.auth.saml.applicationId";
+ private static final String LOGIN_URL = "sonar.auth.saml.loginUrl";
+ private static final String CERTIFICATE = "sonar.auth.saml.certificate.secured";
+
+ private static final String USER_LOGIN_ATTRIBUTE = "sonar.auth.saml.user.login";
+ private static final String USER_NAME_ATTRIBUTE = "sonar.auth.saml.user.name";
+ private static final String USER_EMAIL_ATTRIBUTE = "sonar.auth.saml.user.email";
+ private static final String GROUP_NAME_ATTRIBUTE = "sonar.auth.saml.group.name";
+
+ private static final String CATEGORY = "security";
+ private static final String SUBCATEGORY = "saml";
+
+ private final Configuration configuration;
+
+ public SamlSettings(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ String getProviderId() {
+ return configuration.get(PROVIDER_ID).orElseThrow(() -> new IllegalArgumentException("Provider ID is missing"));
+ }
+
+ String getProviderName() {
+ return configuration.get(PROVIDER_NAME).orElseThrow(() -> new IllegalArgumentException("Provider Name is missing"));
+ }
+
+ String getApplicationId() {
+ return configuration.get(APPLICATION_ID).orElseThrow(() -> new IllegalArgumentException("Application ID is missing"));
+ }
+
+ String getLoginUrl() {
+ return configuration.get(LOGIN_URL).orElseThrow(() -> new IllegalArgumentException("Login URL is missing"));
+ }
+
+ String getCertificate() {
+ return configuration.get(CERTIFICATE).orElseThrow(() -> new IllegalArgumentException("Certificate is missing"));
+ }
+
+ String getUserLogin() {
+ return configuration.get(USER_LOGIN_ATTRIBUTE).orElseThrow(() -> new IllegalArgumentException("User login attribute is missing"));
+ }
+
+ String getUserName() {
+ return configuration.get(USER_NAME_ATTRIBUTE).orElseThrow(() -> new IllegalArgumentException("User name attribute is missing"));
+ }
+
+ Optional<String> getUserEmail() {
+ return configuration.get(USER_EMAIL_ATTRIBUTE);
+ }
+
+ Optional<String> getGroupName() {
+ return configuration.get(GROUP_NAME_ATTRIBUTE);
+ }
+
+ boolean isEnabled() {
+ return configuration.getBoolean(ENABLED).orElse(false) &&
+ configuration.get(PROVIDER_ID).isPresent() &&
+ configuration.get(APPLICATION_ID).isPresent() &&
+ configuration.get(LOGIN_URL).isPresent() &&
+ configuration.get(CERTIFICATE).isPresent() &&
+ configuration.get(USER_LOGIN_ATTRIBUTE).isPresent() &&
+ configuration.get(USER_NAME_ATTRIBUTE).isPresent();
+ }
+
+ static List<PropertyDefinition> definitions() {
+ return Arrays.asList(
+ PropertyDefinition.builder(ENABLED)
+ .name("Enabled")
+ .description("Enable SAML users to login. Value is ignored if provider ID, login url, certificate, login, name attributes are not defined.")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .type(BOOLEAN)
+ .defaultValue(valueOf(false))
+ .index(1)
+ .build(),
+ PropertyDefinition.builder(APPLICATION_ID)
+ .name("Application ID")
+ .description("Identifier of the application.")
+ .defaultValue("sonarqube")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .index(2)
+ .build(),
+ PropertyDefinition.builder(PROVIDER_NAME)
+ .name("Provider Name")
+ .description("Name displayed for the provider in the login page.")
+ .defaultValue("SAML")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .index(3)
+ .build(),
+ PropertyDefinition.builder(PROVIDER_ID)
+ .name("Provider ID")
+ .description("Identifier of the identity provider, the entity that provides SAML authentication.")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .index(4)
+ .build(),
+ PropertyDefinition.builder(LOGIN_URL)
+ .name("SAML login url")
+ .description("SAML login URL for the identity provider.")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .index(5)
+ .build(),
+ PropertyDefinition.builder(CERTIFICATE)
+ .name("Provider certificate")
+ .description("X.509 certificate for the identity provider.")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .index(6)
+ .build(),
+ PropertyDefinition.builder(USER_LOGIN_ATTRIBUTE)
+ .name("SAML user login attribute")
+ .description("Attribute defining the user login in SAML.")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .index(7)
+ .build(),
+ PropertyDefinition.builder(USER_NAME_ATTRIBUTE)
+ .name("SAML user name attribute")
+ .description("Attribute defining the user name in SAML.")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .index(8)
+ .build(),
+ PropertyDefinition.builder(USER_EMAIL_ATTRIBUTE)
+ .name("SAML user email attribute")
+ .description("Attribute defining the user email in SAML.")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .index(9)
+ .build(),
+ PropertyDefinition.builder(GROUP_NAME_ATTRIBUTE)
+ .name("SAML group attribute")
+ .description("Attribute defining the user groups in SAML. " +
+ "Users are associated to the default group only if no attribute is defined.")
+ .category(CATEGORY)
+ .subCategory(SUBCATEGORY)
+ .index(10)
+ .build());
+ }
+}
diff --git a/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/package-info.java b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/package-info.java
new file mode 100644
index 00000000000..a6fcdbac892
--- /dev/null
+++ b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.auth.saml;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlIdentityProviderTest.java b/server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlIdentityProviderTest.java
new file mode 100644
index 00000000000..507b2df8882
--- /dev/null
+++ b/server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlIdentityProviderTest.java
@@ -0,0 +1,337 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.auth.saml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.internal.apachecommons.io.IOUtils;
+import org.sonar.api.server.authentication.OAuth2IdentityProvider;
+import org.sonar.api.server.authentication.UnauthorizedException;
+import org.sonar.api.server.authentication.UserIdentity;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SamlIdentityProviderTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MapSettings settings = new MapSettings(new PropertyDefinitions(SamlSettings.definitions()));
+
+ private SamlIdentityProvider underTest = new SamlIdentityProvider(new SamlSettings(settings.asConfig()));
+
+ @Test
+ public void check_fields() {
+ setSettings(true);
+ assertThat(underTest.getKey()).isEqualTo("saml");
+ assertThat(underTest.getName()).isEqualTo("SAML");
+ assertThat(underTest.getDisplay().getIconPath()).isEqualTo("/static/authsaml/saml.png");
+ assertThat(underTest.getDisplay().getBackgroundColor()).isEqualTo("#444444");
+ assertThat(underTest.allowsUsersToSignUp()).isTrue();
+ }
+
+ @Test
+ public void provider_name_is_provided_by_setting() {
+ // Default value
+ assertThat(underTest.getName()).isEqualTo("SAML");
+
+ settings.setProperty("sonar.auth.saml.providerName", "My Provider");
+ assertThat(underTest.getName()).isEqualTo("My Provider");
+ }
+
+ @Test
+ public void is_enabled() {
+ setSettings(true);
+ assertThat(underTest.isEnabled()).isTrue();
+
+ setSettings(false);
+ assertThat(underTest.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void init() throws IOException {
+ setSettings(true);
+ DumbInitContext context = new DumbInitContext();
+
+ underTest.init(context);
+
+ verify(context.response).sendRedirect(anyString());
+ assertThat(context.generateCsrfState.get()).isTrue();
+ }
+
+ @Test
+ public void fail_to_init_when_login_url_is_invalid() {
+ setSettings(true);
+ settings.setProperty("sonar.auth.saml.loginUrl", "invalid");
+ DumbInitContext context = new DumbInitContext();
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Fail to create Auth");
+
+ underTest.init(context);
+ }
+
+ @Test
+ public void callback() {
+ setSettings(true);
+ DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_full_response.txt");
+
+ underTest.callback(callbackContext);
+
+ assertThat(callbackContext.redirectedToRequestedPage.get()).isTrue();
+ assertThat(callbackContext.userIdentity.getLogin()).isEqualTo("johndoe");
+ assertThat(callbackContext.verifyState.get()).isTrue();
+ }
+
+ @Test
+ public void callback_on_full_response() {
+ setSettings(true);
+ DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_full_response.txt");
+
+ underTest.callback(callbackContext);
+
+ assertThat(callbackContext.userIdentity.getLogin()).isEqualTo("johndoe");
+ assertThat(callbackContext.userIdentity.getName()).isEqualTo("John Doe");
+ assertThat(callbackContext.userIdentity.getEmail()).isEqualTo("johndoe@email.com");
+ assertThat(callbackContext.userIdentity.getProviderLogin()).isEqualTo("johndoe");
+ assertThat(callbackContext.userIdentity.getGroups()).containsExactlyInAnyOrder("developer", "product-manager");
+ }
+
+ @Test
+ public void callback_on_minimal_response() {
+ setSettings(true);
+ DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_minimal_response.txt");
+
+ underTest.callback(callbackContext);
+
+ assertThat(callbackContext.userIdentity.getLogin()).isEqualTo("johndoe");
+ assertThat(callbackContext.userIdentity.getName()).isEqualTo("John Doe");
+ assertThat(callbackContext.userIdentity.getEmail()).isNull();
+ assertThat(callbackContext.userIdentity.getProviderLogin()).isEqualTo("johndoe");
+ assertThat(callbackContext.userIdentity.getGroups()).isEmpty();
+ }
+
+ @Test
+ public void callback_does_not_sync_group_when_group_setting_is_not_set() {
+ setSettings(true);
+ settings.setProperty("sonar.auth.saml.group.name", (String) null);
+ DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_full_response.txt");
+
+ underTest.callback(callbackContext);
+
+ assertThat(callbackContext.userIdentity.getLogin()).isEqualTo("johndoe");
+ assertThat(callbackContext.userIdentity.getGroups()).isEmpty();
+ }
+
+ @Test
+ public void fail_to_callback_when_login_is_missing() {
+ setSettings(true);
+ DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_response_without_login.txt");
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("login is missing");
+
+ underTest.callback(callbackContext);
+ }
+
+ @Test
+ public void fail_to_callback_when_name_is_missing() {
+ setSettings(true);
+ DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_response_without_name.txt");
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("name is missing");
+
+ underTest.callback(callbackContext);
+ }
+
+ @Test
+ public void fail_to_callback_when_certificate_is_invalid() {
+ setSettings(true);
+ settings.setProperty("sonar.auth.saml.certificate.secured", "invalid");
+ DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_full_response.txt");
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Fail to create Auth");
+
+ underTest.callback(callbackContext);
+ }
+
+ @Test
+ public void fail_to_callback_when_using_wrong_certificate() {
+ setSettings(true);
+ settings.setProperty("sonar.auth.saml.certificate.secured", "-----BEGIN CERTIFICATE-----\n" +
+ "MIIEIzCCAwugAwIBAgIUHUzPjy5E2TmnsmTRT2sIUBRXFF8wDQYJKoZIhvcNAQEF\n" +
+ "BQAwXDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC1NvbmFyU291cmNlMRUwEwYDVQQL\n" +
+ "DAxPbmVMb2dpbiBJZFAxIDAeBgNVBAMMF09uZUxvZ2luIEFjY291bnQgMTMxMTkx\n" +
+ "MB4XDTE4MDcxOTA4NDUwNVoXDTIzMDcxOTA4NDUwNVowXDELMAkGA1UEBhMCVVMx\n" +
+ "FDASBgNVBAoMC1NvbmFyU291cmNlMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxIDAe\n" +
+ "BgNVBAMMF09uZUxvZ2luIEFjY291bnQgMTMxMTkxMIIBIjANBgkqhkiG9w0BAQEF\n" +
+ "AAOCAQ8AMIIBCgKCAQEArlpKHm4EkJiQyy+4GtZBixcy7fWnreB96T7cOoWLmWkK\n" +
+ "05FM5M/boWHZsvaNAuHsoCAMzIY3/l+55WbORzAxsloH7rvDaDrdPYQN+sU9bzsD\n" +
+ "ZkmDGDmA3QBSm/h/p5SiMkWU5Jg34toDdM0rmzUStIOMq6Gh/Ykx3fRRSjswy48x\n" +
+ "wfZLy+0wU7lasHqdfk54dVbb7mCm9J3iHZizvOt2lbtzGbP6vrrjpzvZm43ZRgP8\n" +
+ "FapYA8G3lczdIaG4IaLW6kYIRORd0UwI7IAwkao3uIo12rh1T6DLVyzjOs9PdIkb\n" +
+ "HbICN2EehB/ut3wohuPwmwp2UmqopIMVVaBSsmSlYwIDAQABo4HcMIHZMAwGA1Ud\n" +
+ "EwEB/wQCMAAwHQYDVR0OBBYEFAXGFMKYgtpzCpfpBUPQ1H/9AeDrMIGZBgNVHSME\n" +
+ "gZEwgY6AFAXGFMKYgtpzCpfpBUPQ1H/9AeDroWCkXjBcMQswCQYDVQQGEwJVUzEU\n" +
+ "MBIGA1UECgwLU29uYXJTb3VyY2UxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEgMB4G\n" +
+ "A1UEAwwXT25lTG9naW4gQWNjb3VudCAxMzExOTGCFB1Mz48uRNk5p7Jk0U9rCFAU\n" +
+ "VxRfMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQUFAAOCAQEAPHgi9IdDaTxD\n" +
+ "R5R8KHMdt385Uq8XC5pd0Li6y5RR2k6SKjThCt+eQU7D0Y2CyYU27vfCa2DQV4hJ\n" +
+ "4v4UfQv3NR/fYfkVSsNpxjBXBI3YWouxt2yg7uwdZBdgGYd37Yv3g9PdIZenjOhr\n" +
+ "Ck6WjdleMAWHRgJpocmB4IOESSyTfUul3jFupWnkbnn8c0ue6zwXd7LA1/yjVT2l\n" +
+ "Yh45+lz25aIOlyyo7OUw2TD15LIl8OOIuWRS4+UWy5+VdhXMbmpSEQH+Byod90g6\n" +
+ "A1bKpOFhRBzcxaZ6B2hB4SqjTBzS9zdmJyyFs/WNJxHri3aorcdqG9oUakjJJqqX\n" +
+ "E13skIMV2g==\n" +
+ "-----END CERTIFICATE-----\n");
+ DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_full_response.txt");
+
+ expectedException.expect(UnauthorizedException.class);
+ expectedException.expectMessage("Signature validation failed. SAML Response rejected");
+
+ underTest.callback(callbackContext);
+ }
+
+ private void setSettings(boolean enabled) {
+ if (enabled) {
+ settings.setProperty("sonar.auth.saml.applicationId", "MyApp");
+ settings.setProperty("sonar.auth.saml.providerId", "http://localhost:8080/auth/realms/sonarqube");
+ settings.setProperty("sonar.auth.saml.loginUrl", "http://localhost:8080/auth/realms/sonarqube/protocol/saml");
+ settings.setProperty("sonar.auth.saml.certificate.secured",
+ "MIICoTCCAYkCBgFksusMzTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlzb25hcnF1YmUwHhcNMTgwNzE5MTQyMDA2WhcNMjgwNzE5MTQyMTQ2WjAUMRIwEAYDVQQDDAlzb25hcnF1YmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEOth5gxpTs1f3bFGUD8hO97eMIsDZvvE3PZeKoeTRG7mOLu6rfLXphG3fE3E6/xqUhPP5p9hJl9DwgaMewhdZhfHqtOw6/SPMCQNFVNw9FQ7lprWKg8cZygYLDxhObEvCWPek8KcMb/vlKD8c8ha374O9qET51CVogDM5ropp02q0ELxoUKXqphKH4+sGXRVnDHaEsFHxse1HnciZT5mF1G45vxDItdAnWKkXYKVHC+Et52tCieqM0ygpQF1lWVJFXVOqsi03YkMu7IkWvSSfAw+uEcfmquT7FbxJ2n5gp94odAkQB0HK3fABrHr+G+n2QvWG6WwQPJTL0Ov0w+tNAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACQfOrJF98nunKz6CN+YZXXMYhzQiqTD0MlzCg+Rdhir+WC/ru3Kz8omv52W/sXEMNQbEZBksVLl8W/1xeBS41Sf1nfutU560v/j3/OmOcnCw4qebqFH7nB8RL8vA4rGx430W/PeeUMikY1mSjlwhnJGiICQ3Y8I2qM6QWEr/Df2/gFCW2YnHbnS6Q/OwRQi+UFIzKklSQQa0gAnqfM4oSKU2OMhzScinWg1buMYfJSXgd4qIhPvRsZpqBsdt/OSrU2D5Y2YfSu8oIcxBRgJoERH5BV9GdOID4fS+TYw0M0QO/ORetNw1mA/8Npsy8okF8Cn7fDgbnWC8uz+/xDc14I=");
+ settings.setProperty("sonar.auth.saml.user.login", "login");
+ settings.setProperty("sonar.auth.saml.user.name", "name");
+ settings.setProperty("sonar.auth.saml.user.email", "email");
+ settings.setProperty("sonar.auth.saml.group.name", "groups");
+ settings.setProperty("sonar.auth.saml.enabled", true);
+ } else {
+ settings.setProperty("sonar.auth.saml.enabled", false);
+ }
+ }
+
+ private static class DumbInitContext implements OAuth2IdentityProvider.InitContext {
+
+ private HttpServletResponse response = mock(HttpServletResponse.class);
+ private final AtomicBoolean generateCsrfState = new AtomicBoolean(false);
+
+ @Override
+ public String generateCsrfState() {
+ generateCsrfState.set(true);
+ return null;
+ }
+
+ @Override
+ public void redirectTo(String url) {
+ }
+
+ @Override
+ public String getCallbackUrl() {
+ return "http://localhost/oauth/callback/saml";
+ }
+
+ @Override
+ public HttpServletRequest getRequest() {
+ return mock(HttpServletRequest.class);
+ }
+
+ @Override
+ public HttpServletResponse getResponse() {
+ return response;
+ }
+ }
+
+ private static class DumbCallbackContext implements OAuth2IdentityProvider.CallbackContext {
+
+ private HttpServletResponse response = mock(HttpServletResponse.class);
+ private HttpServletRequest request = mock(HttpServletRequest.class);
+
+ private final AtomicBoolean redirectedToRequestedPage = new AtomicBoolean(false);
+ private final AtomicBoolean verifyState = new AtomicBoolean(false);
+
+ private UserIdentity userIdentity = null;
+
+ public DumbCallbackContext(String encodedResponseFile) {
+ when(getRequest().getRequestURL()).thenReturn(new StringBuffer("http://localhost/oauth/callback/saml"));
+ Map<String, String[]> parameterMap = new HashMap<>();
+ parameterMap.put("SAMLResponse", new String[] {loadResponse(encodedResponseFile)});
+ when(getRequest().getParameterMap()).thenReturn(parameterMap);
+ }
+
+ private String loadResponse(String file) {
+ try (InputStream json = getClass().getResourceAsStream("SamlIdentityProviderTest/" + file)) {
+ return IOUtils.toString(json, StandardCharsets.UTF_8.name());
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void verifyCsrfState() {
+ throw new IllegalStateException("This method should not be called !");
+ }
+
+ @Override
+ public void verifyCsrfState(String parameterName) {
+ assertThat(parameterName).isEqualTo("RelayState");
+ verifyState.set(true);
+ }
+
+ @Override
+ public void redirectToRequestedPage() {
+ redirectedToRequestedPage.set(true);
+ }
+
+ @Override
+ public void authenticate(UserIdentity userIdentity) {
+ this.userIdentity = userIdentity;
+ }
+
+ @Override
+ public String getCallbackUrl() {
+ return "http://localhost/oauth/callback/saml";
+ }
+
+ @Override
+ public HttpServletRequest getRequest() {
+ return request;
+ }
+
+ @Override
+ public HttpServletResponse getResponse() {
+ return response;
+ }
+ }
+}
diff --git a/server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlModuleTest.java b/server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlModuleTest.java
new file mode 100644
index 00000000000..ff9eb83ed73
--- /dev/null
+++ b/server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlModuleTest.java
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.auth.saml;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
+
+public class SamlModuleTest {
+
+ @Test
+ public void verify_count_of_added_components() {
+ ComponentContainer container = new ComponentContainer();
+ new SamlModule().configure(container);
+ assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 12);
+ }
+}
diff --git a/server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlSettingsTest.java b/server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlSettingsTest.java
new file mode 100644
index 00000000000..8e7b7b84ebc
--- /dev/null
+++ b/server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlSettingsTest.java
@@ -0,0 +1,227 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.auth.saml;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.internal.MapSettings;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class SamlSettingsTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MapSettings settings = new MapSettings(new PropertyDefinitions(SamlSettings.definitions()));
+
+ private SamlSettings underTest = new SamlSettings(settings.asConfig());
+
+ @Test
+ public void return_application_id() {
+ settings.setProperty("sonar.auth.saml.applicationId", "MyApp");
+
+ assertThat(underTest.getApplicationId()).isEqualTo("MyApp");
+ }
+
+ @Test
+ public void return_default_value_of_application_id() {
+ assertThat(underTest.getApplicationId()).isEqualTo("sonarqube");
+ }
+
+ @Test
+ public void return_provider_name() {
+ settings.setProperty("sonar.auth.saml.providerName", "MyProviderName");
+
+ assertThat(underTest.getProviderName()).isEqualTo("MyProviderName");
+ }
+
+ @Test
+ public void return_default_value_of_application_name() {
+ assertThat(underTest.getProviderName()).isEqualTo("SAML");
+ }
+
+ @Test
+ public void return_provider_id() {
+ settings.setProperty("sonar.auth.saml.applicationId", "http://localhost:8080/auth/realms/sonarqube");
+
+ assertThat(underTest.getApplicationId()).isEqualTo("http://localhost:8080/auth/realms/sonarqube");
+ }
+
+ @Test
+ public void return_login_url() {
+ settings.setProperty("sonar.auth.saml.loginUrl", "http://localhost:8080/");
+ assertThat(underTest.getLoginUrl()).isEqualTo("http://localhost:8080/");
+
+ settings.setProperty("sonar.auth.saml.loginUrl", "http://localhost:8080");
+ assertThat(underTest.getLoginUrl()).isEqualTo("http://localhost:8080");
+ }
+
+ @Test
+ public void return_certificate() {
+ settings.setProperty("sonar.auth.saml.certificate.secured", "ABCDEFG");
+
+ assertThat(underTest.getCertificate()).isEqualTo("ABCDEFG");
+ }
+
+ @Test
+ public void return_user_login_attribute() {
+ settings.setProperty("sonar.auth.saml.user.login", "userLogin");
+
+ assertThat(underTest.getUserLogin()).isEqualTo("userLogin");
+ }
+
+ @Test
+ public void return_user_name_attribute() {
+ settings.setProperty("sonar.auth.saml.user.name", "userName");
+
+ assertThat(underTest.getUserName()).isEqualTo("userName");
+ }
+
+ @Test
+ public void return_user_email_attribute() {
+ settings.setProperty("sonar.auth.saml.user.email", "userEmail");
+
+ assertThat(underTest.getUserEmail().get()).isEqualTo("userEmail");
+ }
+
+ @Test
+ public void return_empty_user_email_when_no_setting() {
+ assertThat(underTest.getUserEmail()).isNotPresent();
+ }
+
+ @Test
+ public void return_group_name_attribute() {
+ settings.setProperty("sonar.auth.saml.group.name", "groupName");
+
+ assertThat(underTest.getGroupName().get()).isEqualTo("groupName");
+ }
+
+ @Test
+ public void return_empty_group_name_when_no_setting() {
+ assertThat(underTest.getGroupName()).isNotPresent();
+ }
+
+ @Test
+ public void is_enabled() {
+ settings.setProperty("sonar.auth.saml.applicationId", "MyApp");
+ settings.setProperty("sonar.auth.saml.providerId", "http://localhost:8080/auth/realms/sonarqube");
+ settings.setProperty("sonar.auth.saml.loginUrl", "http://localhost:8080/auth/realms/sonarqube/protocol/saml");
+ settings.setProperty("sonar.auth.saml.certificate.secured", "ABCDEFG");
+ settings.setProperty("sonar.auth.saml.user.login", "login");
+ settings.setProperty("sonar.auth.saml.user.name", "name");
+
+ settings.setProperty("sonar.auth.saml.enabled", true);
+ assertThat(underTest.isEnabled()).isTrue();
+
+ settings.setProperty("sonar.auth.saml.enabled", false);
+ assertThat(underTest.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void is_enabled_using_default_values() {
+ settings.setProperty("sonar.auth.saml.providerId", "http://localhost:8080/auth/realms/sonarqube");
+ settings.setProperty("sonar.auth.saml.loginUrl", "http://localhost:8080/auth/realms/sonarqube/protocol/saml");
+ settings.setProperty("sonar.auth.saml.certificate.secured", "ABCDEFG");
+ settings.setProperty("sonar.auth.saml.user.login", "login");
+ settings.setProperty("sonar.auth.saml.user.name", "name");
+ settings.setProperty("sonar.auth.saml.enabled", true);
+ assertThat(underTest.isEnabled()).isTrue();
+ }
+
+ @DataProvider
+ public static Object[][] settingsRequiredToEnablePlugin() {
+ return new Object[][] {
+ {"sonar.auth.saml.providerId"},
+ {"sonar.auth.saml.loginUrl"},
+ {"sonar.auth.saml.certificate.secured"},
+ {"sonar.auth.saml.user.login"},
+ {"sonar.auth.saml.user.name"},
+ {"sonar.auth.saml.enabled"},
+ };
+ }
+
+ @Test
+ @UseDataProvider("settingsRequiredToEnablePlugin")
+ public void is_enabled_return_false_when_one_required_setting_is_missing(String setting) {
+ initAllSettings();
+ settings.setProperty(setting, (String) null);
+
+ assertThat(underTest.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void fail_to_get_provider_id_when_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Provider ID is missing");
+
+ underTest.getProviderId();
+ }
+
+ @Test
+ public void fail_to_get_login_url_when_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Login URL is missing");
+
+ underTest.getLoginUrl();
+ }
+
+ @Test
+ public void fail_to_get_certificate_when_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Certificate is missing");
+
+ underTest.getCertificate();
+ }
+
+ @Test
+ public void fail_to_get_user_login_attribute_when_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("User login attribute is missing");
+
+ underTest.getUserLogin();
+ }
+
+ @Test
+ public void fail_to_get_user_name_attribute_when_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("User name attribute is missing");
+
+ underTest.getUserName();
+ }
+
+ private void initAllSettings() {
+ settings.setProperty("sonar.auth.saml.applicationId", "MyApp");
+ settings.setProperty("sonar.auth.saml.providerId", "http://localhost:8080/auth/realms/sonarqube");
+ settings.setProperty("sonar.auth.saml.loginUrl", "http://localhost:8080/auth/realms/sonarqube/protocol/saml");
+ settings.setProperty("sonar.auth.saml.certificate.secured", "ABCDEFG");
+ settings.setProperty("sonar.auth.saml.user.login", "login");
+ settings.setProperty("sonar.auth.saml.user.name", "name");
+ settings.setProperty("sonar.auth.saml.enabled", true);
+ }
+
+}
diff --git a/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_full_response.txt b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_full_response.txt
new file mode 100644
index 00000000000..95f2ffd7b9b
--- /dev/null
+++ b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_full_response.txt
@@ -0,0 +1 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjkwMDAvb2F1dGgyL2NhbGxiYWNrL3NhbWwiIElEPSJJRF82NThhMzk1Ni1kN2M0LTQ3N2MtODg4Zi1jZjRlYTI0ZTljMjciIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fNmM5YTc3Y2UtYWZiOS00Njk0LTllMzctOTg4MTAzYTQwYzQ4IiBJc3N1ZUluc3RhbnQ9IjIwMTgtMDctMjRUMTE6MTQ6MjkuNjYxWiIgVmVyc2lvbj0iMi4wIj48c2FtbDpJc3N1ZXI+aHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3NvbmFycXViZTwvc2FtbDpJc3N1ZXI+PGRzaWc6U2lnbmF0dXJlIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkc2lnOlNpZ25lZEluZm8+PGRzaWc6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkc2lnOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48ZHNpZzpSZWZlcmVuY2UgVVJJPSIjSURfNjU4YTM5NTYtZDdjNC00NzdjLTg4OGYtY2Y0ZWEyNGU5YzI3Ij48ZHNpZzpUcmFuc2Zvcm1zPjxkc2lnOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzaWc6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kc2lnOlRyYW5zZm9ybXM+PGRzaWc6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8+PGRzaWc6RGlnZXN0VmFsdWU+S2Z2ZXNxMFhyQjRWTlRjZG1yenVFUFVhRWxXdnpHNFRKSjRwcmVUN1NGdz08L2RzaWc6RGlnZXN0VmFsdWU+PC9kc2lnOlJlZmVyZW5jZT48L2RzaWc6U2lnbmVkSW5mbz48ZHNpZzpTaWduYXR1cmVWYWx1ZT5WbVVRam5pNE5rNXFNd3U0eDkwV2VHNExLNjViNGk4Slh1bVR2UlRmL01zUmVlRCt1bmd4cjM1SUxIVW9tRml3c2hPZTNjaVZ3bWFnZkZNQnJ5N0VqV0RUWEhSbVlMMkFSYUFLWUgwdVpNVGE2VE9iZHYxVGVpdjBITElUWFJNd052em9XMXJrUUU4NHMzUVpYRVgwUEJXSnYvYi9XS21WZVNFRWZIQnpzeHB6YVpGcWZra3gwZFN6VWxvRG5iWG9sTUREOWg0ZU5rc3JHK3V2TzdSK2FLditPd1lKVXJXU0lBbkx2WGx2NnRmcjJWc2U4UXNoTVNnRThwYkEwNDd5NFJJcTN0M1dnTkRlYVJFeUljN2I3ZkthNEdISWp2MWI3YUYyRmg2NldBSElPSStxdTB3SnMzcmVZSlVGd1lxTjVyVUNmc3Z6WEhoQ2dQSFRiZmhnZ0E9PTwvZHNpZzpTaWduYXR1cmVWYWx1ZT48ZHNpZzpLZXlJbmZvPjxkc2lnOktleU5hbWU+OXlpX2VjM0FLSlV6ZzZNZkRfeFlpSUtjZzR6bEJpSHFURHNFQ0lHZWhTazwvZHNpZzpLZXlOYW1lPjxkc2lnOlg1MDlEYXRhPjxkc2lnOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDb1RDQ0FZa0NCZ0Zrc3VzTXpUQU5CZ2txaGtpRzl3MEJBUXNGQURBVU1SSXdFQVlEVlFRRERBbHpiMjVoY25GMVltVXdIaGNOTVRnd056RTVNVFF5TURBMldoY05Namd3TnpFNU1UUXlNVFEyV2pBVU1SSXdFQVlEVlFRRERBbHpiMjVoY25GMVltVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFERU90aDVneHBUczFmM2JGR1VEOGhPOTdlTUlzRFp2dkUzUFplS29lVFJHN21PTHU2cmZMWHBoRzNmRTNFNi94cVVoUFA1cDloSmw5RHdnYU1ld2hkWmhmSHF0T3c2L1NQTUNRTkZWTnc5RlE3bHByV0tnOGNaeWdZTER4aE9iRXZDV1BlazhLY01iL3ZsS0Q4YzhoYTM3NE85cUVUNTFDVm9nRE01cm9wcDAycTBFTHhvVUtYcXBoS0g0K3NHWFJWbkRIYUVzRkh4c2UxSG5jaVpUNW1GMUc0NXZ4REl0ZEFuV0trWFlLVkhDK0V0NTJ0Q2llcU0weWdwUUYxbFdWSkZYVk9xc2kwM1lrTXU3SWtXdlNTZkF3K3VFY2ZtcXVUN0ZieEoybjVncDk0b2RBa1FCMEhLM2ZBQnJIcitHK24yUXZXRzZXd1FQSlRMME92MHcrdE5BZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDUWZPckpGOThudW5LejZDTitZWlhYTVloelFpcVREME1sekNnK1JkaGlyK1dDL3J1M0t6OG9tdjUyVy9zWEVNTlFiRVpCa3NWTGw4Vy8xeGVCUzQxU2YxbmZ1dFU1NjB2L2ozL09tT2NuQ3c0cWVicUZIN25COFJMOHZBNHJHeDQzMFcvUGVlVU1pa1kxbVNqbHdobkpHaUlDUTNZOEkycU02UVdFci9EZjIvZ0ZDVzJZbkhiblM2US9Pd1JRaStVRkl6S2tsU1FRYTBnQW5xZk00b1NLVTJPTWh6U2NpbldnMWJ1TVlmSlNYZ2Q0cUloUHZSc1pwcUJzZHQvT1NyVTJENVkyWWZTdThvSWN4QlJnSm9FUkg1QlY5R2RPSUQ0ZlMrVFl3ME0wUU8vT1JldE53MW1BLzhOcHN5OG9rRjhDbjdmRGdibldDOHV6Ky94RGMxNEk9PC9kc2lnOlg1MDlDZXJ0aWZpY2F0ZT48L2RzaWc6WDUwOURhdGE+PGRzaWc6S2V5VmFsdWU+PGRzaWc6UlNBS2V5VmFsdWU+PGRzaWc6TW9kdWx1cz54RHJZZVlNYVU3Tlg5MnhSbEEvSVR2ZTNqQ0xBMmI3eE56MlhpcUhrMFJ1NWppN3VxM3kxNllSdDN4TnhPdjhhbElUeithZllTWmZROElHakhzSVhXWVh4NnJUc092MGp6QWtEUlZUY1BSVU81YWExaW9QSEdjb0dDdzhZVG14THdsajNwUENuREcvNzVTZy9IUElXdCsrRHZhaEUrZFFsYUlBek9hNkthZE5xdEJDOGFGQ2w2cVlTaCtQckJsMFZad3gyaExCUjhiSHRSNTNJbVUrWmhkUnVPYjhReUxYUUoxaXBGMkNsUnd2aExlZHJRb25xak5Nb0tVQmRaVmxTUlYxVHFySXROMkpETHV5SkZyMGtud01QcmhISDVxcmsreFc4U2RwK1lLZmVLSFFKRUFkQnl0M3dBYXg2L2h2cDlrTDFodWxzRUR5VXk5RHI5TVByVFE9PTwvZHNpZzpNb2R1bHVzPjxkc2lnOkV4cG9uZW50PkFRQUI8L2RzaWc6RXhwb25lbnQ+PC9kc2lnOlJTQUtleVZhbHVlPjwvZHNpZzpLZXlWYWx1ZT48L2RzaWc6S2V5SW5mbz48L2RzaWc6U2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9IklEXzMwNTFkZDc1LTJlMGUtNGYyMi05YjA3LWIwZDhlNDg5YjlkMSIgSXNzdWVJbnN0YW50PSIyMDE4LTA3LTI0VDExOjE0OjI5LjY2MVoiIFZlcnNpb249IjIuMCI+PHNhbWw6SXNzdWVyPmh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9zb25hcnF1YmU8L3NhbWw6SXNzdWVyPjxkc2lnOlNpZ25hdHVyZSB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHNpZzpTaWduZWRJbmZvPjxkc2lnOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHNpZzpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+PGRzaWc6UmVmZXJlbmNlIFVSST0iI0lEXzMwNTFkZDc1LTJlMGUtNGYyMi05YjA3LWIwZDhlNDg5YjlkMSI+PGRzaWc6VHJhbnNmb3Jtcz48ZHNpZzpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkc2lnOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHNpZzpUcmFuc2Zvcm1zPjxkc2lnOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPjxkc2lnOkRpZ2VzdFZhbHVlPk9qNVFtQitRaTJheVVJV2RDS0pvaE9BNW4wc0JNNkI3ajVOSUlaYlhrVXM9PC9kc2lnOkRpZ2VzdFZhbHVlPjwvZHNpZzpSZWZlcmVuY2U+PC9kc2lnOlNpZ25lZEluZm8+PGRzaWc6U2lnbmF0dXJlVmFsdWU+T3VXUDNEL1lVVXpDWUtKdSs4R3dwUWc3dE9NenE3aUMxN0dSWnNoVUw4SS9tQVlLRGd0THVkd0g4MmJrZ0Jxd291N1MyZWhCQWlKekQwS0tYdGJEYzZSeWRYTllONzRMandFMW1FM2pveStrTG5pZGdzNHZBVTJCSHpjbHVxNXcxNUF3cUhrcUJJSGI3NmNqNkNrUVpWNHpHL0pWclBFa2tMWnFyRFRNcCtWQ3BEQXhBVDd0cDlBdnhhWUYwNUU4a24xWEJmYTI5N28wTW8wWXczUnZFMHVFb05iMnkwZ2tiSGRtcVhSdmJPR3FYTlc0NExCSEIrVXM4bmRZUDUxUTg5cUh2VElWV1I2bWJ2c1diU3JpbnVad1lzNnZSS2p0K0puR1lZK1p6REk2VU9XYjJOWFlTNFJDUlcwKyt6UFFKZy9GaUNxVFV1aEVwUmpybFUvL0FRPT08L2RzaWc6U2lnbmF0dXJlVmFsdWU+PGRzaWc6S2V5SW5mbz48ZHNpZzpLZXlOYW1lPjl5aV9lYzNBS0pVemc2TWZEX3hZaUlLY2c0emxCaUhxVERzRUNJR2VoU2s8L2RzaWc6S2V5TmFtZT48ZHNpZzpYNTA5RGF0YT48ZHNpZzpYNTA5Q2VydGlmaWNhdGU+TUlJQ29UQ0NBWWtDQmdGa3N1c016VEFOQmdrcWhraUc5dzBCQVFzRkFEQVVNUkl3RUFZRFZRUUREQWx6YjI1aGNuRjFZbVV3SGhjTk1UZ3dOekU1TVRReU1EQTJXaGNOTWpnd056RTVNVFF5TVRRMldqQVVNUkl3RUFZRFZRUUREQWx6YjI1aGNuRjFZbVV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRREVPdGg1Z3hwVHMxZjNiRkdVRDhoTzk3ZU1Jc0RadnZFM1BaZUtvZVRSRzdtT0x1NnJmTFhwaEczZkUzRTYveHFVaFBQNXA5aEpsOUR3Z2FNZXdoZFpoZkhxdE93Ni9TUE1DUU5GVk53OUZRN2xwcldLZzhjWnlnWUxEeGhPYkV2Q1dQZWs4S2NNYi92bEtEOGM4aGEzNzRPOXFFVDUxQ1ZvZ0RNNXJvcHAwMnEwRUx4b1VLWHFwaEtINCtzR1hSVm5ESGFFc0ZIeHNlMUhuY2laVDVtRjFHNDV2eERJdGRBbldLa1hZS1ZIQytFdDUydENpZXFNMHlncFFGMWxXVkpGWFZPcXNpMDNZa011N0lrV3ZTU2ZBdyt1RWNmbXF1VDdGYnhKMm41Z3A5NG9kQWtRQjBISzNmQUJySHIrRytuMlF2V0c2V3dRUEpUTDBPdjB3K3ROQWdNQkFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQ1FmT3JKRjk4bnVuS3o2Q04rWVpYWE1ZaHpRaXFURDBNbHpDZytSZGhpcitXQy9ydTNLejhvbXY1Mlcvc1hFTU5RYkVaQmtzVkxsOFcvMXhlQlM0MVNmMW5mdXRVNTYwdi9qMy9PbU9jbkN3NHFlYnFGSDduQjhSTDh2QTRyR3g0MzBXL1BlZVVNaWtZMW1Tamx3aG5KR2lJQ1EzWThJMnFNNlFXRXIvRGYyL2dGQ1cyWW5IYm5TNlEvT3dSUWkrVUZJektrbFNRUWEwZ0FucWZNNG9TS1UyT01oelNjaW5XZzFidU1ZZkpTWGdkNHFJaFB2UnNacHFCc2R0L09TclUyRDVZMllmU3U4b0ljeEJSZ0pvRVJINUJWOUdkT0lENGZTK1RZdzBNMFFPL09SZXROdzFtQS84TnBzeThva0Y4Q243ZkRnYm5XQzh1eisveERjMTRJPTwvZHNpZzpYNTA5Q2VydGlmaWNhdGU+PC9kc2lnOlg1MDlEYXRhPjxkc2lnOktleVZhbHVlPjxkc2lnOlJTQUtleVZhbHVlPjxkc2lnOk1vZHVsdXM+eERyWWVZTWFVN05YOTJ4UmxBL0lUdmUzakNMQTJiN3hOejJYaXFIazBSdTVqaTd1cTN5MTZZUnQzeE54T3Y4YWxJVHorYWZZU1pmUThJR2pIc0lYV1lYeDZyVHNPdjBqekFrRFJWVGNQUlVPNWFhMWlvUEhHY29HQ3c4WVRteEx3bGozcFBDbkRHLzc1U2cvSFBJV3QrK0R2YWhFK2RRbGFJQXpPYTZLYWROcXRCQzhhRkNsNnFZU2grUHJCbDBWWnd4MmhMQlI4Ykh0UjUzSW1VK1poZFJ1T2I4UXlMWFFKMWlwRjJDbFJ3dmhMZWRyUW9ucWpOTW9LVUJkWlZsU1JWMVRxckl0TjJKREx1eUpGcjBrbndNUHJoSEg1cXJrK3hXOFNkcCtZS2ZlS0hRSkVBZEJ5dDN3QWF4Ni9odnA5a0wxaHVsc0VEeVV5OURyOU1QclRRPT08L2RzaWc6TW9kdWx1cz48ZHNpZzpFeHBvbmVudD5BUUFCPC9kc2lnOkV4cG9uZW50PjwvZHNpZzpSU0FLZXlWYWx1ZT48L2RzaWc6S2V5VmFsdWU+PC9kc2lnOktleUluZm8+PC9kc2lnOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj5qb2huZG9lPC9zYW1sOk5hbWVJRD48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl82YzlhNzdjZS1hZmI5LTQ2OTQtOWUzNy05ODgxMDNhNDBjNDgiIE5vdE9uT3JBZnRlcj0iMjAxOC0wNy0yNFQxMToxOToyNy42NjFaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMC9vYXV0aDIvY2FsbGJhY2svc2FtbCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE4LTA3LTI0VDExOjE0OjI3LjY2MVoiIE5vdE9uT3JBZnRlcj0iMjAxOC0wNy0yNFQxMToxNToyNy42NjFaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPnNvbmFycXViZTwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTgtMDctMjRUMTE6MTQ6MjkuNjYxWiIgU2Vzc2lvbkluZGV4PSI0MDBhNDdkNy04Yzc2LTQyOTYtYmMxMy1mNjE0NmFkYjc2NzQ6OmI3OWFkZDMwLWFhN2MtNDQ1ZS1hYmU0LTJjZDliZTg3YmMxZCI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOnVuc3BlY2lmaWVkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJVc2VybmFtZSIgTmFtZT0ibG9naW4iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+am9obmRvZTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBGcmllbmRseU5hbWU9IkVtYWlsIiBOYW1lPSJlbWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5qb2huZG9lQGVtYWlsLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBGcmllbmRseU5hbWU9Ik5hbWUiIE5hbWU9Im5hbWUiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+Sm9obiBEb2U8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJHcm91cHMiIE5hbWU9Imdyb3VwcyIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5kZXZlbG9wZXI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5wcm9kdWN0LW1hbmFnZXI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4= \ No newline at end of file
diff --git a/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_minimal_response.txt b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_minimal_response.txt
new file mode 100644
index 00000000000..e4b4fd44e7e
--- /dev/null
+++ b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_minimal_response.txt
@@ -0,0 +1 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjkwMDAvb2F1dGgyL2NhbGxiYWNrL3NhbWwiIElEPSJJRF83NDA0MWNiOC05MzEzLTRlYWUtYmExYy1hMTVlZDcxNzAzZGMiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fM2Y4N2U1YmYtYjNiZi00N2E0LWIzYTMtYmI4N2U3MmQ1M2E4IiBJc3N1ZUluc3RhbnQ9IjIwMTgtMDctMjRUMTE6NDY6MTkuMDQzWiIgVmVyc2lvbj0iMi4wIj48c2FtbDpJc3N1ZXI+aHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3NvbmFycXViZTwvc2FtbDpJc3N1ZXI+PGRzaWc6U2lnbmF0dXJlIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkc2lnOlNpZ25lZEluZm8+PGRzaWc6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkc2lnOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48ZHNpZzpSZWZlcmVuY2UgVVJJPSIjSURfNzQwNDFjYjgtOTMxMy00ZWFlLWJhMWMtYTE1ZWQ3MTcwM2RjIj48ZHNpZzpUcmFuc2Zvcm1zPjxkc2lnOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzaWc6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kc2lnOlRyYW5zZm9ybXM+PGRzaWc6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8+PGRzaWc6RGlnZXN0VmFsdWU+bmt0NjNacUtydXpTNWdGd0lnL2FvZ3ZhVVVYTXRjQUpyQ1NHazBxUytHQT08L2RzaWc6RGlnZXN0VmFsdWU+PC9kc2lnOlJlZmVyZW5jZT48L2RzaWc6U2lnbmVkSW5mbz48ZHNpZzpTaWduYXR1cmVWYWx1ZT5KRG04U1pCQjRGa3J6bjRaMU9OQ1BFY1JkRWRPQ3MxL0Y4QjhINXVuUEFOTnJFaml5VFoyenY4RVRvcXU1THB0YUpjbm4zMVE0Zzl1RXFidTFZdktCR3ZRZTV3aXplcFFtTXVaSk1kWmdIZzFlaFhSWlZ4bmNhb2kvY0FFbXhyZ3poVnpyYlpjTGRITEpjSitSSW9qK2l0V0w1d01KcWFLT1hRVVNXTkdzNjRFVWFPT0hKRWVxRllRbFVvbGsxNE5kMVBoY2hYd2V4YlBGQmdwOEZnK25xTTAvNzMzQ0tNL1VScmYyQ2puZE1OWWxSOG9scThLNHp5OE5kUmRUSnM1NjB1bkNPOVNMZ05hWnlNYnRPdkxlaXZlZGFhVHAvNHFub2g0NFRNVmJxNEdOU2FUYVNmQVlTMlMzT0FjdHdGTmRZTm55QXE2Ym04WU9sU0ZDNThqc2c9PTwvZHNpZzpTaWduYXR1cmVWYWx1ZT48ZHNpZzpLZXlJbmZvPjxkc2lnOktleU5hbWU+OXlpX2VjM0FLSlV6ZzZNZkRfeFlpSUtjZzR6bEJpSHFURHNFQ0lHZWhTazwvZHNpZzpLZXlOYW1lPjxkc2lnOlg1MDlEYXRhPjxkc2lnOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDb1RDQ0FZa0NCZ0Zrc3VzTXpUQU5CZ2txaGtpRzl3MEJBUXNGQURBVU1SSXdFQVlEVlFRRERBbHpiMjVoY25GMVltVXdIaGNOTVRnd056RTVNVFF5TURBMldoY05Namd3TnpFNU1UUXlNVFEyV2pBVU1SSXdFQVlEVlFRRERBbHpiMjVoY25GMVltVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFERU90aDVneHBUczFmM2JGR1VEOGhPOTdlTUlzRFp2dkUzUFplS29lVFJHN21PTHU2cmZMWHBoRzNmRTNFNi94cVVoUFA1cDloSmw5RHdnYU1ld2hkWmhmSHF0T3c2L1NQTUNRTkZWTnc5RlE3bHByV0tnOGNaeWdZTER4aE9iRXZDV1BlazhLY01iL3ZsS0Q4YzhoYTM3NE85cUVUNTFDVm9nRE01cm9wcDAycTBFTHhvVUtYcXBoS0g0K3NHWFJWbkRIYUVzRkh4c2UxSG5jaVpUNW1GMUc0NXZ4REl0ZEFuV0trWFlLVkhDK0V0NTJ0Q2llcU0weWdwUUYxbFdWSkZYVk9xc2kwM1lrTXU3SWtXdlNTZkF3K3VFY2ZtcXVUN0ZieEoybjVncDk0b2RBa1FCMEhLM2ZBQnJIcitHK24yUXZXRzZXd1FQSlRMME92MHcrdE5BZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDUWZPckpGOThudW5LejZDTitZWlhYTVloelFpcVREME1sekNnK1JkaGlyK1dDL3J1M0t6OG9tdjUyVy9zWEVNTlFiRVpCa3NWTGw4Vy8xeGVCUzQxU2YxbmZ1dFU1NjB2L2ozL09tT2NuQ3c0cWVicUZIN25COFJMOHZBNHJHeDQzMFcvUGVlVU1pa1kxbVNqbHdobkpHaUlDUTNZOEkycU02UVdFci9EZjIvZ0ZDVzJZbkhiblM2US9Pd1JRaStVRkl6S2tsU1FRYTBnQW5xZk00b1NLVTJPTWh6U2NpbldnMWJ1TVlmSlNYZ2Q0cUloUHZSc1pwcUJzZHQvT1NyVTJENVkyWWZTdThvSWN4QlJnSm9FUkg1QlY5R2RPSUQ0ZlMrVFl3ME0wUU8vT1JldE53MW1BLzhOcHN5OG9rRjhDbjdmRGdibldDOHV6Ky94RGMxNEk9PC9kc2lnOlg1MDlDZXJ0aWZpY2F0ZT48L2RzaWc6WDUwOURhdGE+PGRzaWc6S2V5VmFsdWU+PGRzaWc6UlNBS2V5VmFsdWU+PGRzaWc6TW9kdWx1cz54RHJZZVlNYVU3Tlg5MnhSbEEvSVR2ZTNqQ0xBMmI3eE56MlhpcUhrMFJ1NWppN3VxM3kxNllSdDN4TnhPdjhhbElUeithZllTWmZROElHakhzSVhXWVh4NnJUc092MGp6QWtEUlZUY1BSVU81YWExaW9QSEdjb0dDdzhZVG14THdsajNwUENuREcvNzVTZy9IUElXdCsrRHZhaEUrZFFsYUlBek9hNkthZE5xdEJDOGFGQ2w2cVlTaCtQckJsMFZad3gyaExCUjhiSHRSNTNJbVUrWmhkUnVPYjhReUxYUUoxaXBGMkNsUnd2aExlZHJRb25xak5Nb0tVQmRaVmxTUlYxVHFySXROMkpETHV5SkZyMGtud01QcmhISDVxcmsreFc4U2RwK1lLZmVLSFFKRUFkQnl0M3dBYXg2L2h2cDlrTDFodWxzRUR5VXk5RHI5TVByVFE9PTwvZHNpZzpNb2R1bHVzPjxkc2lnOkV4cG9uZW50PkFRQUI8L2RzaWc6RXhwb25lbnQ+PC9kc2lnOlJTQUtleVZhbHVlPjwvZHNpZzpLZXlWYWx1ZT48L2RzaWc6S2V5SW5mbz48L2RzaWc6U2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9IklEXzM3MDEzMGFmLTExY2MtNDg4OS04NThjLTFhZWUxYzE2ZmYxYiIgSXNzdWVJbnN0YW50PSIyMDE4LTA3LTI0VDExOjQ2OjE5LjA0M1oiIFZlcnNpb249IjIuMCI+PHNhbWw6SXNzdWVyPmh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9zb25hcnF1YmU8L3NhbWw6SXNzdWVyPjxkc2lnOlNpZ25hdHVyZSB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHNpZzpTaWduZWRJbmZvPjxkc2lnOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHNpZzpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+PGRzaWc6UmVmZXJlbmNlIFVSST0iI0lEXzM3MDEzMGFmLTExY2MtNDg4OS04NThjLTFhZWUxYzE2ZmYxYiI+PGRzaWc6VHJhbnNmb3Jtcz48ZHNpZzpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkc2lnOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHNpZzpUcmFuc2Zvcm1zPjxkc2lnOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPjxkc2lnOkRpZ2VzdFZhbHVlPm5rNUR4QXllWFZyM3Bsd1FHbTQ4MTJIa0NzY3pCYk9QZHl3NkJSMThOZDg9PC9kc2lnOkRpZ2VzdFZhbHVlPjwvZHNpZzpSZWZlcmVuY2U+PC9kc2lnOlNpZ25lZEluZm8+PGRzaWc6U2lnbmF0dXJlVmFsdWU+U0h4MFFMMFpSeVF5UlRxYytkc0RvUHhaUmRCWG9PR3pmaU1TeUp3dWhBWUpoVWxjdzRLRWlnUFliYkhzKy9MMExQRC9aVllEMnUreXdsSXk4R2ljQWFncVFzME9zUnNPZG1ncnB6THVBeWxUNWpjeXJDUWJqbG5WdE5XM2FXTkR6UzBRTDN3UnhyM09UemRlVjVGa0Q1UEZnSTJzYXpIVUtLeTE0cFVQMHR0Ykh5QWV2MXJtRzdXMFdlbkxNU1ZuUTlSRjNjbHRrcHU1bUlXNmMwNGdKMTNVclV2dUpBSkRhOHpKTEEzVWNXdEo1ZFBlRkxYamc5dExSUkVUbHlXV0Y0dGdwd2psSVg3bW8rMHZOcG1zdlpmS3d1V0owNlpacE12NlhSaWoxeHVmVXJYQXMwaFFORzc3T2cwRENoSVhiUTl3a0lKMU54dThORGVGY3JVazRnPT08L2RzaWc6U2lnbmF0dXJlVmFsdWU+PGRzaWc6S2V5SW5mbz48ZHNpZzpLZXlOYW1lPjl5aV9lYzNBS0pVemc2TWZEX3hZaUlLY2c0emxCaUhxVERzRUNJR2VoU2s8L2RzaWc6S2V5TmFtZT48ZHNpZzpYNTA5RGF0YT48ZHNpZzpYNTA5Q2VydGlmaWNhdGU+TUlJQ29UQ0NBWWtDQmdGa3N1c016VEFOQmdrcWhraUc5dzBCQVFzRkFEQVVNUkl3RUFZRFZRUUREQWx6YjI1aGNuRjFZbVV3SGhjTk1UZ3dOekU1TVRReU1EQTJXaGNOTWpnd056RTVNVFF5TVRRMldqQVVNUkl3RUFZRFZRUUREQWx6YjI1aGNuRjFZbVV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRREVPdGg1Z3hwVHMxZjNiRkdVRDhoTzk3ZU1Jc0RadnZFM1BaZUtvZVRSRzdtT0x1NnJmTFhwaEczZkUzRTYveHFVaFBQNXA5aEpsOUR3Z2FNZXdoZFpoZkhxdE93Ni9TUE1DUU5GVk53OUZRN2xwcldLZzhjWnlnWUxEeGhPYkV2Q1dQZWs4S2NNYi92bEtEOGM4aGEzNzRPOXFFVDUxQ1ZvZ0RNNXJvcHAwMnEwRUx4b1VLWHFwaEtINCtzR1hSVm5ESGFFc0ZIeHNlMUhuY2laVDVtRjFHNDV2eERJdGRBbldLa1hZS1ZIQytFdDUydENpZXFNMHlncFFGMWxXVkpGWFZPcXNpMDNZa011N0lrV3ZTU2ZBdyt1RWNmbXF1VDdGYnhKMm41Z3A5NG9kQWtRQjBISzNmQUJySHIrRytuMlF2V0c2V3dRUEpUTDBPdjB3K3ROQWdNQkFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQ1FmT3JKRjk4bnVuS3o2Q04rWVpYWE1ZaHpRaXFURDBNbHpDZytSZGhpcitXQy9ydTNLejhvbXY1Mlcvc1hFTU5RYkVaQmtzVkxsOFcvMXhlQlM0MVNmMW5mdXRVNTYwdi9qMy9PbU9jbkN3NHFlYnFGSDduQjhSTDh2QTRyR3g0MzBXL1BlZVVNaWtZMW1Tamx3aG5KR2lJQ1EzWThJMnFNNlFXRXIvRGYyL2dGQ1cyWW5IYm5TNlEvT3dSUWkrVUZJektrbFNRUWEwZ0FucWZNNG9TS1UyT01oelNjaW5XZzFidU1ZZkpTWGdkNHFJaFB2UnNacHFCc2R0L09TclUyRDVZMllmU3U4b0ljeEJSZ0pvRVJINUJWOUdkT0lENGZTK1RZdzBNMFFPL09SZXROdzFtQS84TnBzeThva0Y4Q243ZkRnYm5XQzh1eisveERjMTRJPTwvZHNpZzpYNTA5Q2VydGlmaWNhdGU+PC9kc2lnOlg1MDlEYXRhPjxkc2lnOktleVZhbHVlPjxkc2lnOlJTQUtleVZhbHVlPjxkc2lnOk1vZHVsdXM+eERyWWVZTWFVN05YOTJ4UmxBL0lUdmUzakNMQTJiN3hOejJYaXFIazBSdTVqaTd1cTN5MTZZUnQzeE54T3Y4YWxJVHorYWZZU1pmUThJR2pIc0lYV1lYeDZyVHNPdjBqekFrRFJWVGNQUlVPNWFhMWlvUEhHY29HQ3c4WVRteEx3bGozcFBDbkRHLzc1U2cvSFBJV3QrK0R2YWhFK2RRbGFJQXpPYTZLYWROcXRCQzhhRkNsNnFZU2grUHJCbDBWWnd4MmhMQlI4Ykh0UjUzSW1VK1poZFJ1T2I4UXlMWFFKMWlwRjJDbFJ3dmhMZWRyUW9ucWpOTW9LVUJkWlZsU1JWMVRxckl0TjJKREx1eUpGcjBrbndNUHJoSEg1cXJrK3hXOFNkcCtZS2ZlS0hRSkVBZEJ5dDN3QWF4Ni9odnA5a0wxaHVsc0VEeVV5OURyOU1QclRRPT08L2RzaWc6TW9kdWx1cz48ZHNpZzpFeHBvbmVudD5BUUFCPC9kc2lnOkV4cG9uZW50PjwvZHNpZzpSU0FLZXlWYWx1ZT48L2RzaWc6S2V5VmFsdWU+PC9kc2lnOktleUluZm8+PC9kc2lnOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj5qb2huZG9lPC9zYW1sOk5hbWVJRD48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl8zZjg3ZTViZi1iM2JmLTQ3YTQtYjNhMy1iYjg3ZTcyZDUzYTgiIE5vdE9uT3JBZnRlcj0iMjAxOC0wNy0yNFQxMTo1MToxNy4wNDNaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMC9vYXV0aDIvY2FsbGJhY2svc2FtbCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE4LTA3LTI0VDExOjQ2OjE3LjA0M1oiIE5vdE9uT3JBZnRlcj0iMjAxOC0wNy0yNFQxMTo0NzoxNy4wNDNaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPnNvbmFycXViZTwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTgtMDctMjRUMTE6NDY6MTkuMDQzWiIgU2Vzc2lvbkluZGV4PSI0MDBhNDdkNy04Yzc2LTQyOTYtYmMxMy1mNjE0NmFkYjc2NzQ6OmI3OWFkZDMwLWFhN2MtNDQ1ZS1hYmU0LTJjZDliZTg3YmMxZCI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOnVuc3BlY2lmaWVkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJVc2VybmFtZSIgTmFtZT0ibG9naW4iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+am9obmRvZTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBGcmllbmRseU5hbWU9Ik5hbWUiIE5hbWU9Im5hbWUiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+Sm9obiBEb2U8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4= \ No newline at end of file
diff --git a/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_response_without_login.txt b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_response_without_login.txt
new file mode 100644
index 00000000000..cf3754717e4
--- /dev/null
+++ b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_response_without_login.txt
@@ -0,0 +1 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjkwMDAvb2F1dGgyL2NhbGxiYWNrL3NhbWwiIElEPSJJRF8zMjM0ZDJjMC1lZGUzLTQ5Y2YtYTk5My1mNjFiYjg4ODE3N2QiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fYWY3ZDNmMDMtNmUxMS00NWRlLWFiMjctOGE2NzEwMGE3MTMwIiBJc3N1ZUluc3RhbnQ9IjIwMTgtMDctMjRUMTE6NTc6MzYuOTk5WiIgVmVyc2lvbj0iMi4wIj48c2FtbDpJc3N1ZXI+aHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3NvbmFycXViZTwvc2FtbDpJc3N1ZXI+PGRzaWc6U2lnbmF0dXJlIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkc2lnOlNpZ25lZEluZm8+PGRzaWc6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkc2lnOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48ZHNpZzpSZWZlcmVuY2UgVVJJPSIjSURfMzIzNGQyYzAtZWRlMy00OWNmLWE5OTMtZjYxYmI4ODgxNzdkIj48ZHNpZzpUcmFuc2Zvcm1zPjxkc2lnOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzaWc6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kc2lnOlRyYW5zZm9ybXM+PGRzaWc6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8+PGRzaWc6RGlnZXN0VmFsdWU+V2FhemFlVURGb3AwYVhXa3pwM2p4dGZyUGlNN0owS1N5ZjlSUU1sNEJwcz08L2RzaWc6RGlnZXN0VmFsdWU+PC9kc2lnOlJlZmVyZW5jZT48L2RzaWc6U2lnbmVkSW5mbz48ZHNpZzpTaWduYXR1cmVWYWx1ZT5oVVY0N0NHTDloOFNFbHdlY0FlTEp2YnR0SUx1SSttMGRiOElqRVZqUXdqQzgrSHFvM0hiMjFsWDNQcWFnZENLZkZ1elEyVkhZY0pSWkFFWThocXpaMklsOWFGQWhnSy82ZnBPMzNNUVdOMkorRmZocGhqOTRVZFBDUGhLTVdIUWF4KzZaYVhjL0ZjT21lK0svYk01a3QveVFrNTN5NDJ5Z3p5SDlvZ3JaaW5PZVE3WThjRnNMdlcrdjMvVlVrVnNYZUo3R0pmdTNSYXY2TlhNSTdtQi9COEZlZDlTWWN5a0tFVmRzOVoxMDVxSDB5WUFoT0psalpqdnpDK3VoWEI3T2lUenY0aHVYT1FndmppVWVsTjZMN0xRTWwwWjFLRjRuT2xUYkR1Q2NMSjQrNTNHbEoxaGROeUFaS05MdFRVVEc1bVE3WUFmK3cxTnhIeE1DL0FHVkE9PTwvZHNpZzpTaWduYXR1cmVWYWx1ZT48ZHNpZzpLZXlJbmZvPjxkc2lnOktleU5hbWU+OXlpX2VjM0FLSlV6ZzZNZkRfeFlpSUtjZzR6bEJpSHFURHNFQ0lHZWhTazwvZHNpZzpLZXlOYW1lPjxkc2lnOlg1MDlEYXRhPjxkc2lnOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDb1RDQ0FZa0NCZ0Zrc3VzTXpUQU5CZ2txaGtpRzl3MEJBUXNGQURBVU1SSXdFQVlEVlFRRERBbHpiMjVoY25GMVltVXdIaGNOTVRnd056RTVNVFF5TURBMldoY05Namd3TnpFNU1UUXlNVFEyV2pBVU1SSXdFQVlEVlFRRERBbHpiMjVoY25GMVltVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFERU90aDVneHBUczFmM2JGR1VEOGhPOTdlTUlzRFp2dkUzUFplS29lVFJHN21PTHU2cmZMWHBoRzNmRTNFNi94cVVoUFA1cDloSmw5RHdnYU1ld2hkWmhmSHF0T3c2L1NQTUNRTkZWTnc5RlE3bHByV0tnOGNaeWdZTER4aE9iRXZDV1BlazhLY01iL3ZsS0Q4YzhoYTM3NE85cUVUNTFDVm9nRE01cm9wcDAycTBFTHhvVUtYcXBoS0g0K3NHWFJWbkRIYUVzRkh4c2UxSG5jaVpUNW1GMUc0NXZ4REl0ZEFuV0trWFlLVkhDK0V0NTJ0Q2llcU0weWdwUUYxbFdWSkZYVk9xc2kwM1lrTXU3SWtXdlNTZkF3K3VFY2ZtcXVUN0ZieEoybjVncDk0b2RBa1FCMEhLM2ZBQnJIcitHK24yUXZXRzZXd1FQSlRMME92MHcrdE5BZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDUWZPckpGOThudW5LejZDTitZWlhYTVloelFpcVREME1sekNnK1JkaGlyK1dDL3J1M0t6OG9tdjUyVy9zWEVNTlFiRVpCa3NWTGw4Vy8xeGVCUzQxU2YxbmZ1dFU1NjB2L2ozL09tT2NuQ3c0cWVicUZIN25COFJMOHZBNHJHeDQzMFcvUGVlVU1pa1kxbVNqbHdobkpHaUlDUTNZOEkycU02UVdFci9EZjIvZ0ZDVzJZbkhiblM2US9Pd1JRaStVRkl6S2tsU1FRYTBnQW5xZk00b1NLVTJPTWh6U2NpbldnMWJ1TVlmSlNYZ2Q0cUloUHZSc1pwcUJzZHQvT1NyVTJENVkyWWZTdThvSWN4QlJnSm9FUkg1QlY5R2RPSUQ0ZlMrVFl3ME0wUU8vT1JldE53MW1BLzhOcHN5OG9rRjhDbjdmRGdibldDOHV6Ky94RGMxNEk9PC9kc2lnOlg1MDlDZXJ0aWZpY2F0ZT48L2RzaWc6WDUwOURhdGE+PGRzaWc6S2V5VmFsdWU+PGRzaWc6UlNBS2V5VmFsdWU+PGRzaWc6TW9kdWx1cz54RHJZZVlNYVU3Tlg5MnhSbEEvSVR2ZTNqQ0xBMmI3eE56MlhpcUhrMFJ1NWppN3VxM3kxNllSdDN4TnhPdjhhbElUeithZllTWmZROElHakhzSVhXWVh4NnJUc092MGp6QWtEUlZUY1BSVU81YWExaW9QSEdjb0dDdzhZVG14THdsajNwUENuREcvNzVTZy9IUElXdCsrRHZhaEUrZFFsYUlBek9hNkthZE5xdEJDOGFGQ2w2cVlTaCtQckJsMFZad3gyaExCUjhiSHRSNTNJbVUrWmhkUnVPYjhReUxYUUoxaXBGMkNsUnd2aExlZHJRb25xak5Nb0tVQmRaVmxTUlYxVHFySXROMkpETHV5SkZyMGtud01QcmhISDVxcmsreFc4U2RwK1lLZmVLSFFKRUFkQnl0M3dBYXg2L2h2cDlrTDFodWxzRUR5VXk5RHI5TVByVFE9PTwvZHNpZzpNb2R1bHVzPjxkc2lnOkV4cG9uZW50PkFRQUI8L2RzaWc6RXhwb25lbnQ+PC9kc2lnOlJTQUtleVZhbHVlPjwvZHNpZzpLZXlWYWx1ZT48L2RzaWc6S2V5SW5mbz48L2RzaWc6U2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9IklEXzQxMTgxOTA4LTA1MTMtNDg2Ny1hYTgwLTE2YTI1YTBhMTg5ZSIgSXNzdWVJbnN0YW50PSIyMDE4LTA3LTI0VDExOjU3OjM2Ljk5OVoiIFZlcnNpb249IjIuMCI+PHNhbWw6SXNzdWVyPmh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9zb25hcnF1YmU8L3NhbWw6SXNzdWVyPjxkc2lnOlNpZ25hdHVyZSB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHNpZzpTaWduZWRJbmZvPjxkc2lnOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHNpZzpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+PGRzaWc6UmVmZXJlbmNlIFVSST0iI0lEXzQxMTgxOTA4LTA1MTMtNDg2Ny1hYTgwLTE2YTI1YTBhMTg5ZSI+PGRzaWc6VHJhbnNmb3Jtcz48ZHNpZzpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkc2lnOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHNpZzpUcmFuc2Zvcm1zPjxkc2lnOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPjxkc2lnOkRpZ2VzdFZhbHVlPnlJem4vWTBWYWtyRDRITHpxZHpNWFNhMU4yN1FjNEVoUDJyOWs2ZndLYVk9PC9kc2lnOkRpZ2VzdFZhbHVlPjwvZHNpZzpSZWZlcmVuY2U+PC9kc2lnOlNpZ25lZEluZm8+PGRzaWc6U2lnbmF0dXJlVmFsdWU+ZEphbk9VU2dlek5rUnl5Y1RRMk82cmdBQzl4WHlZTGwwY0NuSitkb2FzTTlYY3NhKzZoZEtRUkVEWWdEa1N4R09YWTVuZE9IVjFadFBGeTlreGxQeU5WcTZmNm92b0QvdEE5MkhlMWkvL0tTVjV6c252ZGZYUnpyQVFleHJmcWZCZEx5U3lHKzF4eFA3K1hMMzVzSjdFeUYreEREZDBhTUZEbitOUmFxMHJHNitzejVoRGs5T3k3THJIUmgxTVF1emMyNlhqeWlZcHM2TkJOcHI2VEJuWnNhV1VBYndiY0JVRzBaYi90eHN5bTZDQm5oZjJtNTErWlUvZ0pKWmtiLysxTDluL2pyZ01Xc3hXSUVLdW9RcTVJbkJsUzBTNjk4ZjliUldRSGtiQVZGbTRlYk5xTDZRMEZaUS9xZEd4UTNzeVE2dzBrRjQvT3M5djFXTmJzN3FBPT08L2RzaWc6U2lnbmF0dXJlVmFsdWU+PGRzaWc6S2V5SW5mbz48ZHNpZzpLZXlOYW1lPjl5aV9lYzNBS0pVemc2TWZEX3hZaUlLY2c0emxCaUhxVERzRUNJR2VoU2s8L2RzaWc6S2V5TmFtZT48ZHNpZzpYNTA5RGF0YT48ZHNpZzpYNTA5Q2VydGlmaWNhdGU+TUlJQ29UQ0NBWWtDQmdGa3N1c016VEFOQmdrcWhraUc5dzBCQVFzRkFEQVVNUkl3RUFZRFZRUUREQWx6YjI1aGNuRjFZbVV3SGhjTk1UZ3dOekU1TVRReU1EQTJXaGNOTWpnd056RTVNVFF5TVRRMldqQVVNUkl3RUFZRFZRUUREQWx6YjI1aGNuRjFZbVV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRREVPdGg1Z3hwVHMxZjNiRkdVRDhoTzk3ZU1Jc0RadnZFM1BaZUtvZVRSRzdtT0x1NnJmTFhwaEczZkUzRTYveHFVaFBQNXA5aEpsOUR3Z2FNZXdoZFpoZkhxdE93Ni9TUE1DUU5GVk53OUZRN2xwcldLZzhjWnlnWUxEeGhPYkV2Q1dQZWs4S2NNYi92bEtEOGM4aGEzNzRPOXFFVDUxQ1ZvZ0RNNXJvcHAwMnEwRUx4b1VLWHFwaEtINCtzR1hSVm5ESGFFc0ZIeHNlMUhuY2laVDVtRjFHNDV2eERJdGRBbldLa1hZS1ZIQytFdDUydENpZXFNMHlncFFGMWxXVkpGWFZPcXNpMDNZa011N0lrV3ZTU2ZBdyt1RWNmbXF1VDdGYnhKMm41Z3A5NG9kQWtRQjBISzNmQUJySHIrRytuMlF2V0c2V3dRUEpUTDBPdjB3K3ROQWdNQkFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQ1FmT3JKRjk4bnVuS3o2Q04rWVpYWE1ZaHpRaXFURDBNbHpDZytSZGhpcitXQy9ydTNLejhvbXY1Mlcvc1hFTU5RYkVaQmtzVkxsOFcvMXhlQlM0MVNmMW5mdXRVNTYwdi9qMy9PbU9jbkN3NHFlYnFGSDduQjhSTDh2QTRyR3g0MzBXL1BlZVVNaWtZMW1Tamx3aG5KR2lJQ1EzWThJMnFNNlFXRXIvRGYyL2dGQ1cyWW5IYm5TNlEvT3dSUWkrVUZJektrbFNRUWEwZ0FucWZNNG9TS1UyT01oelNjaW5XZzFidU1ZZkpTWGdkNHFJaFB2UnNacHFCc2R0L09TclUyRDVZMllmU3U4b0ljeEJSZ0pvRVJINUJWOUdkT0lENGZTK1RZdzBNMFFPL09SZXROdzFtQS84TnBzeThva0Y4Q243ZkRnYm5XQzh1eisveERjMTRJPTwvZHNpZzpYNTA5Q2VydGlmaWNhdGU+PC9kc2lnOlg1MDlEYXRhPjxkc2lnOktleVZhbHVlPjxkc2lnOlJTQUtleVZhbHVlPjxkc2lnOk1vZHVsdXM+eERyWWVZTWFVN05YOTJ4UmxBL0lUdmUzakNMQTJiN3hOejJYaXFIazBSdTVqaTd1cTN5MTZZUnQzeE54T3Y4YWxJVHorYWZZU1pmUThJR2pIc0lYV1lYeDZyVHNPdjBqekFrRFJWVGNQUlVPNWFhMWlvUEhHY29HQ3c4WVRteEx3bGozcFBDbkRHLzc1U2cvSFBJV3QrK0R2YWhFK2RRbGFJQXpPYTZLYWROcXRCQzhhRkNsNnFZU2grUHJCbDBWWnd4MmhMQlI4Ykh0UjUzSW1VK1poZFJ1T2I4UXlMWFFKMWlwRjJDbFJ3dmhMZWRyUW9ucWpOTW9LVUJkWlZsU1JWMVRxckl0TjJKREx1eUpGcjBrbndNUHJoSEg1cXJrK3hXOFNkcCtZS2ZlS0hRSkVBZEJ5dDN3QWF4Ni9odnA5a0wxaHVsc0VEeVV5OURyOU1QclRRPT08L2RzaWc6TW9kdWx1cz48ZHNpZzpFeHBvbmVudD5BUUFCPC9kc2lnOkV4cG9uZW50PjwvZHNpZzpSU0FLZXlWYWx1ZT48L2RzaWc6S2V5VmFsdWU+PC9kc2lnOktleUluZm8+PC9kc2lnOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj5qb2huZG9lPC9zYW1sOk5hbWVJRD48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl9hZjdkM2YwMy02ZTExLTQ1ZGUtYWIyNy04YTY3MTAwYTcxMzAiIE5vdE9uT3JBZnRlcj0iMjAxOC0wNy0yNFQxMjowMjozNC45OTlaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMC9vYXV0aDIvY2FsbGJhY2svc2FtbCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE4LTA3LTI0VDExOjU3OjM0Ljk5OVoiIE5vdE9uT3JBZnRlcj0iMjAxOC0wNy0yNFQxMTo1ODozNC45OTlaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPnNvbmFycXViZTwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTgtMDctMjRUMTE6NTc6MzYuOTk5WiIgU2Vzc2lvbkluZGV4PSI0MDBhNDdkNy04Yzc2LTQyOTYtYmMxMy1mNjE0NmFkYjc2NzQ6OmI3OWFkZDMwLWFhN2MtNDQ1ZS1hYmU0LTJjZDliZTg3YmMxZCI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOnVuc3BlY2lmaWVkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJVc2VybmFtZSIgTmFtZT0ibG9naW4yIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmpvaG5kb2U8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJFbWFpbCIgTmFtZT0iZW1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+am9obmRvZWVAZW1haWwuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIEZyaWVuZGx5TmFtZT0iTmFtZSIgTmFtZT0ibmFtZSIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5Kb2huIERvZTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBGcmllbmRseU5hbWU9Ikdyb3VwcyIgTmFtZT0iZ3JvdXBzIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmRldmVsb3Blcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnByb2R1Y3QtbWFuYWdlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file
diff --git a/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_response_without_name.txt b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_response_without_name.txt
new file mode 100644
index 00000000000..7bccf124c53
--- /dev/null
+++ b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/encoded_response_without_name.txt
@@ -0,0 +1 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjkwMDAvb2F1dGgyL2NhbGxiYWNrL3NhbWwiIElEPSJJRF9hNmRkZmM2YS03NzRkLTQ1ZmQtODUyNy0xMDE1MWYxN2MwMDMiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fMDE4YTliZWYtMTNiNS00NDZmLTlmODAtNTE1ZTA0NzQzNWI3IiBJc3N1ZUluc3RhbnQ9IjIwMTgtMDctMjRUMTE6NTU6NTguNjE0WiIgVmVyc2lvbj0iMi4wIj48c2FtbDpJc3N1ZXI+aHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3NvbmFycXViZTwvc2FtbDpJc3N1ZXI+PGRzaWc6U2lnbmF0dXJlIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkc2lnOlNpZ25lZEluZm8+PGRzaWc6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkc2lnOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48ZHNpZzpSZWZlcmVuY2UgVVJJPSIjSURfYTZkZGZjNmEtNzc0ZC00NWZkLTg1MjctMTAxNTFmMTdjMDAzIj48ZHNpZzpUcmFuc2Zvcm1zPjxkc2lnOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzaWc6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kc2lnOlRyYW5zZm9ybXM+PGRzaWc6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8+PGRzaWc6RGlnZXN0VmFsdWU+ajdDclU1SGxLaUxpUndOcmp6Sk44MGFlVUNnV3Q5YzJURWxSalc3dlNHTT08L2RzaWc6RGlnZXN0VmFsdWU+PC9kc2lnOlJlZmVyZW5jZT48L2RzaWc6U2lnbmVkSW5mbz48ZHNpZzpTaWduYXR1cmVWYWx1ZT5zbG1LenRtSkNnWWJIWFg5NEY4V2pKTGxjam4weEd1azNrRjRabGttMkRBZUdzZ0xEMUxMbXl6Sy9iMmRmRVZsOUNCRzFOTzB1RnpiZnZZcWQyVWZUZ1NVRm5FdEtPdXZHd1dIbkptYUtkdzdaeDd4amJONTgvcU8xcmFvdDRoTStMYTlFS2lKUzJSN3B1a1owcW1lM2V4Qk04b2R6eXpOWUpyQ2txUGNBUEJlVncza0RSUnNmaExFNTVqKy9lK1loVGlONXp6L1J0blliZDNFbXU4eVJDdEVEYlVxZGYxaFRwMEpPNFFGekpqT05jNUg4RWx2b3l4OVVra1oxSC9MaW15czR3WE9md2NSWVpGWHNlMWRZeWF3bWVIVmc0cG9XaEF3ZngremVtZmxReWxWaDd0b1FHRUpIeDdQVzd0b0RORWo5YnhvOW5MTjREMXhqVW5EalE9PTwvZHNpZzpTaWduYXR1cmVWYWx1ZT48ZHNpZzpLZXlJbmZvPjxkc2lnOktleU5hbWU+OXlpX2VjM0FLSlV6ZzZNZkRfeFlpSUtjZzR6bEJpSHFURHNFQ0lHZWhTazwvZHNpZzpLZXlOYW1lPjxkc2lnOlg1MDlEYXRhPjxkc2lnOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDb1RDQ0FZa0NCZ0Zrc3VzTXpUQU5CZ2txaGtpRzl3MEJBUXNGQURBVU1SSXdFQVlEVlFRRERBbHpiMjVoY25GMVltVXdIaGNOTVRnd056RTVNVFF5TURBMldoY05Namd3TnpFNU1UUXlNVFEyV2pBVU1SSXdFQVlEVlFRRERBbHpiMjVoY25GMVltVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFERU90aDVneHBUczFmM2JGR1VEOGhPOTdlTUlzRFp2dkUzUFplS29lVFJHN21PTHU2cmZMWHBoRzNmRTNFNi94cVVoUFA1cDloSmw5RHdnYU1ld2hkWmhmSHF0T3c2L1NQTUNRTkZWTnc5RlE3bHByV0tnOGNaeWdZTER4aE9iRXZDV1BlazhLY01iL3ZsS0Q4YzhoYTM3NE85cUVUNTFDVm9nRE01cm9wcDAycTBFTHhvVUtYcXBoS0g0K3NHWFJWbkRIYUVzRkh4c2UxSG5jaVpUNW1GMUc0NXZ4REl0ZEFuV0trWFlLVkhDK0V0NTJ0Q2llcU0weWdwUUYxbFdWSkZYVk9xc2kwM1lrTXU3SWtXdlNTZkF3K3VFY2ZtcXVUN0ZieEoybjVncDk0b2RBa1FCMEhLM2ZBQnJIcitHK24yUXZXRzZXd1FQSlRMME92MHcrdE5BZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDUWZPckpGOThudW5LejZDTitZWlhYTVloelFpcVREME1sekNnK1JkaGlyK1dDL3J1M0t6OG9tdjUyVy9zWEVNTlFiRVpCa3NWTGw4Vy8xeGVCUzQxU2YxbmZ1dFU1NjB2L2ozL09tT2NuQ3c0cWVicUZIN25COFJMOHZBNHJHeDQzMFcvUGVlVU1pa1kxbVNqbHdobkpHaUlDUTNZOEkycU02UVdFci9EZjIvZ0ZDVzJZbkhiblM2US9Pd1JRaStVRkl6S2tsU1FRYTBnQW5xZk00b1NLVTJPTWh6U2NpbldnMWJ1TVlmSlNYZ2Q0cUloUHZSc1pwcUJzZHQvT1NyVTJENVkyWWZTdThvSWN4QlJnSm9FUkg1QlY5R2RPSUQ0ZlMrVFl3ME0wUU8vT1JldE53MW1BLzhOcHN5OG9rRjhDbjdmRGdibldDOHV6Ky94RGMxNEk9PC9kc2lnOlg1MDlDZXJ0aWZpY2F0ZT48L2RzaWc6WDUwOURhdGE+PGRzaWc6S2V5VmFsdWU+PGRzaWc6UlNBS2V5VmFsdWU+PGRzaWc6TW9kdWx1cz54RHJZZVlNYVU3Tlg5MnhSbEEvSVR2ZTNqQ0xBMmI3eE56MlhpcUhrMFJ1NWppN3VxM3kxNllSdDN4TnhPdjhhbElUeithZllTWmZROElHakhzSVhXWVh4NnJUc092MGp6QWtEUlZUY1BSVU81YWExaW9QSEdjb0dDdzhZVG14THdsajNwUENuREcvNzVTZy9IUElXdCsrRHZhaEUrZFFsYUlBek9hNkthZE5xdEJDOGFGQ2w2cVlTaCtQckJsMFZad3gyaExCUjhiSHRSNTNJbVUrWmhkUnVPYjhReUxYUUoxaXBGMkNsUnd2aExlZHJRb25xak5Nb0tVQmRaVmxTUlYxVHFySXROMkpETHV5SkZyMGtud01QcmhISDVxcmsreFc4U2RwK1lLZmVLSFFKRUFkQnl0M3dBYXg2L2h2cDlrTDFodWxzRUR5VXk5RHI5TVByVFE9PTwvZHNpZzpNb2R1bHVzPjxkc2lnOkV4cG9uZW50PkFRQUI8L2RzaWc6RXhwb25lbnQ+PC9kc2lnOlJTQUtleVZhbHVlPjwvZHNpZzpLZXlWYWx1ZT48L2RzaWc6S2V5SW5mbz48L2RzaWc6U2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9IklEXzk5Njk4NjQwLTA2NDgtNDM3Zi1hODQ3LTE5OWY0N2U5ZTA3NCIgSXNzdWVJbnN0YW50PSIyMDE4LTA3LTI0VDExOjU1OjU4LjYxNFoiIFZlcnNpb249IjIuMCI+PHNhbWw6SXNzdWVyPmh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9zb25hcnF1YmU8L3NhbWw6SXNzdWVyPjxkc2lnOlNpZ25hdHVyZSB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHNpZzpTaWduZWRJbmZvPjxkc2lnOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHNpZzpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+PGRzaWc6UmVmZXJlbmNlIFVSST0iI0lEXzk5Njk4NjQwLTA2NDgtNDM3Zi1hODQ3LTE5OWY0N2U5ZTA3NCI+PGRzaWc6VHJhbnNmb3Jtcz48ZHNpZzpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkc2lnOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHNpZzpUcmFuc2Zvcm1zPjxkc2lnOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPjxkc2lnOkRpZ2VzdFZhbHVlPnE4U21wYUJaUGlHR0F1VURzNkp2dURaQjdVY0Rxd002azNuUVFTRzNGTUE9PC9kc2lnOkRpZ2VzdFZhbHVlPjwvZHNpZzpSZWZlcmVuY2U+PC9kc2lnOlNpZ25lZEluZm8+PGRzaWc6U2lnbmF0dXJlVmFsdWU+R2IvSFFIYzh3N2JnYlR0MGtLaDIxMVhHRStaSEMwQkEvVURtKzYvaG5jaDZyZisvNzBSd2h3SEtLd24zdkVPVU1pcHAxSmlEZjQxR2xNMHZhL1lWbE9KTEYzNmhuQlB0Qmo3RGhnd0xGdkpXQlFVVmozRk5tR3N5T2RmalNyUFhrcEE1ZENJRWR1K08rMnN2M1o3UE1BdWtVY3NwN28zRzR6UzRkbU81eS9weUxQWFN4S1BaQnpQNThwK3dUSUhZcjgrN0tLMjhRTWxsV2JYTXRmTUpidlJtTmpPT2pkS1hqTHNpcTVSRFl2UEw1cHhmM2dYZXBneWVGdlJmWGZNamNwdXpEZWwzMDIxcjREazYyb2wzcWppbXJVT1p1cldCLzJjVkY3V3RNckx3NDduRWd6SDZtc3dRbUJPRVB3dUkyRTJVRE9XYkxzUCsrT3cwb3h1NVZ3PT08L2RzaWc6U2lnbmF0dXJlVmFsdWU+PGRzaWc6S2V5SW5mbz48ZHNpZzpLZXlOYW1lPjl5aV9lYzNBS0pVemc2TWZEX3hZaUlLY2c0emxCaUhxVERzRUNJR2VoU2s8L2RzaWc6S2V5TmFtZT48ZHNpZzpYNTA5RGF0YT48ZHNpZzpYNTA5Q2VydGlmaWNhdGU+TUlJQ29UQ0NBWWtDQmdGa3N1c016VEFOQmdrcWhraUc5dzBCQVFzRkFEQVVNUkl3RUFZRFZRUUREQWx6YjI1aGNuRjFZbVV3SGhjTk1UZ3dOekU1TVRReU1EQTJXaGNOTWpnd056RTVNVFF5TVRRMldqQVVNUkl3RUFZRFZRUUREQWx6YjI1aGNuRjFZbVV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRREVPdGg1Z3hwVHMxZjNiRkdVRDhoTzk3ZU1Jc0RadnZFM1BaZUtvZVRSRzdtT0x1NnJmTFhwaEczZkUzRTYveHFVaFBQNXA5aEpsOUR3Z2FNZXdoZFpoZkhxdE93Ni9TUE1DUU5GVk53OUZRN2xwcldLZzhjWnlnWUxEeGhPYkV2Q1dQZWs4S2NNYi92bEtEOGM4aGEzNzRPOXFFVDUxQ1ZvZ0RNNXJvcHAwMnEwRUx4b1VLWHFwaEtINCtzR1hSVm5ESGFFc0ZIeHNlMUhuY2laVDVtRjFHNDV2eERJdGRBbldLa1hZS1ZIQytFdDUydENpZXFNMHlncFFGMWxXVkpGWFZPcXNpMDNZa011N0lrV3ZTU2ZBdyt1RWNmbXF1VDdGYnhKMm41Z3A5NG9kQWtRQjBISzNmQUJySHIrRytuMlF2V0c2V3dRUEpUTDBPdjB3K3ROQWdNQkFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQ1FmT3JKRjk4bnVuS3o2Q04rWVpYWE1ZaHpRaXFURDBNbHpDZytSZGhpcitXQy9ydTNLejhvbXY1Mlcvc1hFTU5RYkVaQmtzVkxsOFcvMXhlQlM0MVNmMW5mdXRVNTYwdi9qMy9PbU9jbkN3NHFlYnFGSDduQjhSTDh2QTRyR3g0MzBXL1BlZVVNaWtZMW1Tamx3aG5KR2lJQ1EzWThJMnFNNlFXRXIvRGYyL2dGQ1cyWW5IYm5TNlEvT3dSUWkrVUZJektrbFNRUWEwZ0FucWZNNG9TS1UyT01oelNjaW5XZzFidU1ZZkpTWGdkNHFJaFB2UnNacHFCc2R0L09TclUyRDVZMllmU3U4b0ljeEJSZ0pvRVJINUJWOUdkT0lENGZTK1RZdzBNMFFPL09SZXROdzFtQS84TnBzeThva0Y4Q243ZkRnYm5XQzh1eisveERjMTRJPTwvZHNpZzpYNTA5Q2VydGlmaWNhdGU+PC9kc2lnOlg1MDlEYXRhPjxkc2lnOktleVZhbHVlPjxkc2lnOlJTQUtleVZhbHVlPjxkc2lnOk1vZHVsdXM+eERyWWVZTWFVN05YOTJ4UmxBL0lUdmUzakNMQTJiN3hOejJYaXFIazBSdTVqaTd1cTN5MTZZUnQzeE54T3Y4YWxJVHorYWZZU1pmUThJR2pIc0lYV1lYeDZyVHNPdjBqekFrRFJWVGNQUlVPNWFhMWlvUEhHY29HQ3c4WVRteEx3bGozcFBDbkRHLzc1U2cvSFBJV3QrK0R2YWhFK2RRbGFJQXpPYTZLYWROcXRCQzhhRkNsNnFZU2grUHJCbDBWWnd4MmhMQlI4Ykh0UjUzSW1VK1poZFJ1T2I4UXlMWFFKMWlwRjJDbFJ3dmhMZWRyUW9ucWpOTW9LVUJkWlZsU1JWMVRxckl0TjJKREx1eUpGcjBrbndNUHJoSEg1cXJrK3hXOFNkcCtZS2ZlS0hRSkVBZEJ5dDN3QWF4Ni9odnA5a0wxaHVsc0VEeVV5OURyOU1QclRRPT08L2RzaWc6TW9kdWx1cz48ZHNpZzpFeHBvbmVudD5BUUFCPC9kc2lnOkV4cG9uZW50PjwvZHNpZzpSU0FLZXlWYWx1ZT48L2RzaWc6S2V5VmFsdWU+PC9kc2lnOktleUluZm8+PC9kc2lnOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj5qb2huZG9lPC9zYW1sOk5hbWVJRD48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl8wMThhOWJlZi0xM2I1LTQ0NmYtOWY4MC01MTVlMDQ3NDM1YjciIE5vdE9uT3JBZnRlcj0iMjAxOC0wNy0yNFQxMjowMDo1Ni42MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMC9vYXV0aDIvY2FsbGJhY2svc2FtbCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE4LTA3LTI0VDExOjU1OjU2LjYxNFoiIE5vdE9uT3JBZnRlcj0iMjAxOC0wNy0yNFQxMTo1Njo1Ni42MTRaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPnNvbmFycXViZTwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTgtMDctMjRUMTE6NTU6NTguNjE0WiIgU2Vzc2lvbkluZGV4PSI0MDBhNDdkNy04Yzc2LTQyOTYtYmMxMy1mNjE0NmFkYjc2NzQ6OmI3OWFkZDMwLWFhN2MtNDQ1ZS1hYmU0LTJjZDliZTg3YmMxZCI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOnVuc3BlY2lmaWVkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJVc2VybmFtZSIgTmFtZT0ibG9naW4iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+am9obmRvZTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBGcmllbmRseU5hbWU9IkVtYWlsIiBOYW1lPSJlbWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5qb2huZG9lZUBlbWFpbC5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJOYW1lIiBOYW1lPSJuYW1lMiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5Kb2huIERvZTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBGcmllbmRseU5hbWU9Ikdyb3VwcyIgTmFtZT0iZ3JvdXBzIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmRldmVsb3Blcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnByb2R1Y3QtbWFuYWdlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file
diff --git a/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/how_to_generate_test_response.txt b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/how_to_generate_test_response.txt
new file mode 100644
index 00000000000..bd832eed711
--- /dev/null
+++ b/server/sonar-auth-saml/src/test/resources/org/sonar/auth/saml/SamlIdentityProviderTest/how_to_generate_test_response.txt
@@ -0,0 +1,6 @@
+# How to generate test responses for unit tests requiring encoded user response
+
+1. Set the server log in TRACE
+2. Login with a user
+3. Search in the logs for "[c.o.saml2.Auth] processResponse success -->"
+4. The value after the "-->" is the encoded response that can be used in test \ No newline at end of file
diff --git a/server/sonar-docs/src/pages/instance-administration/delegated-auth.md b/server/sonar-docs/src/pages/instance-administration/delegated-auth.md
index 07c874b3377..3ccac89cf18 100644
--- a/server/sonar-docs/src/pages/instance-administration/delegated-auth.md
+++ b/server/sonar-docs/src/pages/instance-administration/delegated-auth.md
@@ -131,12 +131,12 @@ The following example may be useful if you're using Keycloak as a SAML Identity
[[collapse]]
| ## In SonarQube, Configure SAML authentication
-| Go to **[Administration > Configuration > General Settings > SAML > Authentication](/#sonarqube-admin#/admin/settings?category=saml)**
+| Go to **[Administration > Configuration > General Settings > Security > SAML](/#sonarqube-admin#/admin/settings?category=security)**
| * **Enabled** should be set to true
| * **Application ID** is the value of the "Client ID" you set in Keycloak (for example "sonarqube")
| * **Provider ID** is the value of the "EntityDescriptor" > "entityID" attribute in the XML configuration file (for example "http://keycloak:8080/auth/realms/sonarqube" where sonarqube is the name of the realm)
| * **SAML login url** is the value of "SingleSignOnService" > "Location" attribute in the XML configuration file (for example "http://keycloak:8080/auth/realms/sonarqube/protocol/saml")
-| * **Provider certificate** is the value of "dsig:X509Certificate" node in the XML configuration file
+| * **Provider certificate** is the value you get from *Reaml Settings* -> *Keys* -> click on the *Certificate* button
| * **SAML user login attribute** is the value set in the login mapper in "SAML Attribute Name"
| * **SAML user name attribute** is the value set in the name mapper in "SAML Attribute Name"
| * (Optional) **SAML user email attribute** is the value set in the email mapper in "SAML Attribute Name"
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java b/server/sonar-server-common/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
index 3c28d130cbd..63254f0553c 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
@@ -47,7 +47,7 @@ import static org.sonar.core.extension.ExtensionProviderSupport.isExtensionProvi
*/
public abstract class ServerExtensionInstaller {
- private static final Set<String> NO_MORE_COMPATIBLE_PLUGINS = ImmutableSet.of("authgithub", "authgitlab");
+ private static final Set<String> NO_MORE_COMPATIBLE_PLUGINS = ImmutableSet.of("authgithub", "authgitlab", "authsaml");
private final SonarRuntime sonarRuntime;
private final PluginRepository pluginRepository;
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java
index 1aa8714d547..d5be9ce92aa 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java
@@ -88,6 +88,18 @@ public class ServerExtensionInstallerTest {
underTest.installExtensions(componentContainer);
}
+ @Test
+ public void fail_when_detecting_saml_auth_plugin() {
+ PluginInfo foo = newPlugin("authsaml", "SAML Auth");
+ pluginRepository.add(foo, mock(Plugin.class));
+ ComponentContainer componentContainer = new ComponentContainer();
+
+ expectedException.expect(MessageException.class);
+ expectedException.expectMessage("Plugins 'SAML Auth' are no more compatible with SonarQube");
+
+ underTest.installExtensions(componentContainer);
+ }
+
private static PluginInfo newPlugin(String key, String name) {
PluginInfo plugin = mock(PluginInfo.class);
when(plugin.getKey()).thenReturn(key);
diff --git a/server/sonar-web/public/images/saml.png b/server/sonar-web/public/images/saml.png
new file mode 100644
index 00000000000..173c5b1e5be
--- /dev/null
+++ b/server/sonar-web/public/images/saml.png
Binary files differ
diff --git a/server/sonar-webserver/build.gradle b/server/sonar-webserver/build.gradle
index ff0bcaecbbc..b502aa97e21 100644
--- a/server/sonar-webserver/build.gradle
+++ b/server/sonar-webserver/build.gradle
@@ -14,6 +14,7 @@ dependencies {
compile project(':sonar-core')
compile project(':server:sonar-auth-github')
compile project(':server:sonar-auth-gitlab')
+ compile project(':server:sonar-auth-saml')
compile project(':server:sonar-ce-task-projectanalysis')
compile project(':server:sonar-process')
compile project(':server:sonar-webserver-core')
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index beeb497e738..39f3f44cb34 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -30,6 +30,7 @@ import org.sonar.api.rules.XMLRuleParser;
import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
import org.sonar.auth.github.GitHubModule;
import org.sonar.auth.gitlab.GitLabModule;
+import org.sonar.auth.saml.SamlModule;
import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationModule;
import org.sonar.ce.task.projectanalysis.taskprocessor.ReportTaskProcessor;
import org.sonar.core.component.DefaultResourceTypes;
@@ -354,6 +355,7 @@ public class PlatformLevel4 extends PlatformLevel {
AuthenticationWsModule.class,
GitHubModule.class,
GitLabModule.class,
+ SamlModule.class,
// users
UserSessionFactoryImpl.class,
@@ -467,7 +469,7 @@ public class PlatformLevel4 extends PlatformLevel {
TypeValidationModule.class,
- //New Code Periods
+ // New Code Periods
NewCodePeriodsWsModule.class,
// Project Links
diff --git a/settings.gradle b/settings.gradle
index d736e103bf8..588990d2772 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -5,6 +5,7 @@ include 'plugins:sonar-xoo-plugin'
include 'server:sonar-auth-common'
include 'server:sonar-auth-github'
include 'server:sonar-auth-gitlab'
+include 'server:sonar-auth-saml'
include 'server:sonar-ce'
include 'server:sonar-ce-common'
include 'server:sonar-ce-task'
diff --git a/sonar-application/build.gradle b/sonar-application/build.gradle
index 2981154d0c7..b233360ed0d 100644
--- a/sonar-application/build.gradle
+++ b/sonar-application/build.gradle
@@ -50,7 +50,6 @@ dependencies {
jdbc_mssql 'com.microsoft.sqlserver:mssql-jdbc'
jdbc_postgresql 'org.postgresql:postgresql'
- bundledPlugin 'org.sonarsource.auth.saml:sonar-auth-saml-plugin:1.1.0.181@jar'
bundledPlugin 'org.sonarsource.css:sonar-css-plugin@jar'
bundledPlugin "org.sonarsource.dotnet:sonar-csharp-plugin@jar"
bundledPlugin "org.sonarsource.dotnet:sonar-vbnet-plugin@jar"
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 7b60bfc9909..6d63d977958 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -937,6 +937,8 @@ property.category.security.encryption=Encryption
property.category.security.github=GitHub
property.category.security.github.description=In order to enable GitHub authentication:<ul><li>SonarQube must be publicly accessible through HTTPS only</li><li>The property 'sonar.core.serverBaseURL' must be set to this public HTTPS URL</li><li>In your GitHub profile, you need to create a Developer Application for which the 'Authorization callback URL' must be set to <code>'&lt;value_of_sonar.core.serverBaseURL_property&gt;/oauth2/callback'</code>.</li></ul>
property.category.security.gitlab=Gitlab
+property.category.security.saml=SAML
+property.category.security.saml.description=In order to enable SAML authentication, the property 'sonar.core.serverBaseURL' must be set to the public URL
property.category.java=Java
property.category.differentialViews=New Code
property.category.codeCoverage=Code Coverage