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

import (
	"encoding/json"
	"time"

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

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
	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
Sietse Ringers's avatar
Sietse Ringers committed
24

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
39
40
41
	// 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
42
	switch entry.Type {
43
	case irma.ActionSigning:
44
45
46
47
48
49
50
51
52
		if session.IsInteractive() {
			entry.SignedMessage = []byte(session.jwt.(*irma.SignatureRequestorJwt).Request.Request.Message)
		} else {
			request, ok := session.irmaSession.(*irma.SignatureRequest)
			if !ok {
				return nil, errors.New("Session does not contain a valid Signature Request")
			}
			entry.SignedMessage = []byte(request.Message)
		}
Sietse Ringers's avatar
Sietse Ringers committed
53
		fallthrough
54
	case irma.ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
55
56
57
		if prooflist, ok = response.(gabi.ProofList); !ok {
			return nil, errors.New("Response was not a ProofList")
		}
58
	case irma.ActionIssuing:
Sietse Ringers's avatar
Sietse Ringers committed
59
		if entry.Received == nil {
60
			entry.Received = map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
Sietse Ringers's avatar
Sietse Ringers committed
61
		}
62
		for _, req := range session.jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials {
63
			list, err := req.AttributeList(session.client.Configuration, getMetadataVersion(session.Version))
Sietse Ringers's avatar
Sietse Ringers committed
64
65
66
			if err != nil {
				continue // TODO?
			}
Sietse Ringers's avatar
Sietse Ringers committed
67
68
69
70
71
72
73
			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
74
75
76
77
78
		}
	default:
		return nil, errors.New("Invalid log type")
	}

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

Sietse Ringers's avatar
Sietse Ringers committed
98
99
100
	return entry, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
101
102
// Jwt returns the JWT from the requestor that started the IRMA session which the
// current log entry tracks.
103
104
func (entry *LogEntry) Jwt() (irma.RequestorJwt, error) {
	return irma.ParseRequestorJwt(entry.Type, entry.SessionInfo.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
105
106
}

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

Sietse Ringers's avatar
Sietse Ringers committed
128
	return entry.response, nil
Sietse Ringers's avatar
Sietse Ringers committed
129
130
131
}

type jsonLogEntry struct {
132
133
	Type        irma.Action
	Time        irma.Timestamp
Sietse Ringers's avatar
Sietse Ringers committed
134
	SessionInfo *logSessionInfo
Sietse Ringers's avatar
Sietse Ringers committed
135

136
137
138
139
	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"`
Sietse Ringers's avatar
Sietse Ringers committed
140
141

	Response json.RawMessage
Sietse Ringers's avatar
Sietse Ringers committed
142
143
}

Sietse Ringers's avatar
Sietse Ringers committed
144
// UnmarshalJSON implements json.Unmarshaler.
Sietse Ringers's avatar
Sietse Ringers committed
145
146
147
148
149
150
151
152
153
154
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,
155
		SessionInfo: &irma.SessionInfo{
Sietse Ringers's avatar
Sietse Ringers committed
156
157
158
			Jwt:     temp.SessionInfo.Jwt,
			Nonce:   temp.SessionInfo.Nonce,
			Context: temp.SessionInfo.Context,
159
			Keys:    make(map[irma.IssuerIdentifier]int),
Sietse Ringers's avatar
Sietse Ringers committed
160
		},
161
162
163
164
165
		Removed:       temp.Removed,
		Disclosed:     temp.Disclosed,
		Received:      temp.Received,
		SignedMessage: temp.SignedMessage,
		rawResponse:   temp.Response,
Sietse Ringers's avatar
Sietse Ringers committed
166
167
168
169
	}

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

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

Sietse Ringers's avatar
Sietse Ringers committed
176
// MarshalJSON implements json.Marshaler.
Sietse Ringers's avatar
Sietse Ringers committed
177
178
179
180
181
182
183
184
185
186
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
		}
	}

187
188
189
	var si *logSessionInfo
	if entry.SessionInfo != nil {
		si = &logSessionInfo{
Sietse Ringers's avatar
Sietse Ringers committed
190
191
192
193
			Jwt:     entry.SessionInfo.Jwt,
			Nonce:   entry.SessionInfo.Nonce,
			Context: entry.SessionInfo.Context,
			Keys:    make(map[string]int),
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{
201
202
203
204
205
206
207
208
		Type:          entry.Type,
		Time:          entry.Time,
		Response:      entry.rawResponse,
		SessionInfo:   si,
		Removed:       entry.Removed,
		Disclosed:     entry.Disclosed,
		Received:      entry.Received,
		SignedMessage: entry.SignedMessage,
Sietse Ringers's avatar
Sietse Ringers committed
209
210
211
212
	}

	return json.Marshal(temp)
}