descriptions.go 6.92 KB
Newer Older
1
package irma
2
3
4

import (
	"encoding/xml"
Sietse Ringers's avatar
Sietse Ringers committed
5
	"fmt"
6
	"path/filepath"
7

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

12
13
14
// This file contains data types for scheme managers, issuers, credential types
// matching the XML files in irma_configuration.

15
16
// SchemeManager describes a scheme manager.
type SchemeManager struct {
17
18
	ID                string           `xml:"Id"`
	Name              TranslatedString `xml:"Name"`
19
20
	URL               string           `xml:"Url"`
	Contact           string           `xml:"contact"`
21
	Demo              bool             `xml:"Demo"` // Decides whether to download private keys
22
	Description       TranslatedString
23
	MinimumAppVersion SchemeAppVersion
24
25
26
	KeyshareServer    string
	KeyshareWebsite   string
	KeyshareAttribute string
27
	TimestampServer   string
28
29
	XMLVersion        int      `xml:"version,attr"`
	XMLName           xml.Name `xml:"SchemeManager"`
30

31
32
	Status SchemeManagerStatus `xml:"-"`
	Valid  bool                `xml:"-"` // true iff Status == SchemeManagerStatusValid
33

34
35
	Timestamp Timestamp

36
	index SchemeManagerIndex
37
38
}

39
40
41
42
43
type SchemeAppVersion struct {
	Android int `xml:"Android"`
	IOS     int `xml:"iOS"`
}

44
45
// Issuer describes an issuer.
type Issuer struct {
46
47
48
49
50
51
	ID              string           `xml:"ID"`
	Name            TranslatedString `xml:"Name"`
	ShortName       TranslatedString `xml:"ShortName"`
	SchemeManagerID string           `xml:"SchemeManager"`
	ContactAddress  string
	ContactEMail    string
52
	DeprecatedSince Timestamp
53
	XMLVersion      int `xml:"version,attr"`
54
55

	Valid bool `xml:"-"`
56
57
58
59
}

// CredentialType is a description of a credential type, specifying (a.o.) its name, issuer, and attributes.
type CredentialType struct {
60
61
62
63
64
65
	ID              string           `xml:"CredentialID"`
	Name            TranslatedString `xml:"Name"`
	ShortName       TranslatedString `xml:"ShortName"`
	IssuerID        string           `xml:"IssuerID"`
	SchemeManagerID string           `xml:"SchemeManager"`
	IsSingleton     bool             `xml:"ShouldBeSingleton"`
66
	DisallowDelete  bool             `xml:"DisallowDelete"`
67
	Description     TranslatedString
68
	AttributeTypes  []*AttributeType `xml:"Attributes>Attribute" json:"-"`
69
70
	XMLVersion      int              `xml:"version,attr"`
	XMLName         xml.Name         `xml:"IssueSpecification"`
71
	IssueURL        TranslatedString `xml:"IssueURL"`
72
	DeprecatedSince Timestamp
73
74

	Valid bool `xml:"-"`
75
76
}

77
78
// AttributeType is a description of an attribute within a credential type.
type AttributeType struct {
79
	ID          string `xml:"id,attr"`
80
	Optional    string `xml:"optional,attr"  json:",omitempty"`
81
82
	Name        TranslatedString
	Description TranslatedString
83
84
85
86
87
88
89
90

	Index        int  `xml:"-"`
	DisplayIndex *int `xml:"displayIndex,attr" json:",omitempty"`

	// Taken from containing CredentialType
	CredentialTypeID string `xml:"-"`
	IssuerID         string `xml:"-"`
	SchemeManagerID  string `xml:"-"`
91
92
}

93
94
func (ad AttributeType) GetAttributeTypeIdentifier() AttributeTypeIdentifier {
	return NewAttributeTypeIdentifier(fmt.Sprintf("%s.%s.%s.%s", ad.SchemeManagerID, ad.IssuerID, ad.CredentialTypeID, ad.ID))
95
96
}

97
func (ad AttributeType) IsOptional() bool {
98
99
100
	return ad.Optional == "true"
}

101
102
// ContainsAttribute tests whether the specified attribute is contained in this
// credentialtype.
103
func (ct *CredentialType) ContainsAttribute(ai AttributeTypeIdentifier) bool {
104
	if ai.CredentialTypeIdentifier().String() != ct.Identifier().String() {
105
106
		return false
	}
107
	for _, desc := range ct.AttributeTypes {
108
109
110
111
112
113
114
		if desc.ID == ai.Name() {
			return true
		}
	}
	return false
}

Sietse Ringers's avatar
Sietse Ringers committed
115
116
// IndexOf returns the index of the specified attribute if present,
// or an error (and -1) if not present.
117
118
119
120
func (ct CredentialType) IndexOf(ai AttributeTypeIdentifier) (int, error) {
	if ai.CredentialTypeIdentifier() != ct.Identifier() {
		return -1, errors.New("Wrong credential type")
	}
121
	for i, description := range ct.AttributeTypes {
122
123
124
125
126
127
128
		if description.ID == ai.Name() {
			return i, nil
		}
	}
	return -1, errors.New("Attribute identifier not found")
}

129
func (ct CredentialType) AttributeType(ai AttributeTypeIdentifier) *AttributeType {
130
131
132
133
	i, _ := ct.IndexOf(ai)
	if i == -1 {
		return nil
	}
134
	return ct.AttributeTypes[i]
135
136
}

Sietse Ringers's avatar
Sietse Ringers committed
137
138
// TranslatedString is a map of translated strings.
type TranslatedString map[string]string
139

140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
type xmlTranslation struct {
	XMLName xml.Name
	Text    string `xml:",chardata"`
}

type xmlTranslatedString struct {
	Translations []xmlTranslation `xml:",any"`
}

// MarshalXML implements xml.Marshaler.
func (ts *TranslatedString) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	temp := &xmlTranslatedString{}
	for lang, text := range *ts {
		temp.Translations = append(temp.Translations,
			xmlTranslation{XMLName: xml.Name{Local: lang}, Text: text},
		)
	}
	return e.EncodeElement(temp, start)
}

Sietse Ringers's avatar
Sietse Ringers committed
160
161
162
163
164
165
166
// UnmarshalXML unmarshals an XML tag containing a string translated to multiple languages,
// for example: <Foo><en>Hello world</en><nl>Hallo wereld</nl></Foo>
// into a TranslatedString: { "en": "Hello world" , "nl": "Hallo wereld" }
func (ts *TranslatedString) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	if map[string]string(*ts) == nil {
		*ts = TranslatedString(make(map[string]string))
	}
167
	temp := &xmlTranslatedString{}
Sietse Ringers's avatar
Sietse Ringers committed
168
169
170
171
172
	if err := d.DecodeElement(temp, &start); err != nil {
		return err
	}
	for _, translation := range temp.Translations {
		(*ts)[translation.XMLName.Local] = translation.Text
173
	}
Sietse Ringers's avatar
Sietse Ringers committed
174
	return nil
175
176
177
}

// Identifier returns the identifier of the specified credential type.
178
179
func (ct *CredentialType) Identifier() CredentialTypeIdentifier {
	return NewCredentialTypeIdentifier(ct.SchemeManagerID + "." + ct.IssuerID + "." + ct.ID)
180
181
182
}

// IssuerIdentifier returns the issuer identifier of the specified credential type.
183
184
func (ct *CredentialType) IssuerIdentifier() IssuerIdentifier {
	return NewIssuerIdentifier(ct.SchemeManagerID + "." + ct.IssuerID)
185
186
}

187
188
189
190
func (ct *CredentialType) SchemeManagerIdentifier() SchemeManagerIdentifier {
	return NewSchemeManagerIdentifier(ct.SchemeManagerID)
}

Sietse Ringers's avatar
Sietse Ringers committed
191
func (ct *CredentialType) Logo(conf *Configuration) string {
192
	path := filepath.Join(conf.Path, ct.SchemeManagerID, ct.IssuerID, "Issues", ct.ID, "logo.png")
Sietse Ringers's avatar
Sietse Ringers committed
193
194
195
196
197
198
199
	exists, err := fs.PathExists(path)
	if err != nil || !exists {
		return ""
	}
	return path
}

200
// Identifier returns the identifier of the specified issuer description.
201
202
203
204
func (id *Issuer) Identifier() IssuerIdentifier {
	return NewIssuerIdentifier(id.SchemeManagerID + "." + id.ID)
}

205
206
207
208
func (id *Issuer) SchemeManagerIdentifier() SchemeManagerIdentifier {
	return NewSchemeManagerIdentifier(id.SchemeManagerID)
}

209
210
211
212
func NewSchemeManager(name string) *SchemeManager {
	return &SchemeManager{ID: name, Status: SchemeManagerStatusUnprocessed, Valid: false}
}

213
214
215
// Identifier returns the identifier of the specified scheme manager.
func (sm *SchemeManager) Identifier() SchemeManagerIdentifier {
	return NewSchemeManagerIdentifier(sm.ID)
216
217
}

218
// Distributed indicates if this scheme manager uses a keyshare server.
Sietse Ringers's avatar
Sietse Ringers committed
219
220
221
func (sm *SchemeManager) Distributed() bool {
	return len(sm.KeyshareServer) > 0
}