Commit da47abab authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Allow attribute disjunctions to require values for a subset of the attributes

Previously, an attribute disjunction erroneously either had no values for any of the
attributes, or it required a value for all attributes. A required value of null in an
attribute disjunction now specifies that the disjunction imposes no value on the attribute.
parent aac4fb5b
......@@ -324,7 +324,7 @@ type DisclosureChoice struct {
type AttributeDisjunction struct {
Label string
Attributes []AttributeTypeIdentifier
Values map[AttributeTypeIdentifier]string
Values map[AttributeTypeIdentifier]*string
selected *AttributeTypeIdentifier
}
......@@ -442,8 +442,8 @@ func (disjunction *AttributeDisjunction) MarshalJSON() ([]byte, error) {
}
temp := struct {
Label string `json:"label"`
Attributes map[AttributeTypeIdentifier]string `json:"attributes"`
Label string `json:"label"`
Attributes map[AttributeTypeIdentifier]*string `json:"attributes"`
}{
Label: disjunction.Label,
Attributes: disjunction.Values,
......@@ -454,7 +454,7 @@ func (disjunction *AttributeDisjunction) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals an attribute disjunction from JSON.
func (disjunction *AttributeDisjunction) UnmarshalJSON(bytes []byte) error {
if disjunction.Values == nil {
disjunction.Values = make(map[AttributeTypeIdentifier]string)
disjunction.Values = make(map[AttributeTypeIdentifier]*string)
}
if disjunction.Attributes == nil {
disjunction.Attributes = make([]AttributeTypeIdentifier, 0, 3)
......@@ -475,8 +475,8 @@ func (disjunction *AttributeDisjunction) UnmarshalJSON(bytes []byte) error {
switch temp.Attributes.(type) {
case map[string]interface{}:
temp := struct {
Label string `json:"label"`
Attributes map[string]string `json:"attributes"`
Label string `json:"label"`
Attributes map[string]*string `json:"attributes"`
}{}
if err := json.Unmarshal(bytes, &temp); err != nil {
return err
......
......@@ -453,8 +453,13 @@ func (client *Client) Candidates(disjunction *irma.AttributeDisjunction) []*irma
if val == nil {
continue
}
if !disjunction.HasValues() || *val == disjunction.Values[attribute] {
if !disjunction.HasValues() {
candidates = append(candidates, id)
} else {
requiredValue, present := disjunction.Values[attribute]
if !present || requiredValue == nil || *val == *requiredValue {
candidates = append(candidates, id)
}
}
}
}
......
package irmaclient
import (
"encoding/json"
"math/big"
"os"
"testing"
......@@ -148,35 +149,68 @@ func TestLogging(t *testing.T) {
test.ClearTestStorage(t)
}
// TestCandidates tests the correctness of the function of the client that, given a disjunction of attributes
// requested by the verifier, calculates a list of candidate attributes contained by the client that would
// satisfy the attribute disjunction.
func TestCandidates(t *testing.T) {
client := parseStorage(t)
// client contains one instance of the studentCard credential, whose studentID attribute is 456.
attrtype := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
// If the disjunction contains no required values at all, then our attribute is a candidate
disjunction := &irma.AttributeDisjunction{
Attributes: []irma.AttributeTypeIdentifier{attrtype},
}
attrs := client.Candidates(disjunction)
require.NotNil(t, attrs)
require.Len(t, attrs, 1)
require.NotNil(t, attrs[0])
require.Equal(t, attrs[0].Type, attrtype)
attr := attrs[0]
require.NotNil(t, attr)
require.Equal(t, attr.Type, attrtype)
// If the disjunction requires our attribute to have 456 as value, which it does,
// then our attribute is a candidate
reqval := "456"
disjunction = &irma.AttributeDisjunction{
Attributes: []irma.AttributeTypeIdentifier{attrtype},
Values: map[irma.AttributeTypeIdentifier]string{attrtype: "456"},
Values: map[irma.AttributeTypeIdentifier]*string{attrtype: &reqval},
}
attrs = client.Candidates(disjunction)
require.NotNil(t, attrs)
require.Len(t, attrs, 1)
require.NotNil(t, attrs[0])
require.Equal(t, attrs[0].Type, attrtype)
disjunction = &irma.AttributeDisjunction{
Attributes: []irma.AttributeTypeIdentifier{attrtype},
Values: map[irma.AttributeTypeIdentifier]string{attrtype: "foobarbaz"},
}
// If the disjunction requires our attribute to have a different value than it does,
// then it is NOT a match.
reqval = "foobarbaz"
disjunction.Values[attrtype] = &reqval
attrs = client.Candidates(disjunction)
require.NotNil(t, attrs)
require.Empty(t, attrs)
// A required value of nil counts as no requirement on the value, so our attribute is a candidate
disjunction.Values[attrtype] = nil
attrs = client.Candidates(disjunction)
require.NotNil(t, attrs)
require.Len(t, attrs, 1)
require.NotNil(t, attrs[0])
require.Equal(t, attrs[0].Type, attrtype)
// This test should be equivalent to the one above
disjunction = &irma.AttributeDisjunction{}
json.Unmarshal([]byte(`{"attributes":{"irma-demo.RU.studentCard.studentID":null}}`), &disjunction)
attrs = client.Candidates(disjunction)
require.NotNil(t, attrs)
require.Len(t, attrs, 1)
require.NotNil(t, attrs[0])
require.Equal(t, attrs[0].Type, attrtype)
// A required value of null counts as no requirement on the value, but we must still satisfy the disjunction
// We do not have an instance of this attribute so we have no candidate
disjunction = &irma.AttributeDisjunction{}
json.Unmarshal([]byte(`{"attributes":{"irma-demo.MijnOverheid.ageLower.over12":null}}`), &disjunction)
attrs = client.Candidates(disjunction)
require.Empty(t, attrs)
test.ClearTestStorage(t)
......
......@@ -143,7 +143,7 @@ func TestAttributeDisjunctionMarshaling(t *testing.T) {
require.True(t, disjunction.HasValues())
require.Contains(t, disjunction.Attributes, id)
require.Contains(t, disjunction.Values, id)
require.Equal(t, disjunction.Values[id], "yes")
require.Equal(t, *disjunction.Values[id], "yes")
disjunction = AttributeDisjunction{}
attrsjson = `
......
......@@ -2,10 +2,11 @@ package irma
import (
"fmt"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
"math/big"
"time"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
)
// ProofStatus is the status of the complete proof
......@@ -42,7 +43,7 @@ type DisclosedCredentialList []*DisclosedCredential
// This is the case if:
// attribute is contained in disclosed AND if a value is present: equal to that value
// al can be nil if you don't want to include attribute status for proof
func (disclosed DisclosedCredentialList) isAttributeSatisfied(attributeId AttributeTypeIdentifier, requestedValue string) (bool, *AttributeResult) {
func (disclosed DisclosedCredentialList) isAttributeSatisfied(attributeId AttributeTypeIdentifier, requestedValue *string) (bool, *AttributeResult) {
ar := AttributeResult{
AttributeId: attributeId,
}
......@@ -61,7 +62,7 @@ func (disclosed DisclosedCredentialList) isAttributeSatisfied(attributeId Attrib
// - Value is empty OR value equal to disclosedValue
ar.AttributeValue = disclosedAttributeValue
if requestedValue == "" || disclosedAttributeValue == requestedValue {
if requestedValue == nil || disclosedAttributeValue == *requestedValue {
ar.AttributeProofStatus = PRESENT
return true, &ar
} else {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment