taler-mailbox

Service for asynchronous wallet-to-wallet payment messages
Log | Files | Refs | Submodules | README | LICENSE

main_test.go (14905B)


      1 package main_test
      2 
      3 import (
      4 	"bytes"
      5 	"crypto/ed25519"
      6 	"crypto/rand"
      7 	"crypto/sha512"
      8 	"database/sql"
      9 	"encoding/binary"
     10 	"encoding/json"
     11 	"fmt"
     12 	"log"
     13 	"net/http"
     14 	"net/http/httptest"
     15 	"os"
     16 	"strconv"
     17 	"strings"
     18 	"testing"
     19 	"time"
     20 
     21 	"github.com/schanzen/taler-go/pkg/merchant"
     22 	talerutil "github.com/schanzen/taler-go/pkg/util"
     23 	"gopkg.in/ini.v1"
     24 	"taler.net/taler-mailbox/internal/gana"
     25 	"taler.net/taler-mailbox/internal/util"
     26 	"taler.net/taler-mailbox/pkg/rest"
     27 )
     28 
     29 var a mailbox.Mailbox
     30 
     31 var testAliceSigningKeyPriv ed25519.PrivateKey
     32 var testAliceSigningKey ed25519.PublicKey
     33 var testAliceHashedSigningKeyString string
     34 
     35 const merchantConfigResponse = `{
     36   "currency": "KUDOS",
     37   "currencies": {
     38     "KUDOS": {
     39       "name": "Kudos (Taler Demonstrator)",
     40       "currency": "KUDOS",
     41       "num_fractional_input_digits": 2,
     42       "num_fractional_normal_digits": 2,
     43       "num_fractional_trailing_zero_digits": 2,
     44       "alt_unit_names": {
     45         "0": "ク"
     46       }
     47     }
     48   },
     49   "exchanges": [
     50     {
     51       "master_pub": "F80MFRG8HVH6R9CQ47KRFQSJP3T6DBJ4K1D9B703RJY3Z39TBMJ0",
     52       "currency": "KUDOS",
     53       "base_url": "https://exchange.demo.taler.net/"
     54     }
     55   ],
     56   "implementation": "urn:net:taler:specs:taler-merchant:c-reference",
     57   "name": "taler-merchant",
     58   "version": "18:0:15"
     59 }`
     60 
     61 func executeRequest(req *http.Request) *httptest.ResponseRecorder {
     62 	rr := httptest.NewRecorder()
     63 	a.Router.ServeHTTP(rr, req)
     64 	return rr
     65 }
     66 
     67 func checkResponseCode(t *testing.T, expected, actual int) bool {
     68 	if expected != actual {
     69 		t.Errorf("Expected response code %d, Got %d\n", expected, actual)
     70 	}
     71 	return expected == actual
     72 }
     73 
     74 var merchServerRespondsPaid = false
     75 
     76 func shouldReturnPaid() bool {
     77 	return merchServerRespondsPaid
     78 }
     79 
     80 func TestMain(m *testing.M) {
     81 	cfg, err := ini.Load("test-mailbox.conf")
     82 	if err != nil {
     83 		fmt.Printf("Failed to read config: %v", err)
     84 		os.Exit(1)
     85 	}
     86 	psqlconn := cfg.Section("mailbox-pq").Key("connection_string").MustString("postgres:///taler-mailbox")
     87 	segments := strings.Split(strings.Split(psqlconn, "?")[0], "/")
     88 	dbName := segments[len(segments)-1]
     89 
     90 	db, err := sql.Open("postgres", psqlconn)
     91 	if err != nil {
     92 		log.Panic(err)
     93 	}
     94 	defer db.Close()
     95 	err = talerutil.DBInit(db, "../..", dbName, "taler-mailbox")
     96 	if err != nil {
     97 		log.Fatalf("Failed to apply versioning or patches: %v", err)
     98 	}
     99 	merchServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    100 		var orderResp merchant.PostOrderRequest
    101 		if r.URL.Path == "/config" {
    102 			w.WriteHeader(http.StatusOK)
    103 			w.Write([]byte(merchantConfigResponse))
    104 			return
    105 		}
    106 		if !strings.HasPrefix(r.URL.Path, "/private/orders") {
    107 			fmt.Printf("Expected to request '/private/orders', got: %s\n", r.URL.Path)
    108 			return
    109 		}
    110 		if r.Method == http.MethodPost {
    111 			err := json.NewDecoder(r.Body).Decode(&orderResp)
    112 			if err != nil {
    113 				fmt.Printf("Error %s\n", err)
    114 			}
    115 			jsonResp := fmt.Sprintf("{\"order_id\":\"%s\"}", "uniqueOrderId")
    116 			w.WriteHeader(http.StatusOK)
    117 			w.Write([]byte(jsonResp))
    118 		} else {
    119 			fmt.Printf("Responding always paid: %v\n", merchServerRespondsPaid)
    120 			if shouldReturnPaid() {
    121 				jsonResp := "{\"order_status\":\"paid\"}"
    122 				w.WriteHeader(http.StatusOK)
    123 				w.Write([]byte(jsonResp))
    124 			} else {
    125 				jsonResp := "{\"order_status\":\"unpaid\", \"taler_pay_uri\": \"somepaytouri\"}"
    126 				w.WriteHeader(http.StatusOK)
    127 				w.Write([]byte(jsonResp))
    128 			}
    129 		}
    130 	}))
    131 	defer merchServer.Close()
    132 	merch := merchant.NewMerchant(merchServer.URL, "supersecret")
    133 	a.Initialize(mailbox.MailboxConfig{
    134 		Version:  "testing",
    135 		DB:       db,
    136 		Merchant: merch,
    137 		Ini:      cfg})
    138 	testAliceSigningKey, testAliceSigningKeyPriv, _ = ed25519.GenerateKey(nil)
    139 	h := sha512.New()
    140 	h.Write(testAliceSigningKey)
    141 	testAliceHashedSigningKeyString = util.Base32CrockfordEncode(h.Sum(nil))
    142 
    143 	a.Merchant = merchant.NewMerchant(merchServer.URL, "")
    144 
    145 	code := m.Run()
    146 	// Purge DB
    147 	mailbox.DeleteAllInboxEntriesFromDatabase(a.DB)
    148 	os.Exit(code)
    149 }
    150 
    151 func TestEmptyMailbox(t *testing.T) {
    152 	mailbox.DeleteAllInboxEntriesFromDatabase(a.DB)
    153 	req, _ := http.NewRequest("GET", "/"+testAliceHashedSigningKeyString, nil)
    154 	response := executeRequest(req)
    155 
    156 	checkResponseCode(t, http.StatusNoContent, response.Code)
    157 
    158 	body := response.Body.String()
    159 	if body != "" {
    160 		t.Errorf("Expected empty response, Got %s", body)
    161 	}
    162 }
    163 
    164 func TestSendMessage(t *testing.T) {
    165 	testMessage := make([]byte, 256)
    166 	mailbox.DeleteAllInboxEntriesFromDatabase(a.DB)
    167 	req, _ := http.NewRequest("POST", "/"+testAliceHashedSigningKeyString, bytes.NewReader(testMessage))
    168 	response := executeRequest(req)
    169 
    170 	checkResponseCode(t, http.StatusNoContent, response.Code)
    171 
    172 	body := response.Body.String()
    173 	if body != "" {
    174 		t.Errorf("Expected empty response, Got %s", body)
    175 	}
    176 }
    177 
    178 func setMailboxPaid(isPaid bool) {
    179 	var messageFee talerutil.Amount
    180 	if isPaid {
    181 		messageFee = talerutil.NewAmount("KUDOS", 1, 0)
    182 	} else {
    183 		messageFee = talerutil.NewAmount("KUDOS", 0, 0)
    184 	}
    185 	a.MessageFee = &messageFee
    186 	a.FreeMessageQuota = 1
    187 }
    188 
    189 func TestSendMessagePaid(t *testing.T) {
    190 
    191 	// Make paid
    192 	setMailboxPaid(true)
    193 
    194 	// Cleanup
    195 	mailbox.DeleteAllInboxEntriesFromDatabase(a.DB)
    196 
    197 	testMessage := make([]byte, 256)
    198 	rand.Read(testMessage)
    199 	req, _ := http.NewRequest("POST", "/"+testAliceHashedSigningKeyString, bytes.NewReader(testMessage))
    200 	response := executeRequest(req)
    201 
    202 	checkResponseCode(t, http.StatusNoContent, response.Code)
    203 
    204 	body := response.Body.String()
    205 	if body != "" {
    206 		t.Errorf("Expected empty response, Got %s", body)
    207 	}
    208 	testMessage2 := make([]byte, 256)
    209 	rand.Read(testMessage2)
    210 	req, _ = http.NewRequest("POST", "/"+testAliceHashedSigningKeyString, bytes.NewReader(testMessage2))
    211 	response = executeRequest(req)
    212 
    213 	checkResponseCode(t, http.StatusPaymentRequired, response.Code)
    214 
    215 	body = response.Body.String()
    216 	if body != "" {
    217 		t.Errorf("Expected empty response, Got %s", body)
    218 	}
    219 	setMailboxPaid(false)
    220 }
    221 
    222 func TestGetKeysEmpty(t *testing.T) {
    223 	req, _ := http.NewRequest("GET", "/info/"+testAliceHashedSigningKeyString, nil)
    224 	response := executeRequest(req)
    225 	checkResponseCode(t, http.StatusNotFound, response.Code)
    226 }
    227 
    228 func TestMailboxRegistration(t *testing.T) {
    229 	var msg mailbox.MailboxRegistrationRequest
    230 	// Dummy pubkey
    231 	encKey := make([]byte, 32)
    232 	aliceSigningKey := util.Base32CrockfordEncode(testAliceSigningKey)
    233 	msg.MailboxMetadata.EncryptionKey = util.Base32CrockfordEncode(encKey)
    234 	msg.MailboxMetadata.EncryptionKeyType = "X25519"
    235 	msg.MailboxMetadata.Expiration.Seconds = uint64(time.Now().Add(time.Hour * 24 * 365).Unix())
    236 	msg.MailboxMetadata.SigningKey = aliceSigningKey
    237 	msg.MailboxMetadata.SigningKeyType = "EdDSA"
    238 	expNbo := make([]byte, 8)
    239 	binary.BigEndian.PutUint64(expNbo, msg.MailboxMetadata.Expiration.Seconds)
    240 	h := sha512.New()
    241 	h.Write([]byte(msg.MailboxMetadata.EncryptionKeyType))
    242 	h.Write(encKey)
    243 	h.Write(expNbo)
    244 	var signedMsg [64 + 4 + 4]byte
    245 	size := signedMsg[0:4]
    246 	binary.BigEndian.PutUint32(size, 64+4+4)
    247 	purp := signedMsg[4:8]
    248 	binary.BigEndian.PutUint32(purp, gana.TalerSignaturePurposeMailboxRegister)
    249 	copy(signedMsg[8:], h.Sum(nil))
    250 	sig := ed25519.Sign(testAliceSigningKeyPriv, signedMsg[0:])
    251 	if !ed25519.Verify(testAliceSigningKey, signedMsg[0:], sig) {
    252 		t.Errorf("Signature invalid!")
    253 	}
    254 	msg.Signature = util.Base32CrockfordEncode(sig)
    255 	jsonMsg, _ := json.Marshal(msg)
    256 	req, _ := http.NewRequest("POST", "/register", bytes.NewReader(jsonMsg))
    257 	response := executeRequest(req)
    258 	checkResponseCode(t, http.StatusNoContent, response.Code)
    259 	req, _ = http.NewRequest("GET", "/info/"+testAliceHashedSigningKeyString, nil)
    260 	response = executeRequest(req)
    261 	checkResponseCode(t, http.StatusOK, response.Code)
    262 	body := response.Body.String()
    263 	if body == "" {
    264 		t.Errorf("Expected response, Got %s", body)
    265 		return
    266 	}
    267 	var respMsg mailbox.MailboxMetadata
    268 	err := json.NewDecoder(response.Body).Decode(&respMsg)
    269 	fmt.Println(respMsg)
    270 	if err != nil {
    271 		t.Errorf("Error %s\n", err)
    272 	}
    273 	if respMsg.SigningKey != msg.MailboxMetadata.SigningKey {
    274 		fmt.Printf("Keys mismatch! %v %v\n", respMsg, msg.MailboxMetadata)
    275 	}
    276 	if respMsg.EncryptionKey != msg.MailboxMetadata.EncryptionKey {
    277 		fmt.Printf("Keys mismatch! %v %v\n", respMsg, msg.MailboxMetadata)
    278 	}
    279 	mailbox.DeleteAllMailboxesFromDatabase(a.DB)
    280 	mailbox.DeleteAllPendingRegistrationsFromDatabase(a.DB)
    281 }
    282 
    283 func createMailboxMetadata(encKey []byte, signKey []byte, info string) mailbox.MailboxMetadata {
    284 	var mb mailbox.MailboxMetadata
    285 	aliceSigningKey := util.Base32CrockfordEncode(signKey)
    286 	mb.EncryptionKey = util.Base32CrockfordEncode(encKey)
    287 	mb.EncryptionKeyType = "X25519"
    288 	mb.Expiration.Seconds = uint64(time.Now().Add(time.Hour * 24 * 365).Unix())
    289 	mb.SigningKey = aliceSigningKey
    290 	mb.SigningKeyType = "EdDSA"
    291 	if info != "" {
    292 		mb.Info = info
    293 	}
    294 	return mb
    295 }
    296 
    297 func createMailboxRegistrationMessage(encKey []byte, md mailbox.MailboxMetadata) mailbox.MailboxRegistrationRequest {
    298   var msg mailbox.MailboxRegistrationRequest
    299 	msg.MailboxMetadata = md
    300 	expNbo := make([]byte, 8)
    301 	binary.BigEndian.PutUint64(expNbo, msg.MailboxMetadata.Expiration.Seconds)
    302 	h := sha512.New()
    303 	h.Write([]byte(msg.MailboxMetadata.EncryptionKeyType))
    304 	h.Write(encKey)
    305 	h.Write(expNbo)
    306 	var signedMsg [64 + 4 + 4]byte
    307 	size := signedMsg[0:4]
    308 	binary.BigEndian.PutUint32(size, 64+4+4)
    309 	purp := signedMsg[4:8]
    310 	binary.BigEndian.PutUint32(purp, gana.TalerSignaturePurposeMailboxRegister)
    311 	copy(signedMsg[8:], h.Sum(nil))
    312 	sig := ed25519.Sign(testAliceSigningKeyPriv, signedMsg[0:])
    313 	msg.Signature = util.Base32CrockfordEncode(sig)
    314 	return msg
    315 }
    316 
    317 func TestMailboxRegistrationWithInfo(t *testing.T) {
    318 	var msg mailbox.MailboxRegistrationRequest
    319 	encKey := make([]byte, 32)
    320 	md := createMailboxMetadata(encKey, testAliceSigningKey, "")
    321 	msg = createMailboxRegistrationMessage(encKey, md)
    322 	jsonMsg, _ := json.Marshal(msg)
    323 	req, _ := http.NewRequest("POST", "/register", bytes.NewReader(jsonMsg))
    324 	response := executeRequest(req)
    325 	checkResponseCode(t, http.StatusNoContent, response.Code)
    326 	req, _ = http.NewRequest("GET", "/info/"+testAliceHashedSigningKeyString, nil)
    327 	response = executeRequest(req)
    328 	checkResponseCode(t, http.StatusOK, response.Code)
    329 	body := response.Body.String()
    330 	if body == "" {
    331 		t.Errorf("Expected response, Got %s", body)
    332 		return
    333 	}
    334 	var respMsg mailbox.MailboxMetadata
    335 	err := json.NewDecoder(response.Body).Decode(&respMsg)
    336 	if err != nil {
    337 		t.Errorf("Error %s\n", err)
    338 	}
    339 	if respMsg.SigningKey != msg.MailboxMetadata.SigningKey {
    340 		fmt.Printf("Keys mismatch! %v %v\n", respMsg, msg.MailboxMetadata)
    341 	}
    342 	if respMsg.EncryptionKey != msg.MailboxMetadata.EncryptionKey {
    343 		fmt.Printf("Keys mismatch! %v %v\n", respMsg, msg.MailboxMetadata)
    344 	}
    345 	if respMsg.Info != "Hello World" {
    346 		fmt.Printf("Info field  missing! %v %v\n", respMsg, msg.MailboxMetadata)
    347 	}
    348 	mailbox.DeleteAllMailboxesFromDatabase(a.DB)
    349 	mailbox.DeleteAllPendingRegistrationsFromDatabase(a.DB)
    350 }
    351 
    352 func TestMailboxRegistrationPaid(t *testing.T) {
    353 	var msg mailbox.MailboxRegistrationRequest
    354 
    355 	// Make paid
    356 	registrationUpdateFee := talerutil.NewAmount("KUDOS", 1, 0)
    357 	monthlyFee := talerutil.NewAmount("KUDOS", 2, 0)
    358 	a.RegistrationUpdateFee = &registrationUpdateFee
    359 	a.MonthlyFee = &monthlyFee
    360 
    361 	// Dummy pubkey
    362 	encKey := make([]byte, 32)
    363 	md := createMailboxMetadata(encKey, testAliceSigningKey, "")
    364 	msg = createMailboxRegistrationMessage(encKey, md)
    365 	jsonMsg, _ := json.Marshal(msg)
    366 	req, _ := http.NewRequest("POST", "/register", bytes.NewReader(jsonMsg))
    367 	response := executeRequest(req)
    368 	checkResponseCode(t, http.StatusPaymentRequired, response.Code)
    369 
    370 	req, _ = http.NewRequest("GET", "/info/"+testAliceHashedSigningKeyString, nil)
    371 	response = executeRequest(req)
    372 	checkResponseCode(t, http.StatusNotFound, response.Code)
    373 
    374 	merchServerRespondsPaid = true
    375 	req, _ = http.NewRequest("GET", "/info/"+testAliceHashedSigningKeyString, nil)
    376 	response = executeRequest(req)
    377 	checkResponseCode(t, http.StatusOK, response.Code)
    378 	merchServerRespondsPaid = false
    379 
    380 	body := response.Body.String()
    381 	if body == "" {
    382 		t.Errorf("Expected response, Got %s", body)
    383 		return
    384 	}
    385 	var respMsg mailbox.MailboxMetadata
    386 	err := json.NewDecoder(response.Body).Decode(&respMsg)
    387 	if err != nil {
    388 		fmt.Printf("Error %s\n", err)
    389 	}
    390 	if respMsg.SigningKey != msg.MailboxMetadata.SigningKey {
    391 		fmt.Printf("Keys mismatch! %v %v\n", respMsg, msg.MailboxMetadata)
    392 	}
    393 	if respMsg.EncryptionKey != msg.MailboxMetadata.EncryptionKey {
    394 		fmt.Printf("Keys mismatch! %v %v\n", respMsg, msg.MailboxMetadata)
    395 	}
    396 }
    397 
    398 func TestPostThenDeleteMessage(t *testing.T) {
    399 
    400 	// make not paid
    401 	numMessagesToPost := (a.MessageResponseLimit + 7)
    402 	testMessages := make([]byte, 256*numMessagesToPost)
    403 	_, _ = rand.Read(testMessages)
    404 	mailbox.DeleteAllInboxEntriesFromDatabase(a.DB)
    405 
    406 	for i := 0; i < int(numMessagesToPost); i++ {
    407 		testMessage := testMessages[i*256 : (i+1)*256]
    408 		req, _ := http.NewRequest("POST", "/"+testAliceHashedSigningKeyString, bytes.NewReader(testMessage))
    409 		response := executeRequest(req)
    410 
    411 		checkResponseCode(t, http.StatusNoContent, response.Code)
    412 
    413 		body := response.Body.String()
    414 		if body != "" {
    415 			t.Errorf("Expected empty response, Got %s", body)
    416 		}
    417 	}
    418 
    419 	req, _ := http.NewRequest("GET", "/"+testAliceHashedSigningKeyString, nil)
    420 	response := executeRequest(req)
    421 
    422 	checkResponseCode(t, http.StatusOK, response.Code)
    423 
    424 	if response.Body.Len() != int(256*a.MessageResponseLimit) {
    425 		t.Errorf("Expected response of 25600 bytes, Got %d", response.Body.Len())
    426 	}
    427 
    428 	etag := response.Result().Header.Get("ETag")
    429 
    430 	if etag == "" {
    431 		t.Errorf("ETag missing!\n")
    432 	}
    433 
    434 	// Now  delete 10 messages
    435 	h := sha512.New()
    436 	for i := 0; i < int(a.MessageResponseLimit); i++ {
    437 		h.Write(testMessages[i*256 : (i+1)*256])
    438 	}
    439 	etagInt, _ := strconv.Atoi(etag)
    440 	var signedMsg [4 * 4]byte
    441 	binary.BigEndian.PutUint32(signedMsg[0:4], 4*4)
    442 	binary.BigEndian.PutUint32(signedMsg[4:8], gana.TalerSignaturePurposeMailboxMessagesDelete)
    443 	binary.BigEndian.PutUint32(signedMsg[8:12], uint32(etagInt))
    444 	binary.BigEndian.PutUint32(signedMsg[12:16], uint32(a.MessageResponseLimit))
    445 	sig := ed25519.Sign(testAliceSigningKeyPriv, signedMsg[0:])
    446 	if !ed25519.Verify(testAliceSigningKey, signedMsg[0:], sig) {
    447 		t.Errorf("Signature invalid!")
    448 	}
    449 	hAddress := util.Base32CrockfordEncode(testAliceSigningKey)
    450 	req, _ = http.NewRequest("DELETE", "/"+hAddress+"?count="+strconv.Itoa(int(a.MessageResponseLimit)), nil)
    451 	req.Header.Add("If-Match", etag)
    452 	req.Header.Add("Taler-Mailbox-Delete-Signature", util.Base32CrockfordEncode(sig))
    453 	response = executeRequest(req)
    454 
    455 	checkResponseCode(t, http.StatusNoContent, response.Code)
    456 
    457 	body := response.Body.String()
    458 	if body != "" {
    459 		t.Errorf("Expected empty response, Got %s", body)
    460 	}
    461 
    462 	req, _ = http.NewRequest("GET", "/"+testAliceHashedSigningKeyString, nil)
    463 	response = executeRequest(req)
    464 
    465 	checkResponseCode(t, http.StatusOK, response.Code)
    466 
    467 	if response.Body.Len() != int(256*7) {
    468 		t.Errorf("Expected response of 256*7 bytes, Got %d", response.Body.Len())
    469 	}
    470 }