Commit 8d0c0554 authored by Sietse Ringers's avatar Sietse Ringers

feat: add revocation server endpoints to servercore

parent 33c3b9b6
......@@ -10,6 +10,7 @@ import (
"net/http"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
......@@ -17,6 +18,7 @@ import (
"github.com/jasonlvhit/gocron"
"github.com/privacybydesign/gabi"
"github.com/privacybydesign/gabi/big"
"github.com/privacybydesign/gabi/revocation"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/fs"
"github.com/privacybydesign/irmago/server"
......@@ -168,6 +170,7 @@ func (s *Server) verifyPrivateKeys(configuration *server.Configuration) error {
if err = db.LoadCurrent(); err != nil {
return server.LogError(err)
}
s.conf.RevocableCredentials[credid] = struct{}{}
}
}
}
......@@ -293,13 +296,21 @@ func (s *Server) CancelSession(token string) error {
return nil
}
func ParsePath(path string) (string, string, error) {
pattern := regexp.MustCompile("session/(\\w+)/?(|commitments|proofs|status|statusevents)$")
matches := pattern.FindStringSubmatch(path)
if len(matches) != 3 {
return "", "", server.LogWarning(errors.Errorf("Invalid URL: %s", path))
func ParsePath(path string) (token, noun string, arg []string, err error) {
client := regexp.MustCompile("session/(\\w+)/?(|commitments|proofs|status|statusevents)$")
matches := client.FindStringSubmatch(path)
if len(matches) == 3 {
return matches[1], matches[2], nil, nil
}
return matches[1], matches[2], nil
rev := regexp.MustCompile("-/revocation/(records)/?(.*)$")
matches = rev.FindStringSubmatch(path)
if len(matches) == 3 {
args := strings.Split(matches[2], "/")
return "", matches[1], args, nil
}
return "", "", nil, server.LogWarning(errors.Errorf("Invalid URL: %s", path))
}
func (s *Server) SubscribeServerSentEvents(w http.ResponseWriter, r *http.Request, token string, requestor bool) error {
......@@ -375,12 +386,22 @@ func (s *Server) handleProtocolMessage(
}
}
token, noun, err := ParsePath(path)
token, noun, args, err := ParsePath(path)
if err != nil {
status, output = server.JsonResponse(nil, server.RemoteError(server.ErrorUnsupported, ""))
return
}
if token != "" {
status, output, result = s.handleClientMessage(token, noun, method, headers, message)
} else {
status, output = s.handleRevocationMessage(noun, method, args, headers, message)
}
return
}
func (s *Server) handleClientMessage(
token, noun, method string, headers map[string][]string, message []byte,
) (status int, output []byte, result *server.SessionResult) {
// Fetch the session
session := s.sessions.clientGet(token)
if session == nil {
......@@ -498,3 +519,67 @@ func (s *Server) handleProtocolMessage(
return
}
}
func (s *Server) handleRevocationMessage(
noun, method string, args []string, headers map[string][]string, message []byte,
) (int, []byte) {
if noun == "records" && method == http.MethodGet {
if len(args) != 2 {
return server.JsonResponse(nil, server.RemoteError(server.ErrorInvalidRequest, "GET records expects 2 url arguments"))
}
index, err := strconv.Atoi(args[1])
if err != nil {
return server.JsonResponse(nil, server.RemoteError(server.ErrorMalformedInput, err.Error()))
}
cred := irma.NewCredentialTypeIdentifier(args[0])
return server.JsonResponse(s.handleGetRevocationRecords(cred, index))
}
if noun == "records" && method == http.MethodPost {
if len(args) != 1 {
return server.JsonResponse(nil, server.RemoteError(server.ErrorInvalidRequest, "POST records expects 1 url arguments"))
}
cred := irma.NewCredentialTypeIdentifier(args[0])
var records []*revocation.Record
if err := json.Unmarshal(message, &records); err != nil {
return server.JsonResponse(nil, server.RemoteError(server.ErrorMalformedInput, err.Error()))
}
return server.JsonResponse(s.handlePostRevocationRecords(cred, records))
}
return server.JsonResponse(nil, server.RemoteError(server.ErrorInvalidRequest, ""))
}
func (s *Server) handlePostRevocationRecords(
cred irma.CredentialTypeIdentifier, records []*revocation.Record,
) (interface{}, *irma.RemoteError) {
if _, ok := s.conf.RevocableCredentials[cred]; !ok {
return nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server")
}
db, err := s.conf.IrmaConfiguration.RevocationDB(cred)
if err != nil {
return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
}
for _, r := range records {
if err = db.Add(r.Message, r.PublicKeyIndex); err != nil {
return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
}
}
return nil, nil
}
func (s *Server) handleGetRevocationRecords(
cred irma.CredentialTypeIdentifier, index int,
) ([]revocation.Record, *irma.RemoteError) {
if _, ok := s.conf.RevocableCredentials[cred]; !ok {
return nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server")
}
db, err := s.conf.IrmaConfiguration.RevocationDB(cred)
if err != nil {
return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
}
records, err := db.RevocationRecords(index)
if err != nil {
return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
}
return records, nil
}
package server
import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
"io/ioutil"
......@@ -40,6 +42,8 @@ type Configuration struct {
IssuerPrivateKeys map[irma.IssuerIdentifier]*gabi.PrivateKey `json:"-"`
// Path at which to store revocation databases
RevocationPath string `json:"revocation_path" mapstructure:"revocation_path"`
// Credentials types for which revocation database should be hosted
RevocableCredentials map[irma.CredentialTypeIdentifier]struct{} `json:"-"`
// URL at which the IRMA app can reach this server during sessions
URL string `json:"url" mapstructure:"url"`
// Required to be set to true if URL does not begin with https:// in production mode.
......@@ -170,13 +174,29 @@ func RemoteError(err Error, message string) *irma.RemoteError {
// JsonResponse JSON-marshals the specified object or error
// and returns it along with a suitable HTTP status code
func JsonResponse(v interface{}, err *irma.RemoteError) (int, []byte) {
return encodeValOrError(v, err, json.Marshal)
}
func GobResponse(v interface{}, err *irma.RemoteError) (int, []byte) {
return encodeValOrError(v, err, gobMarshal)
}
func gobMarshal(v interface{}) ([]byte, error) {
var b bytes.Buffer
if err := gob.NewEncoder(&b).Encode(v); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func encodeValOrError(v interface{}, err *irma.RemoteError, encoder func(interface{}) ([]byte, error)) (int, []byte) {
msg := v
status := http.StatusOK
if err != nil {
msg = err
status = err.Status
}
b, e := json.Marshal(msg)
b, e := encoder(msg)
if e != nil {
Logger.Error("Failed to serialize response:", e.Error())
return http.StatusInternalServerError, nil
......
......@@ -126,8 +126,8 @@ func (s *Server) HandlerFunc() http.HandlerFunc {
}
}
token, noun, err := servercore.ParsePath(r.URL.Path)
if err == nil && noun == "statusevents" { // if err != nil we let it be handled by HandleProtocolMessage below
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
if err = s.SubscribeServerSentEvents(w, r, token, false); err != nil {
server.WriteResponse(w, nil, &irma.RemoteError{
Status: server.ErrorUnsupported.Status,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment