main.go 5.37 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
2
3
4
// Package irmaserver is a library that allows IRMA verifiers, issuers or attribute-based signature
// applications to perform IRMA sessions with irmaclient instances (i.e. the IRMA app). It exposes
// functions for handling IRMA sessions and a HTTP handler that handles the sessions with the
// irmaclient.
5
package irmaserver
6
7
8
9
10

import (
	"io/ioutil"
	"net/http"

11
	"github.com/go-errors/errors"
12
	"github.com/privacybydesign/irmago"
13
	"github.com/privacybydesign/irmago/internal/servercore"
Sietse Ringers's avatar
Sietse Ringers committed
14
	"github.com/privacybydesign/irmago/server"
15
16
)

Sietse Ringers's avatar
Sietse Ringers committed
17
// Server is an irmaserver instance.
18
type Server struct {
19
	*servercore.Server
20
21
22
	handlers map[string]SessionHandler
}

23
24
// SessionHandler is a function that can handle a session result
// once an IRMA session has completed.
Sietse Ringers's avatar
Sietse Ringers committed
25
type SessionHandler func(*server.SessionResult)
26

27
28
29
30
31
32
33
34
35
// Default server instance
var s *Server

// Initialize the default server instance with the specified configuration using New().
func Initialize(conf *server.Configuration) (err error) {
	s, err = New(conf)
	return
}

Sietse Ringers's avatar
Sietse Ringers committed
36
// New creates a new Server instance with the specified configuration.
37
func New(conf *server.Configuration) (*Server, error) {
38
	s, err := servercore.New(conf)
39
40
41
42
43
44
45
	if err != nil {
		return nil, err
	}
	return &Server{
		Server:   s,
		handlers: make(map[string]SessionHandler),
	}, nil
46
47
}

48
49
50
51
52
53
54
55
// Stop the server.
func Stop() {
	s.Stop()
}
func (s *Server) Stop() {
	s.Server.Stop()
}

56
57
58
// StartSession starts an IRMA session, running the handler on completion, if specified.
// The session token (the second return parameter) can be used in GetSessionResult()
// and CancelSession().
59
60
// The request parameter can be an irma.RequestorRequest, or an irma.SessionRequest, or a
// ([]byte or string) JSON representation of one of those (for more details, see server.ParseSessionRequest().)
61
62
63
func StartSession(request interface{}, handler SessionHandler) (*irma.Qr, string, error) {
	return s.StartSession(request, handler)
}
64
65
func (s *Server) StartSession(request interface{}, handler SessionHandler) (*irma.Qr, string, error) {
	qr, token, err := s.Server.StartSession(request)
66
67
68
69
	if err != nil {
		return nil, "", err
	}
	if handler != nil {
70
		s.handlers[token] = handler
71
72
73
74
	}
	return qr, token, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
75
// GetSessionResult retrieves the result of the specified IRMA session.
76
77
78
func GetSessionResult(token string) *server.SessionResult {
	return s.GetSessionResult(token)
}
Sietse Ringers's avatar
Sietse Ringers committed
79
80
81
82
83
func (s *Server) GetSessionResult(token string) *server.SessionResult {
	return s.Server.GetSessionResult(token)
}

// GetRequest retrieves the request submitted by the requestor that started the specified IRMA session.
84
85
86
func GetRequest(token string) irma.RequestorRequest {
	return s.GetRequest(token)
}
Sietse Ringers's avatar
Sietse Ringers committed
87
88
89
90
91
func (s *Server) GetRequest(token string) irma.RequestorRequest {
	return s.Server.GetRequest(token)
}

// CancelSession cancels the specified IRMA session.
92
93
94
func CancelSession(token string) error {
	return s.CancelSession(token)
}
Sietse Ringers's avatar
Sietse Ringers committed
95
96
97
98
func (s *Server) CancelSession(token string) error {
	return s.Server.CancelSession(token)
}

Sietse Ringers's avatar
Sietse Ringers committed
99
100
101
// Revoke revokes the earlier issued credential specified by key. (Can only be used if this server
// is the revocation server for the specified credential type and if the corresponding
// issuer private key is present in the server configuration.)
102
103
104
105
106
107
108
func Revoke(credid irma.CredentialTypeIdentifier, key string) error {
	return s.Revoke(credid, key)
}
func (s *Server) Revoke(credid irma.CredentialTypeIdentifier, key string) error {
	return s.Server.Revoke(credid, key)
}

Sietse Ringers's avatar
Sietse Ringers committed
109
110
// SubscribeServerSentEvents subscribes the HTTP client to server sent events on status updates
// of the specified IRMA session.
111
112
func SubscribeServerSentEvents(w http.ResponseWriter, r *http.Request, token string, requestor bool) error {
	return s.SubscribeServerSentEvents(w, r, token, requestor)
113
}
114
115
func (s *Server) SubscribeServerSentEvents(w http.ResponseWriter, r *http.Request, token string, requestor bool) error {
	return s.Server.SubscribeServerSentEvents(w, r, token, requestor)
Sietse Ringers's avatar
Sietse Ringers committed
116
117
}

Sietse Ringers's avatar
Sietse Ringers committed
118
// HandlerFunc returns a http.HandlerFunc that handles the IRMA protocol
119
// with IRMA apps.
120
121
//
// Example usage:
Sietse Ringers's avatar
Sietse Ringers committed
122
//   http.HandleFunc("/irma/", irmaserver.HandlerFunc())
123
124
//
// The IRMA app can then perform IRMA sessions at https://example.com/irma.
125
126
127
func HandlerFunc() http.HandlerFunc {
	return s.HandlerFunc()
}
Sietse Ringers's avatar
Sietse Ringers committed
128
func (s *Server) HandlerFunc() http.HandlerFunc {
129
130
	return func(w http.ResponseWriter, r *http.Request) {
		var message []byte
131
132
133
134
135
136
137
138
		var err error
		if r.Method == http.MethodPost {
			if message, err = ioutil.ReadAll(r.Body); err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				return
			}
		}

139
140
		token, noun, _, err := servercore.ParsePath(r.URL.Path)
		if err == nil && token != "" && noun == "statusevents" { // if err != nil we let it be handled by HandleProtocolMessage below
141
			if err = s.SubscribeServerSentEvents(w, r, token, false); err != nil {
142
143
144
145
146
				server.WriteResponse(w, nil, &irma.RemoteError{
					Status:      server.ErrorUnsupported.Status,
					ErrorName:   string(server.ErrorUnsupported.Type),
					Description: server.ErrorUnsupported.Description,
				})
147
			}
148
149
			return
		}
150

151
		status, response, result := s.HandleProtocolMessage(r.URL.Path, r.Method, r.Header, message)
152
		w.WriteHeader(status)
153
154
155
156
		_, err = w.Write(response)
		if err != nil {
			_ = server.LogError(errors.WrapPrefix(err, "http.ResponseWriter.Write() returned error", 0))
		}
157
		if result != nil && result.Status.Finished() {
158
			if handler := s.handlers[result.Token]; handler != nil {
159
160
161
162
163
				go handler(result)
			}
		}
	}
}