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==

2 comments :

  1. Have you referred to the migration guides which explains how to revert to the previous behavior? You can find a link the detailed migration guides at http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#m3to4 Reverting the ROLE prefix using XML configuration is detailed in http://docs.spring.io/spring-security/site/migrate/current/3-to-4/html5/migrate-3-to-4-xml.html

    ReplyDelete
    Replies
    1. While I must admit I have missed the reference you provided the intent of the post (as I remember now...) was to address the issue of not being able to convert/enrich Groups to application Roles so that already written applications can be used unchanged (for instance if using isUserInRole or other JSF/Primefaces constructs to adapt the UI accordingly). The RoleHierarchyVoter which looks like providing the required functionality did only work, if I remember correctly, just for Spring configuration definitions.

      Delete