blob: 18fa9cc268cbb56160397b77d3c36dc01feb8543 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.auth.saml2;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.auth.core.AuthenticationSupport;
import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler;
import org.apache.sling.auth.core.spi.AuthenticationHandler;
import org.apache.sling.auth.core.spi.AuthenticationInfo;
import org.apache.sling.testing.paxexam.SlingOptions;
import org.apache.sling.testing.paxexam.TestSupport;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.options.ModifiableCompositeOption;
import org.ops4j.pax.exam.options.extra.VMOption;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerClass;
import org.ops4j.pax.exam.util.Filter;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import static org.apache.sling.auth.core.spi.AuthenticationHandler.REQUEST_LOGIN_PARAMETER;
import static org.apache.sling.auth.saml2.impl.JKSHelper.IDP_ALIAS;
import static org.apache.sling.auth.saml2.impl.JKSHelper.SP_ALIAS;
import static org.apache.sling.testing.paxexam.SlingOptions.logback;
import static org.apache.sling.testing.paxexam.SlingOptions.slingAuthForm;
import static org.apache.sling.testing.paxexam.SlingOptions.slingQuickstartOakTar;
import static org.apache.sling.testing.paxexam.SlingOptions.versionResolver;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.when;
import static org.ops4j.pax.exam.CoreOptions.composite;
import static org.ops4j.pax.exam.CoreOptions.junitBundles;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import static org.ops4j.pax.exam.CoreOptions.vmOption;
import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
/**
* PAX Exam Integration Tests for AuthenticationHandlerSaml2 and Saml2UserMgtService
*/
@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class SamlHandlerIT extends TestSupport {
static int HTTP_PORT = 8080;
static int DEST_HTTP_PORT = 8484;
private static Logger logger = LoggerFactory.getLogger(SamlHandlerIT.class);
ResourceResolver resourceResolver = null;
Session session;
JackrabbitSession jrSession;
UserManager userManager;
@Inject
protected BundleContext bundleContext;
@Inject
protected ConfigurationAdmin configurationAdmin;
@Inject
AuthenticationSupport authenticationSupport;
@Inject
HttpService httpService;
@Inject
ResourceResolverFactory resolverFactory;
@Inject
@Filter(value = "(saml2SPEncryptAndSign=false)")
AuthenticationHandler authHandler;
@Inject
@Filter(value = "(saml2SPEncryptAndSign=true)")
AuthenticationHandler authHandlerEnc;
@Inject
Saml2UserMgtService saml2UserMgtService;
@Configuration
public Option[] configuration() {
// Patch versions of features provided by SlingOptions
HTTP_PORT = findFreePort();
DEST_HTTP_PORT = findFreePort();
// String OAK_VERSION = "1.32.0";
String OAK_VERSION = "1.38.0";
versionResolver.setVersion("commons-codec", "commons-codec", "1.14");
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-jackrabbit-api", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-auth-external", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-api", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-core-spi", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-commons", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "jackrabbit-jcr-commons", "2.20.0");
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-blob-plugins", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-store-spi", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-core", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-blob", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-store-composite", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-store-document", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-jcr", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-lucene", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-authorization-principalbased", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-query-spi", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-security-spi", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.jackrabbit", "oak-segment-tar", OAK_VERSION);
SlingOptions.versionResolver.setVersion("org.apache.tika", "tika-core", "1.24");
SlingOptions.versionResolver.setVersion("org.apache.sling", "org.apache.sling.jcr.oak.server", "1.2.10");
SlingOptions.versionResolver.setVersion("org.apache.sling", "org.apache.sling.api", "2.23.0");
SlingOptions.versionResolver.setVersion("org.apache.sling", "org.apache.sling.resourceresolver", "1.7.2");
SlingOptions.versionResolver.setVersion("org.apache.sling", "org.apache.sling.scripting.core", "2.3.4");
SlingOptions.versionResolver.setVersion("org.apache.sling", "org.apache.sling.commons.compiler", "2.4.0");
SlingOptions.versionResolver.setVersion("org.apache.sling", "org.apache.sling.servlets.resolver", "2.7.12");
Option[] options = new Option[]{
systemProperty("org.osgi.service.http.port").value(String.valueOf(HTTP_PORT)),
systemProperty("sling.home").value("./sling"),
baseConfiguration(),
slingQuickstart(),
slingAuthForm(),
failOnUnresolvedBundles(),
mavenBundle().groupId("org.apache.jackrabbit").artifactId("oak-jackrabbit-api").version(versionResolver),
mavenBundle().groupId("org.apache.jackrabbit").artifactId("oak-auth-external").version(versionResolver),
mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.converter").version("1.0.14"),
mavenBundle().groupId("org.mockito").artifactId("mockito-core").version("3.3.3"),
mavenBundle().groupId("net.bytebuddy").artifactId("byte-buddy").version("1.10.5"),
mavenBundle().groupId("net.bytebuddy").artifactId("byte-buddy-agent").version("1.10.5"),
mavenBundle().groupId("org.objenesis").artifactId("objenesis").version("2.6"),
mavenBundle().groupId("org.bouncycastle").artifactId("bcprov-jdk15on").version("1.64"),
mavenBundle().groupId("org.bouncycastle").artifactId("bcpkix-jdk15on").version("1.64"),
mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.commons.compiler").version(versionResolver),
factoryConfiguration("org.apache.sling.jcr.repoinit.RepositoryInitializer")
.put("scripts", new String[]{"create service user saml2-user-mgt\n\n set ACL for saml2-user-mgt\n\n allow jcr:all on /home\n\n end\n\n create group sling-authors with path /home/groups/sling-authors"})
.asOption(),
newConfiguration("org.apache.sling.jcr.base.internal.LoginAdminWhitelist")
.put("whitelist.bypass", "true").asOption(),
// build artifact
junitBundles(),
logback(),
optionalRemoteDebug(),
optionalJacoco(),
factoryConfiguration("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended")
.put("user.mapping", new String[]{"org.apache.sling.auth.saml2:Saml2UserMgtService=saml2-user-mgt"})
.asOption(),
newConfiguration("org.apache.sling.engine.impl.auth.SlingAuthenticator")
.put("auth.annonymous", false)
.asOption(),
// supply the required configuration so the auth handler service will activate
testBundle("bundle.filename"), // from TestSupport
// urn:oid:1.2.840.113549.1.9.1=profile/email
// urn:oid:2.5.4.4=profile/surname
// urn:oid:2.5.4.42=profile/givenName
// phone=profile/phone is configured but not included in assertion
factoryConfiguration("org.apache.sling.auth.saml2.AuthenticationHandlerSAML2")
.put("path", "/")
.put("entityID", "http://localhost:8080/")
.put("acsPath", "/sp/consumer")
.put("saml2userIDAttr", "urn:oid:0.9.2342.19200300.100.1.1")
.put("saml2userHome", "/home/users/saml")
.put("saml2groupMembershipAttr", "urn:oid:2.16.840.1.113719.1.1.4.1.25")
.put("syncAttrs", new String[]{"urn:oid:2.5.4.4=./profile/surname","urn:oid:2.5.4.42=./profile/givenName","phone=./profile/phone","urn:oid:1.2.840.113549.1.9.1=./profile/email"})
.put("saml2SPEnabled", true)
.put("saml2SPEncryptAndSign", false)
.put("jksFileLocation", "")
.put("jksStorePassword", "")
.put("idpCertAlias","")
.put("spKeysAlias","")
.put("spKeysPassword","")
.asOption(),
factoryConfiguration("org.apache.sling.auth.saml2.AuthenticationHandlerSAML2")
.put("path", "/")
.put("entityID", "http://localhost:8080/")
.put("acsPath", "/sp/consumer")
.put("saml2userIDAttr", "username")
.put("saml2userHome", "/home/users/saml")
.put("saml2groupMembershipAttr", "groupMembership")
.put("syncAttrs", new String[]{"urn:oid:2.5.4.4","urn:oid:2.5.4.42","phone","urn:oid:1.2.840.113549.1.9.1"})
.put("saml2SPEnabled", true)
.put("saml2SPEncryptAndSign", true)
.put("jksFileLocation", "./src/test/resources/exampleSaml2.jks")
.put("jksStorePassword", "password")
.put("idpCertAlias",IDP_ALIAS)
.put("spKeysAlias",SP_ALIAS)
.put("spKeysPassword","sppassword")
.asOption(),
};
return options;
}
/**
* Optionally configure remote debugging on the port supplied by the "debugPort"
* system property.
*/
protected ModifiableCompositeOption optionalRemoteDebug() {
VMOption option = null;
String property = System.getProperty("debugPort");
if (property != null) {
option = vmOption(String.format("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=%s", property));
}
return composite(option);
}
protected ModifiableCompositeOption optionalJacoco(){
VMOption jacocoCommand = null;
final String jacocoOpt = System.getProperty("jacoco.command");
if (StringUtils.isNotEmpty(jacocoOpt)) {
jacocoCommand = new VMOption(jacocoOpt);
}
return composite(jacocoCommand);
}
protected Option slingQuickstart() {
final String workingDirectory = workingDirectory(); // from TestSupport
return composite(
slingQuickstartOakTar(workingDirectory, HTTP_PORT) // from SlingOptions
);
}
protected Bundle findBundle(final String symbolicName) {
for (final Bundle bundle : bundleContext.getBundles()) {
if (symbolicName.equals(bundle.getSymbolicName())) {
return bundle;
}
}
return null;
}
void logBundles() {
for (final Bundle bundle : bundleContext.getBundles()) {
// logs to target/test.log
String active = bundle.getState() == Bundle.ACTIVE ? "active" : ""+bundle.getState();
logger.info(bundle.getSymbolicName()+":"+bundle.getVersion().toString()+ "state:"+active);
}
}
@Before
public void before(){
try {
resourceResolver = resolverFactory.getAdministrativeResourceResolver(null);
session = resourceResolver.adaptTo(Session.class);
jrSession = (JackrabbitSession) session;
userManager = jrSession.getUserManager();
} catch (RepositoryException | LoginException e) {
fail(e.getMessage());
}
saml2UserMgtService.setUp();
}
@After
public void after(){
resourceResolver.close();
saml2UserMgtService.cleanUp();
session = null;
jrSession = null;
userManager = null;
}
@Test
public void test_setup(){
assertNotNull(bundleContext);
assertNotNull(configurationAdmin);
assertNotNull(authenticationSupport);
assertNotNull(httpService);
assertNotNull(resolverFactory);
assertNotNull(saml2UserMgtService);
assertNotNull(authHandler);
assertNotNull(authHandlerEnc);
logBundles();
}
@Test
public void test_samlBundleActive(){
Bundle samlBundle = findBundle("org.apache.sling.auth.saml2");
assertTrue(samlBundle.getState() == Bundle.ACTIVE);
}
@Test
public void test_userServiceSetup(){
assertTrue(saml2UserMgtService.setUp());
}
@Test
public void test_getOrCreateSamlUser(){
saml2UserMgtService.setUp();
Saml2User saml2User = new Saml2User();
saml2User.setId("example-saml");
User user = saml2UserMgtService.getOrCreateSamlUser(saml2User);
assertNotNull(user);
assertTrue(saml2UserMgtService.updateUserProperties(saml2User));
try {
user.getPath().startsWith("/home/users/saml");
} catch (RepositoryException e) {
fail(e.getMessage());
}
saml2UserMgtService.cleanUp();
}
@Test
public void test_createSamlUserWithHomePath(){
saml2UserMgtService.setUp();
Saml2User saml2User = new Saml2User();
saml2User.setId("example-saml");
User user = saml2UserMgtService.getOrCreateSamlUser(saml2User,"/home/users/mypath");
assertNotNull(user);
try {
user.getPath().startsWith("/home/users/mypath");
} catch (RepositoryException e) {
fail(e.getMessage());
}
saml2UserMgtService.cleanUp();
}
@Test
public void test_groupMembership(){
saml2UserMgtService.setUp();
Saml2User saml2User = new Saml2User();
saml2User.setId("example-saml");
saml2User.addGroupMembership("sling-authors");
assertTrue(saml2UserMgtService.updateGroupMembership(saml2User));
try {
Authorizable user = userManager.getAuthorizable("example-saml");
Group group = (Group) userManager.getAuthorizable("sling-authors");
// confirm that group sling-authors now has a property called managedGroup set to true
assertTrue(group.isMember(user));
// confirm that group sling-authors now has a member example-saml
assertTrue(group.hasProperty("managedGroup"));
// and is a managed group
assertTrue(Arrays.stream(group.getProperty("managedGroup")).anyMatch(value -> true));
} catch (RepositoryException e) {
fail(e.getMessage());
}
saml2UserMgtService.cleanUp();
}
@Test
public void test_request_credentials() throws IOException {
HttpServletRequest requestIgnore = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
when(requestIgnore.getParameter(REQUEST_LOGIN_PARAMETER)).thenReturn("FORM");
// request credentials returns false when param ?sling:authRequestLogin=<something other than SAML2>
assertFalse(authHandler.requestCredentials(requestIgnore,response));
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpSession httpSession = Mockito.mock(HttpSession.class);
when(request.getRequestURL()).thenReturn(new StringBuffer("/"));
when(request.getContextPath()).thenReturn("/");
when(request.getMethod()).thenReturn("POST");
when(request.getHeader("Referer")).thenReturn("/not/my/sp/consumer");
when(request.getSession()).thenReturn(httpSession);
// requestCredentials returns true when SAML is enabled and request is not specifying another auth handler
assertTrue(authHandler.requestCredentials(request,response));
}
@Test
public void test_encACSPath(){
String base64EndSamlResp = "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjgwODAvc3AvY29uc3VtZXIiIElEPSJJRF9iOGZhYjUwOC1kOWM3LTQzYmEtOGM4My00Y2I1M2Y3ZjlmZmEiIEluUmVzcG9uc2VUbz0iXzRlNjhiMWIxNTk2ZDE0MmYwZTJhYWMzNjI0YzQxZDFiIiBJc3N1ZUluc3RhbnQ9IjIwMjEtMDQtMDVUMTg6MjE6MTYuMzIxWiIgVmVyc2lvbj0iMi4wIj48c2FtbDpJc3N1ZXI+aHR0cDovL2xvY2FsaG9zdDo4NDg0L2F1dGgvcmVhbG1zL3NsaW5nPC9zYW1sOklzc3Vlcj48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48c2FtbDpFbmNyeXB0ZWRBc3NlcnRpb24+PHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNhZXMxMjgtY2JjIi8+PGRzOktleUluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjx4ZW5jOkVuY3J5cHRlZEtleT48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjcnNhLW9hZXAtbWdmMXAiLz48eGVuYzpDaXBoZXJEYXRhPjx4ZW5jOkNpcGhlclZhbHVlPkhHUzU5VDZvMk1RUWtIZ21BYjIyUDBpNlJmRko2YzVBUzhWV2RrUEJIeDhFdHN4R003c0dVUTlLK0VCKzFhaE9NeDJRczZsZjJmc2cwWUo4Q3MvQmJKR056TjFvQ1d6TXc4ZVZmbENtL3dxaGhSS01xKzNDVm5pRHQ1QWoxa1pPZS9XM3RUbEplOE40aFpySVZ0RHBJZ25KdmxzUWM1ckJ3SHZXT2NSanlRd1ZBOFJYYndFYXZDYWFiOEZOWmh1a1BVazUvNEFBMHpTV3N1dUtXYk00d3NrT1BNam5yS3M1R1RhZ0VHQTBuT0tROTBpUGIrSDBPUU1SNUhsamwvRHNtQ0ZWaE1OMlhGc1J2ZHk1aVh0VWk3NStVdXBwMHFYNjRpcjZnSlRyMXJtYnVNQkVVcnJzSGJLN2t0N3JseHdyR0RZMHYrczBndm9ZczZxUDFKcGtHMldXZnNBTmxLQUFGVkdrS2J6eWxrRU1ueDVMNC9GdHJoVjRLUmgvMmV5UjBrL29keElYQmlhQXhPNjNPYXNyczF3ZlFDanN2RFpwYnpEbmhndFdBekVlV0lEZDM1d2o2YisxOEp3N1pTUEFQYzEvVGFVejFodkcvY0hYN05mQnFhbVA0NFFLOHpxQjJkWEdCNTQ3cUVUMk9hWEE1YWc4dW1oTTcvNm5tVEtrVkg1bENabUtWMkQyY1NZUzREK1dlV3FVckZ6NHZoT3Y2WXlIa3gyeTk4VEVMV0VsdDVqaTNTT0g4NmtHWmZvaW1JRUZkaUZwdFdHcWRnd21ZeHZSdGJXK1lXR1YxR01zeVdyclUvMnhLekZ2a29vZ3JGNnVoOGVDbktXM29oc2hGdjdzeERqZFhxakhEQy81d3crUFVFVm1VeDNGbGNzZ0hEUkRyY0tyekw4PTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhPjx4ZW5jOkNpcGhlclZhbHVlPmszU0pYVDFXT3lKVFhXOEsyOTFJZW4ySDJWL01MZDd6WlY3aEY5d09FcE1iTmtDL2VETTY4ZFZvQytjMmcwUXpGMFRHYWxWeHJ4Vk1pdmFLbVorNU4rM0VsUk5xc1NxWGpsVCswOXk3RjNXZFUyVXhZdGxVY2VnT1d1bzFCMFUvdFpjVGJGS1FhaHlaQ3IvajBJTVk2NGFsVFo3d1JIREk0eFpDZU1kUTlBVEZXVjdHM1VJbGwxcmhqM3MwZ2l3Ti9kRENEMzBYRy9NTlZ1SGQ5VllMeDB0QUJ5NWdZQm56eFdEemxPL094aUl1OUc0dXdIVjFuU1BESXhtbzlibzM1ZTBya2xscWZRS21TeVZzRWdJQ0xhU05JdllPMjA5ZEwyZDd1RkNoZExraW5IQ2I4UHE1OTZVaFRNUmcxdUZMb1Frb3dnN0toNXgrNTNrVVhKM0xvYjJkL1NkMzMvRUJnUWJGcXFiODM1dk0vMDZCSDNGekFTZG1IRjcxYUtmazRlaW5KaExObWRLOVI0RzQzSGZBUFdJb0NGVGVYU1pSMUwzUEJFOVlnQkZrSzRvZGpFb1ZKaGQ2blJjMXpwdDJjeXdwNHoxRTgza01DZ1hVUDAwWXlibGR2STkrWXdwRXZwZ2NLSkhBdVRNZksyU3p5QnRiNXNjNkFzcllWNkdsdEZyQnZqdzZyVi8yOWhIdFE3Y29iY1NUWUtIWkpDb2pxOWhSSHJUcGVFTXFNTE9yeUpreHJHR3Nodm9HcktWVkhidkNzUmJ3Q2hYRkV6NzU5UWdoeXdGWXJ5Smc0aml6dGg2T21peXphR3Q3VEpRbHNRSkV3YytIOTRHcEJRUWMrWkJSL2Z4dWF4ZWxuOEdVTVhGTmZFbEdvTW1GVG8rSUc2aFdGMWxKZGpzZFdBb056Z2VwRzd3WTBUVUtKV0NQMXVra0JNeitIazJoUWlROHlGTXI2Wll5aUZkVXpyRlFpcVVRY1FKaEd6QWYyUDVvQnNxbkIxN0tpUmc1TVg1UmwvekkrRzM3ZzBCb0t4YStmckJ5OFdlYm51emN4NS9CR3ZwSEltRlJvbS9ORGxTNUEySTBpMWVCTTdsL1lzZFJqTzYwNmlVYlJiUDgxZ2xlZkluQUJIQVRTUWdNN3VyaUlrZzhDNmJIVHVrdVBFSy9NNkx6SmVDYjg5R2ZtYUtnVzRORVJGa0puMVJMY0ZMUEN6VDM4QTRqYjltNFJIYS9iZHVNa1h3NUpwMVR6VGd1VWtHbnNjd1puWUV5WHhHNUdhcTJieWc3aGJLNkY4QVd3YzRGdGVxSk9pVnVvdEswUlFaZUZ6am9jakN2RFRZYU4zdHVZdktvSzM4OFJDWVFVRkx1Y2pKcFdIRGhVV2FEUytxekdEd0tpeUlHTmFvbHFlSXVjbG05UEhoVjhtTHNQZVRRVjlLdklMSFNOMEdsQlpiZk55RXFLZy9wRVFic2Z1amJhTkVON1BIWnF2TktIY2oyZTdtYnZaYjRjYlI4V0xZaENvQTVkdlI0Zks0SkJSYTAxRWJTQmhQb3dPc29LRTIrSDdua0NTVmlvMEdwU0lBQ3NIZUhrS1psU3U1RXdDbm1RdDczbUxhUnlGcVNSQ3JXLzRPUVBRWkY3d1dNaTBjbnVYK25xdTRLcmRBVFVJK2lIQU5DTzI0a3V0UjRWK3ZpT3ozT3lYMksyMkVHaWUwUHlMV2c2ZFlJTXMrQmZrTHJDYTh1dnlvUkZPUDlmajRvc3JjalN1ZXNFTzk5TXBsN0pDMERVQlFNZ1BrUTdjTTFSaENpMGlzMm1hTTVzczdtMUhBZ1EzT1BsN0drbUhSTDY5dEJRM09GbGc3NkxBRzdjbWtieDhKTnVOZVlnRHJTQ2dSVFBKRUlhaStlRVBBNmt1dkRXaDlldTZKdzNqNWt4YjZhK0tQcUNFaEMrQzdlY2N4YW1OUDdEVFFuT3hGQ3hDWjBubWxkdFZrMVFPek1ic29jZ0t4VFMvcTlRMlNUSlI4M1d1WWNyWUZSeWJHeDdrUVRhTERRNmU4eU42bVkxMGZrZDlnMEQ4ZWdWc3g1bUo0UjZkWlNsTWhmUkJYRnR4NnF5c2pvZzZjeEVFeGh6dFhpc1ZZMFQ4YktjNWtzR1F3MTFrVHpJRy9QYlV5Yis0WVgvSGZNYzc0OXllbytJVE1MSE0wRHpvZGsybmg2aEl2dnB0dWpBNmtUSDlwNlJzT1ZJNEZYTnE0RTFZTW56dUJTTFNKNGhHSk8vVThaYjUvWUhhdWszOFVjZGlwTWI2L2crcWhoNVpZMmozamVqVDZXSExPbWVVR1pvb1g3b1plQWpVdkMralYyYU9xRjRmUCtQWjA5cnZSdVpmbHFSM1dtTVpURCs0bUpzSXRQN203OFVod1grL3krTFJvcmlqYjJOK0x4Q1NTOU1BMGhxZFhEeno5NGNOWFdMVURoVzZ4ZlNmMkZ2S0t3UDVFNjJXOUhDbjM4VzhCM2preXkwWFZweitYRlFjZUxQZmtRL0JQaFdkSVU4UEFQbTh6RXIzMC9xRWlQc0hUWkZrczlNTlFsQk94bEJwcVk5RS9nbWt4ZklLbHJKdnhJNzI4NnpxNFFhR0VtRWErbkZHcWlOM1hPS1RING84ZVJhU3pjMk9CZlBWQ2ZmcHZQRUxJY0tSVUM5Q0d2VG1JNEh2V2tkT1NGbGFKME9od1RtWVU1MFp2alFYN2RrUDFTVEhBWVA5UjdSbjl3cGMwNy9INVNrYjI0cFNwSnVsWTRvcGJTaFhEMlJsK2krUTRpWTNIWXNJczZDQVlsNUxMTUpDUWtUbTIzSDUvTFlDZmViUGl3ODBBc3ZKTWpyMDF4dCtoVFNJWEFwUHVsNkFkZzUzZldCSkZuMVErTVlyZ25VRnRHckFxa0hORVJ6b3hpZFVxOHd3STBxejcrazRRMFRLOStJUndyQ1dORE1WdTU1TWpJM2J1T0hoMXJzbEVrZ0xoN0wxMlBGM082NElZblJTWW1HODRkVXZvV0NLY2RFei8wZEtyNFFHTjBUUkV6NUp3eDNYaC9xYTNWOWRkYlBxNFpGbXVzRFl5QzBwNTRaQ0JzcW1qanUzYzNsZWNrK213YlZlRnBzT0FSS0JYU2xzZlpGK3Q3RnhFdzJEdlBnbEY0T2grMFVnd2lLTExJem40MkptR2JvMktLQWx3U1NwVEsvcHdzWlUzRE1XaDVER05DckUxckNuWkdtQXlHRkZiZGMxaUhXNkpocWt1ZjMvN29LSlBaU20zQXZkS0J1ZTlxK1M2QmRIZVh6NlhRNUFyZHBaZk5PQmFVTDQwUVpyODYvZlVUdG5RVlNlWFBNeDVJOVdyT01WYlJtZ096ZFhCdVBxVEtyZ0cvcG1pR0RaK0FhbzRybUFqTUxUQXpyWlY0NmtPMkhRVGZCMFU3aTh4SENLWWx2b1RCUVFZMUtSQlB4VVVyR3pDWXlzc0dFdnpqMUppQVdvZW1PRU05MmJGTkdKRjNaWFBqaS8ycFlQT1pGOHZxejVlWlRUcWdOUDhGcFk0QTJEY01oZGY1bkpDdC9kZXJrcENJcERKT2piN0E1ajBUc3dyWmJHaUN0WXZDQlFpTnk2UUJ3aTZ4UWpIc3hYdGRVeStDV3JWUVAwWm0xSW9KMmFxMXdoYzZMVEhidVNuVHlMUVZSSE5oYzdxek0vdmZoUXluKzY5N3prSzJTV1lvMzJ6ZUsyd3BDQTNLazBWemp6VlVRYkpHQTM1SkJKVlIvQll1THMrV1lxdjM2LzdQZjBiVkgxTmdldlkwVG5zRitvTDJGeTdkZzJNdzNzMmlXeHBNT1pUdEg3R29OVW5VQ0NyM292RXZReldzbmY4dmNJL2w3UERVZVNGN25vMWdHSEtZNzV6WTZrUHhVOTlJbDFEN3pEeXdteHlJdTVHZ1c5c3QxLzFuWEVoSGVXdWtsbTA1RnJKK3ZONnlOcy9iZ3Bkc2s0VnJqbEhFQTdXUTBSZFBMcE1IcXltMnI5cXZySGVBc2VCWktxYnFxVnNIODJnb2ZLc0pTTVJQZkVoSlZ3bjBYYTRNNFVxVFM5U3A1L2tTZWI3WkNWWXhqdU1iYjJ3WGNkZ0VmM0dnVmFobFZRbVZHcGZBNWdGYis2elpsY3pMaWgrTGNLOUxYT3VVNXVUcDViSjBwTkRkZlNVeFpPcG51UUhKS2NIdlV5ZWtHUG82Z0NPYk1aY3V4Z3duR2c0N2F6Tlpqc2xhb0tHTVFld0E3TDBHdjFDbFdSeTB1T3lOVHFzRkU3aW1iL0hJS0hBRklvT3FtRzQ1YUQxMVR4OExyMENGVjR1enJ0WXhyWWNxbkNJZkFlZTNrWnZjSW01SXJSWTdONHA3NXpBVERvYXQ2cWUxZHQ3NFo0OVl4U2trNys2aWxFT0hGbHBETmxwTDR4cVoyVWR4LzNhYkhRZ3Fjb0Rrbld0cU9sZy9LeGpUejdpM2g2OHlVMFhrMVIzUWtpaUNKWFVob3E1aXF0MnFIb2tOZjhuRXRGbjRxeFNTdDZVRWJ5dEI1azA5eGV3UkFsWW5GT3pTdXZ3TWVaUXp2WkFoVDYySVlhN3E4RWplcTRxcGljejBVdVFPMlNJYVkrRG5SaXptTHphTCszVTVSY3lJQkhWTW9qZTh3bXZkZ3l6dHNXQkhta2x2cUhYQ3U4cDR3SXFFYmFxUTY5Um42cWtOWkF6dU1MbE1KV1NEY2hIS0xOb1I0eXRxZlhUNnJCS1R2ZHRObnR3KzN0bi9HL2tKcXVJZ3Bkb1ZxWlN1V1ByVVRMbFpiM3JHaEtSSWp6SFhZbkFZK015cG5wV3Jrai9YendIemtIV2FkQTdQdDRTNDNDQ0RCOWZqejhNWnNBWDJ1YlVmMDRnbXRjRXVoMkFxWXlLVG9VMWgyOTJOeVBwRldnYVhDWm9VQThmNHgrSGVMa1h3Rlh4ZGpiaEVadEZIb1A4Y3JWMzRGTURhcFFkZVJTemR2VjRranVzTjRnWjR6bHFTWWxWVmwzSGxJUDdzTXl6eGNHRUFMa09abXFFQ0hZR29TZHA5eSt6dVN0VW1ZekNMc2pSeGhjSzYrR0FLMENQeXdYZzY5V1hYRHdwUlJnNGpPQkdENDI3S0hqZFU2bU10Vk81NGcxNnNPWWxuOCtoZE44bnhER1UrRi9GNDZIUHFwN01GUnJLZzJXV3VTNVpXNTZGRVIyQUFNb1JsR2gxaW1jY1VsaG9pcmlXeUNRMVJLZ0tXMG91YTJyY3JwaEViUFBkTUU2bjZnbGgvMDJVdmxPbXE1elFzOWZRVVo0R3NQa2M0R01HaWhwZ3JSdkxzV0gxYS9zbWhVenNXSktrZTdWV1ZycUVMRTJsVnpQbTdOZkVDNHJlSlkwUDBpelJ6ZjEzZzg0M3pvcEtKd0Z6WkNLS3MyRjhIcFJKMGVKT0xCdU1mSjUyZVBwYjhSbnpGMTZpVkpkN29nakpvQnZxamo4eEtrbTNsazljRlpGbnQ3TkhIQXRIQlVBWmt2OTJUYXI2cjg5ZUtKS0VTT21CV2RweTJJbUVaT0hmQ1JjRXdBa0RoTXZxeERJTzkrdW8rek1TWTBLcWVQaUdYd3B6eFdzb2JsRW52M0NaR3ZDMGxDaFJ5TXZ3eWhBVElXdEpQZFFkTTY1b2lISzhVK05JNXFNVDRVRmdtcGJMMEFFS29LdkYvOEpraHRzYzB6YmtXeG9WMnpQbndsNWJpSm81N3RpU1RvR2RoZWN2eDdPSVpzOUkxWVo5Q0duL0Jad1p6cnU3ZThHSEgwQlowQTF0b3RxeWdvd2VZL2U3a2hjT1AxQkh1cFhUWkV0aCtmYUVUVERZcXVJOEhMRUsyT0pMcXA4T1MzRnNLN3crbmdTNUVrc2JibVB5aUpXUWxVTVFLRzgxOG5qY3hQSHVIM3gvcHRJWEFqbStyUHJYMFZVNUF1ZDQ3REJTa1JVRCtUa2ZRaTVYdjRGVzJFSlo5VWRTcXBPdS9jMERNT29XOG1uVlJXVGk3QmZMcnplbDc1UU90V1VXcFkxWDU0aDRIenR3RVc4ajhVaTk4RFoyY0ZNaE43bHlXK2NwakZTU1o0d05zcmdKbjNNMkVmUnlSODNGbmhLNGJpcHZRR2hBb1d2eUlTdjNIZEVJckRzeGRBOHlzLzNZY05nMVB1dlpLbnBlN2JrbnkyT2hhZWlOYjZhVHh1NDU4MmhVZlAwZXRuSGRFS29IMXdRcTRKc3J4OXkydFJzejh5d3ROZ1ZvWUVkMzQ2aUVSWXJwVzAyS1cxQ3pWZGdqR2NuRFR1d1Y3MXQ0QmlqNHN5QjNybmVmakx4Q2YvOFRmb0tnQ0N6T05Wb1ErZEVSZEx2VXBvR3dQOEpPaXlleUlWbGcrU1R0RFlyR045eGllUXZldDN0K1NDaUthbkI5MGdlTlhqNXdDRGtwSXV1WDdOamxXNkVRdk1OQkUrUUwzVzBpUzZaWW9UR2tWeDA2OFlQTUN5REdEOUM4d2NoMkRaOERNMGFTQmoxWVoxZSt0QjExRk4xTmFkOXhzVXNLRXEycis3R0Vnb3FxbTBFdWNUUzlsTDA0M1Z4b2F6QktacC8waHFtY3IvNVN0MXdKT0hnYUpzMTBSVWtESTgxdFdLNnFwRVJLR2s1bVZLWTlwd0g3aWpQNEh6WDdSZDk4TzNFclF4bW5uaWwvS01NWHNRemd3d2N0dXVJYUtZbWZ2UnpCWnFSZzZpSnNqT3FZNG5SV3EvOGdVY1d1YzAwdEpPb2Q3SElZang3c29GeXo2enI5M2FDaUxwWnBNTDMyWFNoc2huRFljSm5EUHZUOUZIOG9RcUNwem1hWWNaczdyb01qNXl3Wm5PWlV3Qnh4QjNodUJFUnlzcHgrNDJOWUZTOTAxNitjQjJiRE0rM0ViNWxMRjV3Q2RpWjUzbkdkKytjbDkzNEwwODdCNmlxcWpGU05SbnpMQzVNelErdnVwcVp2cHpzVUlRYlp1UWZrbm5hcTA5Y2ZiVjB6WEI0am5GRWhlYlJyS2hPekxGNUU3NXJzcXZudGpGV0tjYVpQa1VvNUlLSTQ0MElZYlNlQUtFVUc3R01KZStVNzZpSy9zdUYrT2VMaGRqUFV2RXppNzN5NkhoUVZ5QXlrNldyKzcwWVJLcnJidlB4U2tjWEVEb1FaKytmdVpQTFhiRkNBQTl1bjV4TTlZS3pNTFU0UDVyaHgzSTg2dDBTOVB2RUptUnAyRnQ2U1NhdTNqekYxdmhvRjhTSmp1bDNLSlBGZDNkNkQzYmovNVd4c0IrS3J0aDNyU3FUMjlVZFcybi9nUTdMajBQeEJHVFhtc2tlTnIzQVZacGRHRTk4dytsaktCR25McFFKeWxwVkVubDQrOUpEdnRDMTBqNXZoT0ZLN09mVUJuVWM1VXBiNjUzWDcwdFBlaUdvS0dNR0QwZ1Roa1I2L0xqb1RoQUVKVkN6OG1pTzlBcHpiMnZLODR2NHFDY3RxZUFKcS9OZ3FMYlFzMXpKWWFmRjF4V21KSHdzMmZKR2plZnZvQnNFNlhYc005Zk1rK0R0Yk9DWmJhL0NkemxTTXNDUnJJZ0JYdVFON2VTZWtoWEs5T2ZGc09TakFJTXl2YkNZNURvaDh5NTBVZFFDUmVra1dBeG5VeTIwQ3pVSHRnRzdMTkg1dHRrdld2OU9LYkFuWHdSTmhBam5FYVZxU0ovMnYzcEVkUmJEVk5nYy8xcVJnZ1pkY1VIbnJ1eEdQUmRHemNoWmk3OFJYcW13MGtLdFF1c2NrVjRWUFVvbFBvYVp0VkFsbHhUY0lRcHIvQ3kxV3JsdUUrdm9ERlJaTlBMN0N2bGhKcUowT3dEamJYanZHVnYzRW1NMzFGVjZTWU94UGtzYnNwd3ZrVjFTTmsvaURUblNQcmRPT29lT25iNlcraXhHakdOZk5GNmQxYlh1WFdacFdRcjFXZ2pQcE8wZlVoSU5nWDZZNmdZTmhlM29uTG5YQmFhc0hjOWF2RHduQnFlcnl3QTFqQ0RtNzArVWVUN3U0U0xRYUhzRkFONDY4dW1pdkxTSTVjZVE4YXZua1NqSnN3czFkcUF0T1pKWmJsKytzbWdlNWZyTm5teXRQN3AvM25FL05mZXdyWDJha2FLZHFhRC8xSVpUc2Z3NDNaZTMzSklhRGJaSHRmZnBTd25XdWtiQmczdldZeFN1Z200dWhOZkl4M3V2M2xyTUJFYkI3NmVLMUx1eVpsWDdYQ1dMTUxVbTNmS1hwbnIxQUVVUEp5bjdnNU90bUxveWFXNldEOW5hZ3ROZHdhYy9vZHdsRWxpOWJXRTlzcEtOWFdnTWdEZTI2R0w4RXB0NXJUL1Q1cVRpUGkybEorc2Z1SG5vV1RCQkxFK0hHNnVmbnFyeWJKZFpTTkpOQSt3VXB4L1FoUDh3a0RpWWdrc3RLL0pFT2RSU0VyNk5MV3IrMSt5cHRPK1BaV09FMy9UTHBKZkVpaUhnek5vK0Zicmg2WDA2NC8wYWE0ejlQejVCT1oxbGNiVWdWckxBRXZSNENpWGE3Z1RKMmFLTFBLc3FsZ1BubytOcXEvWjZyWUhOOTlPOFoweWNNN2xsUlIySk9iK2dETlBROFdoTlNNeUZFdkJpb1pOQU5UUkdYL2lEMnh1VWM5aGkxTU94UUVxZWNUamZBTVNZUUFxSUJrZjVycktBdFpLeWtOeVVUWndsNTAzNy9WM2lBOG5meVFhWTh6c1d3bDdZakxxSzczODZRK2dTRG5QeUpHY1N2b1JvL1NYN0p2OTJRK0I1eVBLV0tVTUNBWHM1ejRUT01teXFNUzYwSzVXYzZtekhJNDdHd2R6OWlrcmdmaWhlYnV2SVR3bHBBMEtGdWJOMXNHdExMSkpCeUFzQnJ5bjQ5d1JvN2UyV1l1bWozRU5uUzAxVmVVWlVJRFoxYVMvTjRwVGxqN1V2M1ZwLzhnME83anZjanB6bGhwZUlSY3FqMWNsc2E4Sy93YStGNnk5a0RTRlFhSVFaM1JmWXc1a21rbFNHVk1vZWxYekh2MDVsUmh1UVdneVFxK0FYL3J6TDdTRm1Pdmd4eWQrYWhobnhQUjhxdjMyc1owT1ZkS0RiT0Y1Ylg5NFZRRjNCcmhhbHI4TmdTRzZqL21NQnl0cmVUcldGZE1LZkhQdzBtVVdQbWJ4Q2tGOGNrSWV1VFIxbnRmQStFYitDMjZYVW5Hd0RYN1pKSG5oWmV0b0htMTYxWUZMVzJrSGcxN2pzVGllb1kxN2tpRmM1UDBGdFhaeksyQzNabzYydkExZkJhM3RpMjN0Z0t5Mnl5OG1JYWQrWDVoRnJXcWJrMEFSSXh4eU5ZR3V5RE9oMW8vRGhwa05nMnZ0ejdyOTIwRlRmTjUyYTZTWmxPVGtsdlk4WHEwMG1UWUNhdXJxRGZOeVFGY0RBUkk1ME5XVnZiNFZ5cy8rTmZ0dC9sbFNWUUpsd0ZkZEFmaWNsR2xRZi9YdGtXemkrQzlKc0xRSW9kaXZ3NEZkTGk2aVV6MlA2aFR0eEMrd0FOUkZUdGlNcm1Eem12V3laVGJRKzcrVnMzQlZCd0lVSkVlMjdNUW81TUtlci9McGN3ZndyYjhmeEFMbVNjVmxidUlaZXpvWEI0Mkh4T241VVM4TWxXSnU0bjN2RlhJd25TMkdKR0VFdlhMTjdHQ21aOUZDOFdSZE9iUjl6eFZFNElhWXViNisrZ0JHdXRPRjF5cXBmRXU5TVVselJ0RlI4ZklhWDg4MGZRUDlFNHpCVFV5bElwMEV4eGRtWGR4MElUTWJpU0R2MktBOGFPS1FnTkdIalNHRVMwdkh3TmNpem9SdlRvV0tqMGROeWRUdWlvWjUwM3RKMHdSZ1JkaEpKZUoxUEFuQzhMa29xY2ZYRGtSc0R5MTJvQVJXeDFQdklWNFFRZDVia2lQMTRFa2xIZTg2NUlEMVRpVW5kaDFCYmhoUGE0YmVJT2VzdWcyLzdsRmVoM3oyTFVjbFRtczVaMk15NllZblBMczZYelI1WFlranB2TmlqNHZiSEUvb2dlbVUxdnFDSmszbEk2S3J5RW5EQ1h1NjFxRE1UeXhHN3p4MlZzZUNTL0FCcis5OWo2NEZqRzVrQ3RQQWxJbkhFVGJib1l4WWZMVkh2bVptRXRzSkRZTGdYaWdWVUpTUFoyZkJudGZPSjRZWEtQa3dZNk5BaW13dlU0QkwyYmRuOWFUQzhMKytrTDYrYkRBZVd3SzEwR3BWQ0tIRHBINTFjWVBDem4zaHZXMFFGelViejhETldPYTBxcVFlblgzRVZxUWlEdWhWeUVNdHJtbTZOeHFQbWVOLy9KR05ha2taSzlLSysrOVhhdyt5SDg2NFdhNmExVzl4N1A1cmZrUU1wclFtR3BPaExZSHpWT09STXpGRDZUV2poeDZkV25EVnlRVSsvb3lMTndZUk4vKzA0bG1WaFArcHQ2YWE4R3QxTkNRemlxcVRtM2J4VXF4NHU4Z29GRDVWL3paTC9wVFdVY1F4UU5yNUg2aEJHTlNpVGYzQjFCcTMrRjVCeUdKNVpJcGt2dWlGZ3luT0pkejFWTGtjUFNhS0NMOUg4WU00a3dIL0pNRTlxK2ZteTNjRXpaL1hxeTRjU1pUZ3V1YU1lMXptQ2E0Qnk3Sll0OWdRaTl3aVBoNGZPcmhVaDEyQ0VoZm9rUmlxWnQxcVRUbDV0TmpHVFJhLzBKUTBCRXpKamVNd0xBMDkyakZ5NWxZMnYwd0w2WmVkWGFxYXJkdzNRaFlrUmlReS9DRkdadWV3Y0p6TXdXcUFBMmJOdFdHK0hYaGMwS1J4dVY0TDlqVkVjVTNiWDZFS29JVlppeGwwUHpUTytkT3JPbVQyVTdML0dZdXJHZkN3bHErMEVzUFJSODZzU1p5S21VdnpwUC9hZFNvbEdIWE5xbDhMWnlhUldmVktncHRoaWptYjlsZ01qQVRqVGpGQ2VQa1h5M0JYUGhxb3ZjL1lUSXVWM0pXL1NrU1dRSG42K3ZKdmMyOTBVQnNvdnJIeHZlZVUwdDNmMnFMa0NnaXEvQTRhMlhZTmZ2WHZEek5iNHhrY1F3Z2ZZdnhNRTArQ2RtYTFEZWdiTm42Nnd1eWxBNmdXNTVMTXU1cnZMQ2g2WmJhdXVHMCtINFFRVnRjTlppTm15bGxmTDdCMnJzMHZ1ajFGT1o4eXRjbGZNZGZEWnNxenhTbk96dlU3S0hScmdUVHo5M0R1SHE1NWovc1VpTEVkdFhqTUhOMnJBWHBNaktjcTlSU2JhSGhDWjV4TVBYK2c2cmhzTzZSaHA3WXFYMFBiMUorY0hYYytjMVY0RUpXTjM4N0NUbW1FbmJTTnhNZlN2TTFjK1ViVDE1b2lrZElkSnRobitlNThBZVRTbVFtcVBWaDVnVkIwS0ZubFd6cUZHNndwVEROU1pXY0RXMzBQZ21ib3F3cndkcm1aTUlXNjZYNWp1K3FzSEthcUx4WTBwYTZHeDBQSXhUZkZIUmFwa2JwVmpMSVhlT3BWelR3M0NqTDFSNElNc3ZQOXV5ODVFejFldDJBSnY5MGtESnFQb2NKaW9lazdoVmFSV3lFZTRCb3ZBcmRaM1JEUmk4WldXYUtSZmUxY0I0MUtWNGR0K2Y3M05oWE1ITkxpZnQyT2xFSzI4LzRqOUhSVEtpT3hkSFJoV3ozWEhWTUwxNENvOExSVjlLQ3dQdU9FOWQ2NjFVcEo2a241aVc1UWdRcGJ0RUtSQVJLa2ZIUGpWMVhTTmUvM1o4SkFDOFgzaEwvdUVBYXYvcW1BUDdHOGpoREFIY1pTSkFtcjJ3NkRDSnB0Vm5jSGhvNzlZVGUzSFo3VjNTS2ZYMHpXNzlFcDYxSlQrWUV5ZStHR21veks4RWVQR01ybTVPMlBRbjNvRW9IUXZobFc2c2hPRDFid01jMGppdFlXa1ptc3NtdE9Ba2YvaXBMcy9yK1NLYVdNeGdjSlFFTzFzUWJFMGVNQXpEdWJnQmIwM2RZNjh3azJYd3BPRzEwckNDNmN4bHlqOEhYaTMvektQcUs0ckNPQ1o2dVUyU1N0Q2REaVMrLy9YempGWVhrbm1jZFFqTTdkemltMFRLSVpTRHlScC8xa2JyZlRGOTUzc0Q1NjRVb2cxWFR5dnJ1MnFtRjZDYnhnVmZmSFF2RTBtekFYMEZvMStaVkk3eS9NbWxJZnJLaHplU1ZSSkMrWFpHdkVJcXQ4aEZJSXo5bWQ2ZXhtNjVqTVZMaXdXMWdjNTBDK2hiVHRLdkhXU1RRdDZjT0ZhK25KNms1NWRkNTZ4WGdKemh2TGVybmdDTWU5MFBMQzY3Szl5Tkp2UDFUSGVQcnRHWllZKzRjWVMwNmpiMG9ETzR4d0QxYyttalhSK3c3clJzV01MQkZFSUVDV2xOQ0JIdnhnL0VxekNGM3Z2cXdEREhwOS9Mc2txRkxRZkkwd3VaNFkwZkVsVjA4a1MyemFGZkVVOXFtVU9pdnlVZ21OVzR6NjY1NXJNWE9xeDhQWUF5aVU4VnUwQUlJeVFoVTQ3UXpJNzEwUmxOWmQxQjNTRUlET0g1bDBwblM1a3ZzcWRWRDBrVjZONEtYc0ZXc2FnUWZSUmRUQmNpL2d4a0R5amNQWDFSZUNnZHUvdlNSZDI4VStmTzhZOUg1M25CUXdIT3VpZDgyQ1ArZ3RzVWpCcjlWRGtRamloaVlNRE4yK3UwMkdsTlFmQkJ3Ym1LQ2l5VTQ1MkV6blczVlpVWWMyNlpQS3NyK1BnbGwzbHY2Q0R2ZEVpaVFHbnRIV0J1UmNuQTJHUW1GQWZ4SHpLQ011YXRPOC9jR09sWEs0ay8wbWpwVVBlY1RUdkMwcmNKZElNUHdMUllGWmpQbVpjMW5GLzlZQkV3UVozZWp6dlNtNnRCZHpBZmFvWnJUTXlQNlFCaEg2UzBiNjJUem1MWWFDTURHcmFSbThkUy9ZZ3ZyTFlocCtTU3ZBQVlVVlZTR3p5YzNzZFpOOWEyOUUxMGVYYSt4eGd6dlF3ZHdTQnFsSXN6VndUcW1CcURVNkpNT3JnREd0Sk9CTGg5Qmw2YW9Hd3h4UEpKdXRmOUZMR2ZzOVN1cmlXNXhSSlF2bC9lTldvczBhenc1eHgxdkwvbzA0VWhINGoyUzcxSlJyaWF0ajlNaDVUaWNvS1FwOTAraDJ4anhLU2FEcDVLUWdyeHU3Rm1xZEw5VFNKM1dqZWtqT2JTVkJoOTMwMXdIU0E2NDU5aUpOU2NnS2FOUWNyMVdEeWI3Qk9QdnZnZ29IZXJtcnhJeTZraERMRWdpU0twRTA3NnRJZk9OMVBVSis0STErenNDMDlFUEF6OTF5YlpxMkZlS29pVkM5RnBMYVVqcWlBL2xzNjl3SXU2MlpwRkhXMGVvb0RUbVNJYUV6NTlSVTlFOC9Sb3Y0WG9DWjFxWG14emNCcGo2YUpiL0VnYkNJUkREZkp2U2NJcFc1VFRITGdLbXJEckF3SEFNM2dOL0paZjNxYWkwVlF6anBkVjAybHUvZk9aMDRFRFdkVmE4RWd2SUpmODJINXNrKzEvVStMbzhzY2c3Mm10TVBycUVpWnZLNStsRC9FZHRzMlMzL0p4dUQ0UHlid0dYRENRd0dhdytNbzNuYzN4VDNhaTJiRFNvaVgvUFpWMlc5Qjh4N3Roc1IzNzlzVXlBQ2l6c3JZR0tFczB6enZiMzUydDErS0Q2UnE1QnJ4bUpmMkxWTGloa09iZTdqbHpnVE1MRWpWL1RnVzM4TUVyTUtyTldQaytleHVaQUsvNU8yNlZsdTdicmZSU1U0b3NtTVFTZVEzUHBjekZsV1JnaHA0d0hTcXRlTlQ5eXJ6cFZqYm5HUG1xWnZJNVk5RWpBcE9VWkpEZFcveXl3MncyaUl3UGMvbk1ZZU9zdHRKd2RFSDhWYk1NV3B3dklXSkFqYjZ2Q2ZhRUlFa1A0UGVCVWlmNzhCMjdabVdoTVV3ZklRS0x0UnlMZm9MNVFaa1RoblJSczFBYWFiNXVWNDEzNU5hQU52eXBGRjlEMTRTM0d1d3dnQXNMbUR3SzJTU2pXTlMwemFsMDRVODZmTDNHVEhQL2llRFpkbXUrY1A0clNtejhTcVVjNEZUdzd0dGdUbVcrZy80OTNqMWFmSVdxdFBRY3dNZ2U4QzEyM1pCVEEwMGJTN1FDell6UDdRSTFvNk1EOHd5NGZpR0ZLY20vaXVSR3hGNUdNNlBuUU93VTU2TXRIRHp0alhENkFXTVYvYW0wb3dxV3lETWVVcHZGSWVWOCtndVluMFVIWTUvcnZNOWRpdDJtSkxRS0dkYllEWDFFL0E1QzJUUmg0SGZndEM4MzBuK2lXWG1uY2dGTmh1eFBVWlZVYUNxUnZYSy9IczRyVlc5dlpsS1hFK0Q3VmlLK0tQZlVkYXBTTUlpemU0cnZMditGV20xOFQ4RDQ5ZmxrQk1tM0txdW9FbnhpRDg4aml2Z29uYWd1dmdyZWNqUzF6VEU5dWM3ak1ua0tRL3Fyb0NpOHUwQnhJV1ZlVkZna29ZWUxvZGNxN3FQS3g0T29HZ1ZqMW5heC9ZY0dmNEVZZkovK3d5K295YThRWGVzeDh2MjgxYXdnemc4SWJ4a21yaklIaVpXSWZIejlrVk9CK1hsblMyRHhsL254UWNleVpQaVFlQkdvMzVmMWRzSkwvS3RYbnd6ZUh2RWJQSHJQVHVQSG1uVzJnT1ZWV0pKa0RRVEVUR3JrdVJQdi96R1N3T3dKaTZFS0dDbXA2YnhTZ2tPL29McTZLTUdaUXMrMVVFVHB5UGJSYkk3SE9lYzRpamREUHRDVGNEVHhKNVhqYnR1NUlaSTZ0NVp6a25qUnVmclFRTnplMWYzdEVtODdOS0tYT3NQdTVsVFgyVXdidFIraVRjc0ZHaXlLb2VNQ3BZRW5KdHZJTU5MNFc5UzFKL3JsREJsMStZWjBnWXY0Mi8zTk10TDlJODJVcnU4YUxBYTE5Vmp0cG9YUStPWEFFVFVsVVBHMmFRSFNFc1BJbmVYY2NreXd2R3ZURG1GZlBZakNSYzZlWjFlSXRaRjUrTDhZaGZuQjR3MFhMZnFpUFgxc1lJNmJCdVJoUDJST3hHakJDQkFpVXpuN3cvaHh0SkhZS1dseGl5Vnk2V3o5ZWxldndUUU90ZDRhcG1mb3lLU3ZsVGFlNmQ1T3NiQ01GMW8rYTNnQlBqYkUrU2xyemUzclcrTmw2TEtMVmgwTE0wRDU0TFUrWUtNeGNaV1Q2RGJQTTJTNUNlcHk1VHovTEFvZGpTWis5bDF6bkdCZEJrNTdWMXJNZ2NqUDVIZTJ2UkxRV0RoSjhTMzB6OVY0RmRUaVhhN3V4K1lsOEJCaE9INk9GWkpmR25JUS84b2Nvemg0MWwvQU14OHFYaFpwQ0xpeGdGQkNxS0J4d1NFOHdubnZwRlIvZzRDL1BjaTRrUVpIeUN2Z3BuU0wyaEEvMXJBdFdlYXlRdHgxOXlOMWN0QzBqLytBSHdQSUhiV3Z0ZTRaa3A0ejRzNjBqOHJ4S0NDOVY3cEEyaHp2SmxIT2NBTGNZUExPL1ZtYnc4RFp2S25zemVqTkN6ZlVpbzNOK3ZnR3I5NENNTjBzS3dUUHE1WnFVSE1SQ0wwRkhPKzdBMG9LcllQb3ZQaDExczZGY1NSQW5UY0g1QUNkN2pxSC8wMW1YL0x2emp2bWhweXBhWnAvdm9naDIzWC9ubFZQVm1OK1FkaHB5Q3VKb041aTh4MVh1VVJRWmkxckQzYUhXZWVCOEt3ZDRIOVQrWWFTTlJORE0rYStLZzhEU2RrSDJJcnlKU3RkbmU3K1lzQ1ZHaGticzk5WTZMRW1wT2taUExEOFBaWXB0dzBuSC8vR1BpcGkvb3R6UWc5OWJ5VitkaFR2UE1HRHZURUpwV01Wd25QelBqV3FGak9hV2ZLUitFYU9xeUFWbnFHNHJvV04wWW9VTTlML1J1a201ekk1MGd4M3lJSmlLNE41enBFV053RlBHbXNxTU1ZSi95eWZPVHo3MlJCanh3M2M3SE0wQmdOMjl6VDVncURpNDgvSENwSjFsNHhGdzlJTS8xQjFVbGpESS92NmE2bGFWdTFlVEgzLzNqb2VwM21kemFWWHVtb29PZnVDRFZVOUFWT1Vlb2dQdHp0VzdqRlJ4Z29XT1hienBlOUlBU2U2bWpLTGhGZmtpei9CMFJ3NThYUkRoam5XTy9Uck5rWmpOU29jQ01vbG0xVFVTcUM1UDhWK05GN09GSFplVURONUwydzVHbTZnZ20rR05oUXZOSXE3TjZ6QUFoaWtFQ3lZVlJid1lhOXhtUFM0MXIycnIvQzB6eUZmdUliVmtLUXp1WjBvdkU2VW16b1V4bldLbW9EV0Rvc3p2d1BFOTBCT1Z6cnI1MU5wMFhVYzhycG5vZXlhUzdjMzViZy9nZUVFbHNOREEyVmlwOW83aHJtWG1BVEZXb3ZUMUwySXpOczh0a3h3RnlOMGVVVStsM3dzcG45MTYrbG9LYS9EZ1ZTOGZXOU5OSm1pRW9BaTVzNHNRdUpqeWVBZ2NTZGQxL0VQVFl4RWVtQWVJWXFQdU8zRnVpUUlzVGIyVzdpQlN6b2w5QWZXK0kwb0FtRXRYT2xBYmVhdDBJeDBNM080T0dZYjI4aHRLQ3VGTE9pOVNLZGVQSlR5aU03eS9uU2tkVnpJT2ZrVTNqU3RyRysrQWV3ZTBLWm84Z2RHOEQ1R1NSQ3JlYXk0PTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkRGF0YT48L3NhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+";
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
when(request.getRequestURI()).thenReturn("/sp/consumer");
when(request.getMethod()).thenReturn("POST");
when(request.getParameter("RelayState")).thenReturn("kojit9j9o1ff9q6vpeo8dnsfc9");
when(request.getHeader("Origin")).thenReturn("http://localhost:8484");
when(request.getHeader("Referer")).thenReturn("http://localhost:8484/");
HttpSession httpSession = Mockito.mock(HttpSession.class);
when(httpSession.getAttribute("saml2AuthInfo")).thenReturn("kojit9j9o1ff9q6vpeo8dnsfc9");
when(httpSession.getAttribute("saml2RequestID")).thenReturn("_4e68b1b1596d142f0e2aac3624c41d1b");
when(request.getSession(false)).thenReturn(httpSession);
when(request.getParameter("SAMLResponse")).thenReturn(base64EndSamlResp);
assertNull(authHandlerEnc.extractCredentials(request, response));
((AuthenticationFeedbackHandler)authHandler).authenticationFailed(request,response,null);
}
@Test
public void test_goodLogin() throws IOException, RepositoryException {
userManager.createGroup("all_tenants");
userManager.createGroup("pcms-authors");
session.save();
String base64EndSamlResp = buildAuthResponse();
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
when(request.getRequestURI()).thenReturn("/sp/consumer");
when(request.getMethod()).thenReturn("POST");
when(request.getParameter("RelayState")).thenReturn("ncu7lhndv8o4o096im9065ijqn");
when(request.getHeader("Origin")).thenReturn("http://localhost:8484");
when(request.getHeader("Referer")).thenReturn("http://localhost:8484/");
HttpSession httpSession = Mockito.mock(HttpSession.class);
when(httpSession.getAttribute("saml2AuthInfo")).thenReturn("ncu7lhndv8o4o096im9065ijqn");
when(httpSession.getAttribute("saml2RequestID")).thenReturn("_f96afa62dc16b1cc99efab06db0c750d");
when(request.getSession(false)).thenReturn(httpSession);
when(request.getSession()).thenReturn(httpSession);
when(request.getParameter("SAMLResponse")).thenReturn(base64EndSamlResp);
AuthenticationInfo authenticationInfo = authHandler.extractCredentials(request, response);
assertNotNull(authenticationInfo);
assertTrue(((AuthenticationFeedbackHandler)authHandler).authenticationSucceeded(request,response,authenticationInfo));
authHandler.dropCredentials(request,response);
User user = (User) userManager.getAuthorizable("saml2Example");
assertNotNull(user);
// verify user properties sync
assertEquals("saml2@example.com", user.getProperty("./profile/email")[0].getString());
assertEquals("Saml2", user.getProperty("./profile/surname")[0].getString());
assertEquals("Example", user.getProperty("./profile/givenName")[0].getString());
// verify group membership
List groups = new ArrayList<String>();
groups.add("all_tenants");
groups.add("authors");
groups.add("pcms-authors");
Iterator<Group> groupsIt = user.declaredMemberOf();
while (groupsIt.hasNext()){
Group group = groupsIt.next();
assertTrue(groups.contains(group.getID()));
// verify managedGroup flag
assertTrue(group.getProperty("managedGroup")[0].getBoolean());
}
// authors group was not created initially and still does not exist
assertNull(userManager.getAuthorizable("authors"));
}
String buildAuthResponse(){
LocalDateTime dateTime = LocalDateTime.now();
LocalDateTime notBefore = LocalDateTime.now().minusSeconds(3);
LocalDateTime notOnOrAfter = LocalDateTime.now().plusMinutes(5);
String currentTime = dateTime.format(DateTimeFormatter.ISO_DATE_TIME);
String notOnOrAfterTime = notOnOrAfter.format(DateTimeFormatter.ISO_DATE_TIME);
String notBeforeTime = notBefore.format(DateTimeFormatter.ISO_DATE_TIME);
String samlResp0 = "<samlp:Response Destination=\"http://localhost:8080/sp/consumer\" ID=\"ID_5232eed3-8fd5-4562-92bb-69af0246c341\" InResponseTo=\"_f96afa62dc16b1cc99efab06db0c750d\" ";
String issueInstance = "IssueInstant=\""+currentTime+"\" ";
String samlResp1 ="Version=\"2.0\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"><saml:Issuer>http://localhost:8484/auth/realms/sling</saml:Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/></samlp:Status><saml:Assertion ID=\"ID_c78146f1-6c37-4ad5-b2ee-0371667c3aeb\" ";
//issueInstance
String samlResp2 = "Version=\"2.0\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"><saml:Issuer>http://localhost:8484/auth/realms/sling</saml:Issuer><saml:Subject><saml:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:transient\">G-212b1981-a621-4c67-84ac-cd75551a0250</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"_f96afa62dc16b1cc99efab06db0c750d\" ";
String samlResp3 = "Recipient=\"http://localhost:8080/sp/consumer\"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions ";
String notBeforeStr = "NotBefore=\""+notBeforeTime+"\" ";
String notOnOrAfterStr = "NotOnOrAfter=\""+notOnOrAfterTime+"\" ";
String samlResp4 = "><saml:AudienceRestriction><saml:Audience>http://localhost:8080/</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement ";
String authInstance = "AuthnInstant=\""+notBeforeTime+"\" ";
String session = "SessionIndex=\"4d647e71-cc20-4779-ad58-7df15816a8c5::901bf7ca-3984-4a5b-8f8b-c1c737738102\" ";
String sessionNotOnOrAfter = "SessionNotOnOrAfter=\""+notOnOrAfterTime+"\"> ";
String samlResp5 = "<saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute FriendlyName=\"givenName\" Name=\"urn:oid:2.5.4.42\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"> <saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Example</saml:AttributeValue></saml:Attribute><saml:Attribute FriendlyName=\"lastName\" Name=\"lastName\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Saml2</saml:AttributeValue></saml:Attribute><saml:Attribute FriendlyName=\"groupMembership\" Name=\"urn:oid:2.16.840.1.113719.1.1.4.1.25\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">all_tenants</saml:AttributeValue><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">authors</saml:AttributeValue><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">pcms-authors</saml:AttributeValue></saml:Attribute><saml:Attribute FriendlyName=\"email\" Name=\"urn:oid:1.2.840.113549.1.9.1\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">saml2@example.com</saml:AttributeValue></saml:Attribute><saml:Attribute FriendlyName=\"userid\" Name=\"urn:oid:0.9.2342.19200300.100.1.1\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">saml2Example</saml:AttributeValue></saml:Attribute><saml:Attribute FriendlyName=\"surname\" Name=\"urn:oid:2.5.4.4\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Saml2</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">uma_authorization</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">offline_access</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">view-profile</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">manage-account-links</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"> <saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">manage-account</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>";
String preEncoding =
samlResp0 +
issueInstance +
samlResp1 +
issueInstance +
samlResp2 +
notOnOrAfterStr +
samlResp3 +
notBeforeStr +
notOnOrAfterStr +
samlResp4 +
authInstance +
session +
sessionNotOnOrAfter +
samlResp5;
return Base64.getEncoder().encodeToString(preEncoding.getBytes());
}
}