Commit 8e5e0cb7 authored by Sietse Ringers's avatar Sietse Ringers
Browse files

fix: handling and data structure of missing attributes in irmaclient during unsatisfiable requests

parent 0e3802dc
...@@ -96,7 +96,7 @@ func (th TestHandler) Failure(err *irma.SessionError) { ...@@ -96,7 +96,7 @@ func (th TestHandler) Failure(err *irma.SessionError) {
th.t.Fatal(err) th.t.Fatal(err)
} }
} }
func (th TestHandler) UnsatisfiableRequest(request irma.SessionRequest, serverName irma.TranslatedString, missing map[int]map[int]irma.AttributeCon) { func (th TestHandler) UnsatisfiableRequest(request irma.SessionRequest, serverName irma.TranslatedString, missing irmaclient.MissingAttributes) {
th.Failure(&irma.SessionError{ th.Failure(&irma.SessionError{
ErrorType: irma.ErrorType("UnsatisfiableRequest"), ErrorType: irma.ErrorType("UnsatisfiableRequest"),
}) })
...@@ -128,6 +128,19 @@ type SessionResult struct { ...@@ -128,6 +128,19 @@ type SessionResult struct {
Err error Err error
SignatureResult *irma.SignedMessage SignatureResult *irma.SignedMessage
DisclosureResult *irma.Disclosure DisclosureResult *irma.Disclosure
Missing irmaclient.MissingAttributes
}
type UnsatisfiableTestHandler struct {
TestHandler
}
func (th UnsatisfiableTestHandler) UnsatisfiableRequest(request irma.SessionRequest, serverName irma.TranslatedString, missing irmaclient.MissingAttributes) {
th.c <- &SessionResult{Missing: missing}
}
func (th UnsatisfiableTestHandler) Success(result string) {
th.Failure(&irma.SessionError{ErrorType: irma.ErrorType("Unsatisfiable request succeeded")})
} }
// ManualTestHandler embeds a TestHandler to inherit its methods. // ManualTestHandler embeds a TestHandler to inherit its methods.
......
...@@ -12,24 +12,27 @@ import ( ...@@ -12,24 +12,27 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func requestorSessionHelper(t *testing.T, request irma.SessionRequest, client *irmaclient.Client) *server.SessionResult { type sessionOption int
StartIrmaServer(t, false)
defer StopIrmaServer()
return requestorSesionWorker(t, request, client)
}
func requestorUpdatedSessionHelper(t *testing.T, request irma.SessionRequest, client *irmaclient.Client) *server.SessionResult { const (
StartIrmaServer(t, true) sessionOptionUpdatedIrmaConfiguration = iota
defer StopIrmaServer() sessionOptionUnsatisfiableRequest
return requestorSesionWorker(t, request, client) )
type requestorSessionResult struct {
*server.SessionResult
Missing irmaclient.MissingAttributes
} }
func requestorSesionWorker(t *testing.T, request irma.SessionRequest, client *irmaclient.Client) *server.SessionResult { func requestorSessionHelper(t *testing.T, request irma.SessionRequest, client *irmaclient.Client, options ...sessionOption) *requestorSessionResult {
if client == nil { if client == nil {
client, _ = parseStorage(t) client, _ = parseStorage(t)
defer test.ClearTestStorage(t) defer test.ClearTestStorage(t)
} }
StartIrmaServer(t, len(options) == 1 && options[0] == sessionOptionUpdatedIrmaConfiguration)
defer StopIrmaServer()
clientChan := make(chan *SessionResult) clientChan := make(chan *SessionResult)
serverChan := make(chan *server.SessionResult) serverChan := make(chan *server.SessionResult)
...@@ -38,7 +41,12 @@ func requestorSesionWorker(t *testing.T, request irma.SessionRequest, client *ir ...@@ -38,7 +41,12 @@ func requestorSesionWorker(t *testing.T, request irma.SessionRequest, client *ir
}) })
require.NoError(t, err) require.NoError(t, err)
h := TestHandler{t, clientChan, client, nil} var h irmaclient.Handler
if len(options) == 1 && options[0] == sessionOptionUnsatisfiableRequest {
h = UnsatisfiableTestHandler{TestHandler{t, clientChan, client, nil}}
} else {
h = TestHandler{t, clientChan, client, nil}
}
j, err := json.Marshal(qr) j, err := json.Marshal(qr)
require.NoError(t, err) require.NoError(t, err)
client.NewSession(string(j), h) client.NewSession(string(j), h)
...@@ -47,10 +55,13 @@ func requestorSesionWorker(t *testing.T, request irma.SessionRequest, client *ir ...@@ -47,10 +55,13 @@ func requestorSesionWorker(t *testing.T, request irma.SessionRequest, client *ir
require.NoError(t, clientResult.Err) require.NoError(t, clientResult.Err)
} }
serverResult := <-serverChan if len(options) == 1 && options[0] == sessionOptionUnsatisfiableRequest {
return &requestorSessionResult{nil, clientResult.Missing}
require.Equal(t, token, serverResult.Token) } else {
return serverResult serverResult := <-serverChan
require.Equal(t, token, serverResult.Token)
return &requestorSessionResult{serverResult, nil}
}
} }
// Check that nonexistent IRMA identifiers in the session request fail the session // Check that nonexistent IRMA identifiers in the session request fail the session
...@@ -110,7 +121,7 @@ func testRequestorDisclosure(t *testing.T, request *irma.DisclosureRequest) *ser ...@@ -110,7 +121,7 @@ func testRequestorDisclosure(t *testing.T, request *irma.DisclosureRequest) *ser
serverResult := requestorSessionHelper(t, request, nil) serverResult := requestorSessionHelper(t, request, nil)
require.Nil(t, serverResult.Err) require.Nil(t, serverResult.Err)
require.Equal(t, irma.ProofStatusValid, serverResult.ProofStatus) require.Equal(t, irma.ProofStatusValid, serverResult.ProofStatus)
return serverResult return serverResult.SessionResult
} }
func TestRequestorIssuanceSession(t *testing.T) { func TestRequestorIssuanceSession(t *testing.T) {
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/privacybydesign/irmago" "github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/fs" "github.com/privacybydesign/irmago/internal/fs"
"github.com/privacybydesign/irmago/internal/test" "github.com/privacybydesign/irmago/internal/test"
"github.com/privacybydesign/irmago/irmaclient"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -103,6 +104,48 @@ func TestIssuanceSingletonCredential(t *testing.T) { ...@@ -103,6 +104,48 @@ func TestIssuanceSingletonCredential(t *testing.T) {
require.Nil(t, client.Attributes(credid, 1)) require.Nil(t, client.Attributes(credid, 1))
} }
func TestUnsatisfiableDisclosureSession(t *testing.T) {
client, _ := parseStorage(t)
defer test.ClearTestStorage(t)
request := irma.NewDisclosureRequest()
request.Disclose = irma.AttributeConDisCon{
irma.AttributeDisCon{
irma.AttributeCon{
irma.NewAttributeRequest("irma-demo.MijnOverheid.root.BSN"),
irma.NewAttributeRequest("irma-demo.RU.studentCard.level"),
},
irma.AttributeCon{
irma.NewAttributeRequest("test.test.mijnirma.email"),
irma.NewAttributeRequest("irma-demo.MijnOverheid.fullName.firstname"),
irma.NewAttributeRequest("irma-demo.MijnOverheid.fullName.familyname"),
},
},
irma.AttributeDisCon{
irma.AttributeCon{
irma.NewAttributeRequest("irma-demo.RU.studentCard.level"),
},
},
}
missing := irmaclient.MissingAttributes{}
require.NoError(t, json.Unmarshal([]byte(`{
"0": [
{
"0": {"type": "irma-demo.MijnOverheid.root.BSN"}
},
{
"1": {"type": "irma-demo.MijnOverheid.fullName.firstname"},
"2": {"type": "irma-demo.MijnOverheid.fullName.familyname"}
}
]
}`), &missing))
require.True(t, reflect.DeepEqual(
missing,
requestorSessionHelper(t, request, client, sessionOptionUnsatisfiableRequest).Missing),
)
}
/* There is an annoying difference between how Java and Go convert big integers to and from /* There is an annoying difference between how Java and Go convert big integers to and from
byte arrays: in Java the sign of the integer is taken into account, but not in Go. This means byte arrays: in Java the sign of the integer is taken into account, but not in Go. This means
that in Java, when converting a bigint to or from a byte array, the most significant bit that in Java, when converting a bigint to or from a byte array, the most significant bit
...@@ -144,7 +187,7 @@ func TestOutdatedClientIrmaConfiguration(t *testing.T) { ...@@ -144,7 +187,7 @@ func TestOutdatedClientIrmaConfiguration(t *testing.T) {
// and the server does. Disclose an attribute from this credential. The client implicitly discloses value 0 // and the server does. Disclose an attribute from this credential. The client implicitly discloses value 0
// for the new attribute, and the server accepts. // for the new attribute, and the server accepts.
req := getDisclosureRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level")) req := getDisclosureRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level"))
require.Nil(t, requestorUpdatedSessionHelper(t, req, client).Err) require.Nil(t, requestorSessionHelper(t, req, client, sessionOptionUpdatedIrmaConfiguration).Err)
} }
func TestDisclosureNewAttributeUpdateSchemeManager(t *testing.T) { func TestDisclosureNewAttributeUpdateSchemeManager(t *testing.T) {
...@@ -179,7 +222,7 @@ func TestDisclosureNewAttributeUpdateSchemeManager(t *testing.T) { ...@@ -179,7 +222,7 @@ func TestDisclosureNewAttributeUpdateSchemeManager(t *testing.T) {
// Disclose newAttribute to a server with a new configuration. This attribute was added // Disclose newAttribute to a server with a new configuration. This attribute was added
// after we received a credential without it, so its value in this credential is 0. // after we received a credential without it, so its value in this credential is 0.
res := requestorUpdatedSessionHelper(t, newAttrRequest, client) res := requestorSessionHelper(t, newAttrRequest, client, sessionOptionUpdatedIrmaConfiguration)
require.Nil(t, res.Err) require.Nil(t, res.Err)
require.Nil(t, res.Disclosed[0][0].RawValue) require.Nil(t, res.Disclosed[0][0].RawValue)
} }
......
...@@ -93,6 +93,14 @@ type ClientHandler interface { ...@@ -93,6 +93,14 @@ type ClientHandler interface {
UpdateAttributes() UpdateAttributes()
} }
// MissingAttributes contains all attribute requests that the client cannot satisfy with its
// current attributes.
type MissingAttributes map[int][]map[int]MissingAttribute
// MissingAttribute is an irma.AttributeRequest that is satisfied by none of the client's attributes
// (with Go's default JSON marshaler instead of that of irma.AttributeRequest).
type MissingAttribute irma.AttributeRequest
type secretKey struct { type secretKey struct {
Key *big.Int Key *big.Int
} }
...@@ -513,7 +521,7 @@ func cartesianProduct(candidates [][]*irma.CredentialIdentifier) credCandidateSe ...@@ -513,7 +521,7 @@ func cartesianProduct(candidates [][]*irma.CredentialIdentifier) credCandidateSe
// currently posesses (ie. len(candidates) == 0), then the second return parameter lists the missing // currently posesses (ie. len(candidates) == 0), then the second return parameter lists the missing
// attributes that would be necessary to satisfy the disjunction. // attributes that would be necessary to satisfy the disjunction.
func (client *Client) Candidates(discon irma.AttributeDisCon) ( func (client *Client) Candidates(discon irma.AttributeDisCon) (
candidates [][]*irma.AttributeIdentifier, missing map[int]irma.AttributeCon, candidates [][]*irma.AttributeIdentifier, missing []map[int]MissingAttribute,
) { ) {
candidates = [][]*irma.AttributeIdentifier{} candidates = [][]*irma.AttributeIdentifier{}
...@@ -558,22 +566,24 @@ func (client *Client) Candidates(discon irma.AttributeDisCon) ( ...@@ -558,22 +566,24 @@ func (client *Client) Candidates(discon irma.AttributeDisCon) (
// missingAttributes returns for each of the conjunctions in the specified disjunction // missingAttributes returns for each of the conjunctions in the specified disjunction
// a list of attributes that the client does not posess but which would be required to // a list of attributes that the client does not posess but which would be required to
// satisfy the conjunction. // satisfy the conjunction.
func (client *Client) missingAttributes(discon irma.AttributeDisCon) map[int]irma.AttributeCon { func (client *Client) missingAttributes(discon irma.AttributeDisCon) []map[int]MissingAttribute {
missing := map[int]irma.AttributeCon{} missing := make([]map[int]MissingAttribute, len(discon))
for i, con := range discon { for i, con := range discon {
for _, attr := range con { missing[i] = map[int]MissingAttribute{}
creds := client.attributes[attr.Type.CredentialTypeIdentifier()] conloop:
for j, req := range con {
creds := client.attributes[req.Type.CredentialTypeIdentifier()]
if len(creds) == 0 { if len(creds) == 0 {
missing[i] = append(missing[i], attr) missing[i][j] = MissingAttribute(req)
continue continue
} }
for _, cred := range creds { for _, cred := range creds {
if attr.Satisfy(attr.Type, cred.UntranslatedAttribute(attr.Type)) { if req.Satisfy(req.Type, cred.UntranslatedAttribute(req.Type)) {
continue continue conloop
} }
} }
missing[i] = append(missing[i], attr) missing[i][j] = MissingAttribute(req)
} }
} }
...@@ -584,13 +594,13 @@ func (client *Client) missingAttributes(discon irma.AttributeDisCon) map[int]irm ...@@ -584,13 +594,13 @@ func (client *Client) missingAttributes(discon irma.AttributeDisCon) map[int]irm
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions // to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned. // are returned.
func (client *Client) CheckSatisfiability(condiscon irma.AttributeConDisCon) ( func (client *Client) CheckSatisfiability(condiscon irma.AttributeConDisCon) (
candidates [][][]*irma.AttributeIdentifier, missing map[int]map[int]irma.AttributeCon, candidates [][][]*irma.AttributeIdentifier, missing MissingAttributes,
) { ) {
candidates = make([][][]*irma.AttributeIdentifier, len(condiscon)) candidates = make([][][]*irma.AttributeIdentifier, len(condiscon))
missing = map[int]map[int]irma.AttributeCon{} missing = MissingAttributes{}
for i, discon := range condiscon { for i, discon := range condiscon {
var m map[int]irma.AttributeCon var m []map[int]MissingAttribute
candidates[i], m = client.Candidates(discon) candidates[i], m = client.Candidates(discon)
if len(candidates[i]) == 0 { if len(candidates[i]) == 0 {
missing[i] = m missing[i] = m
......
...@@ -80,6 +80,6 @@ func (h *keyshareEnrollmentHandler) KeyshareEnrollmentDeleted(manager irma.Schem ...@@ -80,6 +80,6 @@ func (h *keyshareEnrollmentHandler) KeyshareEnrollmentDeleted(manager irma.Schem
func (h *keyshareEnrollmentHandler) KeyshareEnrollmentMissing(manager irma.SchemeManagerIdentifier) { func (h *keyshareEnrollmentHandler) KeyshareEnrollmentMissing(manager irma.SchemeManagerIdentifier) {
h.fail(errors.New("Keyshare enrollment failed: unenrolled")) h.fail(errors.New("Keyshare enrollment failed: unenrolled"))
} }
func (h *keyshareEnrollmentHandler) UnsatisfiableRequest(request irma.SessionRequest, ServerName irma.TranslatedString, missing map[int]map[int]irma.AttributeCon) { func (h *keyshareEnrollmentHandler) UnsatisfiableRequest(request irma.SessionRequest, ServerName irma.TranslatedString, missing MissingAttributes) {
h.fail(errors.New("Keyshare enrollment failed: unsatisfiable")) h.fail(errors.New("Keyshare enrollment failed: unsatisfiable"))
} }
...@@ -34,7 +34,7 @@ type Handler interface { ...@@ -34,7 +34,7 @@ type Handler interface {
Failure(err *irma.SessionError) Failure(err *irma.SessionError)
UnsatisfiableRequest(request irma.SessionRequest, UnsatisfiableRequest(request irma.SessionRequest,
ServerName irma.TranslatedString, ServerName irma.TranslatedString,
missing map[int]map[int]irma.AttributeCon) missing MissingAttributes)
KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int)
KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier)
......
...@@ -174,8 +174,8 @@ type DisclosureChoice struct { ...@@ -174,8 +174,8 @@ type DisclosureChoice struct {
// a specified value, in a session request. // a specified value, in a session request.
type AttributeRequest struct { type AttributeRequest struct {
Type AttributeTypeIdentifier `json:"type"` Type AttributeTypeIdentifier `json:"type"`
Value *string `json:"value"` Value *string `json:"value,omitempty"`
Required bool `json:"required"` Required bool `json:"required,omitempty"`
} }
var ( var (
......
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