logs.go 6.69 KB
Newer Older
1
package irmaclient
Sietse Ringers's avatar
Sietse Ringers committed
2
3
4
5
6

import (
	"encoding/json"
	"time"

7
	"github.com/privacybydesign/irmago"
Sietse Ringers's avatar
Sietse Ringers committed
8
9
10
11
	"github.com/go-errors/errors"
	"github.com/mhe/gabi"
)

Sietse Ringers's avatar
Sietse Ringers committed
12
// LogEntry is a log entry of a past event.
Sietse Ringers's avatar
Sietse Ringers committed
13
type LogEntry struct {
Sietse Ringers's avatar
Sietse Ringers committed
14
	// General info
15
16
17
	Type        irma.Action
	Time        irma.Timestamp    // Time at which the session was completed
	SessionInfo *irma.SessionInfo // Message that started the session
Sietse Ringers's avatar
Sietse Ringers committed
18

Sietse Ringers's avatar
Sietse Ringers committed
19
	// Session type-specific info
20
21
22
23
24
	Disclosed         map[irma.CredentialTypeIdentifier]map[int]irma.TranslatedString // Any session type
	Received          map[irma.CredentialTypeIdentifier][]irma.TranslatedString       // In case of issuance session
	Removed           map[irma.CredentialTypeIdentifier][]irma.TranslatedString       // In case of credential removal
	SignedMessage     []byte                                                          // In case of signature sessions
	SignedMessageType string                                                          // In case of signature sessions
Sietse Ringers's avatar
Sietse Ringers committed
25

Sietse Ringers's avatar
Sietse Ringers committed
26
27
	response    interface{}     // Our response (ProofList or IssueCommitmentMessage)
	rawResponse json.RawMessage // Unparsed []byte version of response
Sietse Ringers's avatar
Sietse Ringers committed
28
29
}

30
const actionRemoval = irma.Action("removal")
31

Sietse Ringers's avatar
Sietse Ringers committed
32
func (session *session) createLogEntry(response interface{}) (*LogEntry, error) {
Sietse Ringers's avatar
Sietse Ringers committed
33
34
	entry := &LogEntry{
		Type:        session.Action,
35
		Time:        irma.Timestamp(time.Now()),
Sietse Ringers's avatar
Sietse Ringers committed
36
		SessionInfo: session.info,
Sietse Ringers's avatar
Sietse Ringers committed
37
		response:    response,
Sietse Ringers's avatar
Sietse Ringers committed
38
39
	}

Sietse Ringers's avatar
Sietse Ringers committed
40
41
42
	// Populate session type-specific fields of the log entry (except for .Disclosed which is handled below)
	var prooflist gabi.ProofList
	var ok bool
Sietse Ringers's avatar
Sietse Ringers committed
43
	switch entry.Type {
44
45
46
	case irma.ActionSigning:
		entry.SignedMessage = []byte(session.jwt.(*irma.SignatureRequestorJwt).Request.Request.Message)
		entry.SignedMessageType = session.jwt.(*irma.SignatureRequestorJwt).Request.Request.MessageType
Sietse Ringers's avatar
Sietse Ringers committed
47
		fallthrough
48
	case irma.ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
49
50
51
		if prooflist, ok = response.(gabi.ProofList); !ok {
			return nil, errors.New("Response was not a ProofList")
		}
52
	case irma.ActionIssuing:
Sietse Ringers's avatar
Sietse Ringers committed
53
		if entry.Received == nil {
54
			entry.Received = map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
Sietse Ringers's avatar
Sietse Ringers committed
55
		}
56
		for _, req := range session.jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials {
57
			list, err := req.AttributeList(session.client.Configuration)
Sietse Ringers's avatar
Sietse Ringers committed
58
59
60
			if err != nil {
				continue // TODO?
			}
Sietse Ringers's avatar
Sietse Ringers committed
61
62
63
64
65
66
67
			entry.Received[list.CredentialType().Identifier()] = list.Strings()
		}
		var msg *gabi.IssueCommitmentMessage
		if msg, ok = response.(*gabi.IssueCommitmentMessage); ok {
			prooflist = msg.Proofs
		} else {
			return nil, errors.New("Response was not a *IssueCommitmentMessage")
Sietse Ringers's avatar
Sietse Ringers committed
68
69
70
71
72
		}
	default:
		return nil, errors.New("Invalid log type")
	}

Sietse Ringers's avatar
Sietse Ringers committed
73
74
75
76
	// Populate the list of disclosed attributes .Disclosed
	for _, proof := range prooflist {
		if proofd, isproofd := proof.(*gabi.ProofD); isproofd {
			if entry.Disclosed == nil {
77
				entry.Disclosed = map[irma.CredentialTypeIdentifier]map[int]irma.TranslatedString{}
Sietse Ringers's avatar
Sietse Ringers committed
78
			}
79
			meta := irma.MetadataFromInt(proofd.ADisclosed[1], session.client.Configuration)
Sietse Ringers's avatar
Sietse Ringers committed
80
			id := meta.CredentialType().Identifier()
81
			entry.Disclosed[id] = map[int]irma.TranslatedString{}
Sietse Ringers's avatar
Sietse Ringers committed
82
83
84
85
86
			for i, attr := range proofd.ADisclosed {
				if i == 1 {
					continue
				}
				val := string(attr.Bytes())
87
				entry.Disclosed[id][i] = irma.TranslatedString{"en": val, "nl": val}
Sietse Ringers's avatar
Sietse Ringers committed
88
89
90
91
			}
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
92
93
94
	return entry, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
95
96
// Jwt returns the JWT from the requestor that started the IRMA session which the
// current log entry tracks.
97
98
func (entry *LogEntry) Jwt() (irma.RequestorJwt, error) {
	return irma.ParseRequestorJwt(entry.Type, entry.SessionInfo.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
99
100
}

Sietse Ringers's avatar
Sietse Ringers committed
101
// GetResponse returns our response to the requestor from the log entry.
Sietse Ringers's avatar
Sietse Ringers committed
102
func (entry *LogEntry) GetResponse() (interface{}, error) {
Sietse Ringers's avatar
Sietse Ringers committed
103
	if entry.response == nil {
Sietse Ringers's avatar
Sietse Ringers committed
104
		switch entry.Type {
105
		case actionRemoval:
Sietse Ringers's avatar
Sietse Ringers committed
106
			return nil, nil
107
		case irma.ActionSigning:
Sietse Ringers's avatar
Sietse Ringers committed
108
			fallthrough
109
		case irma.ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
110
			entry.response = []*gabi.ProofD{}
111
		case irma.ActionIssuing:
Sietse Ringers's avatar
Sietse Ringers committed
112
			entry.response = &gabi.IssueCommitmentMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
113
114
115
		default:
			return nil, errors.New("Invalid log type")
		}
Sietse Ringers's avatar
Sietse Ringers committed
116
		err := json.Unmarshal(entry.rawResponse, entry.response)
Sietse Ringers's avatar
Sietse Ringers committed
117
118
119
120
121
		if err != nil {
			return nil, err
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
122
	return entry.response, nil
Sietse Ringers's avatar
Sietse Ringers committed
123
124
125
}

type jsonLogEntry struct {
126
127
	Type        irma.Action
	Time        irma.Timestamp
Sietse Ringers's avatar
Sietse Ringers committed
128
	SessionInfo *logSessionInfo
Sietse Ringers's avatar
Sietse Ringers committed
129

130
131
132
133
134
	Disclosed         map[irma.CredentialTypeIdentifier]map[int]irma.TranslatedString `json:",omitempty"`
	Received          map[irma.CredentialTypeIdentifier][]irma.TranslatedString       `json:",omitempty"`
	Removed           map[irma.CredentialTypeIdentifier][]irma.TranslatedString       `json:",omitempty"`
	SignedMessage     []byte                                                          `json:",omitempty"`
	SignedMessageType string                                                          `json:",omitempty"`
Sietse Ringers's avatar
Sietse Ringers committed
135
136

	Response json.RawMessage
Sietse Ringers's avatar
Sietse Ringers committed
137
138
}

Sietse Ringers's avatar
Sietse Ringers committed
139
// UnmarshalJSON implements json.Unmarshaler.
Sietse Ringers's avatar
Sietse Ringers committed
140
141
142
143
144
145
146
147
148
149
func (entry *LogEntry) UnmarshalJSON(bytes []byte) error {
	var err error
	temp := &jsonLogEntry{}
	if err = json.Unmarshal(bytes, temp); err != nil {
		return err
	}

	*entry = LogEntry{
		Type: temp.Type,
		Time: temp.Time,
150
		SessionInfo: &irma.SessionInfo{
Sietse Ringers's avatar
Sietse Ringers committed
151
152
153
			Jwt:     temp.SessionInfo.Jwt,
			Nonce:   temp.SessionInfo.Nonce,
			Context: temp.SessionInfo.Context,
154
			Keys:    make(map[irma.IssuerIdentifier]int),
Sietse Ringers's avatar
Sietse Ringers committed
155
		},
156
		Removed:           temp.Removed,
Sietse Ringers's avatar
Sietse Ringers committed
157
158
159
160
161
		Disclosed:         temp.Disclosed,
		Received:          temp.Received,
		SignedMessage:     temp.SignedMessage,
		SignedMessageType: temp.SignedMessageType,
		rawResponse:       temp.Response,
Sietse Ringers's avatar
Sietse Ringers committed
162
163
164
165
	}

	// TODO remove on protocol upgrade
	for iss, count := range temp.SessionInfo.Keys {
166
		entry.SessionInfo.Keys[irma.NewIssuerIdentifier(iss)] = count
Sietse Ringers's avatar
Sietse Ringers committed
167
168
169
170
	}

	return nil
}
Sietse Ringers's avatar
Sietse Ringers committed
171

Sietse Ringers's avatar
Sietse Ringers committed
172
// MarshalJSON implements json.Marshaler.
Sietse Ringers's avatar
Sietse Ringers committed
173
174
175
176
177
178
179
180
181
182
func (entry *LogEntry) MarshalJSON() ([]byte, error) {
	// If the entry was created using createLogEntry(), then entry.rawResponse == nil
	if len(entry.rawResponse) == 0 && entry.response != nil {
		if bytes, err := json.Marshal(entry.response); err == nil {
			entry.rawResponse = json.RawMessage(bytes)
		} else {
			return nil, err
		}
	}

183
184
185
	var si *logSessionInfo
	if entry.SessionInfo != nil {
		si = &logSessionInfo{
Sietse Ringers's avatar
Sietse Ringers committed
186
187
188
189
			Jwt:     entry.SessionInfo.Jwt,
			Nonce:   entry.SessionInfo.Nonce,
			Context: entry.SessionInfo.Context,
			Keys:    make(map[string]int),
190
191
192
193
194
195
196
197
198
199
200
		}
		// TODO remove on protocol upgrade
		for iss, count := range entry.SessionInfo.Keys {
			si.Keys[iss.String()] = count
		}
	}
	temp := &jsonLogEntry{
		Type:              entry.Type,
		Time:              entry.Time,
		Response:          entry.rawResponse,
		SessionInfo:       si,
201
		Removed:           entry.Removed,
Sietse Ringers's avatar
Sietse Ringers committed
202
203
204
205
206
207
208
209
		Disclosed:         entry.Disclosed,
		Received:          entry.Received,
		SignedMessage:     entry.SignedMessage,
		SignedMessageType: entry.SignedMessageType,
	}

	return json.Marshal(temp)
}