Forum Discussion

bdavis's avatar
bdavis
Icon for Nimbostratus rankNimbostratus
Oct 07, 2015

Help pulling multiple Attribute tag names and values out of XML and setting to custom variables.

If anyone could please help me solve this problem. Hopefully I can provide enough detail to show a clear understanding of what I'm trying to accomplish. Essentially APM parses SAML assertions automatically and sets this information as variables, however I want a way to grab these attributes and send them all to the back-end web server without needing to know what the attribute names are, because they change depending on application. So the only logical way I have found that I could do this is grab the assertion from session.saml.last.assertion and pull the information out my self and insert all of them through a loop as described below. If there is a more efficient way to do this I'm open to any recommendations to accomplish the task.

Below I have included a scrubbed snippet of XML out of a much larger XML which gets set to a variable that I'm pulling from. My goal is to take all attribute tags out of the XML no matter how many and set each of them to there own separate variable for additional usage. For example end result of below snippet would be:

Variables:
AttributeName.1=Email
AttributeValue.1=Johndoe@mail.com
AttributeName.2=Role
AttributeValue.2=GROUP1
etc....     

Also I would love be able to count how many AttributeName variable where set so when I want to grab all of them and insert them as headers I can loop it and know exactly how many are out there.

for example:

set attrcount "Number of Attributes"
for {set i 0} {$i <= $attrcount} {incr i}{
    HTTP::header insert "$AttributeName.$i" $AttributeValue.$i
}

I usually don't ask for this much detail on DevCentral but I'm a bit stumped on how to pull multiple tags with the same name out. I initially thought about using a xml parser but it doesn't seem like there is a whole lot out there that doesn't require a package such as tdom which isn't supported on the F5.

   
        
            Johndoe@mail@mail.com
        
        
            GROUP1
        
    

3 Replies

  • You wouldn't be able to use the built-in xpath query engine because that's tied to an XML profile and the XML_CONTENT_BASED_ROUTING event, which only works in an HTTP/XML request context. You could, technically, grab the entire XML payload and send it via sideband call to another VIP that can do the xpath processing, but I think the following might be just as efficient:

    when ACCESS_POLICY_COMPLETED {
         grab the entire AttributeStatements section
        set attr_statement [findstr [ACCESS::session data get session.saml.last.assertion] "" 26 ""]
    
         create an empty list
        set attr_list [list]
    
         split the attributes and loop through them
        foreach x [split $attr_statement "<"] {
            if { $x starts_with "saml2:Attribute Name=" } {
                 found an attribute name - add it to the list
                lappend attr_list [findstr $x "saml2:Attribute Name=\"" 22 "\""] 
            } elseif { $x starts_with "saml2:AttributeValue>" } {
                 found an attribute value - add it to the list
                lappend attr_list [findstr $x "saml2:AttributeValue>" 21 "<"]
            }
        }
    
         result is a list. Ex. [name1 value1 name2 value2 name3 value3]
        log local0. $attr_list
    }
    

    So if you're doing this with an APM IdP assertion, the assertion attributes are between the "" and "" elements. So I grab that value from session.saml.last.assertion, split the remaining content on the "<" character, and then loop through the new list. I then build a list with all of the names and values, example:

    [name1 value1 name2 value2 name3 value3 name4 value4]
    

    As a list object, you can then perform all sorts of really cool operations on it - count, search, etc. to get whatever information you want:

    log local0. "Total number of attributes: [expr [llength $attr_list] / 2]"
    
    for { set i 0 } { $i < [llength $attr_list] } { incr i 2 } {
        log local0. "[lindex $attr_list $i] = [lindex $attr_list [expr $i + 1]]" 
    }
    

    or if I wanted to grab a specific value (ex. the 'state' attribute):

    set state_attr [lindex $attr_list [expr [lsearch $attr_list "state"] + 1]] 
    log local0. "The 'state' attribute: $state_attr"
    

    and optionally check to make sure the index value returned from the lsearch command is an even number, or zero (0,2,4,6,etc), so you're sure it's a name attribute.

  • I did indeed forget about that scenario. 😉 What I've found in testing though is that an empty attribute value will not create an AttributeValue tag at all. Example

    
    

    which of course throws off the list. I've added a counter and some modulus math to the above to account for this possibility:

    when ACCESS_POLICY_COMPLETED {
         grab the entire AttributeStatements section
        set attr_statement [findstr [ACCESS::session data get session.saml.last.assertion] "" 26 ""]
    
         create an empty list
        set attr_list [list]
    
         start counter
        set ctr 0
    
         split the attributes and loop through them
        foreach x [split $attr_statement "<"] {
            if { $x starts_with "saml2:Attribute Name=" } {
                 found an attribute name - check counter to see if counter mod 2 == 0
                if { [expr $ctr % 2] != 0 } {
                     previous attribute had no value - add a null to the list
                    lappend attr_list null
                     increment the counter
                    incr ctr
                }
                lappend attr_list [findstr $x "saml2:Attribute Name=\"" 22 "\""]
                 increment the counter
                incr ctr
            } elseif { $x starts_with "saml2:AttributeValue>" } {
                 found an attribute value - add it to the list
                lappend attr_list [findstr $x "saml2:AttributeValue>" 21 "<"]
                 increment the counter
                incr ctr
            }
        }
    
         if the last counter value is not even (counter mod 2 != 0) then the last item in the list had no value - add one more null value to the list
        if { [expr $ctr % 2] != 0 } {
            lappend attr_list null
        }
    
         result is a list. Ex. [name1 value1 name2 value2 name3 value3]
        log local0. $attr_list  
    }
    
  • You'd capture and store the values from the request in the ACCESS_POLICY_COMPLETED event.

    ACCESS::session data set session.custom.attrlist $attr_list
    

    And then use that session variable in the ACCESS_ACL_ALLOWED event

    when ACCESS_ACL_ALLOWED {
        if { [ACCESS::session data get session.custom.attrlist] ne "" } {
            foreach x [ACCESS::session data get session.custom.attrlist] {
                 ...do stuff
            }
        }
    }