class LdapRealm extends DefaultLdapRealm implements RealmLifecycle, ShiroAuthorizationInfoProvider {

    Set<String> findRoleNamesForUser( String username, LdapContext ldapContext ) throws NamingException
    {
        Set<String> roleNames = new LinkedHashSet<>();

        SearchControls searchCtls = new SearchControls();
        searchCtls.setSearchScope( SearchControls.SUBTREE_SCOPE );
        searchCtls.setReturningAttributes( membershipAttributeNames.toArray( new String[1] ) );

        // Use search argument to prevent potential code injection
        Object[] searchArguments = new Object[]{username};

        NamingEnumeration result = ldapContext.search( userSearchBase, userSearchFilter, searchArguments, searchCtls );

        if ( result.hasMoreElements() )
        {
            SearchResult searchResult = (SearchResult) result.next();

            if ( result.hasMoreElements() )
            {
                securityLog.warn(
                        securityLog.isDebugEnabled() ?
                        withRealm(
                            "LDAP user search for user principal '%s' is ambiguous. The first match that will " +
                                            "be checked for group membership is '%s' but the search also matches '%s'. " +
                                            "Please check your LDAP realm configuration.",
                            username, searchResult.toString(), result.next().toString() )
                        :
                        withRealm(
                            "LDAP user search for user principal '%s' is ambiguous. The search matches more " +
                                            "than one entry. Please check your LDAP realm configuration.",
                            username )
                    );
            }

            Attributes attributes = searchResult.getAttributes();
            if ( attributes != null )
            {
                NamingEnumeration attributeEnumeration = attributes.getAll();
                while ( attributeEnumeration.hasMore() )
                {
                    Attribute attribute = (Attribute) attributeEnumeration.next();
                    String attributeId = attribute.getID();
                    if ( membershipAttributeNames.stream().anyMatch( attributeId::equalsIgnoreCase ) )
                    {
                        Collection<String> groupNames = LdapUtils.getAllAttributeValues( attribute );
                        Collection<String> rolesForGroups = getRoleNamesForGroups( groupNames );
                        roleNames.addAll( rolesForGroups );
                    }
                }
            }
        }
        return roleNames;
    }

    public  LdapRealm( Config config, SecurityLog securityLog, SecureHasher secureHasher );

}