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) {
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{
ErrorType: irma.ErrorType("UnsatisfiableRequest"),
})
......@@ -128,6 +128,19 @@ type SessionResult struct {
Err error
SignatureResult *irma.SignedMessage
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.
......
......@@ -12,24 +12,27 @@ import (
"github.com/stretchr/testify/require"
)
func requestorSessionHelper(t *testing.T, request irma.SessionRequest, client *irmaclient.Client) *server.SessionResult {
StartIrmaServer(t, false)
defer StopIrmaServer()
return requestorSesionWorker(t, request, client)
}
type sessionOption int
func requestorUpdatedSessionHelper(t *testing.T, request irma.SessionRequest, client *irmaclient.Client) *server.SessionResult {
StartIrmaServer(t, true)
defer StopIrmaServer()
return requestorSesionWorker(t, request, client)
const (
sessionOptionUpdatedIrmaConfiguration = iota
sessionOptionUnsatisfiableRequest
)
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 {
client, _ = parseStorage(t)
defer test.ClearTestStorage(t)
}
StartIrmaServer(t, len(options) == 1 && options[0] == sessionOptionUpdatedIrmaConfiguration)
defer StopIrmaServer()
clientChan := make(chan *SessionResult)
serverChan := make(chan *server.SessionResult)
......@@ -38,7 +41,12 @@ func requestorSesionWorker(t *testing.T, request irma.SessionRequest, client *ir
})
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)
require.NoError(t, err)
client.NewSession(string(j), h)
......@@ -47,10 +55,13 @@ func requestorSesionWorker(t *testing.T, request irma.SessionRequest, client *ir
require.NoError(t, clientResult.Err)
}
serverResult := <-serverChan
require.Equal(t, token, serverResult.Token)
return serverResult
if len(options) == 1 && options[0] == sessionOptionUnsatisfiableRequest {
return &requestorSessionResult{nil, clientResult.Missing}
} else {
serverResult := <-serverChan
require.Equal(t, token, serverResult.Token)
return &requestorSessionResult{serverResult, nil}
}
}
// 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
serverResult := requestorSessionHelper(t, request, nil)
require.Nil(t, serverResult.Err)
require.Equal(t, irma.ProofStatusValid, serverResult.ProofStatus)
return serverResult
return serverResult.SessionResult
}
func TestRequestorIssuanceSession(t *testing.T) {
......
......@@ -8,6 +8,7 @@ import (
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/fs"
"github.com/privacybydesign/irmago/internal/test"
"github.com/privacybydesign/irmago/irmaclient"
"github.com/stretchr/testify/require"
)
......@@ -103,6 +104,48 @@ func TestIssuanceSingletonCredential(t *testing.T) {
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
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
......@@ -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
// for the new attribute, and the server accepts.
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) {
......@@ -179,7 +222,7 @@ func TestDisclosureNewAttributeUpdateSchemeManager(t *testing.T) {
// 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.
res := requestorUpdatedSessionHelper(t, newAttrRequest, client)
res := requestorSessionHelper(t, newAttrRequest, client, sessionOptionUpdatedIrmaConfiguration)
require.Nil(t, res.Err)
require.Nil(t, res.Disclosed[0][0].RawValue)
}
......
......@@ -93,6 +93,14 @@ type ClientHandler interface {
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 {
Key *big.Int
}
......@@ -513,7 +521,7 @@ func cartesianProduct(candidates [][]*irma.CredentialIdentifier) credCandidateSe
// currently posesses (ie. len(candidates) == 0), then the second return parameter lists the missing
// attributes that would be necessary to satisfy the disjunction.
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{}
......@@ -558,22 +566,24 @@ func (client *Client) Candidates(discon irma.AttributeDisCon) (
// 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
// satisfy the conjunction.
func (client *Client) missingAttributes(discon irma.AttributeDisCon) map[int]irma.AttributeCon {
missing := map[int]irma.AttributeCon{}
func (client *Client) missingAttributes(discon irma.AttributeDisCon) []map[int]MissingAttribute {
missing := make([]map[int]MissingAttribute, len(discon))
for i, con := range discon {
for _, attr := range con {
creds := client.attributes[attr.Type.CredentialTypeIdentifier()]
missing[i] = map[int]MissingAttribute{}
conloop:
for j, req := range con {
creds := client.attributes[req.Type.CredentialTypeIdentifier()]
if len(creds) == 0 {
missing[i] = append(missing[i], attr)
missing[i][j] = MissingAttribute(req)
continue
}
for _, cred := range creds {
if attr.Satisfy(attr.Type, cred.UntranslatedAttribute(attr.Type)) {
continue
if req.Satisfy(req.Type, cred.UntranslatedAttribute(req.Type)) {
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
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned.
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))
missing = map[int]map[int]irma.AttributeCon{}
missing = MissingAttributes{}
for i, discon := range condiscon {
var m map[int]irma.AttributeCon
var m []map[int]MissingAttribute
candidates[i], m = client.Candidates(discon)
if len(candidates[i]) == 0 {
missing[i] = m
......
......@@ -80,6 +80,6 @@ func (h *keyshareEnrollmentHandler) KeyshareEnrollmentDeleted(manager irma.Schem
func (h *keyshareEnrollmentHandler) KeyshareEnrollmentMissing(manager irma.SchemeManagerIdentifier) {
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"))
}
......@@ -34,7 +34,7 @@ type Handler interface {
Failure(err *irma.SessionError)
UnsatisfiableRequest(request irma.SessionRequest,
ServerName irma.TranslatedString,
missing map[int]map[int]irma.AttributeCon)
missing MissingAttributes)
KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int)
KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier)
......
......@@ -174,8 +174,8 @@ type DisclosureChoice struct {
// a specified value, in a session request.
type AttributeRequest struct {
Type AttributeTypeIdentifier `json:"type"`
Value *string `json:"value"`
Required bool `json:"required"`
Value *string `json:"value,omitempty"`
Required bool `json:"required,omitempty"`
}
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