diff options
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 Binary files differnew file mode 100644 index 00000000000..173c5b1e5be --- /dev/null +++ b/server/sonar-web/public/images/saml.png 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>'<value_of_sonar.core.serverBaseURL_property>/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 |