api.go 7.01 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
package server
2
3

import (
4
	"encoding/json"
5
	"fmt"
6
	"net"
7
	"net/http"
8
9
	"os"
	"path/filepath"
10
	"reflect"
11
	"runtime"
12
13
	"runtime/debug"

14
	"github.com/Sirupsen/logrus"
15
	"github.com/go-errors/errors"
16
	"github.com/privacybydesign/gabi"
17
	"github.com/privacybydesign/irmago"
18
	"github.com/privacybydesign/irmago/internal/fs"
19
20
)

21
22
var Logger *logrus.Logger = logrus.StandardLogger()

23
// Configuration contains configuration for the irmarequestor library and irmaserver.
24
type Configuration struct {
25
26
27
28
	// irma_configuration. If not given, this will be popupated using IrmaConfigurationPath.
	IrmaConfiguration *irma.Configuration `json:"-"`
	// Path to schemes to parse (only used if IrmaConfiguration is not given)
	IrmaConfigurationPath string `json:"irmaconf" mapstructure:"irmaconf"`
29
	// Path to writable dir to write cache to (only used if IrmaConfiguration is not given)
30
	CachePath string `json:"cachepath" mapstructure:"cachepath"`
31
32
	// Whether or not to download default IRMA schemes if the specified irma_configuration is empty
	DownloadDefaultSchemes bool
33
34
35
36
37
38
39
40
	// Path to issuer private keys to parse
	IssuerPrivateKeysPath string `json:"privatekeys" mapstructure:"privatekeys"`
	// Issuer private keys
	IssuerPrivateKeys map[irma.IssuerIdentifier]*gabi.PrivateKey `json:"-"`
	// URL at which the IRMA app can reach this server during sessions
	URL string `json:"url" mapstructure:"url"`
	// Logging
	Logger *logrus.Logger `json:"-"`
41
42
}

43
44
// SessionResult contains session information such as the session status, type, possible errors,
// and disclosed attributes or attribute-based signature if appropriate to the session type.
45
type SessionResult struct {
Sietse Ringers's avatar
Sietse Ringers committed
46
47
	Token       string
	Status      Status
48
	Type        irma.Action
Sietse Ringers's avatar
Sietse Ringers committed
49
50
51
52
	ProofStatus irma.ProofStatus
	Disclosed   []*irma.DisclosedAttribute
	Signature   *irma.SignedMessage
	Err         *irma.RemoteError
53
54
}

Sietse Ringers's avatar
Sietse Ringers committed
55
// Status is the status of an IRMA session.
56
57
58
type Status string

const (
Sietse Ringers's avatar
Sietse Ringers committed
59
60
61
62
63
	StatusInitialized Status = "INITIALIZED" // The session has been started and is waiting for the client
	StatusConnected   Status = "CONNECTED"   // The client has retrieved the session request, we wait for its response
	StatusCancelled   Status = "CANCELLED"   // The session is cancelled, possibly due to an error
	StatusDone        Status = "DONE"        // The session has completed successfully
	StatusTimeout     Status = "TIMEOUT"     // Session timed out
64
)
65

66
// RemoteError converts an error and an explaining message to an *irma.RemoteError.
67
68
func RemoteError(err Error, message string) *irma.RemoteError {
	stack := string(debug.Stack())
69
	Logger.Warnf("Session error: %d %s %s\n%s", err.Status, err.Type, message, stack)
70
71
72
73
74
75
76
77
78
	return &irma.RemoteError{
		Status:      err.Status,
		Description: err.Description,
		ErrorName:   string(err.Type),
		Message:     message,
		Stacktrace:  stack,
	}
}

79
80
// JsonResponse JSON-marshals the specified object or error
// and returns it along with a suitable HTTP status code
81
82
83
84
85
86
87
88
89
90
91
92
func JsonResponse(v interface{}, err *irma.RemoteError) (int, []byte) {
	msg := v
	status := http.StatusOK
	if err != nil {
		msg = err
		status = err.Status
	}
	b, e := json.Marshal(msg)
	if e != nil {
		Logger.Error("Failed to serialize response:", e.Error())
		return http.StatusInternalServerError, nil
	}
93
	Logger.Tracef("HTTP JSON response: %d %s", status, string(b))
94
95
	return status, b
}
96

97
// WriteError writes the specified error and explaining message as JSON to the http.ResponseWriter.
98
func WriteError(w http.ResponseWriter, err Error, msg string) {
99
	WriteResponse(w, nil, RemoteError(err, msg))
100
101
}

102
// WriteJson writes the specified object as JSON to the http.ResponseWriter.
103
func WriteJson(w http.ResponseWriter, object interface{}) {
104
105
106
	WriteResponse(w, object, nil)
}

107
// WriteResponse writes the specified object or error as JSON to the http.ResponseWriter.
108
109
func WriteResponse(w http.ResponseWriter, object interface{}, rerr *irma.RemoteError) {
	status, bts := JsonResponse(object, rerr)
110
111
112
113
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	w.Write(bts)
}
114

115
// WriteString writes the specified string to the http.ResponseWriter.
116
func WriteString(w http.ResponseWriter, str string) {
117
	Logger.Trace("HTTP text/plain response: ", str)
118
119
120
121
	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(str))
}
122

123
124
125
// ParseSessionRequest tries to parse the specified bytes as an
// disclosure request, a signature request, and an issuance request, in that order.
// Returns an error if none of the attempts work.
126
127
128
129
130
131
132
133
134
135
136
137
138
func ParseSessionRequest(bts []byte) (request irma.SessionRequest, err error) {
	request = &irma.DisclosureRequest{}
	if err = irma.UnmarshalValidate(bts, request); err == nil {
		return request, nil
	}
	request = &irma.SignatureRequest{}
	if err = irma.UnmarshalValidate(bts, request); err == nil {
		return request, nil
	}
	request = &irma.IssuanceRequest{}
	if err = irma.UnmarshalValidate(bts, request); err == nil {
		return request, nil
	}
139
	Logger.Warn("Failed to parse as session request: ", string(bts))
140
141
	return nil, errors.New("Invalid or disabled session type")
}
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

func LocalIP() (string, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				continue // not an ipv4 address
			}
			return ip.String(), nil
		}
	}
	return "", errors.New("No IP found")
}

func CachePath() (string, error) {
	candidates := make([]string, 0, 2)
	if runtime.GOOS != "windows" {
183
		candidates = append(candidates, filepath.Join("/var/tmp", "irma"))
184
	}
185
	candidates = append(candidates, filepath.Join(os.TempDir(), "irma"))
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
	path := firstWritablePath(candidates)
	if path == "" {
		return "", errors.New("No writable temporary directory found")
	}
	return path, nil
}

func firstWritablePath(paths []string) string {
	for _, path := range paths {
		if err := fs.EnsureDirectoryExists(path); err != nil {
			continue
		}
		return path
	}
	return ""
}

func Verbosity(level int) logrus.Level {
	switch {
	case level == 1:
		return logrus.DebugLevel
	case level > 1:
		return logrus.TraceLevel
	default:
		return logrus.InfoLevel
	}
}
213

214
215
216
217
func log(level logrus.Level, err error) error {
	writer := Logger.WriterLevel(level)
	if e, ok := err.(*errors.Error); ok && Logger.IsLevelEnabled(logrus.TraceLevel) {
		_, _ = writer.Write([]byte(e.ErrorStack()))
218
	} else {
219
		_, _ = writer.Write([]byte(fmt.Sprintf("%s %s", reflect.TypeOf(err).String(), err.Error())))
220
221
222
223
	}
	return err
}

224
225
226
227
228
229
230
231
func LogFatal(err error) error {
	return log(logrus.FatalLevel, err)
}

func LogError(err error) error {
	return log(logrus.ErrorLevel, err)
}

232
func LogWarning(err error) error {
233
	return log(logrus.WarnLevel, err)
234
235
236
237
238
239
}

func ToJson(o interface{}) string {
	bts, _ := json.Marshal(o)
	return string(bts)
}