locked
Custom Attribute Store multiple claims

    Question

  • Hello,

    I have a Custom Attribute Store that I have plugged into AD FS 2.0, which implements IAttributeStore.  My question is around how to use the following interface correctly: 

           string[][] EndExecuteQuery(IAsyncResult result);

    I am querying the custom attr store using the following statement:

    c:[Type == http://terryc.com/ws/2008/06/claims/ArrayOfClaims]
     => issue(store = "ArrayCustomAttributeStore", types = ("http://terryc.com/ws/2008/06/claims/AppRole"), query = "claimtype={0};arrayclaims={1}", param = "AppRole", param = c.Value);

    This query sucessfully returns 3 values (when I debug & step into my custom attribute store code), all of which are the same "AppRole" claim type, so I am expecting to generate 3 claims; values shown below:

         AP1

         AP2

         AP3

    In code, I am setting the string[][] output value for EndExecuteQuery as shown below:

         string[][] resultData = { new string[] { "AppRole" }, new string[] { "AP1", "AP2", "AP3" } };

    ...I have tried many variations (returning only the ap1, ap2, ap3 claimvalue array, swapping the claim type and values in resultData, etc), however, ADFS consistently returns the following exception:

    The Federation Service encountered an error while processing the WS-Trust request.
    Request type: http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue

    Additional Data
    Exception details:
    Microsoft.IdentityServer.ClaimsPolicy.Language.PolicyEvaluationException: POLICY0019: Query 'claimtype={0};arrayclaims={1}' to attribute store 'ArrayCustomAttributeStore' returned an unexpected number of fields: expected '1', got '3'.
       at Microsoft.IdentityModel.Threading.AsyncResult.End(IAsyncResult result)
       at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract.ProcessCoreAsyncResult.End(IAsyncResult ar)
       at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract.EndProcessCore(IAsyncResult ar, String requestAction, String responseAction, String trustNamespace)

    Any ideas?

    Thanks


    terryc_ms
    • Edited by terryc_ms Friday, December 10, 2010 7:35 PM
    Wednesday, December 08, 2010 8:03 AM

Answers

  • This is resolved now; I was not passing claims into the string[][] correctly. 

    Summary:

    My simple example uses the following ADFS Issuance Transform Rule defined for my Relying Party:

    c:[Type == "http://terryc.com/ws/2008/06/claims/ClaimDataArray"]
    => issue(store = "ArrayCustomAttributeStore", types = ("http://terryc.com/ws/2008/06/claims/AppRole"), query = ";claimtype={0};arrayclaims={1}", param = "AppRole", param = c.Value);
    

    The code in my Custom Attribute Store (shown below) has been updated to the following:

    //for this example, claimValues is populated with a set of hardcoded values
    List<string> claimValues = new List<string>(); 
    claimValues.Add("AP1");
    claimValues.Add("AP2");
    claimValues.Add("AP3");
    
    List<string[]> claimData = new List<string[]>();
    
    //each claim value is added to it's own string array (could have done this above, but showing here for clarity)
    foreach (string claimVal in claimValues)
    {
     claimData.Add(new string[1] { claimVal });
    }
    
    //the claim value string arrays are added to the string [][] that is returned by the Custom Attribute Store EndExecuteQuery()
    string[][] resultData = claimData.ToArray(); //resultData is returned from EndExecuteQuery()
    
    //debug – confirm values are as expected. 
    string claimVal1 = resultData[0][0]; //resultData[0] contains a string array, and the first value in that string array is AP1
    string claimVal2 = resultData[1][0]; //resultData[1] contains a string array, and the first value in that string array is AP2
    string claimVal3 = resultData[2][0]; //...AP3
    
    

    ...so the result is that is returned to the query is a single "column" containing the AppRole values.  Each value in the resultData[0] string array represents a value in a "column" returned to the query, so resultData[0][0] contains the first row, first column.  This is described in the at custom attribute store sample at http://msdn.microsoft.com/en-us/library/ee895358.aspx.  The mistake I had made earlier was to put the second claim value in resultData[0][1], which would be the first row, second column and my query only expects the one column in the return data.  The fix was to correctly format the result so that the second claim value is placed into resultData[1][0], which is the second row, first column.  So in tabular form, the correct result looks like:

    AppRole
    AP1
    AP2
    AP3

    Side note : Test #2 in my "Update" post above would have technically worked; in this test my query expects 3 columns (3 claim types) and the resultData in tabular form looks like:

    AppRole  AppRole  AppRole
    AP1   AP2   AP3
    

    The reason it did not work was an unrelated code bug on my relying party, where the RP code was only looking at the first claim value per claim type.  As mentioned, even if this had worked it would not be ideal because the number of claim values returned is coupled to the number of columns in the query (which does not work for my application since users may have 0 to n number of AppRole claims). 

    Another helpful hint that a person gave me was that ADFS enables troubleshooting claims issuance via trace messages: http://blogs.msdn.com/b/card/archive/2010/01/21/diagnostics-in-ad-fs-2-0.aspx?CommentPosted=true#commentmessage.  This enabled me to view all the claims that were passed into the Custom Attribute Store, plus all the claims that were issued by the Custom Attribute Store.


    terryc_ms
    • Marked as answer by terryc_ms Friday, December 10, 2010 8:01 PM
    Friday, December 10, 2010 7:30 PM

All replies

  • Update:

    I still have the problem, where I can't issue multiple claims with the same claim type (with different values).  The two tests below show what I am facing:

    Test #1:

    In code, if I set the string[][] output value for EndExecuteQuery as shown below:

         string[][] resultData = { new string[] { "AP1", "AP2", "AP3" } };

    ...And change my custom attr store statement as follows:

    c:[Type == http://terryc.com/ws/2008/06/claims/ArrayOfClaims]
     => issue(store = "ArrayCustomAttributeStore", types = ("http://terryc.com/ws/2008/06/claims/AppRole1","http://terryc.com/ws/2008/06/claims/AppRole2","http://terryc.com/ws/2008/06/claims/AppRole3"), query = "claimtype={0};arrayclaims={1}", param = "AppRole", param = c.Value);

    ...Then I get 3 claims back (type / value shown below):

         http://terryc.com/ws/2008/06/claims/AppRole1     AP1

         http://terryc.com/ws/2008/06/claims/AppRole2     AP2

         http://terryc.com/ws/2008/06/claims/AppRole3     AP3

    However, this is not acceptable; each user in my custom attribute store may have 1 to n number of AppRole claims, so I cannot use the statement above that expects 3 claims and only succeeds if 3 claims are returned.

     

    Test #2:

    In code, if I set the string[][] output value for EndExecuteQuery as shown below:

         string[][] resultData = { new string[] { "AP1", "AP2", "AP3" } };

    ...And change my custom attr store statement as follows:

    c:[Type == http://terryc.com/ws/2008/06/claims/ArrayOfClaims]
     => issue(store = "ArrayCustomAttributeStore", types = ("http://terryc.com/ws/2008/06/claims/AppRole","http://terryc.com/ws/2008/06/claims/AppRole","http://terryc.com/ws/2008/06/claims/AppRole"), query = "claimtype={0};arrayclaims={1}", param = "AppRole", param = c.Value);

    ...Then I get 1 claims back (type / value shown below):

         http://terryc.com/ws/2008/06/claims/AppRole     AP1

    This is not acceptable either (same reason as Test #1), but it shows an interesting behavior where only the first claim value is returned.

    Am I not understanding how to feed the query data into the string[][] interface back to the query language, or is there a limitation in the query language that requires 1.) the number of claim types issued to match the number of claim values returned, and 2.) only one claim per claim type?

    ...hoping I am just confused about how to use the interface?


    terryc_ms
    • Edited by terryc_ms Friday, December 10, 2010 7:37 PM
    Wednesday, December 08, 2010 8:22 PM
  • This is resolved now; I was not passing claims into the string[][] correctly. 

    Summary:

    My simple example uses the following ADFS Issuance Transform Rule defined for my Relying Party:

    c:[Type == "http://terryc.com/ws/2008/06/claims/ClaimDataArray"]
    => issue(store = "ArrayCustomAttributeStore", types = ("http://terryc.com/ws/2008/06/claims/AppRole"), query = ";claimtype={0};arrayclaims={1}", param = "AppRole", param = c.Value);
    

    The code in my Custom Attribute Store (shown below) has been updated to the following:

    //for this example, claimValues is populated with a set of hardcoded values
    List<string> claimValues = new List<string>(); 
    claimValues.Add("AP1");
    claimValues.Add("AP2");
    claimValues.Add("AP3");
    
    List<string[]> claimData = new List<string[]>();
    
    //each claim value is added to it's own string array (could have done this above, but showing here for clarity)
    foreach (string claimVal in claimValues)
    {
     claimData.Add(new string[1] { claimVal });
    }
    
    //the claim value string arrays are added to the string [][] that is returned by the Custom Attribute Store EndExecuteQuery()
    string[][] resultData = claimData.ToArray(); //resultData is returned from EndExecuteQuery()
    
    //debug – confirm values are as expected. 
    string claimVal1 = resultData[0][0]; //resultData[0] contains a string array, and the first value in that string array is AP1
    string claimVal2 = resultData[1][0]; //resultData[1] contains a string array, and the first value in that string array is AP2
    string claimVal3 = resultData[2][0]; //...AP3
    
    

    ...so the result is that is returned to the query is a single "column" containing the AppRole values.  Each value in the resultData[0] string array represents a value in a "column" returned to the query, so resultData[0][0] contains the first row, first column.  This is described in the at custom attribute store sample at http://msdn.microsoft.com/en-us/library/ee895358.aspx.  The mistake I had made earlier was to put the second claim value in resultData[0][1], which would be the first row, second column and my query only expects the one column in the return data.  The fix was to correctly format the result so that the second claim value is placed into resultData[1][0], which is the second row, first column.  So in tabular form, the correct result looks like:

    AppRole
    AP1
    AP2
    AP3

    Side note : Test #2 in my "Update" post above would have technically worked; in this test my query expects 3 columns (3 claim types) and the resultData in tabular form looks like:

    AppRole  AppRole  AppRole
    AP1   AP2   AP3
    

    The reason it did not work was an unrelated code bug on my relying party, where the RP code was only looking at the first claim value per claim type.  As mentioned, even if this had worked it would not be ideal because the number of claim values returned is coupled to the number of columns in the query (which does not work for my application since users may have 0 to n number of AppRole claims). 

    Another helpful hint that a person gave me was that ADFS enables troubleshooting claims issuance via trace messages: http://blogs.msdn.com/b/card/archive/2010/01/21/diagnostics-in-ad-fs-2-0.aspx?CommentPosted=true#commentmessage.  This enabled me to view all the claims that were passed into the Custom Attribute Store, plus all the claims that were issued by the Custom Attribute Store.


    terryc_ms
    • Marked as answer by terryc_ms Friday, December 10, 2010 8:01 PM
    Friday, December 10, 2010 7:30 PM
  • I gotta tell ya, you bailed me out.

    I was altering an LDAP attribute store that I found on codeplex and I was getting this same exact error like you described:

    (I took yours)

    Additional Data 
    Exception details: 
    Microsoft.IdentityServer.ClaimsPolicy.Language.PolicyEvaluationException: POLICY0019: Query 'claimtype={0};arrayclaims={1}' to attribute store 'ArrayCustomAttributeStore' returned an unexpected number of fields: expected '1', got '3'.

    I ran through your steps and it worked like a charm. Excellent work!

    Wednesday, October 17, 2012 7:57 PM