taler-mailbox

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

main_test.go (12925B)


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