Saturday, April 18, 2015

Spring Security: ROLE_ Prefix

The ROLE_ prefix plays a special 'role' in spring security since it is prepended to GrantedAuthority name by default. While different XXXAuthoritiesPopulator (as extending DefaultLdapAuthoritiesPopulator) expose a property that can be changed through spring configuration (for instance) this will not help very much since the constant is used as a default value (added as a prefix if not already present in role name!) in many related authorization functionalities (like voters) so getting rid of it is not a trivial task. For example:

<security:intercept-url pattern="/faces/auth/admin/**" access="Admins"/>

will actually check for ROLE_Admins.

Seraching for rolePrefix\s+=\s+"ROLE_"; regex in spring security (4.0.0.RELEASE) will return 10 hits (in 10 different java files):

D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\config\src\main\java\org\springframework\security\config\annotation\authentication\configurers\ldap\LdapAuthenticationProviderConfigurer.java (1 hit)
    Line 61:    private String rolePrefix = "ROLE_";
  D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\core\src\main\java\org\springframework\security\access\annotation\Jsr250MethodSecurityMetadataSource.java (1 hit)
    Line 41:    private String defaultRolePrefix = "ROLE_";
  D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\core\src\main\java\org\springframework\security\access\expression\method\DefaultMethodSecurityExpressionHandler.java (1 hit)
    Line 43:    private String defaultRolePrefix = "ROLE_";
  D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\core\src\main\java\org\springframework\security\access\expression\SecurityExpressionRoot.java (1 hit)
    Line 26:    private String defaultRolePrefix = "ROLE_";
  D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\core\src\main\java\org\springframework\security\access\intercept\RunAsManagerImpl.java (1 hit)
    Line 60:    private String rolePrefix = "ROLE_";
  D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\core\src\main\java\org\springframework\security\access\vote\RoleVoter.java (1 hit)
    Line 55:    private String rolePrefix = "ROLE_";
  D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\ldap\src\main\java\org\springframework\security\ldap\userdetails\DefaultLdapAuthoritiesPopulator.java (1 hit)
    Line 143:   private String rolePrefix = "ROLE_";
  D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\ldap\src\main\java\org\springframework\security\ldap\userdetails\LdapUserDetailsManager.java (1 hit)
    Line 95:    private final String rolePrefix = "ROLE_";
  D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\ldap\src\main\java\org\springframework\security\ldap\userdetails\LdapUserDetailsMapper.java (1 hit)
    Line 43:    private String rolePrefix = "ROLE_";
  D:\Java\Spring\4.0\spring-security-rb4.0.0.RELEASE\web\src\main\java\org\springframework\security\web\access\expression\DefaultWebSecurityExpressionHandler.java (1 hit)
    Line 22:    private String defaultRolePrefix = "ROLE_";

The Conceptual Problem

As specified in JAVA EE 7 tutorial the recommended (simplified) approach for defining authorization policies is as follows (47.5 Working with Realms, Users, Groups, and Roles): Realm->Groups->Map Groups to Roles->Use Roles in defining authorization rules. Unfortunately spring security does not implement this pattern (by mapping automatically a Group to ROLE_Group) which might lead to deployment problems in real life environments. Imagine a potential client for your product that already has a well established policy for group membership and is not willing to enrich (and duplicate) existing AD structure just to accommodate your product Roles requirements!

The Real-Life Problem

Suppose you already have an application that uses restriction policies based on roles and you want to switch to spring security. The chance to have roles used by the application called as ROLE_XXX is minimal so that you will not be able to use out of the box functionality provided by spring security. Please notice that you application might use role based authorization (apart from spring intercept-url type rules, for instance) in other parts like menu entries visibility, code access, different processing rules, data restriction, etc.

The Solution

Supposing we use org.springframework.security.ldap.authentication.LdapAuthenticationProvider extend the XXXAuthoritiesPopulator specified as a constructor argument:

package ro.mycompany.springsecurity.extensions;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.springframework.ldap.core.ContextSource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.ldap.userdetails.NestedLdapAuthoritiesPopulator;

public class NestedLdapAuthoritiesPopulatorEx extends NestedLdapAuthoritiesPopulator {

    public NestedLdapAuthoritiesPopulatorEx(ContextSource contextSource, String groupSearchBase) {
        super(contextSource, groupSearchBase);
    }
    private GroupsToRoles groupsToRoles;

    @Override
    public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
        Set<GrantedAuthority> authorities = super.getGroupMembershipRoles(userDn, username);

        Collection<GrantedAuthority> rga=groupsToRoles.getReachableGrantedAuthorities(authorities);
        Set<GrantedAuthority> ret=new HashSet<>();
        ret.addAll(rga);
        //return authorities;
        return ret;

    }

    public GroupsToRoles getGroupsToRoles() {
        return groupsToRoles;
    }

    public void setGroupsToRoles(GroupsToRoles groupsToRoles) {
        this.groupsToRoles = groupsToRoles;
    }
    
    
}

where GroupsToRoles is defined as follows (inspired by org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl):


package ro.mycompany.springsecurity.extensions;


import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.security.access.hierarchicalroles.CycleInRoleHierarchyException;


public class GroupsToRoles {


    private String roleHierarchyStringRepresentation = null;

    
    private Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneStepMap = null;

    
    private Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneOrMoreStepsMap = null;

    
    public void setHierarchy(String roleHierarchyStringRepresentation) {
        this.roleHierarchyStringRepresentation = roleHierarchyStringRepresentation;

        buildRolesReachableInOneStepMap();
        buildRolesReachableInOneOrMoreStepsMap();
    }

    public Collection<GrantedAuthority> getReachableGrantedAuthorities(
            Collection<? extends GrantedAuthority> authorities) {
        if (authorities == null || authorities.isEmpty()) {
            return AuthorityUtils.NO_AUTHORITIES;
        }

        Set<GrantedAuthority> reachableRoles = new HashSet<>();

        for (GrantedAuthority authority : authorities) {
            addReachableRoles(reachableRoles, authority);
            Set<GrantedAuthority> additionalReachableRoles = getRolesReachableInOneOrMoreSteps(authority);
            if (additionalReachableRoles != null) {
                reachableRoles.addAll(additionalReachableRoles);
            }
        }


        List<GrantedAuthority> reachableRoleList = new ArrayList<>(
                reachableRoles.size());
        reachableRoleList.addAll(reachableRoles);

        return reachableRoleList;
    }

    // SEC-863
    private void addReachableRoles(Set<GrantedAuthority> reachableRoles,
            GrantedAuthority authority) {

        for (GrantedAuthority testAuthority : reachableRoles) {
            String testKey = testAuthority.getAuthority();
            if ((testKey != null) && (testKey.equals(authority.getAuthority()))) {
                return;
            }
        }
        reachableRoles.add(authority);
    }

    
    private Set<GrantedAuthority> getRolesReachableInOneOrMoreSteps(
            GrantedAuthority authority) {

        if (authority.getAuthority() == null) {
            return null;
        }

        for (GrantedAuthority testAuthority : rolesReachableInOneOrMoreStepsMap.keySet()) {
            String testKey = testAuthority.getAuthority();
            if ((testKey != null) && (testKey.equals(authority.getAuthority()))) {
                return rolesReachableInOneOrMoreStepsMap.get(testAuthority);
            }
        }

        return null;
    }

    
    private void buildRolesReachableInOneStepMap() {
        Pattern pattern = Pattern.compile("(\\s*([^\\s>]+)\\s*>\\s*([^\\s>]+))");

        Matcher roleHierarchyMatcher = pattern.matcher(roleHierarchyStringRepresentation);
        rolesReachableInOneStepMap = new HashMap<>();

        while (roleHierarchyMatcher.find()) {
            GrantedAuthority higherRole = new SimpleGrantedAuthority(
                    roleHierarchyMatcher.group(2));
            GrantedAuthority lowerRole = new SimpleGrantedAuthority(
                    roleHierarchyMatcher.group(3));
            Set<GrantedAuthority> rolesReachableInOneStepSet;

            if (!rolesReachableInOneStepMap.containsKey(higherRole)) {
                rolesReachableInOneStepSet = new HashSet<>();
                rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet);
            }
            else {
                rolesReachableInOneStepSet = rolesReachableInOneStepMap.get(higherRole);
            }
            addReachableRoles(rolesReachableInOneStepSet, lowerRole);

        }
    }

    
    private void buildRolesReachableInOneOrMoreStepsMap() {
        rolesReachableInOneOrMoreStepsMap = new HashMap<>();
        // iterate over all higher roles from rolesReachableInOneStepMap

        for (GrantedAuthority role : rolesReachableInOneStepMap.keySet()) {
            Set<GrantedAuthority> rolesToVisitSet = new HashSet<>();

            if (rolesReachableInOneStepMap.containsKey(role)) {
                rolesToVisitSet.addAll(rolesReachableInOneStepMap.get(role));
            }

            Set<GrantedAuthority> visitedRolesSet = new HashSet<>();

            while (!rolesToVisitSet.isEmpty()) {
                // take a role from the rolesToVisit set
                GrantedAuthority aRole = rolesToVisitSet.iterator().next();
                rolesToVisitSet.remove(aRole);
                addReachableRoles(visitedRolesSet, aRole);
                if (rolesReachableInOneStepMap.containsKey(aRole)) {
                    Set<GrantedAuthority> newReachableRoles = rolesReachableInOneStepMap
                            .get(aRole);

                    // definition of a cycle: you can reach the role you are starting from
                    if (rolesToVisitSet.contains(role) || visitedRolesSet.contains(role)) {
                        throw new CycleInRoleHierarchyException();
                    }
                    else {
                        // no cycle
                        rolesToVisitSet.addAll(newReachableRoles);
                    }
                }
            }
            rolesReachableInOneOrMoreStepsMap.put(role, visitedRolesSet);

        }

    }

}

The actual configuration looks like:

    <bean id="AD-LdapProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
        <constructor-arg>
            <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
                <constructor-arg ref="contextSource" />
                <property name="userSearch">
                    <bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
                        <constructor-arg index="0" value="OU=MyCompany,DC=mycompany,DC=local"/>
                        <constructor-arg index="1" value="(&amp;(objectClass=user)(sAMAccountName={0}))"/>
                        <constructor-arg index="2" ref="contextSource" />
                        <property name="searchSubtree" value="true"/>
                    </bean>
                </property>
            </bean>
        </constructor-arg>
        <constructor-arg>
            <!--bean class="org.springframework.security.ldap.userdetails.NestedLdapAuthoritiesPopulator"-->
                
            <bean class="ro.mycompany.springsecurity.extensions.NestedLdapAuthoritiesPopulatorEx">
                
                <constructor-arg ref="contextSource" />
                <constructor-arg value="OU=MyCompany,DC=mycompany,DC=local" />
                <property name="groupSearchFilter" value="(&amp;(objectClass=group)(groupType:1.2.840.113556.1.4.803:=-2147483648)(member={0}))"/><!--http://ldapwiki.willeke.com/wiki/Active%20Directory%20Group%20Related%20Searches#section-Active+Directory+Group+Related+Searches-AllSecurityGroupsLocalGlobalAndUniversal -->
                <property name="rolePrefix" value="ROLE_"/>
                <property name="searchSubtree" value="true"/>
                <property name="convertToUpperCase" value="false"/>
                <property name="groupsToRoles" ref="groupsToRoles"/>
            </bean>
        </constructor-arg>
    </bean>

    <bean id="groupsToRoles" class="ro.mycompany.springsecurity.extensions.GroupsToRoles">
        <property name="hierarchy">
            <value>
                ROLE_Admins>admin
            </value>
        </property>

    </bean>

Now you can use the application unchanged if configuring correctly groupsToRoles.

The Explanation

There is a very good answer on StackOverflow to the question Why does Spring Security's RoleVoter need a prefix?

The Update (Spring Security 4.0.1.RELEASE)

After updating to 4.0.1.RELEASE the authorization through FacesContext.getCurrentInstance().getExternalContext().isUserInRole(role); suddenly failed to work. This is related to https://jira.spring.io/browse/SEC-2926 which actually means that "ROLE_" is automatically prepended (by default if no other specific configuration is done) to role names while checking authorization in spring configuration files (like

<security:intercept-url pattern="/faces/auth/admin/**" access="Admins"/>

that will actually checks for "ROLE_Admins" in GrantedAuthorities) as oposed to FacesContext.getCurrentInstance().getExternalContext().isUserInRole("admin") that will check for "admin" in 4.0.0 and "ROLE_admin" in 4.0.1.

The quick solution is to change the configuration file from

...    
<bean id="groupsToRoles" class="ro.mycompany.springsecurity.extensions.GroupsToRoles">
        <property name="hierarchy">
            <value>
                ROLE_Admins>admin
            </value>
        </property>

    </bean>
...

to

...    
<bean id="groupsToRoles" class="ro.mycompany.springsecurity.extensions.GroupsToRoles">
        <property name="hierarchy">
            <value>
                ROLE_Admins>ROLE_admin
            </value>
        </property>

    </bean>
...

The difference (in this respect) between the two versions come from the following snippet in SecurityContextHolderAwareRequestFilter

public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {
    // ~ Instance fields
    // ================================================================================================

    private String rolePrefix;

    private HttpServletRequestFactory requestFactory;

in 4.0.0 and

public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {
    // ~ Instance fields
    // ================================================================================================

    private String rolePrefix = "ROLE_";

    private HttpServletRequestFactory requestFactory;

in 4.0.1.

The rolePrefix is actually used in SecurityContextHolderAwareRequestWrapper as

    private boolean isGranted(String role) {
        Authentication auth = getAuthentication();

        if (rolePrefix != null) {
            role = rolePrefix + role;
        }

        if ((auth == null) || (auth.getPrincipal() == null)) {
            return false;
        }

        Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();

        if (authorities == null) {
            return false;
        }

        for (GrantedAuthority grantedAuthority : authorities) {
            if (role.equals(grantedAuthority.getAuthority())) {
                return true;
            }
        }

        return false;
    }

As we can see the rolePrefix is prepended (if not null) unconditionally to checked role which is a different behaviour than the one implemented in voters (if the role starts with the default/configured prefix this will not be prepended any more); that's why FacesContext.getCurrentInstance().getExternalContext().isUserInRole("ROLE_admin") will ot work since the actually match will be done against "ROLE_ROLE_admin"!

VGhlIFJPTEVfIHByZWZpeCBwbGF5cyBhIHNwZWNpYWwgJ3JvbGUnIGluIHNwcmluZyBzZWN1cml0eSBzaW5jZSBpdCBpcyBwcmVwZW5kZWQgdG8gR3JhbnRlZEF1dGhvcml0eSBuYW1lIGJ5IGRlZmF1bHQuIFdoaWxlIGRpZmZlcmVudCBYWFhBdXRob3JpdGllc1BvcHVsYXRvciAoYXMgZXh0ZW5kaW5nIERlZmF1bHRMZGFwQXV0aG9yaXRpZXNQb3B1bGF0b3IpIGV4cG9zZSBhIHByb3BlcnR5IHRoYXQgY2FuIGJlIGNoYW5nZWQgdGhyb3VnaCBzcHJpbmcgY29uZmlndXJhdGlvbiAoZm9yIGluc3RhbmNlKSB0aGlzIHdpbGwgbm90IGhlbHAgdmVyeSBtdWNoIHNpbmNlIHRoZSBjb25zdGFudCBpcyB1c2VkIGFzIGEgZGVmYXVsdCB2YWx1ZSAoYWRkZWQgYXMgYSBwcmVmaXggaWYgbm90IGFscmVhZHkgcHJlc2VudCBpbiByb2xlIG5hbWUhKSBpbiBtYW55IHJlbGF0ZWQgYXV0aG9yaXphdGlvbiBmdW5jdGlvbmFsaXRpZXMgKGxpa2Ugdm90ZXJzKSBzbyBnZXR0aW5nIHJpZCBvZiBpdCBpcyBub3QgYSB0cml2aWFsIHRhc2suIEZvciBleGFtcGxlOg0KYGBgeG1sDQo8c2VjdXJpdHk6aW50ZXJjZXB0LXVybCBwYXR0ZXJuPSIvZmFjZXMvYXV0aC9hZG1pbi8qKiIgYWNjZXNzPSJBZG1pbnMiLz4NCmBgYA0Kd2lsbCBhY3R1YWxseSBjaGVjayBmb3IgUk9MRV9BZG1pbnMuDQoNClNlcmFjaGluZyBmb3IgYHJvbGVQcmVmaXhccys9XHMrIlJPTEVfIjtgIHJlZ2V4IGluIHNwcmluZyBzZWN1cml0eSAoNC4wLjAuUkVMRUFTRSkgd2lsbCByZXR1cm4gMTAgaGl0cyAoaW4gMTAgZGlmZmVyZW50IGphdmEgZmlsZXMpOg0KYGBgDQpEOlxKYXZhXFNwcmluZ1w0LjBcc3ByaW5nLXNlY3VyaXR5LXJiNC4wLjAuUkVMRUFTRVxjb25maWdcc3JjXG1haW5camF2YVxvcmdcc3ByaW5nZnJhbWV3b3JrXHNlY3VyaXR5XGNvbmZpZ1xhbm5vdGF0aW9uXGF1dGhlbnRpY2F0aW9uXGNvbmZpZ3VyZXJzXGxkYXBcTGRhcEF1dGhlbnRpY2F0aW9uUHJvdmlkZXJDb25maWd1cmVyLmphdmEgKDEgaGl0KQ0KCUxpbmUgNjE6IAlwcml2YXRlIFN0cmluZyByb2xlUHJlZml4ID0gIlJPTEVfIjsNCiAgRDpcSmF2YVxTcHJpbmdcNC4wXHNwcmluZy1zZWN1cml0eS1yYjQuMC4wLlJFTEVBU0VcY29yZVxzcmNcbWFpblxqYXZhXG9yZ1xzcHJpbmdmcmFtZXdvcmtcc2VjdXJpdHlcYWNjZXNzXGFubm90YXRpb25cSnNyMjUwTWV0aG9kU2VjdXJpdHlNZXRhZGF0YVNvdXJjZS5qYXZhICgxIGhpdCkNCglMaW5lIDQxOiAJcHJpdmF0ZSBTdHJpbmcgZGVmYXVsdFJvbGVQcmVmaXggPSAiUk9MRV8iOw0KICBEOlxKYXZhXFNwcmluZ1w0LjBcc3ByaW5nLXNlY3VyaXR5LXJiNC4wLjAuUkVMRUFTRVxjb3JlXHNyY1xtYWluXGphdmFcb3JnXHNwcmluZ2ZyYW1ld29ya1xzZWN1cml0eVxhY2Nlc3NcZXhwcmVzc2lvblxtZXRob2RcRGVmYXVsdE1ldGhvZFNlY3VyaXR5RXhwcmVzc2lvbkhhbmRsZXIuamF2YSAoMSBoaXQpDQoJTGluZSA0MzogCXByaXZhdGUgU3RyaW5nIGRlZmF1bHRSb2xlUHJlZml4ID0gIlJPTEVfIjsNCiAgRDpcSmF2YVxTcHJpbmdcNC4wXHNwcmluZy1zZWN1cml0eS1yYjQuMC4wLlJFTEVBU0VcY29yZVxzcmNcbWFpblxqYXZhXG9yZ1xzcHJpbmdmcmFtZXdvcmtcc2VjdXJpdHlcYWNjZXNzXGV4cHJlc3Npb25cU2VjdXJpdHlFeHByZXNzaW9uUm9vdC5qYXZhICgxIGhpdCkNCglMaW5lIDI2OiAJcHJpdmF0ZSBTdHJpbmcgZGVmYXVsdFJvbGVQcmVmaXggPSAiUk9MRV8iOw0KICBEOlxKYXZhXFNwcmluZ1w0LjBcc3ByaW5nLXNlY3VyaXR5LXJiNC4wLjAuUkVMRUFTRVxjb3JlXHNyY1xtYWluXGphdmFcb3JnXHNwcmluZ2ZyYW1ld29ya1xzZWN1cml0eVxhY2Nlc3NcaW50ZXJjZXB0XFJ1bkFzTWFuYWdlckltcGwuamF2YSAoMSBoaXQpDQoJTGluZSA2MDogCXByaXZhdGUgU3RyaW5nIHJvbGVQcmVmaXggPSAiUk9MRV8iOw0KICBEOlxKYXZhXFNwcmluZ1w0LjBcc3ByaW5nLXNlY3VyaXR5LXJiNC4wLjAuUkVMRUFTRVxjb3JlXHNyY1xtYWluXGphdmFcb3JnXHNwcmluZ2ZyYW1ld29ya1xzZWN1cml0eVxhY2Nlc3Ncdm90ZVxSb2xlVm90ZXIuamF2YSAoMSBoaXQpDQoJTGluZSA1NTogCXByaXZhdGUgU3RyaW5nIHJvbGVQcmVmaXggPSAiUk9MRV8iOw0KICBEOlxKYXZhXFNwcmluZ1w0LjBcc3ByaW5nLXNlY3VyaXR5LXJiNC4wLjAuUkVMRUFTRVxsZGFwXHNyY1xtYWluXGphdmFcb3JnXHNwcmluZ2ZyYW1ld29ya1xzZWN1cml0eVxsZGFwXHVzZXJkZXRhaWxzXERlZmF1bHRMZGFwQXV0aG9yaXRpZXNQb3B1bGF0b3IuamF2YSAoMSBoaXQpDQoJTGluZSAxNDM6IAlwcml2YXRlIFN0cmluZyByb2xlUHJlZml4ID0gIlJPTEVfIjsNCiAgRDpcSmF2YVxTcHJpbmdcNC4wXHNwcmluZy1zZWN1cml0eS1yYjQuMC4wLlJFTEVBU0VcbGRhcFxzcmNcbWFpblxqYXZhXG9yZ1xzcHJpbmdmcmFtZXdvcmtcc2VjdXJpdHlcbGRhcFx1c2VyZGV0YWlsc1xMZGFwVXNlckRldGFpbHNNYW5hZ2VyLmphdmEgKDEgaGl0KQ0KCUxpbmUgOTU6IAlwcml2YXRlIGZpbmFsIFN0cmluZyByb2xlUHJlZml4ID0gIlJPTEVfIjsNCiAgRDpcSmF2YVxTcHJpbmdcNC4wXHNwcmluZy1zZWN1cml0eS1yYjQuMC4wLlJFTEVBU0VcbGRhcFxzcmNcbWFpblxqYXZhXG9yZ1xzcHJpbmdmcmFtZXdvcmtcc2VjdXJpdHlcbGRhcFx1c2VyZGV0YWlsc1xMZGFwVXNlckRldGFpbHNNYXBwZXIuamF2YSAoMSBoaXQpDQoJTGluZSA0MzogCXByaXZhdGUgU3RyaW5nIHJvbGVQcmVmaXggPSAiUk9MRV8iOw0KICBEOlxKYXZhXFNwcmluZ1w0LjBcc3ByaW5nLXNlY3VyaXR5LXJiNC4wLjAuUkVMRUFTRVx3ZWJcc3JjXG1haW5camF2YVxvcmdcc3ByaW5nZnJhbWV3b3JrXHNlY3VyaXR5XHdlYlxhY2Nlc3NcZXhwcmVzc2lvblxEZWZhdWx0V2ViU2VjdXJpdHlFeHByZXNzaW9uSGFuZGxlci5qYXZhICgxIGhpdCkNCglMaW5lIDIyOiAJcHJpdmF0ZSBTdHJpbmcgZGVmYXVsdFJvbGVQcmVmaXggPSAiUk9MRV8iOw0KYGBgDQojIFRoZSBDb25jZXB0dWFsIFByb2JsZW0NCkFzIHNwZWNpZmllZCBpbiBKQVZBIEVFIDcgdHV0b3JpYWwgdGhlIHJlY29tbWVuZGVkIChzaW1wbGlmaWVkKSBhcHByb2FjaCBmb3IgZGVmaW5pbmcgYXV0aG9yaXphdGlvbiBwb2xpY2llcyBpcyBhcyBmb2xsb3dzIChbNDcuNSBXb3JraW5nIHdpdGggUmVhbG1zLCBVc2VycywgR3JvdXBzLCBhbmQgUm9sZXNdKGh0dHA6Ly9kb2NzLm9yYWNsZS5jb20vamF2YWVlLzcvdHV0b3JpYWwvc2VjdXJpdHktaW50cm8wMDUuaHRtICI0Ny41IFdvcmtpbmcgd2l0aCBSZWFsbXMsIFVzZXJzLCBHcm91cHMsIGFuZCBSb2xlcyIpKToNCioqUmVhbG0qKi0+KipHcm91cHMqKi0+KipNYXAgR3JvdXBzIHRvIFJvbGVzKiotPioqVXNlIFJvbGVzIGluIGRlZmluaW5nIGF1dGhvcml6YXRpb24gcnVsZXMqKi4NClVuZm9ydHVuYXRlbHkgc3ByaW5nIHNlY3VyaXR5IGRvZXMgbm90IGltcGxlbWVudCB0aGlzIHBhdHRlcm4gKGJ5IG1hcHBpbmcgYXV0b21hdGljYWxseSBhIEdyb3VwIHRvIFJPTEVfR3JvdXApIHdoaWNoICBtaWdodCBsZWFkIHRvIGRlcGxveW1lbnQgcHJvYmxlbXMgaW4gcmVhbCBsaWZlIGVudmlyb25tZW50cy4gSW1hZ2luZSBhIHBvdGVudGlhbCBjbGllbnQgZm9yIHlvdXIgcHJvZHVjdCB0aGF0IGFscmVhZHkgaGFzIGEgd2VsbCBlc3RhYmxpc2hlZCBwb2xpY3kgZm9yIGdyb3VwIG1lbWJlcnNoaXAgYW5kIGlzIG5vdCB3aWxsaW5nIHRvIGVucmljaCAoYW5kIGR1cGxpY2F0ZSkgZXhpc3RpbmcgQUQgc3RydWN0dXJlIGp1c3QgdG8gYWNjb21tb2RhdGUgeW91ciBwcm9kdWN0IFJvbGVzIHJlcXVpcmVtZW50cyENCg0KDQoNCiMgVGhlIFJlYWwtTGlmZSBQcm9ibGVtDQpTdXBwb3NlIHlvdSBhbHJlYWR5IGhhdmUgYW4gYXBwbGljYXRpb24gdGhhdCB1c2VzIHJlc3RyaWN0aW9uIHBvbGljaWVzIGJhc2VkIG9uIHJvbGVzIGFuZCB5b3Ugd2FudCB0byBzd2l0Y2ggdG8gc3ByaW5nIHNlY3VyaXR5LiBUaGUgY2hhbmNlIHRvIGhhdmUgcm9sZXMgdXNlZCBieSB0aGUgYXBwbGljYXRpb24gY2FsbGVkIGFzIFJPTEVfWFhYIGlzIG1pbmltYWwgc28gdGhhdCB5b3Ugd2lsbCBub3QgYmUgYWJsZSB0byB1c2Ugb3V0IG9mIHRoZSBib3ggZnVuY3Rpb25hbGl0eSBwcm92aWRlZCBieSBzcHJpbmcgc2VjdXJpdHkuIFBsZWFzZSBub3RpY2UgdGhhdCB5b3UgYXBwbGljYXRpb24gbWlnaHQgdXNlIHJvbGUgYmFzZWQgYXV0aG9yaXphdGlvbiAoYXBhcnQgZnJvbSBzcHJpbmcgYGludGVyY2VwdC11cmxgIHR5cGUgcnVsZXMsIGZvciBpbnN0YW5jZSkgaW4gb3RoZXIgcGFydHMgbGlrZSBtZW51IGVudHJpZXMgdmlzaWJpbGl0eSwgY29kZSBhY2Nlc3MsIGRpZmZlcmVudCBwcm9jZXNzaW5nIHJ1bGVzLCBkYXRhIHJlc3RyaWN0aW9uLCBldGMuDQoNCiMgVGhlIFNvbHV0aW9uDQoNClN1cHBvc2luZyB3ZSB1c2UgYG9yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkubGRhcC5hdXRoZW50aWNhdGlvbi5MZGFwQXV0aGVudGljYXRpb25Qcm92aWRlcmAgZXh0ZW5kIHRoZSBgWFhYQXV0aG9yaXRpZXNQb3B1bGF0b3JgIHNwZWNpZmllZCBhcyBhIGNvbnN0cnVjdG9yIGFyZ3VtZW50Og0KDQpgYGBqYXZhDQpwYWNrYWdlIHJvLm15Y29tcGFueS5zcHJpbmdzZWN1cml0eS5leHRlbnNpb25zOw0KDQppbXBvcnQgamF2YS51dGlsLkNvbGxlY3Rpb247DQppbXBvcnQgamF2YS51dGlsLkhhc2hTZXQ7DQppbXBvcnQgamF2YS51dGlsLlNldDsNCmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLmxkYXAuY29yZS5Db250ZXh0U291cmNlOw0KaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkuY29yZS5HcmFudGVkQXV0aG9yaXR5Ow0KaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkubGRhcC51c2VyZGV0YWlscy5OZXN0ZWRMZGFwQXV0aG9yaXRpZXNQb3B1bGF0b3I7DQoNCnB1YmxpYyBjbGFzcyBOZXN0ZWRMZGFwQXV0aG9yaXRpZXNQb3B1bGF0b3JFeCBleHRlbmRzIE5lc3RlZExkYXBBdXRob3JpdGllc1BvcHVsYXRvciB7DQoNCiAgICBwdWJsaWMgTmVzdGVkTGRhcEF1dGhvcml0aWVzUG9wdWxhdG9yRXgoQ29udGV4dFNvdXJjZSBjb250ZXh0U291cmNlLCBTdHJpbmcgZ3JvdXBTZWFyY2hCYXNlKSB7DQogICAgICAgIHN1cGVyKGNvbnRleHRTb3VyY2UsIGdyb3VwU2VhcmNoQmFzZSk7DQogICAgfQ0KICAgIHByaXZhdGUgR3JvdXBzVG9Sb2xlcyBncm91cHNUb1JvbGVzOw0KDQogICAgQE92ZXJyaWRlDQogICAgcHVibGljIFNldDxHcmFudGVkQXV0aG9yaXR5PiBnZXRHcm91cE1lbWJlcnNoaXBSb2xlcyhTdHJpbmcgdXNlckRuLCBTdHJpbmcgdXNlcm5hbWUpIHsNCiAgICAgICAgU2V0PEdyYW50ZWRBdXRob3JpdHk+IGF1dGhvcml0aWVzID0gc3VwZXIuZ2V0R3JvdXBNZW1iZXJzaGlwUm9sZXModXNlckRuLCB1c2VybmFtZSk7DQoNCiAgICAgICAgQ29sbGVjdGlvbjxHcmFudGVkQXV0aG9yaXR5PiByZ2E9Z3JvdXBzVG9Sb2xlcy5nZXRSZWFjaGFibGVHcmFudGVkQXV0aG9yaXRpZXMoYXV0aG9yaXRpZXMpOw0KICAgICAgICBTZXQ8R3JhbnRlZEF1dGhvcml0eT4gcmV0PW5ldyBIYXNoU2V0PD4oKTsNCiAgICAgICAgcmV0LmFkZEFsbChyZ2EpOw0KICAgICAgICAvL3JldHVybiBhdXRob3JpdGllczsNCiAgICAgICAgcmV0dXJuIHJldDsNCg0KICAgIH0NCg0KICAgIHB1YmxpYyBHcm91cHNUb1JvbGVzIGdldEdyb3Vwc1RvUm9sZXMoKSB7DQogICAgICAgIHJldHVybiBncm91cHNUb1JvbGVzOw0KICAgIH0NCg0KICAgIHB1YmxpYyB2b2lkIHNldEdyb3Vwc1RvUm9sZXMoR3JvdXBzVG9Sb2xlcyBncm91cHNUb1JvbGVzKSB7DQogICAgICAgIHRoaXMuZ3JvdXBzVG9Sb2xlcyA9IGdyb3Vwc1RvUm9sZXM7DQogICAgfQ0KICAgIA0KICAgIA0KfQ0KYGBgDQp3aGVyZSBgR3JvdXBzVG9Sb2xlc2AgaXMgZGVmaW5lZCBhcyBmb2xsb3dzIChpbnNwaXJlZCBieSBgb3JnLnNwcmluZ2ZyYW1ld29yay5zZWN1cml0eS5hY2Nlc3MuaGllcmFyY2hpY2Fscm9sZXMuUm9sZUhpZXJhcmNoeUltcGxgKToNCg0KYGBgamF2YQ0KDQpwYWNrYWdlIHJvLm15Y29tcGFueS5zcHJpbmdzZWN1cml0eS5leHRlbnNpb25zOw0KDQoNCmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNvcmUuR3JhbnRlZEF1dGhvcml0eTsNCmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNvcmUuYXV0aG9yaXR5LkF1dGhvcml0eVV0aWxzOw0KaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkuY29yZS5hdXRob3JpdHkuU2ltcGxlR3JhbnRlZEF1dGhvcml0eTsNCg0KaW1wb3J0IGphdmEudXRpbC4qOw0KaW1wb3J0IGphdmEudXRpbC5yZWdleC5NYXRjaGVyOw0KaW1wb3J0IGphdmEudXRpbC5yZWdleC5QYXR0ZXJuOw0KaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkuYWNjZXNzLmhpZXJhcmNoaWNhbHJvbGVzLkN5Y2xlSW5Sb2xlSGllcmFyY2h5RXhjZXB0aW9uOw0KDQoNCnB1YmxpYyBjbGFzcyBHcm91cHNUb1JvbGVzIHsNCg0KDQoJcHJpdmF0ZSBTdHJpbmcgcm9sZUhpZXJhcmNoeVN0cmluZ1JlcHJlc2VudGF0aW9uID0gbnVsbDsNCg0KCQ0KCXByaXZhdGUgTWFwPEdyYW50ZWRBdXRob3JpdHksIFNldDxHcmFudGVkQXV0aG9yaXR5Pj4gcm9sZXNSZWFjaGFibGVJbk9uZVN0ZXBNYXAgPSBudWxsOw0KDQoJDQoJcHJpdmF0ZSBNYXA8R3JhbnRlZEF1dGhvcml0eSwgU2V0PEdyYW50ZWRBdXRob3JpdHk+PiByb2xlc1JlYWNoYWJsZUluT25lT3JNb3JlU3RlcHNNYXAgPSBudWxsOw0KDQoJDQoJcHVibGljIHZvaWQgc2V0SGllcmFyY2h5KFN0cmluZyByb2xlSGllcmFyY2h5U3RyaW5nUmVwcmVzZW50YXRpb24pIHsNCgkJdGhpcy5yb2xlSGllcmFyY2h5U3RyaW5nUmVwcmVzZW50YXRpb24gPSByb2xlSGllcmFyY2h5U3RyaW5nUmVwcmVzZW50YXRpb247DQoNCgkJYnVpbGRSb2xlc1JlYWNoYWJsZUluT25lU3RlcE1hcCgpOw0KCQlidWlsZFJvbGVzUmVhY2hhYmxlSW5PbmVPck1vcmVTdGVwc01hcCgpOw0KCX0NCg0KCXB1YmxpYyBDb2xsZWN0aW9uPEdyYW50ZWRBdXRob3JpdHk+IGdldFJlYWNoYWJsZUdyYW50ZWRBdXRob3JpdGllcygNCgkJCUNvbGxlY3Rpb248PyBleHRlbmRzIEdyYW50ZWRBdXRob3JpdHk+IGF1dGhvcml0aWVzKSB7DQoJCWlmIChhdXRob3JpdGllcyA9PSBudWxsIHx8IGF1dGhvcml0aWVzLmlzRW1wdHkoKSkgew0KCQkJcmV0dXJuIEF1dGhvcml0eVV0aWxzLk5PX0FVVEhPUklUSUVTOw0KCQl9DQoNCgkJU2V0PEdyYW50ZWRBdXRob3JpdHk+IHJlYWNoYWJsZVJvbGVzID0gbmV3IEhhc2hTZXQ8PigpOw0KDQoJCWZvciAoR3JhbnRlZEF1dGhvcml0eSBhdXRob3JpdHkgOiBhdXRob3JpdGllcykgew0KCQkJYWRkUmVhY2hhYmxlUm9sZXMocmVhY2hhYmxlUm9sZXMsIGF1dGhvcml0eSk7DQoJCQlTZXQ8R3JhbnRlZEF1dGhvcml0eT4gYWRkaXRpb25hbFJlYWNoYWJsZVJvbGVzID0gZ2V0Um9sZXNSZWFjaGFibGVJbk9uZU9yTW9yZVN0ZXBzKGF1dGhvcml0eSk7DQoJCQlpZiAoYWRkaXRpb25hbFJlYWNoYWJsZVJvbGVzICE9IG51bGwpIHsNCgkJCQlyZWFjaGFibGVSb2xlcy5hZGRBbGwoYWRkaXRpb25hbFJlYWNoYWJsZVJvbGVzKTsNCgkJCX0NCgkJfQ0KDQoNCgkJTGlzdDxHcmFudGVkQXV0aG9yaXR5PiByZWFjaGFibGVSb2xlTGlzdCA9IG5ldyBBcnJheUxpc3Q8PigNCgkJCQlyZWFjaGFibGVSb2xlcy5zaXplKCkpOw0KCQlyZWFjaGFibGVSb2xlTGlzdC5hZGRBbGwocmVhY2hhYmxlUm9sZXMpOw0KDQoJCXJldHVybiByZWFjaGFibGVSb2xlTGlzdDsNCgl9DQoNCgkvLyBTRUMtODYzDQoJcHJpdmF0ZSB2b2lkIGFkZFJlYWNoYWJsZVJvbGVzKFNldDxHcmFudGVkQXV0aG9yaXR5PiByZWFjaGFibGVSb2xlcywNCgkJCUdyYW50ZWRBdXRob3JpdHkgYXV0aG9yaXR5KSB7DQoNCgkJZm9yIChHcmFudGVkQXV0aG9yaXR5IHRlc3RBdXRob3JpdHkgOiByZWFjaGFibGVSb2xlcykgew0KCQkJU3RyaW5nIHRlc3RLZXkgPSB0ZXN0QXV0aG9yaXR5LmdldEF1dGhvcml0eSgpOw0KCQkJaWYgKCh0ZXN0S2V5ICE9IG51bGwpICYmICh0ZXN0S2V5LmVxdWFscyhhdXRob3JpdHkuZ2V0QXV0aG9yaXR5KCkpKSkgew0KCQkJCXJldHVybjsNCgkJCX0NCgkJfQ0KCQlyZWFjaGFibGVSb2xlcy5hZGQoYXV0aG9yaXR5KTsNCgl9DQoNCgkNCglwcml2YXRlIFNldDxHcmFudGVkQXV0aG9yaXR5PiBnZXRSb2xlc1JlYWNoYWJsZUluT25lT3JNb3JlU3RlcHMoDQoJCQlHcmFudGVkQXV0aG9yaXR5IGF1dGhvcml0eSkgew0KDQoJCWlmIChhdXRob3JpdHkuZ2V0QXV0aG9yaXR5KCkgPT0gbnVsbCkgew0KCQkJcmV0dXJuIG51bGw7DQoJCX0NCg0KCQlmb3IgKEdyYW50ZWRBdXRob3JpdHkgdGVzdEF1dGhvcml0eSA6IHJvbGVzUmVhY2hhYmxlSW5PbmVPck1vcmVTdGVwc01hcC5rZXlTZXQoKSkgew0KCQkJU3RyaW5nIHRlc3RLZXkgPSB0ZXN0QXV0aG9yaXR5LmdldEF1dGhvcml0eSgpOw0KCQkJaWYgKCh0ZXN0S2V5ICE9IG51bGwpICYmICh0ZXN0S2V5LmVxdWFscyhhdXRob3JpdHkuZ2V0QXV0aG9yaXR5KCkpKSkgew0KCQkJCXJldHVybiByb2xlc1JlYWNoYWJsZUluT25lT3JNb3JlU3RlcHNNYXAuZ2V0KHRlc3RBdXRob3JpdHkpOw0KCQkJfQ0KCQl9DQoNCgkJcmV0dXJuIG51bGw7DQoJfQ0KDQoJDQoJcHJpdmF0ZSB2b2lkIGJ1aWxkUm9sZXNSZWFjaGFibGVJbk9uZVN0ZXBNYXAoKSB7DQoJCVBhdHRlcm4gcGF0dGVybiA9IFBhdHRlcm4uY29tcGlsZSgiKFxccyooW15cXHM+XSspXFxzKj5cXHMqKFteXFxzPl0rKSkiKTsNCg0KCQlNYXRjaGVyIHJvbGVIaWVyYXJjaHlNYXRjaGVyID0gcGF0dGVybi5tYXRjaGVyKHJvbGVIaWVyYXJjaHlTdHJpbmdSZXByZXNlbnRhdGlvbik7DQoJCXJvbGVzUmVhY2hhYmxlSW5PbmVTdGVwTWFwID0gbmV3IEhhc2hNYXA8PigpOw0KDQoJCXdoaWxlIChyb2xlSGllcmFyY2h5TWF0Y2hlci5maW5kKCkpIHsNCgkJCUdyYW50ZWRBdXRob3JpdHkgaGlnaGVyUm9sZSA9IG5ldyBTaW1wbGVHcmFudGVkQXV0aG9yaXR5KA0KCQkJCQlyb2xlSGllcmFyY2h5TWF0Y2hlci5ncm91cCgyKSk7DQoJCQlHcmFudGVkQXV0aG9yaXR5IGxvd2VyUm9sZSA9IG5ldyBTaW1wbGVHcmFudGVkQXV0aG9yaXR5KA0KCQkJCQlyb2xlSGllcmFyY2h5TWF0Y2hlci5ncm91cCgzKSk7DQoJCQlTZXQ8R3JhbnRlZEF1dGhvcml0eT4gcm9sZXNSZWFjaGFibGVJbk9uZVN0ZXBTZXQ7DQoNCgkJCWlmICghcm9sZXNSZWFjaGFibGVJbk9uZVN0ZXBNYXAuY29udGFpbnNLZXkoaGlnaGVyUm9sZSkpIHsNCgkJCQlyb2xlc1JlYWNoYWJsZUluT25lU3RlcFNldCA9IG5ldyBIYXNoU2V0PD4oKTsNCgkJCQlyb2xlc1JlYWNoYWJsZUluT25lU3RlcE1hcC5wdXQoaGlnaGVyUm9sZSwgcm9sZXNSZWFjaGFibGVJbk9uZVN0ZXBTZXQpOw0KCQkJfQ0KCQkJZWxzZSB7DQoJCQkJcm9sZXNSZWFjaGFibGVJbk9uZVN0ZXBTZXQgPSByb2xlc1JlYWNoYWJsZUluT25lU3RlcE1hcC5nZXQoaGlnaGVyUm9sZSk7DQoJCQl9DQoJCQlhZGRSZWFjaGFibGVSb2xlcyhyb2xlc1JlYWNoYWJsZUluT25lU3RlcFNldCwgbG93ZXJSb2xlKTsNCg0KCQl9DQoJfQ0KDQoJDQoJcHJpdmF0ZSB2b2lkIGJ1aWxkUm9sZXNSZWFjaGFibGVJbk9uZU9yTW9yZVN0ZXBzTWFwKCkgew0KCQlyb2xlc1JlYWNoYWJsZUluT25lT3JNb3JlU3RlcHNNYXAgPSBuZXcgSGFzaE1hcDw+KCk7DQoJCS8vIGl0ZXJhdGUgb3ZlciBhbGwgaGlnaGVyIHJvbGVzIGZyb20gcm9sZXNSZWFjaGFibGVJbk9uZVN0ZXBNYXANCg0KCQlmb3IgKEdyYW50ZWRBdXRob3JpdHkgcm9sZSA6IHJvbGVzUmVhY2hhYmxlSW5PbmVTdGVwTWFwLmtleVNldCgpKSB7DQoJCQlTZXQ8R3JhbnRlZEF1dGhvcml0eT4gcm9sZXNUb1Zpc2l0U2V0ID0gbmV3IEhhc2hTZXQ8PigpOw0KDQoJCQlpZiAocm9sZXNSZWFjaGFibGVJbk9uZVN0ZXBNYXAuY29udGFpbnNLZXkocm9sZSkpIHsNCgkJCQlyb2xlc1RvVmlzaXRTZXQuYWRkQWxsKHJvbGVzUmVhY2hhYmxlSW5PbmVTdGVwTWFwLmdldChyb2xlKSk7DQoJCQl9DQoNCgkJCVNldDxHcmFudGVkQXV0aG9yaXR5PiB2aXNpdGVkUm9sZXNTZXQgPSBuZXcgSGFzaFNldDw+KCk7DQoNCgkJCXdoaWxlICghcm9sZXNUb1Zpc2l0U2V0LmlzRW1wdHkoKSkgew0KCQkJCS8vIHRha2UgYSByb2xlIGZyb20gdGhlIHJvbGVzVG9WaXNpdCBzZXQNCgkJCQlHcmFudGVkQXV0aG9yaXR5IGFSb2xlID0gcm9sZXNUb1Zpc2l0U2V0Lml0ZXJhdG9yKCkubmV4dCgpOw0KCQkJCXJvbGVzVG9WaXNpdFNldC5yZW1vdmUoYVJvbGUpOw0KCQkJCWFkZFJlYWNoYWJsZVJvbGVzKHZpc2l0ZWRSb2xlc1NldCwgYVJvbGUpOw0KCQkJCWlmIChyb2xlc1JlYWNoYWJsZUluT25lU3RlcE1hcC5jb250YWluc0tleShhUm9sZSkpIHsNCgkJCQkJU2V0PEdyYW50ZWRBdXRob3JpdHk+IG5ld1JlYWNoYWJsZVJvbGVzID0gcm9sZXNSZWFjaGFibGVJbk9uZVN0ZXBNYXANCgkJCQkJCQkuZ2V0KGFSb2xlKTsNCg0KCQkJCQkvLyBkZWZpbml0aW9uIG9mIGEgY3ljbGU6IHlvdSBjYW4gcmVhY2ggdGhlIHJvbGUgeW91IGFyZSBzdGFydGluZyBmcm9tDQoJCQkJCWlmIChyb2xlc1RvVmlzaXRTZXQuY29udGFpbnMocm9sZSkgfHwgdmlzaXRlZFJvbGVzU2V0LmNvbnRhaW5zKHJvbGUpKSB7DQoJCQkJCQl0aHJvdyBuZXcgQ3ljbGVJblJvbGVIaWVyYXJjaHlFeGNlcHRpb24oKTsNCgkJCQkJfQ0KCQkJCQllbHNlIHsNCgkJCQkJCS8vIG5vIGN5Y2xlDQoJCQkJCQlyb2xlc1RvVmlzaXRTZXQuYWRkQWxsKG5ld1JlYWNoYWJsZVJvbGVzKTsNCgkJCQkJfQ0KCQkJCX0NCgkJCX0NCgkJCXJvbGVzUmVhY2hhYmxlSW5PbmVPck1vcmVTdGVwc01hcC5wdXQocm9sZSwgdmlzaXRlZFJvbGVzU2V0KTsNCg0KCQl9DQoNCgl9DQoNCn0NCg0KYGBgDQoNClRoZSBhY3R1YWwgY29uZmlndXJhdGlvbiBsb29rcyBsaWtlOg0KDQpgYGB4bWwNCiAgICA8YmVhbiBpZD0iQUQtTGRhcFByb3ZpZGVyIiBjbGFzcz0ib3JnLnNwcmluZ2ZyYW1ld29yay5zZWN1cml0eS5sZGFwLmF1dGhlbnRpY2F0aW9uLkxkYXBBdXRoZW50aWNhdGlvblByb3ZpZGVyIj4NCiAgICAgICAgPGNvbnN0cnVjdG9yLWFyZz4NCiAgICAgICAgICAgIDxiZWFuIGNsYXNzPSJvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmxkYXAuYXV0aGVudGljYXRpb24uQmluZEF1dGhlbnRpY2F0b3IiPg0KICAgICAgICAgICAgICAgIDxjb25zdHJ1Y3Rvci1hcmcgcmVmPSJjb250ZXh0U291cmNlIiAvPg0KICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJ1c2VyU2VhcmNoIj4NCiAgICAgICAgICAgICAgICAgICAgPGJlYW4gaWQ9InVzZXJTZWFyY2giIGNsYXNzPSJvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmxkYXAuc2VhcmNoLkZpbHRlckJhc2VkTGRhcFVzZXJTZWFyY2giPg0KICAgICAgICAgICAgICAgICAgICAgICAgPGNvbnN0cnVjdG9yLWFyZyBpbmRleD0iMCIgdmFsdWU9Ik9VPU15Q29tcGFueSxEQz1teWNvbXBhbnksREM9bG9jYWwiLz4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxjb25zdHJ1Y3Rvci1hcmcgaW5kZXg9IjEiIHZhbHVlPSIoJmFtcDsob2JqZWN0Q2xhc3M9dXNlcikoc0FNQWNjb3VudE5hbWU9ezB9KSkiLz4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxjb25zdHJ1Y3Rvci1hcmcgaW5kZXg9IjIiIHJlZj0iY29udGV4dFNvdXJjZSIgLz4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJzZWFyY2hTdWJ0cmVlIiB2YWx1ZT0idHJ1ZSIvPg0KICAgICAgICAgICAgICAgICAgICA8L2JlYW4+DQogICAgICAgICAgICAgICAgPC9wcm9wZXJ0eT4NCiAgICAgICAgICAgIDwvYmVhbj4NCiAgICAgICAgPC9jb25zdHJ1Y3Rvci1hcmc+DQogICAgICAgIDxjb25zdHJ1Y3Rvci1hcmc+DQogICAgICAgICAgICA8IS0tYmVhbiBjbGFzcz0ib3JnLnNwcmluZ2ZyYW1ld29yay5zZWN1cml0eS5sZGFwLnVzZXJkZXRhaWxzLk5lc3RlZExkYXBBdXRob3JpdGllc1BvcHVsYXRvciItLT4NCiAgICAgICAgICAgICAgICANCiAgICAgICAgICAgIDxiZWFuIGNsYXNzPSJyby5teWNvbXBhbnkuc3ByaW5nc2VjdXJpdHkuZXh0ZW5zaW9ucy5OZXN0ZWRMZGFwQXV0aG9yaXRpZXNQb3B1bGF0b3JFeCI+DQogICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgPGNvbnN0cnVjdG9yLWFyZyByZWY9ImNvbnRleHRTb3VyY2UiIC8+DQogICAgICAgICAgICAgICAgPGNvbnN0cnVjdG9yLWFyZyB2YWx1ZT0iT1U9TXlDb21wYW55LERDPW15Y29tcGFueSxEQz1sb2NhbCIgLz4NCiAgICAgICAgICAgICAgICA8cHJvcGVydHkgbmFtZT0iZ3JvdXBTZWFyY2hGaWx0ZXIiIHZhbHVlPSIoJmFtcDsob2JqZWN0Q2xhc3M9Z3JvdXApKGdyb3VwVHlwZToxLjIuODQwLjExMzU1Ni4xLjQuODAzOj0tMjE0NzQ4MzY0OCkobWVtYmVyPXswfSkpIi8+PCEtLWh0dHA6Ly9sZGFwd2lraS53aWxsZWtlLmNvbS93aWtpL0FjdGl2ZSUyMERpcmVjdG9yeSUyMEdyb3VwJTIwUmVsYXRlZCUyMFNlYXJjaGVzI3NlY3Rpb24tQWN0aXZlK0RpcmVjdG9yeStHcm91cCtSZWxhdGVkK1NlYXJjaGVzLUFsbFNlY3VyaXR5R3JvdXBzTG9jYWxHbG9iYWxBbmRVbml2ZXJzYWwgLS0+DQogICAgICAgICAgICAgICAgPHByb3BlcnR5IG5hbWU9InJvbGVQcmVmaXgiIHZhbHVlPSJST0xFXyIvPg0KICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJzZWFyY2hTdWJ0cmVlIiB2YWx1ZT0idHJ1ZSIvPg0KICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJjb252ZXJ0VG9VcHBlckNhc2UiIHZhbHVlPSJmYWxzZSIvPg0KICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJncm91cHNUb1JvbGVzIiByZWY9Imdyb3Vwc1RvUm9sZXMiLz4NCiAgICAgICAgICAgIDwvYmVhbj4NCiAgICAgICAgPC9jb25zdHJ1Y3Rvci1hcmc+DQogICAgPC9iZWFuPg0KDQogICAgPGJlYW4gaWQ9Imdyb3Vwc1RvUm9sZXMiIGNsYXNzPSJyby5teWNvbXBhbnkuc3ByaW5nc2VjdXJpdHkuZXh0ZW5zaW9ucy5Hcm91cHNUb1JvbGVzIj4NCiAgICAgICAgPHByb3BlcnR5IG5hbWU9ImhpZXJhcmNoeSI+DQogICAgICAgICAgICA8dmFsdWU+DQogICAgICAgICAgICAgICAgUk9MRV9BZG1pbnM+YWRtaW4NCiAgICAgICAgICAgIDwvdmFsdWU+DQogICAgICAgIDwvcHJvcGVydHk+DQoNCiAgICA8L2JlYW4+DQoNCmBgYA0KTm93IHlvdSBjYW4gdXNlIHRoZSBhcHBsaWNhdGlvbiB1bmNoYW5nZWQgaWYgY29uZmlndXJpbmcgY29ycmVjdGx5IGBncm91cHNUb1JvbGVzYC4NCg0KIyBUaGUgRXhwbGFuYXRpb24NCg0KVGhlcmUgaXMgYSB2ZXJ5IGdvb2QgYW5zd2VyIG9uIFN0YWNrT3ZlcmZsb3cgdG8gdGhlIHF1ZXN0aW9uIFtXaHkgZG9lcyBTcHJpbmcgU2VjdXJpdHkncyBSb2xlVm90ZXIgbmVlZCBhIHByZWZpeD9dKGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMTE1MzkxNjIvd2h5LWRvZXMtc3ByaW5nLXNlY3VyaXR5cy1yb2xldm90ZXItbmVlZC1hLXByZWZpeCAiV2h5IGRvZXMgU3ByaW5nIFNlY3VyaXR5J3MgUm9sZVZvdGVyIG5lZWQgYSBwcmVmaXg/IikNCg0KIyBUaGUgVXBkYXRlIChTcHJpbmcgU2VjdXJpdHkgNC4wLjEuUkVMRUFTRSkNCg0KQWZ0ZXIgdXBkYXRpbmcgdG8gNC4wLjEuUkVMRUFTRSB0aGUgYXV0aG9yaXphdGlvbiB0aHJvdWdoIGBGYWNlc0NvbnRleHQuZ2V0Q3VycmVudEluc3RhbmNlKCkuZ2V0RXh0ZXJuYWxDb250ZXh0KCkuaXNVc2VySW5Sb2xlKHJvbGUpO2Agc3VkZGVubHkgZmFpbGVkIHRvIHdvcmsuIFRoaXMgaXMgcmVsYXRlZCB0byBbaHR0cHM6Ly9qaXJhLnNwcmluZy5pby9icm93c2UvU0VDLTI5MjZdKGh0dHBzOi8vamlyYS5zcHJpbmcuaW8vYnJvd3NlL1NFQy0yOTI2ICJodHRwczovL2ppcmEuc3ByaW5nLmlvL2Jyb3dzZS9TRUMtMjkyNiIpIHdoaWNoIGFjdHVhbGx5IG1lYW5zIHRoYXQgIlJPTEVfIiBpcyBhdXRvbWF0aWNhbGx5IHByZXBlbmRlZCAoYnkgZGVmYXVsdCBpZiBubyBvdGhlciBzcGVjaWZpYyBjb25maWd1cmF0aW9uIGlzIGRvbmUpIHRvIHJvbGUgbmFtZXMgd2hpbGUgY2hlY2tpbmcgYXV0aG9yaXphdGlvbiBpbiBzcHJpbmcgY29uZmlndXJhdGlvbiBmaWxlcyAobGlrZSAgDQpgYGB4bWwNCjxzZWN1cml0eTppbnRlcmNlcHQtdXJsIHBhdHRlcm49Ii9mYWNlcy9hdXRoL2FkbWluLyoqIiBhY2Nlc3M9IkFkbWlucyIvPg0KYGBgDQp0aGF0IHdpbGwgYWN0dWFsbHkgY2hlY2tzIGZvciAiUk9MRV9BZG1pbnMiIGluIEdyYW50ZWRBdXRob3JpdGllcykgYXMgb3Bvc2VkIHRvIGBGYWNlc0NvbnRleHQuZ2V0Q3VycmVudEluc3RhbmNlKCkuZ2V0RXh0ZXJuYWxDb250ZXh0KCkuaXNVc2VySW5Sb2xlKCJhZG1pbiIpYCB0aGF0IHdpbGwgY2hlY2sgZm9yICJhZG1pbiIgaW4gNC4wLjAgYW5kICJST0xFX2FkbWluIiBpbiA0LjAuMS4NCg0KVGhlIHF1aWNrIHNvbHV0aW9uIGlzIHRvIGNoYW5nZSB0aGUgY29uZmlndXJhdGlvbiBmaWxlIGZyb20NCmBgYHhtbA0KLi4uICAgIA0KPGJlYW4gaWQ9Imdyb3Vwc1RvUm9sZXMiIGNsYXNzPSJyby5teWNvbXBhbnkuc3ByaW5nc2VjdXJpdHkuZXh0ZW5zaW9ucy5Hcm91cHNUb1JvbGVzIj4NCiAgICAgICAgPHByb3BlcnR5IG5hbWU9ImhpZXJhcmNoeSI+DQogICAgICAgICAgICA8dmFsdWU+DQogICAgICAgICAgICAgICAgUk9MRV9BZG1pbnM+YWRtaW4NCiAgICAgICAgICAgIDwvdmFsdWU+DQogICAgICAgIDwvcHJvcGVydHk+DQoNCiAgICA8L2JlYW4+DQouLi4NCmBgYA0KdG8NCmBgYHhtbA0KLi4uICAgIA0KPGJlYW4gaWQ9Imdyb3Vwc1RvUm9sZXMiIGNsYXNzPSJyby5teWNvbXBhbnkuc3ByaW5nc2VjdXJpdHkuZXh0ZW5zaW9ucy5Hcm91cHNUb1JvbGVzIj4NCiAgICAgICAgPHByb3BlcnR5IG5hbWU9ImhpZXJhcmNoeSI+DQogICAgICAgICAgICA8dmFsdWU+DQogICAgICAgICAgICAgICAgUk9MRV9BZG1pbnM+Uk9MRV9hZG1pbg0KICAgICAgICAgICAgPC92YWx1ZT4NCiAgICAgICAgPC9wcm9wZXJ0eT4NCg0KICAgIDwvYmVhbj4NCi4uLg0KYGBgDQoNClRoZSBkaWZmZXJlbmNlIChpbiB0aGlzIHJlc3BlY3QpIGJldHdlZW4gdGhlIHR3byB2ZXJzaW9ucyBjb21lIGZyb20gdGhlIGZvbGxvd2luZyBzbmlwcGV0IGluIGBTZWN1cml0eUNvbnRleHRIb2xkZXJBd2FyZVJlcXVlc3RGaWx0ZXJgDQoNCmBgYGphdmENCnB1YmxpYyBjbGFzcyBTZWN1cml0eUNvbnRleHRIb2xkZXJBd2FyZVJlcXVlc3RGaWx0ZXIgZXh0ZW5kcyBHZW5lcmljRmlsdGVyQmVhbiB7DQoJLy8gfiBJbnN0YW5jZSBmaWVsZHMNCgkvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KCXByaXZhdGUgU3RyaW5nIHJvbGVQcmVmaXg7DQoNCglwcml2YXRlIEh0dHBTZXJ2bGV0UmVxdWVzdEZhY3RvcnkgcmVxdWVzdEZhY3Rvcnk7DQpgYGANCmluIDQuMC4wIGFuZA0KYGBgamF2YQ0KcHVibGljIGNsYXNzIFNlY3VyaXR5Q29udGV4dEhvbGRlckF3YXJlUmVxdWVzdEZpbHRlciBleHRlbmRzIEdlbmVyaWNGaWx0ZXJCZWFuIHsNCgkvLyB+IEluc3RhbmNlIGZpZWxkcw0KCS8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQoJcHJpdmF0ZSBTdHJpbmcgcm9sZVByZWZpeCA9ICJST0xFXyI7DQoNCglwcml2YXRlIEh0dHBTZXJ2bGV0UmVxdWVzdEZhY3RvcnkgcmVxdWVzdEZhY3Rvcnk7DQoNCmBgYA0KaW4gNC4wLjEuDQoNClRoZSBgcm9sZVByZWZpeGAgaXMgYWN0dWFsbHkgdXNlZCBpbiBgU2VjdXJpdHlDb250ZXh0SG9sZGVyQXdhcmVSZXF1ZXN0V3JhcHBlcmAgYXMNCg0KYGBgamF2YQ0KCXByaXZhdGUgYm9vbGVhbiBpc0dyYW50ZWQoU3RyaW5nIHJvbGUpIHsNCgkJQXV0aGVudGljYXRpb24gYXV0aCA9IGdldEF1dGhlbnRpY2F0aW9uKCk7DQoNCgkJaWYgKHJvbGVQcmVmaXggIT0gbnVsbCkgew0KCQkJcm9sZSA9IHJvbGVQcmVmaXggKyByb2xlOw0KCQl9DQoNCgkJaWYgKChhdXRoID09IG51bGwpIHx8IChhdXRoLmdldFByaW5jaXBhbCgpID09IG51bGwpKSB7DQoJCQlyZXR1cm4gZmFsc2U7DQoJCX0NCg0KCQlDb2xsZWN0aW9uPD8gZXh0ZW5kcyBHcmFudGVkQXV0aG9yaXR5PiBhdXRob3JpdGllcyA9IGF1dGguZ2V0QXV0aG9yaXRpZXMoKTsNCg0KCQlpZiAoYXV0aG9yaXRpZXMgPT0gbnVsbCkgew0KCQkJcmV0dXJuIGZhbHNlOw0KCQl9DQoNCgkJZm9yIChHcmFudGVkQXV0aG9yaXR5IGdyYW50ZWRBdXRob3JpdHkgOiBhdXRob3JpdGllcykgew0KCQkJaWYgKHJvbGUuZXF1YWxzKGdyYW50ZWRBdXRob3JpdHkuZ2V0QXV0aG9yaXR5KCkpKSB7DQoJCQkJcmV0dXJuIHRydWU7DQoJCQl9DQoJCX0NCg0KCQlyZXR1cm4gZmFsc2U7DQoJfQ0KDQpgYGAgDQpBcyB3ZSBjYW4gc2VlIHRoZSBgcm9sZVByZWZpeGAgaXMgcHJlcGVuZGVkIChpZiBub3QgbnVsbCkgdW5jb25kaXRpb25hbGx5IHRvIGNoZWNrZWQgcm9sZSB3aGljaCBpcyBhIGRpZmZlcmVudCBiZWhhdmlvdXIgdGhhbiB0aGUgb25lIGltcGxlbWVudGVkIGluIHZvdGVycyAoaWYgdGhlIHJvbGUgc3RhcnRzIHdpdGggdGhlIGRlZmF1bHQvY29uZmlndXJlZCBwcmVmaXggdGhpcyB3aWxsIG5vdCBiZSBwcmVwZW5kZWQgYW55IG1vcmUpOyB0aGF0J3Mgd2h5ICBgRmFjZXNDb250ZXh0LmdldEN1cnJlbnRJbnN0YW5jZSgpLmdldEV4dGVybmFsQ29udGV4dCgpLmlzVXNlckluUm9sZSgiUk9MRV9hZG1pbiIpYCB3aWxsIG90IHdvcmsgc2luY2UgdGhlIGFjdHVhbGx5IG1hdGNoIHdpbGwgYmUgZG9uZSBhZ2FpbnN0ICJST0xFX1JPTEVfYWRtaW4iIQ==

SAML Service Provider Metadata File

SAML Service Provider meta data specifies (among other):

  • The EntityID (a unique ID that distinguishes between the different Service Providers registered with an Identity Provider)
  • Public part of certificates used for signing/encrypting requests
  • URLs for different services (SingleLogoutService, AssertionConsumerService, etc) exposed by the Service Provider as well as for different bindings (HTTP-POST,HTTP-Redirect, etc); the format of URLs is Service Provider specific

Sample meta data:

<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="urn_my_id" entityID="urn:my:id">
    <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:KeyDescriptor use="signing">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>
                        MIIEEDCCAvigAwIBAgIBBTANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMCUk8x
...
                        y3FIAikcj/fdYo1K9d+PrL6JoMiQGIglDLhed8ZcpBqC64mG
                    </ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:KeyDescriptor use="encryption">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>                        
                        MIIEEDCCAvigAwIBAgIBBTANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMCUk8x
...
                        y3FIAikcj/fdYo1K9d+PrL6JoMiQGIglDLhed8ZcpBqC64mG
                    </ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://server.domain.local:9443/path_to_logout"/>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://server.domain.local:9443/path_to_logout"/>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</md:NameIDFormat>
        <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://server.domain.local:9443/path_to_asertion_service" index="0" isDefault="true"/>
    </md:SPSSODescriptor>
</md:EntityDescriptor>
U0FNTCBTZXJ2aWNlIFByb3ZpZGVyIG1ldGEgZGF0YSBzcGVjaWZpZXMgKGFtb25nIG90aGVyKToNCiogVGhlIEVudGl0eUlEIChhIHVuaXF1ZSBJRCB0aGF0IGRpc3Rpbmd1aXNoZXMgYmV0d2VlbiB0aGUgZGlmZmVyZW50IFNlcnZpY2UgUHJvdmlkZXJzIHJlZ2lzdGVyZWQgd2l0aCBhbiBJZGVudGl0eSBQcm92aWRlcikNCiogUHVibGljIHBhcnQgb2YgY2VydGlmaWNhdGVzIHVzZWQgZm9yIHNpZ25pbmcvZW5jcnlwdGluZyByZXF1ZXN0cw0KKiBVUkxzIGZvciBkaWZmZXJlbnQgc2VydmljZXMgKFNpbmdsZUxvZ291dFNlcnZpY2UsIEFzc2VydGlvbkNvbnN1bWVyU2VydmljZSwgZXRjKSBleHBvc2VkIGJ5IHRoZSBTZXJ2aWNlIFByb3ZpZGVyIGFzIHdlbGwgYXMgZm9yIGRpZmZlcmVudCBiaW5kaW5ncyAoSFRUUC1QT1NULEhUVFAtUmVkaXJlY3QsIGV0Yyk7IHRoZSBmb3JtYXQgb2YgVVJMcyBpcyBTZXJ2aWNlIFByb3ZpZGVyIHNwZWNpZmljDQoNClNhbXBsZSBtZXRhIGRhdGE6DQpgYGB4bWwNCjw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IlVURi04Ij8+DQo8bWQ6RW50aXR5RGVzY3JpcHRvciB4bWxuczptZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm1ldGFkYXRhIiBJRD0idXJuX215X2lkIiBlbnRpdHlJRD0idXJuOm15OmlkIj4NCgk8bWQ6U1BTU09EZXNjcmlwdG9yIEF1dGhuUmVxdWVzdHNTaWduZWQ9InRydWUiIFdhbnRBc3NlcnRpb25zU2lnbmVkPSJmYWxzZSIgcHJvdG9jb2xTdXBwb3J0RW51bWVyYXRpb249InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCI+DQoJCTxtZDpLZXlEZXNjcmlwdG9yIHVzZT0ic2lnbmluZyI+DQoJCQk8ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQoJCQkJPGRzOlg1MDlEYXRhPg0KCQkJCQk8ZHM6WDUwOUNlcnRpZmljYXRlPg0KCQkJCQkJTUlJRUVEQ0NBdmlnQXdJQkFnSUJCVEFOQmdrcWhraUc5dzBCQVFzRkFEQ0JoVEVMTUFrR0ExVUVCaE1DVWs4eA0KLi4uDQoJCQkJCQl5M0ZJQWlrY2ovZmRZbzFLOWQrUHJMNkpvTWlRR0lnbERMaGVkOFpjcEJxQzY0bUcNCgkJCQkJPC9kczpYNTA5Q2VydGlmaWNhdGU+DQoJCQkJPC9kczpYNTA5RGF0YT4NCgkJCTwvZHM6S2V5SW5mbz4NCgkJPC9tZDpLZXlEZXNjcmlwdG9yPg0KCQk8bWQ6S2V5RGVzY3JpcHRvciB1c2U9ImVuY3J5cHRpb24iPg0KCQkJPGRzOktleUluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KCQkJCTxkczpYNTA5RGF0YT4NCgkJCQkJPGRzOlg1MDlDZXJ0aWZpY2F0ZT4gICAgICAgICAgICAgICAgICAgICAgICANCgkJCQkJCU1JSUVFRENDQXZpZ0F3SUJBZ0lCQlRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaFRFTE1Ba0dBMVVFQmhNQ1VrOHgNCi4uLg0KCQkJCQkJeTNGSUFpa2NqL2ZkWW8xSzlkK1ByTDZKb01pUUdJZ2xETGhlZDhaY3BCcUM2NG1HDQoJCQkJCTwvZHM6WDUwOUNlcnRpZmljYXRlPg0KCQkJCTwvZHM6WDUwOURhdGE+DQoJCQk8L2RzOktleUluZm8+DQoJCTwvbWQ6S2V5RGVzY3JpcHRvcj4NCgkJPG1kOlNpbmdsZUxvZ291dFNlcnZpY2UgQmluZGluZz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCIgTG9jYXRpb249Imh0dHBzOi8vc2VydmVyLmRvbWFpbi5sb2NhbDo5NDQzL3BhdGhfdG9fbG9nb3V0Ii8+DQoJCTxtZDpTaW5nbGVMb2dvdXRTZXJ2aWNlIEJpbmRpbmc9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpiaW5kaW5nczpIVFRQLVJlZGlyZWN0IiBMb2NhdGlvbj0iaHR0cHM6Ly9zZXJ2ZXIuZG9tYWluLmxvY2FsOjk0NDMvcGF0aF90b19sb2dvdXQiLz4NCgkJPG1kOk5hbWVJREZvcm1hdD51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3M8L21kOk5hbWVJREZvcm1hdD4NCgkJPG1kOk5hbWVJREZvcm1hdD51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDp0cmFuc2llbnQ8L21kOk5hbWVJREZvcm1hdD4NCgkJPG1kOk5hbWVJREZvcm1hdD51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDpwZXJzaXN0ZW50PC9tZDpOYW1lSURGb3JtYXQ+DQoJCTxtZDpOYW1lSURGb3JtYXQ+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6dW5zcGVjaWZpZWQ8L21kOk5hbWVJREZvcm1hdD4NCgkJPG1kOk5hbWVJREZvcm1hdD51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDpYNTA5U3ViamVjdE5hbWU8L21kOk5hbWVJREZvcm1hdD4NCgkJPG1kOkFzc2VydGlvbkNvbnN1bWVyU2VydmljZSBCaW5kaW5nPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YmluZGluZ3M6SFRUUC1QT1NUIiBMb2NhdGlvbj0iaHR0cHM6Ly9zZXJ2ZXIuZG9tYWluLmxvY2FsOjk0NDMvcGF0aF90b19hc2VydGlvbl9zZXJ2aWNlIiBpbmRleD0iMCIgaXNEZWZhdWx0PSJ0cnVlIi8+DQoJPC9tZDpTUFNTT0Rlc2NyaXB0b3I+DQo8L21kOkVudGl0eURlc2NyaXB0b3I+DQpgYGA=

URL format for Spring Security SAML extension

When accessing login URL for a Spring Security SAML based application you can use the following syntax:

https://fqdn-server/context-path/saml/login/alias/url-encoded-sp-alias-name?idp=url-encoded-idp-entity-id
  • url-encoded-sp-alias-name is the alias name of the desired Service Provider (the value of <property name="alias" value="sp-alias-name"/>
  • url-encoded-idp-entity-id is the entityId value (URL encoded) as defined in IDP meta-data <EntityDescriptor entityID="http://idp.ssocircle.com" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">, http%3A%2F%2Fidp.ssocircle.com in our case

There are at least two scenarios when this feature can be useful:

  • Having a single application for multiple companies, each one having it's own internal IDP implementation (each user will access the application through a specific URL containing the appropriate IDP as a query parameter)
  • Having a single configuration/single IDP but multiple deployment environments (like development, test, production); different SP aliases will be used to access a specific environment
V2hlbiBhY2Nlc3NpbmcgbG9naW4gYFVSTGAgZm9yIGEgU3ByaW5nIFNlY3VyaXR5IFNBTUwgYmFzZWQgYXBwbGljYXRpb24geW91IGNhbiB1c2UgdGhlIGZvbGxvd2luZyBzeW50YXg6DQoNCmBgYGh0bWwNCmh0dHBzOi8vZnFkbi1zZXJ2ZXIvY29udGV4dC1wYXRoL3NhbWwvbG9naW4vYWxpYXMvdXJsLWVuY29kZWQtc3AtYWxpYXMtbmFtZT9pZHA9dXJsLWVuY29kZWQtaWRwLWVudGl0eS1pZA0KYGBgDQoqIGB1cmwtZW5jb2RlZC1zcC1hbGlhcy1uYW1lYCBpcyB0aGUgYWxpYXMgbmFtZSBvZiB0aGUgZGVzaXJlZCBTZXJ2aWNlIFByb3ZpZGVyICh0aGUgdmFsdWUgb2YgYDxwcm9wZXJ0eSBuYW1lPSJhbGlhcyIgdmFsdWU9InNwLWFsaWFzLW5hbWUiLz5gDQoqIGB1cmwtZW5jb2RlZC1pZHAtZW50aXR5LWlkYCBpcyB0aGUgYGVudGl0eUlkYCB2YWx1ZSAoVVJMIGVuY29kZWQpIGFzIGRlZmluZWQgaW4gSURQIG1ldGEtZGF0YSBgPEVudGl0eURlc2NyaXB0b3IgZW50aXR5SUQ9Imh0dHA6Ly9pZHAuc3NvY2lyY2xlLmNvbSIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDptZXRhZGF0YSI+YCwgYGh0dHAlM0ElMkYlMkZpZHAuc3NvY2lyY2xlLmNvbWAgaW4gb3VyIGNhc2UNCg0KVGhlcmUgYXJlIGF0IGxlYXN0IHR3byBzY2VuYXJpb3Mgd2hlbiB0aGlzIGZlYXR1cmUgY2FuIGJlIHVzZWZ1bDoNCiogSGF2aW5nIGEgc2luZ2xlIGFwcGxpY2F0aW9uIGZvciBtdWx0aXBsZSBjb21wYW5pZXMsIGVhY2ggb25lIGhhdmluZyBpdCdzIG93biBpbnRlcm5hbCBJRFAgaW1wbGVtZW50YXRpb24gKGVhY2ggdXNlciB3aWxsIGFjY2VzcyB0aGUgYXBwbGljYXRpb24gdGhyb3VnaCBhIHNwZWNpZmljIFVSTCBjb250YWluaW5nIHRoZSBhcHByb3ByaWF0ZSBJRFAgYXMgYSBxdWVyeSBwYXJhbWV0ZXIpDQoqIEhhdmluZyBhIHNpbmdsZSBjb25maWd1cmF0aW9uL3NpbmdsZSBJRFAgYnV0IG11bHRpcGxlIGRlcGxveW1lbnQgZW52aXJvbm1lbnRzIChsaWtlIGRldmVsb3BtZW50LCB0ZXN0LCBwcm9kdWN0aW9uKTsgZGlmZmVyZW50IFNQIGFsaWFzZXMgd2lsbCBiZSB1c2VkIHRvIGFjY2VzcyBhIHNwZWNpZmljIGVudmlyb25tZW50

Markdown Code Styling

  • Edit your MarkDown content with your preferred editor.
  • Copy the resulted HTML content (the content of the body tag) as the blog HTML content
  • Add at the beginning the following lines:
<link href='//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.5/styles/solarized_light.min.css' rel='stylesheet'/>
<script src='//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.5/highlight.min.js'/>
<script>hljs.initHighlightingOnLoad();</script>
<style>

:not(pre)>code{
  margin: 0 0px;
  padding: 0px 5px;
  border: 1px solid #eaeaea;
  color:RoyalBlue;
  background-color: #f8f8f8;
  border-radius: 3px;

}
.hljs{
background-color: #f8f8f8;
font-weight:bold;
border: 2px solid #eaeaea;
}

body, pre {
font-family: Consolas, Arial, Tahoma, Helvetica, FreeSans, sans-serif;

}
body{
font-size: 14px;
  }
pre{
font-size: 12px;

  }
h1, h2, h3, h4, h5, h6 {
  margin: 20px 0 10px;
  padding: 0;
  font-weight: bold;
  -webkit-font-smoothing: antialiased;
}

h1 {
  font-size: 28px;
  color: #000;
}
h2 {
  font-size: 24px;
  border-bottom: 1px solid #ccc;
  color: #000;
}
h3 {
  font-size: 18px;
}
h4 {
  font-size: 16px;
}
h5 {
  font-size: 14px;
}
h6 {
  color: #777;
  font-size: 14px;
}

</style>

Go to https://cdnjs.com/libraries/highlight.js to pick-up your preferred colouring scheme

As an alternative you can paste the above snippet before </head> tag in blog template.

Certificate Management

Certificate creation

Use XCA (http://sourceforge.net/projects/xca)

Create Certification Authority root certificate

  • Create a new database
  • Create a new key by using the CA template (RSA, minimum 2048 bits length)
  • Create a new certificate for CA (using previously created key, minimum SHA256 signing algorithm - SHA1 is about to be not supported anymore starting from 2017)

Create certificates

  • Create a new Template based on HTTPS server existing template (depending on your future needs)
  • Create certificates:
    • Go to Templates tab->right click->Create certificate;if using the option to create a new certificate from Certificates tab do not forget to select a template and click one of the Apply options buttons
    • Change the default option for signing the certificate (->Use this certificate...; select the CA certificate)
    • Change the default signing algorithm to something >SHA1
    • On Subject tab:
      • Change the relevant fields (commonName especially)
      • Create a new private key for certificate (Generate new key button)

Misc operations

Apart from exporting certificates to different formats (including or not private key, including or not certificate chain) you can obtain the public or private key in PEM format by going to Private Keys tab ->right click a key->Export to clipboard, etc

Managing a JKS Store

Use KeyStore Explorer (http://keystore-explorer.sourceforge.net/). An alternative would be to use Poretcle but KeyStore has the advantage (from my point of view) that being a Windows application can be registered as default for JKS files.

Import now the trusted CA certificates (*.cer or *.crt for instance) and the key pairs (certificates containing the private key) (*.p12 for instance).

IyBDZXJ0aWZpY2F0ZSBjcmVhdGlvbg0KDQpVc2UgKipYQ0EqKiAoW2h0dHA6Ly9zb3VyY2Vmb3JnZS5uZXQvcHJvamVjdHMveGNhXShodHRwOi8vc291cmNlZm9yZ2UubmV0L3Byb2plY3RzL3hjYSkpDQoNCiMjIENyZWF0ZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSByb290IGNlcnRpZmljYXRlDQoqIENyZWF0ZSBhIG5ldyBkYXRhYmFzZQ0KKiBDcmVhdGUgYSBuZXcga2V5IGJ5IHVzaW5nIHRoZSBDQSB0ZW1wbGF0ZSAoUlNBLCBtaW5pbXVtIDIwNDggYml0cyBsZW5ndGgpDQoqIENyZWF0ZSBhIG5ldyBjZXJ0aWZpY2F0ZSBmb3IgQ0EgKHVzaW5nIHByZXZpb3VzbHkgY3JlYXRlZCBrZXksIG1pbmltdW0gYFNIQTI1NmAgc2lnbmluZyBhbGdvcml0aG0gLSBgU0hBMWAgaXMgYWJvdXQgdG8gYmUgbm90IHN1cHBvcnRlZCBhbnltb3JlIHN0YXJ0aW5nIGZyb20gMjAxNykNCg0KIyMgQ3JlYXRlIGNlcnRpZmljYXRlcw0KDQoqIENyZWF0ZSBhIG5ldyBUZW1wbGF0ZSBiYXNlZCBvbiBIVFRQUyBzZXJ2ZXIgZXhpc3RpbmcgdGVtcGxhdGUgKGRlcGVuZGluZyBvbiB5b3VyIGZ1dHVyZSBuZWVkcykNCiogQ3JlYXRlIGNlcnRpZmljYXRlczoNCgkqIEdvIHRvIGBUZW1wbGF0ZXNgIHRhYi0+YHJpZ2h0IGNsaWNrYC0+YENyZWF0ZSBjZXJ0aWZpY2F0ZWA7aWYgdXNpbmcgdGhlIG9wdGlvbiB0byBjcmVhdGUgYSBuZXcgY2VydGlmaWNhdGUgZnJvbSBgQ2VydGlmaWNhdGVzYCB0YWIgZG8gbm90IGZvcmdldCB0byBzZWxlY3QgYSB0ZW1wbGF0ZSBhbmQgY2xpY2sgb25lIG9mIHRoZSBgQXBwbHlgIG9wdGlvbnMgYnV0dG9ucw0KCSogQ2hhbmdlIHRoZSBkZWZhdWx0IG9wdGlvbiBmb3Igc2lnbmluZyB0aGUgY2VydGlmaWNhdGUgKC0+VXNlIHRoaXMgY2VydGlmaWNhdGUuLi47IHNlbGVjdCB0aGUgQ0EgY2VydGlmaWNhdGUpDQoJKiBDaGFuZ2UgdGhlIGRlZmF1bHQgc2lnbmluZyBhbGdvcml0aG0gdG8gc29tZXRoaW5nID5TSEExDQoJKiBPbiBgU3ViamVjdGAgdGFiOg0KCQkqIENoYW5nZSB0aGUgcmVsZXZhbnQgZmllbGRzIChjb21tb25OYW1lIGVzcGVjaWFsbHkpDQoJCSogQ3JlYXRlIGEgbmV3IHByaXZhdGUga2V5IGZvciBjZXJ0aWZpY2F0ZSAoYEdlbmVyYXRlIG5ldyBrZXlgIGJ1dHRvbikNCg0KIyMgTWlzYyBvcGVyYXRpb25zDQpBcGFydCBmcm9tIGV4cG9ydGluZyBjZXJ0aWZpY2F0ZXMgdG8gZGlmZmVyZW50IGZvcm1hdHMgKGluY2x1ZGluZyBvciBub3QgcHJpdmF0ZSBrZXksIGluY2x1ZGluZyBvciBub3QgY2VydGlmaWNhdGUgY2hhaW4pIHlvdSBjYW4gb2J0YWluIHRoZSBwdWJsaWMgb3IgcHJpdmF0ZSBrZXkgaW4gUEVNIGZvcm1hdCBieSBnb2luZyB0byBgUHJpdmF0ZSBLZXlzYCB0YWIgLT5gcmlnaHQgY2xpY2tgIGEga2V5LT5gRXhwb3J0YCB0byBjbGlwYm9hcmQsIGV0Yw0KDQoNCiMgTWFuYWdpbmcgYSBKS1MgU3RvcmUNCg0KVXNlIEtleVN0b3JlIEV4cGxvcmVyIChbaHR0cDovL2tleXN0b3JlLWV4cGxvcmVyLnNvdXJjZWZvcmdlLm5ldC9dKGh0dHA6Ly9rZXlzdG9yZS1leHBsb3Jlci5zb3VyY2Vmb3JnZS5uZXQvKSkuDQpBbiBhbHRlcm5hdGl2ZSB3b3VsZCBiZSB0byB1c2UgW1BvcmV0Y2xlXShodHRwOi8vcG9ydGVjbGUuc291cmNlZm9yZ2UubmV0LykgYnV0IEtleVN0b3JlIGhhcyB0aGUgYWR2YW50YWdlIChmcm9tIG15IHBvaW50IG9mIHZpZXcpIHRoYXQgYmVpbmcgYSBXaW5kb3dzIGFwcGxpY2F0aW9uIGNhbiBiZSByZWdpc3RlcmVkIGFzIGRlZmF1bHQgZm9yIEpLUyBmaWxlcy4NCg0KSW1wb3J0IG5vdyB0aGUgdHJ1c3RlZCBDQSBjZXJ0aWZpY2F0ZXMgKFwqLmNlciBvciBcKi5jcnQgZm9yIGluc3RhbmNlKSBhbmQgdGhlIGtleSBwYWlycyAoY2VydGlmaWNhdGVzIGNvbnRhaW5pbmcgdGhlIHByaXZhdGUga2V5KSAoKi5wMTIgZm9yIGluc3RhbmNlKS4=

Chrome Windows Integrated Authentication with ADFS 2.0

Method 1

Since Chrome will use primarily IE configurations add the site in Internet Options->Security->Local Intranet->Sites->Advanced (provide the FDQN, using wildcards if required and press Add)

Method 2

If you do not want to use IE settings run the following registry entries

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome]
"AuthServerWhitelist"="fdqn"
"AuthSchemes"="basic,digest,ntlm,negotiate"
"AuthNegotiateDelegateWhitelist"="fdqn"

fdqn can be a single host qualified name (like www.domain.com ) or a full domain (like *domain.com); the wild card syntax is slightly different the one of IE (no dot required!?)

ADFS/SAML Remark

In order to have WIA working against ADFS (at least version 2.0) we have two constraints:

  • Use integrated authentication (in web.config)
  <microsoft.identityServer.web>
    <localAuthenticationTypes>
      <add name="Integrated" page="auth/integrated/" />
      <add name="Forms" page="FormsSignIn.aspx" />
      <add name="TlsClient" page="auth/sslclient/" />
...
  • Disable Extended Protection for adsf\ls site (Authentication->Windows Authentication->Advanced Settings, set `Extended Protection' to Off)
IyBNZXRob2QgMQ0KU2luY2UgQ2hyb21lIHdpbGwgdXNlIHByaW1hcmlseSAgSUUgY29uZmlndXJhdGlvbnMgYWRkIHRoZSBzaXRlIGluIGBJbnRlcm5ldCBPcHRpb25zYC0+YFNlY3VyaXR5YC0+YExvY2FsIEludHJhbmV0YC0+YFNpdGVzYC0+YEFkdmFuY2VkYCAocHJvdmlkZSB0aGUgRkRRTiwgdXNpbmcgd2lsZGNhcmRzIGlmIHJlcXVpcmVkIGFuZCBwcmVzcyBgQWRkYCkNCg0KIyBNZXRob2QgMg0KSWYgeW91IGRvIG5vdCB3YW50IHRvIHVzZSBJRSBzZXR0aW5ncyBydW4gdGhlIGZvbGxvd2luZyByZWdpc3RyeSBlbnRyaWVzDQogDQpgYGANCldpbmRvd3MgUmVnaXN0cnkgRWRpdG9yIFZlcnNpb24gNS4wMA0KDQpbSEtFWV9MT0NBTF9NQUNISU5FXFNvZnR3YXJlXFBvbGljaWVzXEdvb2dsZVxDaHJvbWVdDQoiQXV0aFNlcnZlcldoaXRlbGlzdCI9ImZkcW4iDQoiQXV0aFNjaGVtZXMiPSJiYXNpYyxkaWdlc3QsbnRsbSxuZWdvdGlhdGUiDQoiQXV0aE5lZ290aWF0ZURlbGVnYXRlV2hpdGVsaXN0Ij0iZmRxbiINCmBgYA0KDQpgZmRxbmAgY2FuIGJlIGEgc2luZ2xlIGhvc3QgcXVhbGlmaWVkIG5hbWUgKGxpa2Ugd3d3LmRvbWFpbi5jb20gKSBvciBhIGZ1bGwgZG9tYWluIChsaWtlICpkb21haW4uY29tKTsgdGhlIHdpbGQgY2FyZCBzeW50YXggaXMgc2xpZ2h0bHkgZGlmZmVyZW50IHRoZSBvbmUgb2YgSUUgKG5vIGRvdCByZXF1aXJlZCE/KQ0KDQojIEFERlMvU0FNTCBSZW1hcmsNCg0KSW4gb3JkZXIgdG8gaGF2ZSBXSUEgd29ya2luZyBhZ2FpbnN0IEFERlMgKGF0IGxlYXN0IHZlcnNpb24gMi4wKSB3ZSBoYXZlIHR3byBjb25zdHJhaW50czoNCiogVXNlIGludGVncmF0ZWQgYXV0aGVudGljYXRpb24gKGluIHdlYi5jb25maWcpDQpgYGB4bWwNCiAgPG1pY3Jvc29mdC5pZGVudGl0eVNlcnZlci53ZWI+DQogICAgPGxvY2FsQXV0aGVudGljYXRpb25UeXBlcz4NCiAgICAgIDxhZGQgbmFtZT0iSW50ZWdyYXRlZCIgcGFnZT0iYXV0aC9pbnRlZ3JhdGVkLyIgLz4NCiAgICAgIDxhZGQgbmFtZT0iRm9ybXMiIHBhZ2U9IkZvcm1zU2lnbkluLmFzcHgiIC8+DQogICAgICA8YWRkIG5hbWU9IlRsc0NsaWVudCIgcGFnZT0iYXV0aC9zc2xjbGllbnQvIiAvPg0KLi4uDQpgYGANCiogRGlzYWJsZSBFeHRlbmRlZCBQcm90ZWN0aW9uIGZvciBhZHNmXFxscyBzaXRlIChgQXV0aGVudGljYXRpb25gLT5gV2luZG93cyBBdXRoZW50aWNhdGlvbmAtPmBBZHZhbmNlZCBTZXR0aW5nc2AsIHNldCBgRXh0ZW5kZWQgUHJvdGVjdGlvbicgdG8gT2ZmKQ==

HTTPS in Wildfly

Create a certificate store (file name wildfly.jks) placed in configuration directory. Add both CA root public certificate and SSL certificate (including private key, like PKCS#12 format).

Add a security-realm in security-realms section.

        <security-realms>
...
            <security-realm name="SslRealm">
                <server-identities>
                    <ssl>
                        <keystore path="wildfly.jks" relative-to="jboss.server.config.dir" keystore-password="secret4jks" alias="alias-of-ssl-certificate" key-password="secret4certificate"/>
                    </ssl>
                </server-identities>
            </security-realm>
        </security-realms>

Add a https-listener referring the previously defined security-realm

        <subsystem xmlns="urn:jboss:domain:undertow:1.2">
            <buffer-cache name="default"/>
            <server name="default-server">
                <http-listener name="default" socket-binding="http" max-parameters="5000"/>
                <https-listener name="default-ssl" socket-binding="https" security-realm="SslRealm"/>
...

PHA+Q3JlYXRlIGEgY2VydGlmaWNhdGUgc3RvcmUgKGZpbGUgbmFtZSA8c3Ryb25nPndpbGRmbHkuamtzPC9zdHJvbmc+KSBwbGFjZWQgaW4gY29uZmlndXJhdGlvbiBkaXJlY3RvcnkuIEFkZCBib3RoIENBIHJvb3QgIHB1YmxpYyBjZXJ0aWZpY2F0ZSBhbmQgU1NMIGNlcnRpZmljYXRlIChpbmNsdWRpbmcgcHJpdmF0ZSBrZXksIGxpa2UgPHN0cm9uZz5QS0NTIzEyPC9zdHJvbmc+IGZvcm1hdCkuPC9wPg0KPHA+QWRkIGEgPGNvZGU+c2VjdXJpdHktcmVhbG08L2NvZGU+IGluIDxjb2RlPnNlY3VyaXR5LXJlYWxtczwvY29kZT4gc2VjdGlvbi48L3A+DQo8cHJlPjxjb2RlIGNsYXNzPSJsYW5ndWFnZS14bWwiPiAgICAgICAgJmx0O3NlY3VyaXR5LXJlYWxtcyZndDsNCi4uLg0KICAgICAgICAgICAgJmx0O3NlY3VyaXR5LXJlYWxtIG5hbWU9JnF1b3Q7U3NsUmVhbG0mcXVvdDsmZ3Q7DQogICAgICAgICAgICAgICAgJmx0O3NlcnZlci1pZGVudGl0aWVzJmd0Ow0KICAgICAgICAgICAgICAgICAgICAmbHQ7c3NsJmd0Ow0KICAgICAgICAgICAgICAgICAgICAgICAgJmx0O2tleXN0b3JlIHBhdGg9JnF1b3Q7d2lsZGZseS5qa3MmcXVvdDsgcmVsYXRpdmUtdG89JnF1b3Q7amJvc3Muc2VydmVyLmNvbmZpZy5kaXImcXVvdDsga2V5c3RvcmUtcGFzc3dvcmQ9JnF1b3Q7c2VjcmV0NGprcyZxdW90OyBhbGlhcz0mcXVvdDthbGlhcy1vZi1zc2wtY2VydGlmaWNhdGUmcXVvdDsga2V5LXBhc3N3b3JkPSZxdW90O3NlY3JldDRjZXJ0aWZpY2F0ZSZxdW90Oy8mZ3Q7DQogICAgICAgICAgICAgICAgICAgICZsdDsvc3NsJmd0Ow0KICAgICAgICAgICAgICAgICZsdDsvc2VydmVyLWlkZW50aXRpZXMmZ3Q7DQogICAgICAgICAgICAmbHQ7L3NlY3VyaXR5LXJlYWxtJmd0Ow0KICAgICAgICAmbHQ7L3NlY3VyaXR5LXJlYWxtcyZndDsNCjwvY29kZT48L3ByZT4NCjxwPkFkZCBhIDxjb2RlPmh0dHBzLWxpc3RlbmVyPC9jb2RlPiByZWZlcnJpbmcgdGhlIHByZXZpb3VzbHkgZGVmaW5lZCA8Y29kZT5zZWN1cml0eS1yZWFsbTwvY29kZT48L3A+DQo8cHJlPjxjb2RlIGNsYXNzPSJsYW5ndWFnZS14bWwiPiAgICAgICAgJmx0O3N1YnN5c3RlbSB4bWxucz0mcXVvdDt1cm46amJvc3M6ZG9tYWluOnVuZGVydG93OjEuMiZxdW90OyZndDsNCiAgICAgICAgICAgICZsdDtidWZmZXItY2FjaGUgbmFtZT0mcXVvdDtkZWZhdWx0JnF1b3Q7LyZndDsNCiAgICAgICAgICAgICZsdDtzZXJ2ZXIgbmFtZT0mcXVvdDtkZWZhdWx0LXNlcnZlciZxdW90OyZndDsNCiAgICAgICAgICAgICAgICAmbHQ7aHR0cC1saXN0ZW5lciBuYW1lPSZxdW90O2RlZmF1bHQmcXVvdDsgc29ja2V0LWJpbmRpbmc9JnF1b3Q7aHR0cCZxdW90OyBtYXgtcGFyYW1ldGVycz0mcXVvdDs1MDAwJnF1b3Q7LyZndDsNCiAgICAgICAgICAgICAgICAmbHQ7aHR0cHMtbGlzdGVuZXIgbmFtZT0mcXVvdDtkZWZhdWx0LXNzbCZxdW90OyBzb2NrZXQtYmluZGluZz0mcXVvdDtodHRwcyZxdW90OyBzZWN1cml0eS1yZWFsbT0mcXVvdDtTc2xSZWFsbSZxdW90Oy8mZ3Q7DQouLi4NCg0KPC9jb2RlPjwvcHJlPg0K

Wednesday, April 15, 2015

MdCharm

  • Paste above any other markup the style tag
  • Remove any & (there is one inside a comment)
  • Comment the aside specification (due to float:right all layout is messed up)
  • Paste the body content below style
  • use Notepad MimeTools plugin to encode Base64 the md content (be sure to select all content before applying the plugin)
  • paste the result inside a <div style="display:none:"> </div>
KiBQYXN0ZSBhYm92ZSBhbnkgb3RoZXIgbWFya3VwIHRoZSAqKnN0eWxlKiogdGFnDQoqIFJlbW92ZSBhbnkgJiAodGhlcmUgaXMgb25lIGluc2lkZSBhIGNvbW1lbnQpDQoqIENvbW1lbnQgdGhlICoqYXNpZGUqKiBzcGVjaWZpY2F0aW9uIChkdWUgdG8gZmxvYXQ6cmlnaHQgYWxsIGxheW91dCBpcyBtZXNzZWQgdXApDQoqIFBhc3RlIHRoZSAqKmJvZHkqKiBjb250ZW50IGJlbG93ICoqc3R5bGUqKg0KKiB1c2UgTm90ZXBhZCBNaW1lVG9vbHMgcGx1Z2luIHRvIGVuY29kZSBCYXNlNjQgdGhlIG1kIGNvbnRlbnQgKGJlIHN1cmUgdG8gc2VsZWN0IGFsbCBjb250ZW50IGJlZm9yZSBhcHBseWluZyB0aGUgcGx1Z2luKQ0KKiBwYXN0ZSB0aGUgcmVzdWx0IGluc2lkZSBhICZsdDtkaXYgc3R5bGU9JnF1b3Q7ZGlzcGxheTpub25lOiZxdW90OyZndDsgJmx0Oy9kaXYmZ3Q7

Tuesday, April 14, 2015

JSF login through Spring Security (4.0.0)

Add the required entries in web.xml

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/applicationContext-security.xml
        </param-value>
    </context-param>
...
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>REQUEST</dispatcher><!-- mandatory to allow the managed bean to forward the request to the filter -->
    </filter-mapping>
...    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>

Be sure that springSecurityFilterChain hase the two dispatcher properties set as shown above otherwise the first method will not work

Configure security rules, authentication manager, authentication provider, etc

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:s="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" 
       
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    
        
    <context:annotation-config />
    
    <!--s:http pattern="/facesx/**" security="none"/-->

    <s:http authentication-manager-ref="authenticationManager">

        <s:intercept-url pattern="/faces/auth/admin/**" access="hasRole('ROLE_Admins')" />
        <s:intercept-url pattern="/faces/auth/**" access="authenticated" />
        <s:intercept-url pattern="/faces/login.xhtml" access="permitAll" />

        <!--s:form-login/-->
        <s:form-login login-page="/faces/login.xhtml" default-target-url="/faces/auth/home.xhtml"/>
        <s:csrf disabled="true"/>
    </s:http>


    <s:authentication-manager id="authenticationManager">
        <s:authentication-provider ref='AD-LdapProvider' />
    </s:authentication-manager>

    <bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
        <constructor-arg value="ldap://mydomain.local:389"/>
        
        <property name="userDn" value="CN=aduser,OU=App Users,OU=MyCompany,DC=mydomain,DC=local" />
        <property name="password" value="******" />
    </bean>

    <bean id="AD-LdapProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
        <constructor-arg>
            <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
                <constructor-arg ref="contextSource" />
                <property name="userSearch">
                    <bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
                        <constructor-arg index="0" value="OU=MyCompany,DC=mydomain,DC=local"/>
                        <constructor-arg index="1" value="(&amp;(objectClass=user)(sAMAccountName={0}))"/>
                        <constructor-arg index="2" ref="contextSource" />
                        <property name="searchSubtree" value="true"/>
                    </bean>
                </property>
            </bean>
        </constructor-arg>
        <constructor-arg>
            <bean class="org.springframework.security.ldap.userdetails.NestedLdapAuthoritiesPopulator">
                <constructor-arg ref="contextSource" />
                <constructor-arg value="OU=MyCompany,DC=mydomain,DC=local" />
                <property name="groupSearchFilter" value="(&amp;(objectClass=group)(groupType:1.2.840.113556.1.4.803:=-2147483648)(member={0}))"/><!--http://ldapwiki.willeke.com/wiki/Active%20Directory%20Group%20Related%20Searches#section-Active+Directory+Group+Related+Searches-AllSecurityGroupsLocalGlobalAndUniversal -->
                <property name="rolePrefix" value="ROLE_"/>
                <property name="searchSubtree" value="true"/>
                <property name="convertToUpperCase" value="false"/>
            </bean>
        </constructor-arg>
    </bean>

</beans>

Remark: groupType:1.2.840.113556.1.4.803:=-2147483648 will filter only security group types

Method 1

Have the login form as follows:

        <h:form prependId="false" id="form">
            <p:panelGrid columns="2" style="border-bottom-width: 0px;">
                <h:outputText value="User:"/>
                <h:inputText value="#{loginController.user}" id="username"/>
                <h:outputText value="Password:"/>
                <h:inputSecret value="#{loginController.password}" id="password"/>
            </p:panelGrid>
            <p:commandButton action="#{loginController.login}" value="Login" ajax="false"/>
        </h:form>

In managed bean, method called at pressing Login button:

Named()
@SessionScoped()
public class LoginController implements Serializable {
...
    public void login() throws ServletException, IOException { 
        
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
        RequestDispatcher dispatcher = ((ServletRequest) context.getRequest()).getRequestDispatcher("/login");
        dispatcher.forward((ServletRequest) context.getRequest(), (ServletResponse) context.getResponse());
        FacesContext.getCurrentInstance().responseComplete();
    }
}

Be sure that:

  • you have not overriden the default mapping for login-processing-url (default value "/login" as oposed to "/j_spring_security_check" for older versions of Spring
  • have the ids of user and password input buttons as "username" and "password" respectively (be aware older Spring versions have the defaults set to "j_xxx"; you can actually configure these values from username-parameter and password-parameter attributes of login element)
  • use ajax="false" in button definition
  • add prependId="false" to form definition
  • have the FORWARD and REQUEST dispatcher parameters set as shown above

Method 2

Make all web.xml and applicationContext-security.xml as above, apart from:

<s:form-login login-page="/faces/login.xhtml"/><!-- no  default-target-url -->

Use a regular JSF login page/controller and perform login as usual:

    public String login() {
        try {
            getRequest().login(lastLoginName, password);
            ...
            return "/auth/home?faces-redirect=true";
        } catch (ServletException ex) {
            Logger.getLogger(CommonController.class.getName()).log(Level.SEVERE, null, ex);
        }
        return "/error?faces-redirect=true";

Method 3

Use the same login form as for Method 1 but add onclick method and remove action:

        <h:form prependId="false" id="form">
            <p:panelGrid columns="2" style="border-bottom-width: 0px;">
                <h:outputText value="User:"/>
                <h:inputText value="#{loginController.user}" id="username"/>
                <h:outputText value="Password:"/>
                <h:inputSecret value="#{loginController.password}" id="password"/>
            </p:panelGrid>
            <p:commandButton value="Login" ajax="false" 
 onclick="$('#form').attr('action',$('#form').attr('action').replace('/faces/login.xhtml','/login'))"/>
        </h:form>

The main disatvantage of this method is that you have no chance to do additional processing after successfull login (but you can do it through JSF events in page defined as default-target-url)