diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4984123..963d182 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,8 +4,8 @@ jobs:
test:
strategy:
matrix:
- go-version: [ 1.13.x, 1.14.x, 1.15.x ]
- os: [ ubuntu-latest ]
+ go-version: [ 1.13.x, 1.14.x, 1.15.x, 1.16.x ]
+ os: [ ubuntu-20.04 ]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
@@ -38,9 +38,7 @@ jobs:
- name: Go test
run: |
go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- - name: Upload coverage report
- uses: codecov/codecov-action@v1
+ - name: Upload coverage report to coverall
+ uses: shogo82148/actions-goveralls@v1
with:
- file: ./coverage.txt
- flags: unittests
- name: codecov-umbrella
+ path-to-profile: coverage.txt
diff --git a/.github/workflows/deploy_staging.yml b/.github/workflows/deploy_staging.yml
index f8377b7..a4a5278 100644
--- a/.github/workflows/deploy_staging.yml
+++ b/.github/workflows/deploy_staging.yml
@@ -16,7 +16,8 @@ jobs:
apt-get install -y zip awscli
- name: Build and zip
run: |
- CGO_ENABLED=0 GOOS=linux go build -o main cmd/lambda/main.go
+ COMMIT=$(shell git rev-parse HEAD)
+ CGO_ENABLED=0 GOOS=linux go build -o main -X 'github.com/bitmaelum/key-resolver-go/internal.GitCommit=${COMMIT}' cmd/lambda/main.go
zip -r ./function.zip main
- name: deploy zip to lambda
run: |
diff --git a/README.md b/README.md
index 5b3677f..3661e84 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,16 @@
+
+
[![Go Report Card](https://goreportcard.com/badge/github.com/bitmaelum/key-resolver-go)](https://goreportcard.com/report/github.com/bitmaelum/key-resolver-go)
-![BitMaelum CI](https://github.com/bitmaelum/key-resolver-go/workflows/BitMaelum%20CI/badge.svg?branch=develop)
-[![codecov](https://codecov.io/gh/bitmaelum/key-resolver-go/branch/develop/graph/badge.svg)](https://codecov.io/gh/bitmaelum/key-resolver-go)
+[![BitMaelum Key Resolver](https://github.com/bitmaelum/key-resolver-go/actions/workflows/ci.yml/badge.svg)](https://github.com/bitmaelum/key-resolver-go/actions/workflows/ci.yml)
+[![Coverage Status](https://coveralls.io/repos/github/bitmaelum/key-resolver-go/badge.svg?branch=master)](https://coveralls.io/github/bitmaelum/key-resolver-go?branch=master)
![License](https://img.shields.io/github/license/bitmaelum/key-resolver-go)
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/bitmaelum/key-resolver-go)
-[![Gitter](https://badges.gitter.im/bitmaelum/community.svg)](https://gitter.im/bitmaelum/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=bitmaelum_bitmaelum-suite&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=bitmaelum_bitmaelum-suite)
-
- ____ _ _ __ __ _
- | _ \(_) | | \/ | | |
- | |_) |_| |_| \ / | __ _ ___| |_ _ _ __ ___
- | _ <| | __| |\/| |/ _` |/ _ \ | | | | '_ ` _ \
- | |_) | | |_| | | | (_| | __/ | |_| | | | | | |
- |____/|_|\__|_| |_|\__,_|\___|_|\__,_|_| |_| |_|
- P r i v a c y i s y o u r s a g a i n
-# Key resolver
+
-[![codecov](https://codecov.io/gh/bitmaelum/key-resolver-go/branch/develop/graph/badge.svg?token=IHXRZZO8KQ)](undefined)
+# Key resolver
This repository holds the (centralized) account and routing resolver for BitMaelum.
diff --git a/cmd/bm-keyresolver/main.go b/cmd/bm-keyresolver/main.go
index b9c285e..4dc830f 100644
--- a/cmd/bm-keyresolver/main.go
+++ b/cmd/bm-keyresolver/main.go
@@ -117,6 +117,9 @@ func main() {
router.HandleFunc("/address/{hash}", requestWrapper(handler.DeleteAddressHash)).Methods("DELETE")
router.HandleFunc("/address/{hash}", requestWrapper(handler.PostAddressHash)).Methods("POST")
+ router.HandleFunc("/address/{hash}/status/{fingerprint}", requestWrapper(handler.GetKeyStatus)).Methods("GET")
+ router.HandleFunc("/address/{hash}/status/{fingerprint}", requestWrapper(handler.SetKeyStatus)).Methods("POST")
+
router.HandleFunc("/routing/{hash}", requestWrapper(handler.GetRoutingHash)).Methods("GET")
router.HandleFunc("/routing/{hash}", requestWrapper(handler.DeleteRoutingHash)).Methods("DELETE")
router.HandleFunc("/routing/{hash}", requestWrapper(handler.PostRoutingHash)).Methods("POST")
diff --git a/cmd/lambda/main.go b/cmd/lambda/main.go
index 7a3b756..679ae9a 100644
--- a/cmd/lambda/main.go
+++ b/cmd/lambda/main.go
@@ -34,6 +34,26 @@ import (
"github.com/bitmaelum/key-resolver-go/internal/http"
)
+type HandlerFunc func(hash.Hash, http.Request) *http.Response
+
+var handlerMapping = map[string]HandlerFunc{
+ "GET /address/{hash}": handler.GetAddressHash,
+ "POST /address/{hash}/delete": handler.SoftDeleteAddressHash,
+ "POST /address/{hash}/undelete": handler.SoftUndeleteAddressHash,
+ "GET /address/{hash}/status/{fingerprint}": handler.GetKeyStatus,
+ "POST /address/{hash}/status/{fingerprint}": handler.SetKeyStatus,
+ "DELETE /address/{hash}": handler.DeleteAddressHash,
+ "POST /address/{hash}": handler.PostAddressHash,
+ "GET /routing/{hash}": handler.GetRoutingHash,
+ "DELETE /routing/{hash}": handler.DeleteRoutingHash,
+ "POST /routing/{hash}": handler.PostRoutingHash,
+ "GET /organisation/{hash}": handler.GetOrganisationHash,
+ "POST /organisation/{hash}/delete": handler.SoftDeleteOrganisationHash,
+ "POST /organisation/{hash}/undelete": handler.SoftUndeleteOrganisationHash,
+ "DELETE /organisation/{hash}": handler.DeleteOrganisationHash,
+ "POST /organisation/{hash}": handler.PostOrganisationHash,
+}
+
// HandleRequest checks the incoming route and calls the correct handler for it
func HandleRequest(req events.APIGatewayV2HTTPRequest) (*events.APIGatewayV2HTTPResponse, error) {
if req.RouteKey == "GET /" {
@@ -53,30 +73,10 @@ func HandleRequest(req events.APIGatewayV2HTTPRequest) (*events.APIGatewayV2HTTP
var httpResp *http.Response
httpReq := apigateway.ReqToHTTP(&req)
- switch req.RouteKey {
- // Address endpoints
- case "GET /address/{hash}":
- httpResp = handler.GetAddressHash(*h, *httpReq)
- case "DELETE /address/{hash}":
- httpResp = handler.DeleteAddressHash(*h, *httpReq)
- case "POST /address/{hash}":
- httpResp = handler.PostAddressHash(*h, *httpReq)
-
- // Routing endpoints
- case "GET /routing/{hash}":
- httpResp = handler.GetRoutingHash(*h, *httpReq)
- case "DELETE /routing/{hash}":
- httpResp = handler.DeleteRoutingHash(*h, *httpReq)
- case "POST /routing/{hash}":
- httpResp = handler.PostRoutingHash(*h, *httpReq)
-
- // Organisation endpoints
- case "GET /organisation/{hash}":
- httpResp = handler.GetOrganisationHash(*h, *httpReq)
- case "DELETE /organisation/{hash}":
- httpResp = handler.DeleteOrganisationHash(*h, *httpReq)
- case "POST /organisation/{hash}":
- httpResp = handler.PostOrganisationHash(*h, *httpReq)
+ // Check mapping and call correct handler func
+ f, ok := handlerMapping[req.RouteKey]
+ if ok {
+ httpResp = f(*h, *httpReq)
}
if httpResp == nil {
diff --git a/cmd/lambda/main_test.go b/cmd/lambda/main_test.go
index d48ae85..8507cb6 100644
--- a/cmd/lambda/main_test.go
+++ b/cmd/lambda/main_test.go
@@ -69,3 +69,16 @@ func TestHandleRequest404(t *testing.T) {
assert.Equal(t, "application/json", res.Headers["Content-Type"])
assert.Equal(t, "{\n \"error\": \"Forbidden\"\n}", res.Body)
}
+
+func TestHandleConfig(t *testing.T) {
+ req := &events.APIGatewayV2HTTPRequest{
+ RouteKey: "GET /config.json",
+ }
+
+ res, err := HandleRequest(*req)
+ assert.NoError(t, err)
+
+ assert.Equal(t, 200, res.StatusCode)
+ assert.Equal(t, "application/json", res.Headers["Content-Type"])
+ assert.JSONEq(t, res.Body, "{\"proof_of_work\":{\"address\": 27,\"organisation\":29}}")
+}
diff --git a/go.mod b/go.mod
index 1391765..88fd405 100644
--- a/go.mod
+++ b/go.mod
@@ -6,9 +6,9 @@ require (
github.com/aws/aws-lambda-go v1.19.1
github.com/aws/aws-sdk-go v1.34.14
github.com/bitmaelum/bitmaelum-suite v0.0.0-20201115094342-919e00359ffc
- github.com/boltdb/bolt v1.3.1
github.com/gorilla/mux v1.7.4
github.com/gusaul/go-dynamock v0.0.0-20200325102056-aaeeb0c0e9c1
github.com/mattn/go-sqlite3 v1.14.1
github.com/stretchr/testify v1.6.1
+ go.etcd.io/bbolt v1.3.5
)
diff --git a/go.sum b/go.sum
index 2ed6c30..feb5571 100644
--- a/go.sum
+++ b/go.sum
@@ -15,8 +15,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bitmaelum/bitmaelum-suite v0.0.0-20201115094342-919e00359ffc h1:12qg9x0s9a38ru7UvggYKxTFnVt3zlDc5xwr8sJY4OY=
github.com/bitmaelum/bitmaelum-suite v0.0.0-20201115094342-919e00359ffc/go.mod h1:t5Rc5fsWnZsjIh3S2PW4xgl07+rPr/CA3QO1apvDHSc=
-github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
-github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -183,6 +181,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zalando/go-keyring v0.1.0/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -259,6 +258,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
diff --git a/internal/address/bolt.go b/internal/address/bolt.go
index fa8a941..cbb7715 100644
--- a/internal/address/bolt.go
+++ b/internal/address/bolt.go
@@ -23,21 +23,21 @@ import (
"encoding/json"
"time"
+ "github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
"github.com/bitmaelum/key-resolver-go/internal"
- "github.com/boltdb/bolt"
+ bolt "go.etcd.io/bbolt"
)
type boltResolver struct {
client *bolt.DB
- bucketName string
+ bucketName []byte
}
// NewBoltResolver returns a new resolver based on BoltDB
func NewBoltResolver() Repository {
-
return &boltResolver{
client: internal.GetBoltDb(),
- bucketName: "address",
+ bucketName: []byte("address"),
}
}
@@ -45,7 +45,7 @@ func (b boltResolver) Get(hash string) (*ResolveInfoType, error) {
rec := &ResolveInfoType{}
err := b.client.View(func(tx *bolt.Tx) error {
- bucket := tx.Bucket([]byte(b.bucketName))
+ bucket := tx.Bucket(b.bucketName)
if bucket == nil {
return ErrNotFound
}
@@ -65,9 +65,9 @@ func (b boltResolver) Get(hash string) (*ResolveInfoType, error) {
return rec, nil
}
-func (b boltResolver) Create(hash, routing, publicKey, proof string) (bool, error) {
+func (b boltResolver) Create(hash, routing string, publicKey *bmcrypto.PubKey, proof string) (bool, error) {
err := b.client.Update(func(tx *bolt.Tx) error {
- bucket, err := tx.CreateBucketIfNotExists([]byte(b.bucketName))
+ bucket, err := tx.CreateBucketIfNotExists(b.bucketName)
if err != nil {
return err
}
@@ -75,15 +75,111 @@ func (b boltResolver) Create(hash, routing, publicKey, proof string) (bool, erro
rec := &ResolveInfoType{
Hash: hash,
RoutingID: routing,
- PubKey: publicKey,
+ PubKey: publicKey.String(),
Proof: proof,
Serial: uint64(time.Now().UnixNano()),
+ Deleted: false,
+ DeletedAt: time.Time{},
}
buf, err := json.Marshal(rec)
if err != nil {
return err
}
+ err = bucket.Put([]byte(hash), buf)
+ if err != nil {
+ return err
+ }
+
+ // Store in history
+ bucket, err = tx.CreateBucketIfNotExists([]byte(hash + "fingerprints"))
+ if err != nil {
+ return err
+ }
+
+ b, err := json.Marshal(KSNormal)
+ if err != nil {
+ return err
+ }
+ return bucket.Put([]byte(publicKey.Fingerprint()), b)
+ })
+
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
+
+func (b boltResolver) Update(info *ResolveInfoType, routing string, publicKey *bmcrypto.PubKey) (bool, error) {
+ err := b.client.Update(func(tx *bolt.Tx) error {
+ bucket := tx.Bucket(b.bucketName)
+ if bucket == nil {
+ return nil
+ }
+
+ rec, err := getFromBucket(bucket, info.Hash)
+ if err != nil {
+ return ErrNotFound
+ }
+
+ if rec.Serial != info.Serial {
+ return ErrNotFound
+ }
+
+ rec.RoutingID = routing
+ rec.PubKey = publicKey.String()
+ buf, err := json.Marshal(rec)
+ if err != nil {
+ return err
+ }
+
+ err = bucket.Put([]byte(info.Hash), buf)
+ if err != nil {
+ return err
+ }
+
+ // Store in history (overwrite if already exists)
+ bucket, err = tx.CreateBucketIfNotExists([]byte(info.Hash + "fingerprints"))
+ if err != nil {
+ return err
+ }
+
+ b, err := json.Marshal(KSNormal)
+ if err != nil {
+ return err
+ }
+ return bucket.Put([]byte(publicKey.Fingerprint()), b)
+ })
+
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
+
+func (b boltResolver) SoftDelete(hash string) (bool, error) {
+ err := b.client.Update(func(tx *bolt.Tx) error {
+ bucket := tx.Bucket(b.bucketName)
+ if bucket == nil {
+ return nil
+ }
+
+ rec, err := getFromBucket(bucket, hash)
+ if err != nil {
+ return ErrNotFound
+ }
+
+ // make record deleted
+ rec.Deleted = true
+ rec.DeletedAt = time.Now()
+
+ // Store
+ buf, err := json.Marshal(rec)
+ if err != nil {
+ return err
+ }
return bucket.Put([]byte(hash), buf)
})
@@ -94,13 +190,40 @@ func (b boltResolver) Create(hash, routing, publicKey, proof string) (bool, erro
return true, nil
}
-func (b boltResolver) Update(info *ResolveInfoType, routing, publicKey string) (bool, error) {
- return b.Create(info.Hash, routing, publicKey, info.Proof)
+func (b boltResolver) SoftUndelete(hash string) (bool, error) {
+ err := b.client.Update(func(tx *bolt.Tx) error {
+ bucket := tx.Bucket(b.bucketName)
+ if bucket == nil {
+ return nil
+ }
+
+ rec, err := getFromBucket(bucket, hash)
+ if err != nil {
+ return ErrNotFound
+ }
+
+ // undelete
+ rec.Deleted = false
+ rec.DeletedAt = time.Time{}
+
+ // Store
+ buf, err := json.Marshal(rec)
+ if err != nil {
+ return err
+ }
+ return bucket.Put([]byte(hash), buf)
+ })
+
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
}
func (b boltResolver) Delete(hash string) (bool, error) {
err := b.client.Update(func(tx *bolt.Tx) error {
- bucket := tx.Bucket([]byte(b.bucketName))
+ bucket := tx.Bucket(b.bucketName)
if bucket == nil {
return nil
}
@@ -114,3 +237,65 @@ func (b boltResolver) Delete(hash string) (bool, error) {
return true, nil
}
+
+func (b boltResolver) GetKeyStatus(hash string, fingerprint string) (KeyStatus, error) {
+ var ks KeyStatus
+
+ err := b.client.View(func(tx *bolt.Tx) error {
+ bucket := tx.Bucket([]byte(hash + "fingerprints"))
+ if bucket == nil {
+ return ErrNotFound
+ }
+
+ result := bucket.Get([]byte(fingerprint))
+ if result == nil {
+ return ErrNotFound
+ }
+
+ err := json.Unmarshal(result, &ks)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+
+ return ks, err
+}
+
+func (b boltResolver) SetKeyStatus(hash string, fingerprint string, status KeyStatus) error {
+ return b.client.Update(func(tx *bolt.Tx) error {
+ bucket := tx.Bucket([]byte(hash + "fingerprints"))
+ if bucket == nil {
+ return nil
+ }
+
+ // Check if hash+fingerprint exist
+ result := bucket.Get([]byte(fingerprint))
+ if result == nil {
+ return ErrNotFound
+ }
+
+ b, err := json.Marshal(status)
+ if err != nil {
+ return err
+ }
+
+ return bucket.Put([]byte(fingerprint), b)
+ })
+}
+
+func getFromBucket(bucket *bolt.Bucket, hash string) (*ResolveInfoType, error) {
+ data := bucket.Get([]byte(hash))
+ if data == nil {
+ return nil, ErrNotFound
+ }
+
+ rec := &ResolveInfoType{}
+ err := json.Unmarshal(data, &rec)
+ if err != nil {
+ return nil, ErrNotFound
+ }
+
+ return rec, nil
+}
diff --git a/internal/address/bolt_test.go b/internal/address/bolt_test.go
new file mode 100644
index 0000000..47efa49
--- /dev/null
+++ b/internal/address/bolt_test.go
@@ -0,0 +1,56 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package address
+
+import (
+ "fmt"
+ "math/rand"
+ "os"
+ "testing"
+)
+
+const tmpDbPath = "/tmp/mockboltdb-%d.db"
+
+func TestBoltResolver(t *testing.T) {
+ // Random path, otherwise we get into issues with running on github actions?
+ p := fmt.Sprintf(tmpDbPath, rand.Int63())
+
+ _ = os.Setenv("USE_BOLT", "1")
+ _ = os.Setenv("BOLT_DB_FILE", p)
+ SetDefaultRepository(nil)
+
+ _ = os.Remove(p)
+ db := NewBoltResolver()
+ runRepositoryCreateUpdateTest(t, db)
+
+ _ = os.Remove(p)
+ db = NewBoltResolver()
+ runRepositoryDeletionTests(t, db)
+
+ _ = os.Remove(p)
+ db = NewBoltResolver()
+ runRepositoryHistoryCheck(t, db)
+
+ _ = os.Remove(p)
+ db = NewBoltResolver()
+ runRepositoryHistoryKeyStatus(t, db)
+
+ _ = os.Remove(p)
+}
diff --git a/internal/address/dynamodb.go b/internal/address/dynamodb.go
index d1e2c5e..aad2923 100644
--- a/internal/address/dynamodb.go
+++ b/internal/address/dynamodb.go
@@ -29,40 +29,54 @@ import (
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
+ "github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
)
type dynamoDbResolver struct {
- Dyna dynamodbiface.DynamoDBAPI
- TableName string
+ Dyna dynamodbiface.DynamoDBAPI
+ TableName string
+ HistoryTableName string
}
-// ErrNotFound will be returned when a record we are looking for is not found in the db
-var ErrNotFound = errors.New("record not found")
+// Error codes
+var (
+ ErrNotFound = errors.New("record not found")
+ ErrCannotUpdate = errors.New("cannot update record")
+)
-// Record holds a DynamoDB record
-type Record struct {
+// record holds a DynamoDB record
+type recordType struct {
Hash string `dynamodbav:"hash"`
Routing string `dynamodbav:"routing"`
PublicKey string `dynamodbav:"public_key"`
Proof string `dynamodbav:"proof"`
Serial uint64 `dynamodbav:"sn"`
+ Deleted bool `dynamodbav:"deleted"`
+ DeletedAt uint64 `dynamodbav:"deleted_at"`
+}
+
+type historyRecordType struct {
+ Hash string `dynamodbav:"hash"`
+ Fingerprint string `dynamodbav:"fingerprint"`
+ Status KeyStatus `dynamodbav:"status"`
}
// NewDynamoDBResolver returns a new resolver based on DynamoDB
-func NewDynamoDBResolver(client dynamodbiface.DynamoDBAPI, tableName string) Repository {
+func NewDynamoDBResolver(client dynamodbiface.DynamoDBAPI, tableName, historyTableName string) Repository {
return &dynamoDbResolver{
- Dyna: client,
- TableName: tableName,
+ Dyna: client,
+ TableName: tableName,
+ HistoryTableName: historyTableName,
}
}
-func (r *dynamoDbResolver) Update(info *ResolveInfoType, routing, publicKey string) (bool, error) {
+func (r *dynamoDbResolver) Update(info *ResolveInfoType, routing string, publicKey *bmcrypto.PubKey) (bool, error) {
serial := strconv.FormatUint(uint64(time.Now().UnixNano()), 10)
input := &dynamodb.UpdateItemInput{
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":s": {S: aws.String(routing)},
- ":pk": {S: aws.String(publicKey)},
+ ":pk": {S: aws.String(publicKey.String())},
":sn": {N: aws.String(serial)},
":csn": {N: aws.String(strconv.FormatUint(info.Serial, 10))},
},
@@ -74,7 +88,14 @@ func (r *dynamoDbResolver) Update(info *ResolveInfoType, routing, publicKey stri
},
}
- _, err := r.Dyna.UpdateItem(input)
+ // Update key history
+ _, err := r.updateKeyHistory(info.Hash, publicKey.Fingerprint(), KSNormal)
+ if err != nil {
+ return false, err
+ }
+
+ // Update address record
+ _, err = r.Dyna.UpdateItem(input)
if err != nil {
log.Print(err)
return false, err
@@ -83,11 +104,11 @@ func (r *dynamoDbResolver) Update(info *ResolveInfoType, routing, publicKey stri
return true, nil
}
-func (r *dynamoDbResolver) Create(hash, routing, publicKey, proof string) (bool, error) {
- record := Record{
+func (r *dynamoDbResolver) Create(hash, routing string, publicKey *bmcrypto.PubKey, proof string) (bool, error) {
+ record := recordType{
Hash: hash,
Routing: routing,
- PublicKey: publicKey,
+ PublicKey: publicKey.String(),
Proof: proof,
Serial: uint64(TimeNow().UnixNano()),
}
@@ -103,6 +124,13 @@ func (r *dynamoDbResolver) Create(hash, routing, publicKey, proof string) (bool,
TableName: aws.String(r.TableName),
}
+ // Update key history
+ _, err = r.updateKeyHistory(hash, publicKey.Fingerprint(), KSNormal)
+ if err != nil {
+ return false, err
+ }
+
+ // Create address record
_, err = r.Dyna.PutItem(input)
return err == nil, err
}
@@ -125,13 +153,18 @@ func (r *dynamoDbResolver) Get(hash string) (*ResolveInfoType, error) {
return nil, ErrNotFound
}
- record := Record{}
+ record := recordType{}
err = dynamodbattribute.UnmarshalMap(result.Item, &record)
if err != nil {
log.Print(err)
return nil, ErrNotFound
}
+ // We would prefer if we didn't retrieve it from the Getitem input
+ if record.Deleted {
+ return nil, ErrNotFound
+ }
+
return &ResolveInfoType{
Hash: record.Hash,
RoutingID: record.Routing,
@@ -157,3 +190,111 @@ func (r *dynamoDbResolver) Delete(hash string) (bool, error) {
return true, nil
}
+
+func (r *dynamoDbResolver) SoftDelete(hash string) (bool, error) {
+ input := &dynamodb.UpdateItemInput{
+ ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
+ ":dt": {N: aws.String(strconv.FormatInt(time.Now().Unix(), 10))},
+ },
+ TableName: aws.String(r.TableName),
+ UpdateExpression: aws.String("SET deleted=1, deleted_at=:dt"),
+ Key: map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String(hash)},
+ },
+ }
+
+ _, err := r.Dyna.UpdateItem(input)
+ if err != nil {
+ log.Print(err)
+ return false, err
+ }
+
+ return true, nil
+}
+
+func (r *dynamoDbResolver) SoftUndelete(hash string) (bool, error) {
+ input := &dynamodb.UpdateItemInput{
+ ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
+ ":dt": {N: aws.String("")},
+ },
+ TableName: aws.String(r.TableName),
+ UpdateExpression: aws.String("SET deleted=0, deleted_at=:dt"),
+ Key: map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String(hash)},
+ },
+ }
+
+ _, err := r.Dyna.UpdateItem(input)
+ if err != nil {
+ log.Print(err)
+ return false, err
+ }
+
+ return true, nil
+}
+
+func (r *dynamoDbResolver) GetKeyStatus(hash string, fingerprint string) (KeyStatus, error) {
+ result, err := r.Dyna.GetItem(&dynamodb.GetItemInput{
+ TableName: aws.String(r.HistoryTableName),
+ Key: map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String(hash)},
+ "fingerprint": {S: aws.String(fingerprint)},
+ },
+ })
+ // Error while fetching record
+ if err != nil {
+ return KSNormal, err
+ }
+
+ // Item not found
+ if result.Item == nil {
+ return KSNormal, ErrNotFound
+ }
+
+ record := historyRecordType{}
+ err = dynamodbattribute.UnmarshalMap(result.Item, &record)
+ if err != nil {
+ log.Print(err)
+ return KSNormal, ErrNotFound
+ }
+
+ return record.Status, nil
+}
+
+func (r *dynamoDbResolver) SetKeyStatus(hash string, fingerprint string, status KeyStatus) error {
+ // Make sure key exists before updating
+ _, err := r.GetKeyStatus(hash, fingerprint)
+ if err != nil {
+ return err
+ }
+
+ ok, err := r.updateKeyHistory(hash, fingerprint, status)
+ if err != nil {
+ return err
+ }
+
+ if !ok {
+ return ErrCannotUpdate
+ }
+
+ return nil
+}
+
+func (r *dynamoDbResolver) updateKeyHistory(hash, fingerprint string, status KeyStatus) (bool, error) {
+ av, err := dynamodbattribute.MarshalMap(historyRecordType{
+ Hash: hash,
+ Fingerprint: fingerprint,
+ Status: status,
+ })
+ if err != nil {
+ return false, err
+ }
+
+ input := &dynamodb.PutItemInput{
+ Item: av,
+ TableName: aws.String(r.HistoryTableName),
+ }
+
+ _, err = r.Dyna.PutItem(input)
+ return err == nil, err
+}
diff --git a/internal/address/dynamodb_test.go b/internal/address/dynamodb_test.go
index 4cb2515..8150de1 100644
--- a/internal/address/dynamodb_test.go
+++ b/internal/address/dynamodb_test.go
@@ -20,12 +20,15 @@
package address
import (
+ "fmt"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
+ "github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
+ "github.com/bitmaelum/bitmaelum-suite/pkg/hash"
dynamock "github.com/gusaul/go-dynamock"
"github.com/stretchr/testify/assert"
)
@@ -37,7 +40,7 @@ var (
func TestGet(t *testing.T) {
var client dynamodbiface.DynamoDBAPI
client, mock = dynamock.New()
- resolver := NewDynamoDBResolver(client, "mock_address_table")
+ resolver := NewDynamoDBResolver(client, "mock_address_table", "mock_history_table")
ri, err := resolver.Get("cf99b895f350b77585881438ab38a935e68c9c7409c5adaad23fb17572ca1ea2")
assert.Error(t, err)
@@ -86,7 +89,7 @@ func TestGet(t *testing.T) {
func TestDelete(t *testing.T) {
var client dynamodbiface.DynamoDBAPI
client, mock = dynamock.New()
- resolver := NewDynamoDBResolver(client, "mock_address_table")
+ resolver := NewDynamoDBResolver(client, "mock_address_table", "mock_history_table")
// No record found
result := dynamodb.DeleteItemOutput{
@@ -106,13 +109,18 @@ func TestDelete(t *testing.T) {
func TestCreate(t *testing.T) {
var client dynamodbiface.DynamoDBAPI
client, mock = dynamock.New()
- resolver := NewDynamoDBResolver(client, "mock_address_table")
+ resolver := NewDynamoDBResolver(client, "mock_address_table", "mock_history_table")
TimeNow = func() time.Time {
return time.Date(2010, 05, 10, 12, 34, 56, 0, time.UTC)
}
- result := dynamodb.PutItemOutput{}
+ historyItems := map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String("cf99b895f350b77585881438ab38a935e68c9c7409c5adaad23fb17572ca1ea2")},
+ "fingerprint": {S: aws.String("b74bb232a9ea0154c10f275da4be8a4233fcf7c3bc42038206fe527cb566f758")},
+ "status": {N: aws.String(fmt.Sprintf("%d", KSNormal))},
+ }
+ mock.ExpectPutItem().ToTable("mock_history_table").WithItems(historyItems).WillReturns(dynamodb.PutItemOutput{})
items := map[string]*dynamodb.AttributeValue{
"hash": {S: aws.String("cf99b895f350b77585881438ab38a935e68c9c7409c5adaad23fb17572ca1ea2")},
@@ -120,9 +128,13 @@ func TestCreate(t *testing.T) {
"public_key": {S: aws.String("ed25519 MCowBQYDK2VwAyEAS2/hs2jf0QJgpuNklMnN/A7EHj26DDpRfvcZyettOjU=")},
"routing": {S: aws.String("12345678")},
"sn": {N: aws.String("1273494896000000000")},
+ "deleted_at": {N: aws.String("0")},
+ "deleted": {BOOL: aws.Bool(false)},
}
- mock.ExpectPutItem().ToTable("mock_address_table").WithItems(items).WillReturns(result)
- ok, err := resolver.Create("cf99b895f350b77585881438ab38a935e68c9c7409c5adaad23fb17572ca1ea2", "12345678", "ed25519 MCowBQYDK2VwAyEAS2/hs2jf0QJgpuNklMnN/A7EHj26DDpRfvcZyettOjU=", "proof")
+ mock.ExpectPutItem().ToTable("mock_address_table").WithItems(items).WillReturns(dynamodb.PutItemOutput{})
+
+ pubkey, _ := bmcrypto.NewPubKey("ed25519 MCowBQYDK2VwAyEAS2/hs2jf0QJgpuNklMnN/A7EHj26DDpRfvcZyettOjU=")
+ ok, err := resolver.Create("cf99b895f350b77585881438ab38a935e68c9c7409c5adaad23fb17572ca1ea2", "12345678", pubkey, "proof")
assert.NoError(t, err)
assert.True(t, ok)
}
@@ -130,7 +142,9 @@ func TestCreate(t *testing.T) {
func TestUpdate(t *testing.T) {
var client dynamodbiface.DynamoDBAPI
client, mock = dynamock.New()
- resolver := NewDynamoDBResolver(client, "mock_address_table")
+ resolver := NewDynamoDBResolver(client, "mock_address_table", "mock_history_table")
+
+ pubkey, _ := bmcrypto.NewPubKey("ed25519 MCowBQYDK2VwAyEAS2/hs2jf0QJgpuNklMnN/A7EHj26DDpRfvcZyettOjU=")
expectKey := map[string]*dynamodb.AttributeValue{
"hash": {
@@ -139,18 +153,75 @@ func TestUpdate(t *testing.T) {
}
mock.ExpectUpdateItem().ToTable("mock_address_table").WithKeys(expectKey)
+ expectedItems := map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String("cf99b895f350b77585881438ab38a935e68c9c7409c5adaad23fb17572ca1ea2")},
+ "fingerprint": {S: aws.String(pubkey.Fingerprint())},
+ "status": {N: aws.String(fmt.Sprintf("%d", KSNormal))},
+ }
+ mock.ExpectPutItem().ToTable("mock_history_table").WithItems(expectedItems)
+
info := &ResolveInfoType{
Hash: "cf99b895f350b77585881438ab38a935e68c9c7409c5adaad23fb17572ca1ea2",
RoutingID: "12345678",
- PubKey: "ed25519 MCowBQYDK2VwAyEAS2/hs2jf0QJgpuNklMnN/A7EHj26DDpRfvcZyettOjU=",
+ PubKey: pubkey.String(),
Proof: "proof",
Serial: 1273494896000000000,
}
- ok, err := resolver.Update(info, "555555555", "pubkey222")
+ ok, err := resolver.Update(info, "555555555", pubkey)
assert.NoError(t, err)
assert.True(t, ok)
}
+func TestHistory(t *testing.T) {
+ var client dynamodbiface.DynamoDBAPI
+ client, mock = dynamock.New()
+ resolver := NewDynamoDBResolver(client, "mock_address_table", "mock_history_table")
+
+ addrHash := hash.Hash("addr1")
+
+ _, pub1, _ := bmcrypto.GenerateKeyPair(bmcrypto.KeyTypeED25519)
+
+ // Cannot set key status when it doesn't exist yet
+ err := resolver.SetKeyStatus(addrHash.String(), pub1.Fingerprint(), KSNormal)
+ assert.Error(t, err)
+
+ // Set first key to normal
+
+ expectedItems := map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String(addrHash.String())},
+ "fingerprint": {S: aws.String(pub1.Fingerprint())},
+ "status": {N: aws.String(fmt.Sprintf("%d", KSNormal))},
+ }
+ mock.ExpectPutItem().ToTable("mock_history_table").WithItems(expectedItems)
+
+ result := dynamodb.GetItemOutput{
+ Item: map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String(addrHash.String())},
+ "fingerprint": {S: aws.String(pub1.Fingerprint())},
+ "status": {N: aws.String("1")},
+ },
+ }
+ mock.ExpectGetItem().ToTable("mock_history_table").WillReturns(result)
+
+ err = resolver.SetKeyStatus(addrHash.String(), pub1.Fingerprint(), KSNormal)
+ assert.NoError(t, err)
+
+ // Set second key to compromised
+ _, pub2, _ := bmcrypto.GenerateKeyPair(bmcrypto.KeyTypeED25519)
+
+ expectedItems = map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String(addrHash.String())},
+ "fingerprint": {S: aws.String(pub2.Fingerprint())},
+ "status": {N: aws.String(fmt.Sprintf("%d", KSCompromised))},
+ }
+ mock.ExpectPutItem().ToTable("mock_history_table").WithItems(expectedItems)
+
+ mock.ExpectGetItem().ToTable("mock_history_table").WillReturns(result)
+
+ err = resolver.SetKeyStatus(addrHash.String(), pub2.Fingerprint(), KSCompromised)
+ assert.NoError(t, err)
+}
+
func TestResolver(t *testing.T) {
r := GetResolveRepository()
assert.NotNil(t, r)
diff --git a/internal/address/repository.go b/internal/address/repository.go
index 04a9be5..e91dce1 100644
--- a/internal/address/repository.go
+++ b/internal/address/repository.go
@@ -20,10 +20,13 @@
package address
import (
+ "errors"
"os"
+ "time"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
+ "github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
)
// ResolveInfoType returns information found in the resolver repository
@@ -33,14 +36,55 @@ type ResolveInfoType struct {
PubKey string
Proof string
Serial uint64
+ Deleted bool
+ DeletedAt time.Time
+}
+
+type KeyStatus int
+
+const (
+ KSNormal KeyStatus = iota + 1 // Regular key, just rotated
+ KSCompromised // Key was compromised
+)
+
+var keyStatusMap = map[KeyStatus]string{
+ KSNormal: "normal",
+ KSCompromised: "compromised",
+}
+
+func (k KeyStatus) ToString() string {
+ return keyStatusMap[k]
+}
+
+func StringToKeyStatus(s string) (KeyStatus, error) {
+ for i := range keyStatusMap {
+ if keyStatusMap[i] == s {
+ return i, nil
+ }
+ }
+
+ return 0, errors.New("keystatus not found")
}
// Repository to resolve records
type Repository interface {
+ // Retrieve from hash
Get(hash string) (*ResolveInfoType, error)
- Create(hash, routing, publicKey, proof string) (bool, error)
- Update(info *ResolveInfoType, routing, publicKey string) (bool, error)
+ // Create a new entry
+ Create(hash, routing string, publicKey *bmcrypto.PubKey, proof string) (bool, error)
+ // Update an existing entry
+ Update(info *ResolveInfoType, routing string, publicKey *bmcrypto.PubKey) (bool, error)
+ // Softdelete an entry
+ SoftDelete(hash string) (bool, error)
+ // Undelete a softdeleted entry
+ SoftUndelete(hash string) (bool, error)
+ // Remove the entry completely (destructive)
Delete(hash string) (bool, error)
+
+ // Get the status of this (old) key
+ GetKeyStatus(hash string, fingerprint string) (KeyStatus, error)
+ // Set the given key status
+ SetKeyStatus(hash string, fingerprint string, status KeyStatus) error
}
var resolver Repository
@@ -60,7 +104,7 @@ func GetResolveRepository() Repository {
SharedConfigState: session.SharedConfigEnable,
}))
- resolver = NewDynamoDBResolver(dynamodb.New(sess), os.Getenv("ADDRESS_TABLE_NAME"))
+ resolver = NewDynamoDBResolver(dynamodb.New(sess), os.Getenv("ADDRESS_TABLE_NAME"), os.Getenv("HISTORY_TABLE_NAME"))
return resolver
}
diff --git a/internal/address/repository_test.go b/internal/address/repository_test.go
index 4352f1d..c116edf 100644
--- a/internal/address/repository_test.go
+++ b/internal/address/repository_test.go
@@ -22,23 +22,280 @@ package address
import (
"os"
+ "github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
+ "github.com/bitmaelum/bitmaelum-suite/pkg/hash"
+ testing2 "github.com/bitmaelum/key-resolver-go/internal/testing"
"github.com/stretchr/testify/assert"
"testing"
)
func TestDynamoRepo(t *testing.T) {
+ _ = os.Setenv("USE_BOLT", "0")
_ = os.Setenv("ADDRESS_TABLE_NAME", "mock")
+ SetDefaultRepository(nil)
r := GetResolveRepository()
- assert.IsType(t, r, NewDynamoDBResolver(nil, ""))
+ assert.IsType(t, r, NewDynamoDBResolver(nil, "", ""))
}
-func TestBoltResolver(t *testing.T) {
+func TestBoltResolverRepo(t *testing.T) {
_ = os.Setenv("USE_BOLT", "1")
- _ = os.Setenv("BOLT_DB_FILE", "./mockdb.db")
+ _ = os.Setenv("BOLT_DB_FILE", "/tmp/mockdb.db")
SetDefaultRepository(nil)
r := GetResolveRepository()
assert.IsType(t, r, NewBoltResolver())
}
+
+func runRepositoryHistoryCheck(t *testing.T, db Repository) {
+ h1 := hash.Hash("address1!")
+ h2 := hash.Hash("address2!")
+
+ _, pub1, _ := testing2.ReadTestKey("../../testdata/key-1.json")
+ _, pub2, _ := testing2.ReadTestKey("../../testdata/key-2.json")
+ _, pub3, _ := testing2.ReadTestKey("../../testdata/key-3.json")
+ _, pub4, _ := testing2.ReadTestKey("../../testdata/key-4.json")
+
+ ok, err := db.Create(h1.String(), "12345678", pub1, "proof")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Correct key
+ res, err := db.GetKeyStatus(h1.String(), pub1.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+ // Other key
+ _, err = db.GetKeyStatus(h1.String(), pub2.Fingerprint())
+ assert.Error(t, err)
+
+ // Other account
+ _, err = db.GetKeyStatus(h2.String(), pub1.Fingerprint())
+ assert.Error(t, err)
+
+ // update key
+ info, _ := db.Get(h1.String())
+ ok, err = db.Update(info, "12345678", pub2)
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Correct key
+ res, err = db.GetKeyStatus(h1.String(), pub1.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+ // Other key is correct as well now
+ res, err = db.GetKeyStatus(h1.String(), pub2.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+ // Other account still not the key
+ _, err = db.GetKeyStatus(h2.String(), pub1.Fingerprint())
+ assert.Error(t, err)
+
+ // update key on other account
+ ok, err = db.Create(h2.String(), "12345678", pub3, "proof")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Correct key
+ res, err = db.GetKeyStatus(h1.String(), pub1.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+ // Other key is correct as well now
+ res, err = db.GetKeyStatus(h1.String(), pub2.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+ // Other account still not the key
+ _, err = db.GetKeyStatus(h2.String(), pub1.Fingerprint())
+ assert.Error(t, err)
+
+ // Other account has other key
+ res, err = db.GetKeyStatus(h2.String(), pub3.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+ // Update first key again
+ info, _ = db.Get(h1.String())
+ ok, err = db.Update(info, "12345678", pub4)
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Correct key
+ res, err = db.GetKeyStatus(h1.String(), pub1.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+ // Other key is correct as well now
+ res, err = db.GetKeyStatus(h1.String(), pub2.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+ // Pub3 is not here
+ _, err = db.GetKeyStatus(h1.String(), pub3.Fingerprint())
+ assert.Error(t, err)
+
+ res, err = db.GetKeyStatus(h1.String(), pub4.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+}
+
+func runRepositoryHistoryKeyStatus(t *testing.T, db Repository) {
+ h1 := hash.Hash("address1!")
+
+ _, pub1, _ := testing2.ReadTestKey("../../testdata/key-1.json")
+ _, pub2, _ := testing2.ReadTestKey("../../testdata/key-2.json")
+
+ ok, err := db.Create(h1.String(), "12345678", pub1, "proof")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Correct key in normal state
+ res, err := db.GetKeyStatus(h1.String(), pub1.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+ // Rotate key
+ info, _ := db.Get(h1.String())
+ ok, err = db.Update(info, "12345678", pub2)
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Set compromised key status of key 1
+ err = db.SetKeyStatus(h1.String(), pub1.Fingerprint(), KSCompromised)
+ assert.NoError(t, err)
+
+ // First key is compromised
+ res, err = db.GetKeyStatus(h1.String(), pub1.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSCompromised, res)
+
+ // Second key is normal
+ res, err = db.GetKeyStatus(h1.String(), pub2.Fingerprint())
+ assert.NoError(t, err)
+ assert.Equal(t, KSNormal, res)
+
+}
+
+func runRepositoryCreateUpdateTest(t *testing.T, db Repository) {
+ h1 := hash.Hash("address1!")
+ h2 := hash.Hash("address2!")
+
+ _, pubkey, _ := bmcrypto.GenerateKeyPair("ed25519")
+
+ // Create key
+ ok, err := db.Create(h1.String(), "12345678", pubkey, "proof")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Fetch unknown hash
+ info, err := db.Get(h2.String())
+ assert.Error(t, err)
+ assert.Nil(t, info)
+
+ // Fetch created hash
+ info, err = db.Get(h1.String())
+ assert.NoError(t, err)
+ assert.Equal(t, "12345678", info.RoutingID)
+ assert.Equal(t, h1.String(), info.Hash)
+ assert.Equal(t, pubkey.String(), info.PubKey)
+ assert.Equal(t, "proof", info.Proof)
+
+ _, pubkey2, _ := bmcrypto.GenerateKeyPair("ed25519")
+
+ // Update info
+ ok, err = db.Update(info, "11112222", pubkey2)
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Fetch info
+ info, err = db.Get(h1.String())
+ assert.NoError(t, err)
+ assert.Equal(t, "11112222", info.RoutingID)
+ assert.Equal(t, h1.String(), info.Hash)
+ assert.Equal(t, pubkey2.String(), info.PubKey)
+ assert.Equal(t, "proof", info.Proof)
+
+ _, pubkey3, _ := bmcrypto.GenerateKeyPair("ed25519")
+
+ // Try and update with incorrect serial number
+ info.Serial = 1234
+ ok, err = db.Update(info, "88881111", pubkey3)
+ assert.False(t, ok)
+ assert.Error(t, err)
+
+ // Read back unmodified info
+ info, err = db.Get(h1.String())
+ assert.NoError(t, err)
+ assert.Equal(t, "11112222", info.RoutingID)
+ assert.Equal(t, h1.String(), info.Hash)
+ assert.Equal(t, pubkey2.String(), info.PubKey)
+ assert.Equal(t, "proof", info.Proof)
+}
+
+func runRepositoryDeletionTests(t *testing.T, db Repository) {
+ h1 := hash.Hash("address1!")
+ h2 := hash.Hash("address2!")
+
+ _, pubkey, _ := bmcrypto.GenerateKeyPair("ed25519")
+
+ // Create key
+ ok, err := db.Create(h1.String(), "12345678", pubkey, "proof")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Fetch created hash
+ info, err := db.Get(h1.String())
+ assert.NoError(t, err)
+ assert.NotNil(t, info)
+
+ // Try and softdelete unknown
+ ok, err = db.SoftDelete(h2.String())
+ assert.Error(t, err)
+ assert.False(t, ok)
+
+ // Softdelete known entry
+ ok, err = db.SoftDelete(h1.String())
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ info, err = db.Get(h1.String())
+ assert.NoError(t, err)
+ assert.NotNil(t, info)
+
+ // Softdelete again
+ ok, err = db.SoftDelete(h1.String())
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Undelete unknown
+ ok, err = db.SoftUndelete(h2.String())
+ assert.Error(t, err)
+ assert.False(t, ok)
+
+ // undelete known
+ ok, err = db.SoftUndelete(h1.String())
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ info, err = db.Get(h1.String())
+ assert.NoError(t, err)
+ assert.Equal(t, h1.String(), info.Hash)
+
+ // permanently delete known
+ ok, err = db.Delete(h1.String())
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // cannot undelete
+ ok, err = db.SoftUndelete(h1.String())
+ assert.Error(t, err)
+ assert.False(t, ok)
+
+ info, err = db.Get(h1.String())
+ assert.Error(t, err)
+ assert.Nil(t, info)
+}
diff --git a/internal/address/sqlite.go b/internal/address/sqlite.go
index 793231f..b643e68 100644
--- a/internal/address/sqlite.go
+++ b/internal/address/sqlite.go
@@ -20,6 +20,7 @@
package address
import (
+ "errors"
"fmt"
"strconv"
"strings"
@@ -27,6 +28,7 @@ import (
"database/sql"
+ "github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
_ "github.com/mattn/go-sqlite3" // SQLite driver
)
@@ -38,7 +40,7 @@ type SqliteDbResolver struct {
}
// NewDynamoDBResolver returns a new resolver based on DynamoDB
-func NewSqliteResolver(dsn string) *SqliteDbResolver {
+func NewSqliteResolver(dsn string) Repository {
if !strings.HasPrefix(dsn, "file:") {
if dsn == ":memory:" {
dsn = "file::memory:?mode=memory"
@@ -58,7 +60,12 @@ func NewSqliteResolver(dsn string) *SqliteDbResolver {
TimeNow: time.Now(),
}
- _, err = db.conn.Exec("CREATE TABLE IF NOT EXISTS mock_address (hash VARCHAR(64) PRIMARY KEY, pubkey TEXT, routing_id VARCHAR(64), proof TEXT, serial INT)")
+ _, err = db.conn.Exec("CREATE TABLE IF NOT EXISTS mock_address (hash VARCHAR(64) PRIMARY KEY, pubkey TEXT, routing_id VARCHAR(64), proof TEXT, serial INTEGER, deleted INTEGER, deleted_at INTEGER)")
+ if err != nil {
+ return nil
+ }
+
+ _, err = db.conn.Exec("CREATE TABLE IF NOT EXISTS mock_history (hash VARCHAR(64), fingerprint VARCHAR(64), status INTEGER, PRIMARY KEY (hash, fingerprint))")
if err != nil {
return nil
}
@@ -66,33 +73,49 @@ func NewSqliteResolver(dsn string) *SqliteDbResolver {
return db
}
-func (r *SqliteDbResolver) Update(info *ResolveInfoType, routing, publicKey string) (bool, error) {
+func (r *SqliteDbResolver) Update(info *ResolveInfoType, routing string, publicKey *bmcrypto.PubKey) (bool, error) {
newSerial := strconv.FormatUint(uint64(r.TimeNow.UnixNano()), 10)
+ _ = r.updateKeyHistory(info.Hash, publicKey.Fingerprint(), KSNormal)
+
st, err := r.conn.Prepare("UPDATE mock_address SET routing_id=?, pubkey=?, serial=? WHERE hash=? AND serial=?")
if err != nil {
return false, err
}
- res, err := st.Exec(routing, publicKey, newSerial, info.Hash, info.Serial)
+ res, err := st.Exec(routing, publicKey.String(), newSerial, info.Hash, info.Serial)
if err != nil {
return false, err
}
count, err := res.RowsAffected()
- return count != 0, err
+ if err != nil {
+ return false, err
+ }
+ if count == 0 {
+ return false, errors.New("not updated")
+ }
+ return true, nil
}
-func (r *SqliteDbResolver) Create(hash, routing, publicKey, proof string) (bool, error) {
+func (r *SqliteDbResolver) Create(hash, routing string, publicKey *bmcrypto.PubKey, proof string) (bool, error) {
serial := strconv.FormatUint(uint64(r.TimeNow.UnixNano()), 10)
- res, err := r.conn.Exec("INSERT INTO mock_address VALUES (?, ?, ?, ?, ?)", hash, publicKey, routing, proof, serial)
+ _ = r.updateKeyHistory(hash, publicKey.Fingerprint(), KSNormal)
+
+ res, err := r.conn.Exec("INSERT INTO mock_address VALUES (?, ?, ?, ?, ?, 0, 0)", hash, publicKey.String(), routing, proof, serial)
if err != nil {
return false, err
}
count, err := res.RowsAffected()
- return count != 0, err
+ if err != nil {
+ return false, err
+ }
+ if count == 0 {
+ return false, errors.New("not created")
+ }
+ return true, nil
}
func (r *SqliteDbResolver) Get(hash string) (*ResolveInfoType, error) {
@@ -102,9 +125,11 @@ func (r *SqliteDbResolver) Get(hash string) (*ResolveInfoType, error) {
rt string
pow string
sn uint64
+ d bool
+ da int64
)
- err := r.conn.QueryRow("SELECT hash, pubkey, routing_id, proof, serial FROM mock_address WHERE hash LIKE ?", hash).Scan(&h, &pk, &rt, &pow, &sn)
+ err := r.conn.QueryRow("SELECT hash, pubkey, routing_id, proof, serial, deleted, deleted_at FROM mock_address WHERE hash LIKE ?", hash).Scan(&h, &pk, &rt, &pow, &sn, &d, &da)
if err != nil {
return nil, ErrNotFound
}
@@ -115,6 +140,8 @@ func (r *SqliteDbResolver) Get(hash string) (*ResolveInfoType, error) {
PubKey: pk,
Proof: pow,
Serial: sn,
+ Deleted: d,
+ DeletedAt: time.Unix(da, 0),
}, nil
}
@@ -125,5 +152,83 @@ func (r *SqliteDbResolver) Delete(hash string) (bool, error) {
}
count, err := res.RowsAffected()
- return count != 0, err
+ if err != nil {
+ return false, err
+ }
+ if count == 0 {
+ return false, errors.New("not deleted")
+ }
+ return true, nil
+}
+
+func (r *SqliteDbResolver) SoftDelete(hash string) (bool, error) {
+ st, err := r.conn.Prepare("UPDATE mock_address SET deleted=1, deleted_at=? WHERE hash=?")
+ if err != nil {
+ return false, err
+ }
+
+ dt := time.Now().Unix()
+ res, err := st.Exec(dt, hash)
+ if err != nil {
+ return false, err
+ }
+
+ count, err := res.RowsAffected()
+ if err != nil {
+ return false, err
+ }
+ if count == 0 {
+ return false, errors.New("not soft deleted")
+ }
+ return true, nil
+}
+
+func (r *SqliteDbResolver) SoftUndelete(hash string) (bool, error) {
+ st, err := r.conn.Prepare("UPDATE mock_address SET deleted=0, deleted_at=0 WHERE hash=?")
+ if err != nil {
+ return false, err
+ }
+
+ res, err := st.Exec(hash)
+ if err != nil {
+ return false, err
+ }
+
+ count, err := res.RowsAffected()
+ if err != nil {
+ return false, err
+ }
+ if count == 0 {
+ return false, errors.New("not soft undeleted")
+ }
+ return true, nil
+}
+
+func (r *SqliteDbResolver) GetKeyStatus(hash string, fingerprint string) (KeyStatus, error) {
+ var ks *KeyStatus
+
+ err := r.conn.QueryRow("SELECT status FROM mock_history WHERE hash LIKE ? AND fingerprint LIKE ?", hash, fingerprint).Scan(&ks)
+ if err != nil {
+ return KSNormal, ErrNotFound
+ }
+
+ return *ks, nil
+}
+
+func (r *SqliteDbResolver) updateKeyHistory(hash string, fingerprint string, status KeyStatus) error {
+ _, err := r.conn.Exec("INSERT OR REPLACE INTO mock_history VALUES (?, ?, ?)", hash, fingerprint, status)
+
+ return err
+}
+
+func (r *SqliteDbResolver) SetKeyStatus(hash string, fingerprint string, status KeyStatus) error {
+ var ks *KeyStatus
+
+ // Make sure key exists before adding status
+ err := r.conn.QueryRow("SELECT status FROM mock_history WHERE hash LIKE ? AND fingerprint LIKE ?", hash, fingerprint).Scan(&ks)
+ if err != nil {
+ return ErrNotFound
+ }
+
+ return r.updateKeyHistory(hash, fingerprint, status)
}
diff --git a/internal/address/sqlite_test.go b/internal/address/sqlite_test.go
new file mode 100644
index 0000000..cbe6bae
--- /dev/null
+++ b/internal/address/sqlite_test.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package address
+
+import (
+ "testing"
+)
+
+func TestSqliteDbResolver(t *testing.T) {
+ db := NewSqliteResolver(":memory:")
+ runRepositoryCreateUpdateTest(t, db)
+
+ db = NewSqliteResolver(":memory:")
+ runRepositoryDeletionTests(t, db)
+
+ db = NewSqliteResolver(":memory:")
+ runRepositoryHistoryCheck(t, db)
+
+ db = NewSqliteResolver(":memory:")
+ runRepositoryHistoryKeyStatus(t, db)
+}
diff --git a/internal/address/token_test.go b/internal/address/token_test.go
index 5afb9de..fe16403 100644
--- a/internal/address/token_test.go
+++ b/internal/address/token_test.go
@@ -20,9 +20,6 @@
package address
import (
- "io/ioutil"
- "log"
- "os"
"testing"
"time"
@@ -81,7 +78,11 @@ func TestToken(t *testing.T) {
assert.False(t, ok)
}
-func TestMain(m *testing.M) {
- log.SetOutput(ioutil.Discard)
- os.Exit(m.Run())
+func TestGenerateToken(t *testing.T) {
+ h1 := hash.Hash("address1")
+ expires := time.Date(2010, 12, 31, 12, 34, 56, 0, time.UTC)
+ priv, _, _ := testing2.ReadTestKey("../../testdata/key-1.json")
+
+ tok := GenerateToken(h1, "12345678", expires, *priv)
+ assert.Equal(t, "YWRkcmVzczE6MTIzNDU2Nzg6MTI5Mzc5ODg5NjrIdRSDpUD51Xmk+Yvfo8PI9DM/nsdJaT2I/nOikqrj/b+NdjWw7tZkEYj8/Vn63cnNdoaAO3xsFBbBClEOz+/Sfvm3JjLJ0aYVJ2IFXbRc2PxOY64ISw3xU+vYCPQLw/7goCN/2ktS5FW8qpuW8KkUepOl7hfVOHp45rJqtdtOypsvxyyPal1LxfGoVE1vg9VXPXbpQob7LS0nWUi6cKTbq2d1y3U92timd9CZofhcuX6q4J+nHuwYD1NVYz1ssDSs5wr8h/rpnCO08q3cJA24+erAsLjFjqMRCbk9wi3AogK1C3dPmrNZ8ZAAyFrMahp18qRQRirGLqdWfE5oczl6", tok)
}
diff --git a/internal/apigateway/request.go b/internal/apigateway/request.go
index b1abece..1d49b1b 100644
--- a/internal/apigateway/request.go
+++ b/internal/apigateway/request.go
@@ -26,10 +26,12 @@ import (
// ReqToHTTP converts a api gateway http request to our internal http request
func ReqToHTTP(req *events.APIGatewayV2HTTPRequest) *http.Request {
+
httpReq := http.NewRequest(
req.RequestContext.HTTP.Method,
req.RequestContext.HTTP.Path,
req.Body,
+ req.PathParameters,
)
// Add headers
diff --git a/internal/apigateway/request_test.go b/internal/apigateway/request_test.go
new file mode 100644
index 0000000..86fd723
--- /dev/null
+++ b/internal/apigateway/request_test.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package apigateway
+
+import (
+ "testing"
+
+ "github.com/aws/aws-lambda-go/events"
+ "github.com/bitmaelum/key-resolver-go/internal/http"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestReqToHTTP(t *testing.T) {
+ req := &events.APIGatewayV2HTTPRequest{
+ Version: "latest",
+ RouteKey: "foo",
+ RawPath: "/foo",
+ Headers: map[string]string{
+ "header-1": "value-1",
+ "header-2": "value-2",
+ },
+ RequestContext: events.APIGatewayV2HTTPRequestContext{
+ RouteKey: "foo",
+ AccountID: "1234",
+ Stage: "test",
+ HTTP: events.APIGatewayV2HTTPRequestContextHTTPDescription{
+ Method: "GET",
+ Path: "/foobar",
+ Protocol: "https",
+ SourceIP: "127.2.3.4",
+ UserAgent: "gotest",
+ },
+ },
+ Body: "body",
+ }
+
+ httpReq := ReqToHTTP(req)
+ assert.Equal(t, httpReq.Body, "body")
+ assert.Equal(t, httpReq.URL, "/foobar")
+ assert.Equal(t, httpReq.Method, "GET")
+ assert.Len(t, httpReq.Headers.Headers, 2)
+ assert.Equal(t, "value-1", httpReq.Headers.Get("header-1"))
+ assert.Equal(t, "value-2", httpReq.Headers.Get("header-2"))
+}
+
+func TestHTTPToResp(t *testing.T) {
+ resp := &http.Response{
+ Body: "this is body",
+ StatusCode: 123,
+ Headers: http.Headers{
+ Headers: map[string]string{
+ "h1": "v1",
+ "h2": "v2",
+ },
+ },
+ }
+
+ apigwResp := HTTPToResp(resp)
+ assert.Equal(t, 123, apigwResp.StatusCode)
+ assert.Equal(t, "this is body", apigwResp.Body)
+ assert.Equal(t, "application/json", apigwResp.Headers["Content-Type"])
+ assert.Len(t, apigwResp.Headers, 1)
+}
diff --git a/internal/bolt.go b/internal/bolt.go
index 6ba6dc8..894c070 100644
--- a/internal/bolt.go
+++ b/internal/bolt.go
@@ -23,7 +23,7 @@ import (
"log"
"os"
- "github.com/boltdb/bolt"
+ bolt "go.etcd.io/bbolt"
)
var boltdb *bolt.DB
diff --git a/internal/handler/address.go b/internal/handler/address.go
index b02dff7..ae79fe7 100644
--- a/internal/handler/address.go
+++ b/internal/handler/address.go
@@ -62,7 +62,7 @@ func GetAddressHash(hash hash.Hash, _ http.Request) *http.Response {
return http.CreateError("hash not found", 404)
}
- if info == nil {
+ if info == nil || info.Deleted {
log.Print(err)
return http.CreateError("hash not found", 404)
}
@@ -153,7 +153,6 @@ func deleteAddressHashByOwner(addrHash hash.Hash, req http.Request) *http.Respon
res, err := repo.Delete(current.Hash)
if err != nil || !res {
- log.Print(err)
return http.CreateError("error while deleting record", 500)
}
@@ -201,13 +200,117 @@ func deleteAddressHashByOrganization(addrHash hash.Hash, organizationInfo *organ
return http.CreateOutput("ok", 200)
}
+func SoftDeleteAddressHash(addrHash hash.Hash, req http.Request) *http.Response {
+ repo := address.GetResolveRepository()
+ current, err := repo.Get(addrHash.String())
+ if err != nil {
+ return http.CreateError("error while fetching record", 500)
+ }
+
+ if !req.ValidateAuthenticationToken(current.PubKey, current.Hash+current.RoutingID+strconv.FormatUint(current.Serial, 10)) {
+ return http.CreateError("unauthenticated", 401)
+ }
+
+ if current == nil || current.Deleted {
+ return http.CreateError("cannot find record", 404)
+ }
+
+ res, err := repo.SoftDelete(current.Hash)
+ if err != nil || !res {
+ return http.CreateError("error while deleting record", 500)
+ }
+
+ return http.CreateOutput("", 204)
+}
+
+func SoftUndeleteAddressHash(addrHash hash.Hash, req http.Request) *http.Response {
+ repo := address.GetResolveRepository()
+ current, err := repo.Get(addrHash.String())
+ if err != nil {
+ return http.CreateError("error while fetching record", 500)
+ }
+
+ if current == nil {
+ return http.CreateError("cannot find record", 404)
+ }
+
+ if !req.ValidateAuthenticationToken(current.PubKey, current.Hash+current.RoutingID+strconv.FormatUint(current.Serial, 10)) {
+ return http.CreateError("unauthenticated", 401)
+ }
+
+ res, err := repo.SoftUndelete(current.Hash)
+ if err != nil || !res {
+ return http.CreateError("error while undeleting record", 500)
+ }
+
+ return http.CreateOutput("", 204)
+}
+
+func GetKeyStatus(hash hash.Hash, req http.Request) *http.Response {
+ fp, ok := req.Params["fingerprint"]
+ if !ok {
+ return http.CreateError("not found", 404)
+ }
+
+ repo := address.GetResolveRepository()
+ ks, err := repo.GetKeyStatus(hash.String(), fp)
+ if err != nil {
+ return http.CreateError("not found", 404)
+ }
+
+ var statusMap = map[address.KeyStatus]int{
+ address.KSNormal: 204,
+ address.KSCompromised: 410,
+ }
+
+ status, ok := statusMap[ks]
+ if !ok {
+ status = 404
+ }
+
+ return http.CreateOutput("", status)
+}
+
+func SetKeyStatus(hash hash.Hash, req http.Request) *http.Response {
+ fp, ok := req.Params["fingerprint"]
+ if !ok {
+ return http.CreateError("not found", 404)
+ }
+
+ type setKeyRequestBody struct {
+ Status string `json:"status"`
+ }
+
+ body := &setKeyRequestBody{}
+ if req.Body != "" {
+ err := json.Unmarshal([]byte(req.Body), body)
+ if err != nil {
+ log.Print(err)
+ return http.CreateError("invalid body data", 400)
+ }
+ }
+
+ ks, err := address.StringToKeyStatus(body.Status)
+ if err != nil {
+ return http.CreateError("invalid status", 400)
+ }
+
+ repo := address.GetResolveRepository()
+ err = repo.SetKeyStatus(hash.String(), fp, ks)
+ if err != nil {
+ return http.CreateError("error while updating", 400)
+ }
+
+ return http.CreateOutput("key updated", 200)
+}
+
func updateAddress(uploadBody addressUploadBody, req http.Request, current *address.ResolveInfoType) *http.Response {
if !req.ValidateAuthenticationToken(current.PubKey, current.Hash+current.RoutingID+strconv.FormatUint(current.Serial, 10)) {
return http.CreateError("unauthenticated", 401)
}
repo := address.GetResolveRepository()
- res, err := repo.Update(current, uploadBody.RoutingID, uploadBody.PublicKey.String())
+ res, err := repo.Update(current, uploadBody.RoutingID, uploadBody.PublicKey)
if err != nil || !res {
log.Print(err)
@@ -229,7 +332,7 @@ func createAddress(addrHash hash.Hash, uploadBody addressUploadBody) *http.Respo
}
repo := address.GetResolveRepository()
- res, err := repo.Create(addrHash.String(), uploadBody.RoutingID, uploadBody.PublicKey.String(), uploadBody.Proof.String())
+ res, err := repo.Create(addrHash.String(), uploadBody.RoutingID, uploadBody.PublicKey, uploadBody.Proof.String())
if err != nil || !res {
log.Print(err)
return http.CreateError("error while creating: ", 500)
diff --git a/internal/handler/address_test.go b/internal/handler/address_test.go
index 2f9ef9c..cabfd65 100644
--- a/internal/handler/address_test.go
+++ b/internal/handler/address_test.go
@@ -56,13 +56,13 @@ func TestAddress(t *testing.T) {
pow := proofofwork.New(22, addr.Hash().String(), 1540921)
// Test fetching unknown hash
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
res := GetAddressHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 404, res.StatusCode)
assert.JSONEq(t, `{ "error": "hash not found" }`, res.Body)
// Insert illegal body
- req = http.NewRequest("GET", "/", "illegal body that should error")
+ req = http.NewRequest("GET", "/", "illegal body that should error", nil)
res = PostAddressHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 400, res.StatusCode)
assert.JSONEq(t, `{ "error": "invalid data" }`, res.Body)
@@ -95,7 +95,7 @@ func TestAddress(t *testing.T) {
assert.Equal(t, `"created"`, res.Body)
// Test fetching known hash
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
info := getAddressRecord(res)
@@ -122,7 +122,7 @@ func TestValidateVerifyHashFailed(t *testing.T) {
Proof: pow,
})
- req := http.NewRequest("GET", "/", string(b))
+ req := http.NewRequest("GET", "/", string(b), nil)
res := PostAddressHash(addr.Hash(), req)
assert.NotNil(t, res)
assert.Equal(t, 400, res.StatusCode)
@@ -146,7 +146,7 @@ func TestValidateVerifyNeedTokenForOrg(t *testing.T) {
Proof: pow,
})
- req := http.NewRequest("GET", "/", string(b))
+ req := http.NewRequest("GET", "/", string(b), nil)
res := PostAddressHash(addr.Hash(), req)
assert.NotNil(t, res)
assert.Equal(t, 400, res.StatusCode)
@@ -170,7 +170,7 @@ func TestValidateVerifyNoTokenForNonOrg(t *testing.T) {
Proof: pow,
})
- req := http.NewRequest("GET", "/", string(b))
+ req := http.NewRequest("GET", "/", string(b), nil)
res := PostAddressHash(addr.Hash(), req)
assert.NotNil(t, res)
assert.Equal(t, 400, res.StatusCode)
@@ -194,7 +194,7 @@ func TestValidateRoutingIDFailed(t *testing.T) {
Proof: pow,
})
- req := http.NewRequest("GET", "/", string(b))
+ req := http.NewRequest("GET", "/", string(b), nil)
res := PostAddressHash(addr.Hash(), req)
assert.NotNil(t, res)
assert.Equal(t, 400, res.StatusCode)
@@ -217,7 +217,7 @@ func TestAddressUpdate(t *testing.T) {
assert.NotNil(t, res)
// Fetch addr1 record
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr1.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
current := getAddressRecord(res)
@@ -245,7 +245,7 @@ func TestAddressUpdate(t *testing.T) {
authToken := http.GenerateAuthenticationToken([]byte(sig), *privKey)
// Update record with correct auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER "+authToken)
// req.Headers.Set("authorization", "BEARER 2UxSWVAUJ/iIr59x76B9bF/CeQXDi4dTY4D73P8iJwE/CRaIpRyg1RHMbfLVM6fz3sfOammn8wzhooxfv6BVAg==")
@@ -254,7 +254,7 @@ func TestAddressUpdate(t *testing.T) {
assert.Equal(t, 200, res.StatusCode)
assert.Equal(t, `"updated"`, res.Body)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr1.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
info := getAddressRecord(res)
@@ -280,29 +280,29 @@ func TestAddressDeletion(t *testing.T) {
assert.NotNil(t, res)
// Delete hash without auth
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "Bearer sdfafsadf")
- res = DeleteAddressHash("efd5631354d823cd64aa8df8149cc317ae30d319295b491e86e9a5ffdab8fd7e", req)
+ res = DeleteAddressHash(addr1.Hash(), req)
assert.Equal(t, 401, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
- res = GetAddressHash("efd5631354d823cd64aa8df8149cc317ae30d319295b491e86e9a5ffdab8fd7e", req)
+ req = http.NewRequest("GET", "/", "", nil)
+ res = GetAddressHash(addr1.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
// Delete hash with wrong auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER okqF4rW/bFoNvmxk29NLb3lbTHCpir8A86i4IiK0j6211+WMOFCr91RodeBLSCXx167VOhC/++")
- res = DeleteAddressHash("efd5631354d823cd64aa8df8149cc317ae30d319295b491e86e9a5ffdab8fd7e", req)
+ res = DeleteAddressHash(addr1.Hash(), req)
assert.Equal(t, 401, res.StatusCode)
// Delete wrong hash with wrong auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER okqF4rW/bFoNvmxk29NLb3lbTHCpir8A86i4IiK0j6211+WMOFCr91RodeBLSCXx167VOhC/++wes1RLx7Q1O26cmcvpsAV/7I0e+ISDSzHHW82zuvLw0IaqZ7xngrkz4QdG00VGi3mS6bNSjQqU4Yxrqoiwk/o/jVD0/MHLxYbJHn+taL2sEeSMBvfkc5zHoqsNAgZQ7anvAsYASF30NR3pGvp/66P801sYxJYrIv4b48U2Z3pQZHozDY2e4YUA+14ZWZIYqQ+K8yCa78KTSTy5mDznP2Hpvnsy6sT8R93u2aLk++vLCmRby3REGfYRaWDxSGxgXjCgVqiLdFRLhg==")
res = DeleteAddressHash("00000000000000000000000000000317ae30d319295b491e86e9a5ffdab8fd7e", req)
assert.Equal(t, 500, res.StatusCode)
// Fetch addr1 record
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr1.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
current := getAddressRecord(res)
@@ -313,16 +313,89 @@ func TestAddressDeletion(t *testing.T) {
authToken := http.GenerateAuthenticationToken([]byte(sig), *privKey)
// Delete hash with auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER "+authToken)
- res = DeleteAddressHash("efd5631354d823cd64aa8df8149cc317ae30d319295b491e86e9a5ffdab8fd7e", req)
+ res = DeleteAddressHash(addr1.Hash(), req)
+ assert.Equal(t, 200, res.StatusCode)
+
+ req = http.NewRequest("GET", "/", "", nil)
+ res = GetAddressHash(addr1.Hash(), req)
+ assert.Equal(t, 404, res.StatusCode)
+ res = GetAddressHash(addr2.Hash(), req)
+ assert.Equal(t, 200, res.StatusCode)
+}
+
+func TestAddressSoftDeletion(t *testing.T) {
+ setupRepo()
+
+ addr1, _ := pkgAddress.NewAddress("foo!")
+ pow1 := proofofwork.New(22, addr1.Hash().String(), 1310761)
+
+ addr2, _ := pkgAddress.NewAddress("bar!")
+ pow2 := proofofwork.New(22, addr2.Hash().String(), 1019732)
+
+ // Insert some records
+ res := insertAddressRecord(*addr1, "../../testdata/key-3.json", fakeRoutingId.String(), "", pow1)
+ assert.NotNil(t, res)
+ res = insertAddressRecord(*addr2, "../../testdata/key-4.json", fakeRoutingId.String(), "", pow2)
+ assert.NotNil(t, res)
+
+ // Delete hash without auth
+ req := http.NewRequest("POST", "/", "", nil)
+ req.Headers.Set("authorization", "Bearer sdfafsadf")
+ res = SoftDeleteAddressHash(addr1.Hash(), req)
+ assert.Equal(t, 401, res.StatusCode)
+
+ req = http.NewRequest("GET", "/", "", nil)
+ res = GetAddressHash(addr1.Hash(), req)
+ assert.Equal(t, 200, res.StatusCode)
+
+ // Delete hash with wrong auth
+ req = http.NewRequest("GET", "/", "", nil)
+ req.Headers.Set("authorization", "BEARER okqF4rW/bFoNvmxk29NLb3lbTHCpir8A86i4IiK0j6211+WMOFCr91RodeBLSCXx167VOhC/++")
+ res = SoftDeleteAddressHash(addr1.Hash(), req)
+ assert.Equal(t, 401, res.StatusCode)
+
+ // Delete wrong hash with wrong auth
+ req = http.NewRequest("GET", "/", "", nil)
+ req.Headers.Set("authorization", "BEARER okqF4rW/bFoNvmxk29NLb3lbTHCpir8A86i4IiK0j6211+WMOFCr91RodeBLSCXx167VOhC/++wes1RLx7Q1O26cmcvpsAV/7I0e+ISDSzHHW82zuvLw0IaqZ7xngrkz4QdG00VGi3mS6bNSjQqU4Yxrqoiwk/o/jVD0/MHLxYbJHn+taL2sEeSMBvfkc5zHoqsNAgZQ7anvAsYASF30NR3pGvp/66P801sYxJYrIv4b48U2Z3pQZHozDY2e4YUA+14ZWZIYqQ+K8yCa78KTSTy5mDznP2Hpvnsy6sT8R93u2aLk++vLCmRby3REGfYRaWDxSGxgXjCgVqiLdFRLhg==")
+ res = SoftDeleteAddressHash("00000000000000000000000000000317ae30d319295b491e86e9a5ffdab8fd7e", req)
+ assert.Equal(t, 500, res.StatusCode)
+
+ // Fetch addr1 record
+ req = http.NewRequest("GET", "/", "", nil)
+ res = GetAddressHash(addr1.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
+ current := getAddressRecord(res)
+
+ // Create authentication token
+ privKey, _, _ := testing2.ReadTestKey("../../testdata/key-3.json")
+ sig := current.Hash + current.RoutingID + strconv.FormatUint(current.Serial, 10)
+ authToken := http.GenerateAuthenticationToken([]byte(sig), *privKey)
+
+ // Soft delete hash with auth
+ req = http.NewRequest("GET", "/", "", nil)
+ req.Headers.Set("authorization", "BEARER "+authToken)
+ res = SoftDeleteAddressHash(addr1.Hash(), req)
+ assert.Equal(t, 204, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr1.Hash(), req)
assert.Equal(t, 404, res.StatusCode)
res = GetAddressHash(addr2.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
+
+ // Soft undelete hash with auth
+ req = http.NewRequest("GET", "/", "", nil)
+ req.Headers.Set("authorization", "BEARER "+authToken)
+ res = SoftUndeleteAddressHash(addr1.Hash(), req)
+ assert.Equal(t, 204, res.StatusCode)
+
+ req = http.NewRequest("GET", "/", "", nil)
+ res = GetAddressHash(addr1.Hash(), req)
+ assert.Equal(t, 200, res.StatusCode)
+ res = GetAddressHash(addr2.Hash(), req)
+ assert.Equal(t, 200, res.StatusCode)
}
func TestAddOrganisationalAddresses(t *testing.T) {
@@ -379,7 +452,7 @@ func TestAddOrganisationalAddresses(t *testing.T) {
assert.Contains(t, res.Body, "created")
// Check if record exists
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
info := getAddressRecord(res)
@@ -424,7 +497,7 @@ func TestAllowUpdateToOrgAddressWithoutToken(t *testing.T) {
assert.NoError(t, err)
// Get serial
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
info := getAddressRecord(res)
@@ -432,7 +505,7 @@ func TestAllowUpdateToOrgAddressWithoutToken(t *testing.T) {
sig := addr.Hash().String() + info.RoutingID + strconv.FormatUint(info.Serial, 10)
authToken := http.GenerateAuthenticationToken([]byte(sig), *privKey)
- req = http.NewRequest("POST", "/account/"+addr.Hash().String(), string(b))
+ req = http.NewRequest("POST", "/account/"+addr.Hash().String(), string(b), nil)
req.Headers.Set("authorization", "BEARER "+authToken)
res = PostAddressHash(addr.Hash(), req)
assert.Equal(t, "\"updated\"", res.Body)
@@ -445,15 +518,15 @@ func TestDeleteOrganisationalAddresses(t *testing.T) {
// Add organisation
orgHash1 := hash.New("acme-inc")
pow1 := proofofwork.New(22, orgHash1.String(), 1305874)
- res := insertOrganisationRecord(orgHash1, "../../testdata/key-5.json", pow1, []string{})
+ _ = insertOrganisationRecord(orgHash1, "../../testdata/key-5.json", pow1, []string{})
orgHash2 := hash.New("example")
pow2 := proofofwork.New(22, orgHash2.String(), 190734)
- res = insertOrganisationRecord(orgHash2, "../../testdata/key-6.json", pow2, []string{})
+ _ = insertOrganisationRecord(orgHash2, "../../testdata/key-6.json", pow2, []string{})
orgHash3 := hash.New("another")
pow3 := proofofwork.New(22, orgHash3.String(), 21232)
- res = insertOrganisationRecord(orgHash3, "../../testdata/key-7.json", pow3, []string{})
+ _ = insertOrganisationRecord(orgHash3, "../../testdata/key-7.json", pow3, []string{})
addr, _ := pkgAddress.NewAddress("example@acme-inc!")
pow4 := proofofwork.New(22, addr.Hash().String(), 11741366)
@@ -462,13 +535,13 @@ func TestDeleteOrganisationalAddresses(t *testing.T) {
privKey, _, _ := testing2.ReadTestKey("../../testdata/key-5.json")
inviteToken := address.GenerateToken(addr.Hash(), fakeRoutingId.String(), time.Date(2010, 05, 05, 12, 0, 0, 0, time.UTC), *privKey)
- res = insertAddressRecord(*addr, "../../testdata/key-4.json", fakeRoutingId.String(), inviteToken, pow4)
+ res := insertAddressRecord(*addr, "../../testdata/key-4.json", fakeRoutingId.String(), inviteToken, pow4)
assert.NotNil(t, res)
assert.Equal(t, 201, res.StatusCode)
assert.Contains(t, res.Body, "created")
// Check if record exists
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
}
@@ -478,8 +551,8 @@ func TestDeleteOrganisationalAddresses(t *testing.T) {
insertRecords()
// Fetch addr record
- req := http.NewRequest("GET", "/", "")
- res = GetAddressHash(addr.Hash(), req)
+ req := http.NewRequest("GET", "/", "", nil)
+ res := GetAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
current := getAddressRecord(res)
@@ -489,13 +562,13 @@ func TestDeleteOrganisationalAddresses(t *testing.T) {
authToken := http.GenerateAuthenticationToken([]byte(sig), *privKey)
// Delete as "regular" user
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
// req.Headers.Set("authorization", "BEARER 2UxSWVAUJ/iIr59x76B9bF/CeQXDi4dTY4D73P8iJwE/CRaIpRyg1RHMbfLVM6fz3sfOammn8wzhooxfv6BVAg==")
req.Headers.Set("authorization", "BEARER "+authToken)
res = DeleteAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr.Hash(), req)
assert.Equal(t, 404, res.StatusCode)
@@ -509,12 +582,12 @@ func TestDeleteOrganisationalAddresses(t *testing.T) {
authToken = http.GenerateAuthenticationToken([]byte(sig), *privKey)
// Delete as correct "organisation" user, but without body
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER "+authToken)
res = DeleteAddressHash(addr.Hash(), req)
assert.Equal(t, 401, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
@@ -526,13 +599,13 @@ func TestDeleteOrganisationalAddresses(t *testing.T) {
}
b, _ := json.Marshal(ob)
- req = http.NewRequest("GET", "/", string(b))
+ req = http.NewRequest("GET", "/", string(b), nil)
req.Headers.Set("authorization", "BEARER "+authToken)
res = DeleteAddressHash(addr.Hash(), req)
assert.Equal(t, 401, res.StatusCode)
assert.Contains(t, res.Body, "error validating address")
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
@@ -544,12 +617,12 @@ func TestDeleteOrganisationalAddresses(t *testing.T) {
}
b, _ = json.Marshal(ob)
- req = http.NewRequest("GET", "/", string(b))
+ req = http.NewRequest("GET", "/", string(b), nil)
req.Headers.Set("authorization", "BEARER "+authToken)
res = DeleteAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr.Hash(), req)
assert.Equal(t, 404, res.StatusCode)
@@ -563,16 +636,82 @@ func TestDeleteOrganisationalAddresses(t *testing.T) {
// Delete as incorrect "other organisation" user
insertRecords()
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER "+authToken)
res = DeleteAddressHash(addr.Hash(), req)
assert.Equal(t, 401, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetAddressHash(addr.Hash(), req)
assert.Equal(t, 200, res.StatusCode)
}
+func TestHistory(t *testing.T) {
+ setupRepo()
+
+ addr, _ := pkgAddress.NewAddress("example!")
+ addr2, _ := pkgAddress.NewAddress("someoneelse!")
+ pow := proofofwork.New(22, addr.Hash().String(), 1540921)
+
+ _, pub, _ := testing2.ReadTestKey("../../testdata/key-4.json")
+ _, pub2, _ := testing2.ReadTestKey("../../testdata/key-3.json")
+
+ // Insert new hash
+ res := insertAddressRecord(*addr, "../../testdata/key-4.json", fakeRoutingId.String(), "", pow)
+ assert.NotNil(t, res)
+ assert.Equal(t, 201, res.StatusCode)
+ assert.Equal(t, `"created"`, res.Body)
+
+ // Test history of key
+ req := http.NewRequest("GET", "/address/"+addr.Hash().String()+"/check/"+pub.Fingerprint(), "", map[string]string{
+ "fingerprint": pub.Fingerprint(),
+ })
+ res = GetKeyStatus(addr.Hash(), req)
+ assert.Equal(t, 204, res.StatusCode)
+
+ // Check history of non-existing key
+ req = http.NewRequest("GET", "/address/"+addr.Hash().String()+"/check/"+pub2.Fingerprint(), "", map[string]string{
+ "fingerprint": pub2.Fingerprint(),
+ })
+ res = GetKeyStatus(addr.Hash(), req)
+ assert.Equal(t, 404, res.StatusCode)
+
+ // Check history of existing key on different account
+ req = http.NewRequest("GET", "/address/"+addr2.Hash().String()+"/check/"+pub.Fingerprint(), "", map[string]string{
+ "fingerprint": pub.Fingerprint(),
+ })
+ res = GetKeyStatus(addr2.Hash(), req)
+ assert.Equal(t, 404, res.StatusCode)
+
+ // Set key to unknown status
+ req = http.NewRequest("GET", "/address/"+addr.Hash().String()+"/check/"+pub.Fingerprint(), "{\"status\":\"unknown\"}", map[string]string{
+ "fingerprint": pub.Fingerprint(),
+ })
+ res = SetKeyStatus(addr.Hash(), req)
+ assert.Equal(t, 400, res.StatusCode)
+
+ // Set key to compromised
+ req = http.NewRequest("GET", "/address/"+addr.Hash().String()+"/check/"+pub.Fingerprint(), "{\"status\":\"compromised\"}", map[string]string{
+ "fingerprint": pub.Fingerprint(),
+ })
+ res = SetKeyStatus(addr.Hash(), req)
+ assert.Equal(t, 200, res.StatusCode)
+
+ // Check history of key again
+ req = http.NewRequest("GET", "/address/"+addr.Hash().String()+"/check/"+pub.Fingerprint(), "", map[string]string{
+ "fingerprint": pub.Fingerprint(),
+ })
+ res = GetKeyStatus(addr.Hash(), req)
+ assert.Equal(t, 410, res.StatusCode)
+
+ // Set key to normal
+ req = http.NewRequest("GET", "/address/"+addr.Hash().String()+"/check/"+pub.Fingerprint(), "{\"status\":\"normal\"}", map[string]string{
+ "fingerprint": pub.Fingerprint(),
+ })
+ res = SetKeyStatus(addr.Hash(), req)
+ assert.Equal(t, 200, res.StatusCode)
+}
+
func setupRepo() {
sr := address.NewSqliteResolver(":memory:")
address.SetDefaultRepository(sr)
@@ -622,7 +761,7 @@ func insertAddressRecord(addr pkgAddress.Address, keyPath, routingId, orgToken s
if err != nil {
return nil
}
- req := http.NewRequest("GET", "/", string(b))
+ req := http.NewRequest("GET", "/", string(b), nil)
return PostAddressHash(addr.Hash(), req)
}
diff --git a/internal/handler/organisation.go b/internal/handler/organisation.go
index 4138107..6a5201f 100644
--- a/internal/handler/organisation.go
+++ b/internal/handler/organisation.go
@@ -159,3 +159,11 @@ func validateOrganisationBody(_ organisationUploadBody) bool {
// PubKey and proof are already validated through the JSON marshalling
return true
}
+
+func SoftDeleteOrganisationHash(orgHash hash.Hash, req http.Request) *http.Response {
+ return nil
+}
+
+func SoftUndeleteOrganisationHash(orgHash hash.Hash, req http.Request) *http.Response {
+ return nil
+}
diff --git a/internal/handler/organisation_test.go b/internal/handler/organisation_test.go
index e37482d..a0f3723 100644
--- a/internal/handler/organisation_test.go
+++ b/internal/handler/organisation_test.go
@@ -52,13 +52,13 @@ func TestOrganisation(t *testing.T) {
pow := proofofwork.New(22, orgHash.String(), 1783097)
// Test fetching unknown hash
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
res := GetOrganisationHash(orgHash, req)
assert.Equal(t, 404, res.StatusCode)
assert.Contains(t, res.Body, "hash not found")
// Insert illegal body
- req = http.NewRequest("GET", "/", "illegal body that should error")
+ req = http.NewRequest("GET", "/", "illegal body that should error", nil)
res = PostOrganisationHash(orgHash, req)
assert.Equal(t, 400, res.StatusCode)
assert.Contains(t, res.Body, "invalid data")
@@ -70,7 +70,7 @@ func TestOrganisation(t *testing.T) {
assert.Equal(t, `"created"`, res.Body)
// Test fetching known hash
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetOrganisationHash(orgHash, req)
assert.Equal(t, 200, res.StatusCode)
info := getOrganisationRecord(res)
@@ -99,7 +99,7 @@ func TestOrganisationUpdate(t *testing.T) {
assert.NotNil(t, res)
// Fetch record
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
res = GetOrganisationHash(orgHash1, req)
assert.Equal(t, 200, res.StatusCode)
current := getOrganisationRecord(res)
@@ -121,14 +121,14 @@ func TestOrganisationUpdate(t *testing.T) {
authToken := http.GenerateAuthenticationToken([]byte(sig), *privKey)
// Update record with correct auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER "+authToken)
setRepoTime(time.Date(2010, 12, 13, 12, 34, 56, 1241511, time.UTC))
res = updateOrganisation(*body, req, ¤t)
assert.Equal(t, 200, res.StatusCode)
assert.Equal(t, `"updated"`, res.Body)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetOrganisationHash(orgHash1, req)
assert.Equal(t, 200, res.StatusCode)
info := getOrganisationRecord(res)
@@ -155,27 +155,27 @@ func TestOrganisationDeletion(t *testing.T) {
assert.NotNil(t, res)
// Delete hash without auth
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "Bearer sdfafsadf")
res = DeleteOrganisationHash(orgHash1, req)
assert.Equal(t, 401, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetOrganisationHash(orgHash1, req)
assert.Equal(t, 200, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetOrganisationHash(orgHash2, req)
assert.Equal(t, 200, res.StatusCode)
// Delete hash with wrong auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER okqF4rW/bFoNvmxk29NLb3lbTHCpir8A86i4IiK0j6211+WMOFCr91RodeBLSCXx167VOhC/++")
res = DeleteOrganisationHash(orgHash1, req)
assert.Equal(t, 401, res.StatusCode)
// Fetch record
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetOrganisationHash(orgHash1, req)
assert.Equal(t, 200, res.StatusCode)
current := getOrganisationRecord(res)
@@ -186,23 +186,23 @@ func TestOrganisationDeletion(t *testing.T) {
authToken := http.GenerateAuthenticationToken([]byte(sig), *privKey)
// Delete wrong hash with correct auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER "+authToken)
res = DeleteOrganisationHash("0000000000000000000000000E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 500, res.StatusCode)
// Delete hash with auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
// req.Headers.Set("authorization", "BEARER neftRnbcaw2mfudfSkXgBT6SJ3nEXsWzyumiIcDed8y6pBoEPkJkgqCHcwqm9TuqVycjzb3PemDYfvMmUfL9BA==")
req.Headers.Set("authorization", "BEARER "+authToken)
res = DeleteOrganisationHash(orgHash1, req)
assert.Equal(t, 200, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetOrganisationHash(orgHash1, req)
assert.Equal(t, 404, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetOrganisationHash(orgHash2, req)
assert.Equal(t, 200, res.StatusCode)
}
@@ -221,7 +221,7 @@ func insertOrganisationRecord(orgHash hash.Hash, keyPath string, pow *proofofwor
if err != nil {
return nil
}
- req := http.NewRequest("GET", "/", string(b))
+ req := http.NewRequest("GET", "/", string(b), nil)
return PostOrganisationHash(orgHash, req)
}
diff --git a/internal/handler/routing_test.go b/internal/handler/routing_test.go
index c1cde3c..2e21718 100644
--- a/internal/handler/routing_test.go
+++ b/internal/handler/routing_test.go
@@ -49,13 +49,13 @@ func TestRouting(t *testing.T) {
sr.TimeNow = time.Date(2010, 04, 07, 12, 34, 56, 0, time.UTC)
// Test fetching unknown hash
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
res := GetRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 404, res.StatusCode)
assert.JSONEq(t, `{ "error": "hash not found" }`, res.Body)
// Insert illegal body
- req = http.NewRequest("GET", "/", "illegal body that should error")
+ req = http.NewRequest("GET", "/", "illegal body that should error", nil)
res = PostRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 400, res.StatusCode)
assert.JSONEq(t, `{ "error": "invalid data" }`, res.Body)
@@ -67,7 +67,7 @@ func TestRouting(t *testing.T) {
assert.Equal(t, `"created"`, res.Body)
// Test fetching known hash
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 200, res.StatusCode)
info := getRoutingRecord(res)
@@ -89,7 +89,7 @@ func TestRoutingUpdate(t *testing.T) {
assert.NotNil(t, res)
// Fetch record
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
res = GetRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 200, res.StatusCode)
current := getRoutingRecord(res)
@@ -111,7 +111,7 @@ func TestRoutingUpdate(t *testing.T) {
authToken := http.GenerateAuthenticationToken([]byte(sig), *privKey)
// Update record with correct auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
// req.Headers.Set("authorization", "BEARER okqF4rW/bFoNvmxk29NLb3lbTHCpir8A86i4IiK0j6211+WMOFCr91RodeBLSCXx167VOhC/++wes1RLx7Q1O26cmcvpsAV/7I0e+ISDSzHHW82zuvLw0IaqZ7xngrkz4QdG00VGi3mS6bNSjQqU4Yxrqoiwk/o/jVD0/MHLxYbJHn+taL2sEeSMBvfkc5zHoqsNAgZQ7anvAsYASF30NR3pGvp/66P801sYxJYrIv4b48U2Z3pQZHozDY2e4YUA+14ZWZIYqQ+K8yCa78KTSTy5mDznP2Hpvnsy6sT8R93u2aLk++vLCmRby3REGfYRaWDxSGxgXjCgVqiLdFRLhg==")
req.Headers.Set("authorization", "BEARER "+authToken)
sr.TimeNow = time.Date(2010, 12, 13, 12, 34, 56, 1241511, time.UTC)
@@ -119,7 +119,7 @@ func TestRoutingUpdate(t *testing.T) {
assert.Equal(t, 200, res.StatusCode)
assert.Equal(t, `"updated"`, res.Body)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 200, res.StatusCode)
info := getRoutingRecord(res)
@@ -141,34 +141,34 @@ func TestRoutingDeletion(t *testing.T) {
assert.NotNil(t, res)
// Delete hash without auth
- req := http.NewRequest("GET", "/", "")
+ req := http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "Bearer sdfafsadf")
res = DeleteRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 401, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 200, res.StatusCode)
// Delete hash with wrong auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER okqF4rW/bFoNvmxk29NLb3lbTHCpir8A86i4IiK0j6211+WMOFCr91RodeBLSCXx167VOhC/++")
res = DeleteRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 401, res.StatusCode)
// Delete wrong hash with wrong auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER okqF4rW/bFoNvmxk29NLb3lbTHCpir8A86i4IiK0j6211+WMOFCr91RodeBLSCXx167VOhC/++wes1RLx7Q1O26cmcvpsAV/7I0e+ISDSzHHW82zuvLw0IaqZ7xngrkz4QdG00VGi3mS6bNSjQqU4Yxrqoiwk/o/jVD0/MHLxYbJHn+taL2sEeSMBvfkc5zHoqsNAgZQ7anvAsYASF30NR3pGvp/66P801sYxJYrIv4b48U2Z3pQZHozDY2e4YUA+14ZWZIYqQ+K8yCa78KTSTy5mDznP2Hpvnsy6sT8R93u2aLk++vLCmRby3REGfYRaWDxSGxgXjCgVqiLdFRLhg==")
res = DeleteRoutingHash("0000000000000000000000000E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 500, res.StatusCode)
// Delete hash with auth
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "BEARER okqF4rW/bFoNvmxk29NLb3lbTHCpir8A86i4IiK0j6211+WMOFCr91RodeBLSCXx167VOhC/++wes1RLx7Q1O26cmcvpsAV/7I0e+ISDSzHHW82zuvLw0IaqZ7xngrkz4QdG00VGi3mS6bNSjQqU4Yxrqoiwk/o/jVD0/MHLxYbJHn+taL2sEeSMBvfkc5zHoqsNAgZQ7anvAsYASF30NR3pGvp/66P801sYxJYrIv4b48U2Z3pQZHozDY2e4YUA+14ZWZIYqQ+K8yCa78KTSTy5mDznP2Hpvnsy6sT8R93u2aLk++vLCmRby3REGfYRaWDxSGxgXjCgVqiLdFRLhg==")
res = DeleteRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 200, res.StatusCode)
- req = http.NewRequest("GET", "/", "")
+ req = http.NewRequest("GET", "/", "", nil)
res = GetRoutingHash("0CD8666848BF286D951C3D230E8B6E092FDE03C3A080E3454467E496E7B14E78", req)
assert.Equal(t, 404, res.StatusCode)
res = GetRoutingHash("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2", req)
@@ -188,7 +188,7 @@ func insertRoutingRecord(routingHash hash.Hash, keyPath string, routing string)
if err != nil {
return nil
}
- req := http.NewRequest("GET", "/", string(b))
+ req := http.NewRequest("GET", "/", string(b), nil)
return PostRoutingHash(routingHash, req)
}
diff --git a/internal/http/http.go b/internal/http/http.go
index e59877d..c52a6bd 100644
--- a/internal/http/http.go
+++ b/internal/http/http.go
@@ -29,6 +29,7 @@ import (
"strings"
"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
+ "github.com/gorilla/mux"
)
type Headers struct {
@@ -60,14 +61,16 @@ type Request struct {
URL string
Body string
Headers Headers
+ Params map[string]string
}
-func NewRequest(method, url, body string) Request {
+func NewRequest(method, url, body string, params map[string]string) Request {
return Request{
Method: method,
URL: url,
Body: body,
Headers: NewHeaders(),
+ Params: params,
}
}
@@ -148,10 +151,11 @@ func (r Request) ValidateAuthenticationToken(pubKey, hashData string) bool {
func NetReqToReq(r http.Request) Request {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
- return NewRequest("", "", "")
+ return NewRequest("", "", "", nil)
}
- req := NewRequest(r.Method, r.URL.String(), string(b))
+ params := mux.Vars(&r)
+ req := NewRequest(r.Method, r.URL.String(), string(b), params)
// Add headers
for k, v := range r.Header {
diff --git a/internal/http/http_test.go b/internal/http/http_test.go
index 22a5015..b949476 100644
--- a/internal/http/http_test.go
+++ b/internal/http/http_test.go
@@ -25,11 +25,11 @@ import (
"os"
"testing"
+ testing2 "github.com/bitmaelum/key-resolver-go/internal/testing"
"github.com/stretchr/testify/assert"
)
const (
- // PrivKeyData string = "rsa MIICXQIBAAKBgQC57qC/BeoYcM6ijazuaCdJkbT8pvPpFEDVzf9ZQ9axswXU3mywSOaR3wflriSjmvRfUNs/BAjshgtJqgviUXx7lE5aG9mcUyvomyFFpfCR2l2Lvow0H8y7JoL6yxMSQf8gpAcaQzPB8dsfGe+DqA+5wjxXPOhC1QUcllt08yBB3wIDAQABAoGBAKSWDKsrtB5wdSnFmcfsYKKqHXjs3Mp9CCt6z0eYWoswesAFKFcgISINOLNi5MICX8GkFIACtVeSDJnnsd9j3HkRD7kwxmvVVXltaIrbrEunKgdRK1ACk2Bkb7UUDImDjiZztJvCSL+WLu9Fphn8IfPzwAIPWAKKBoD1kuI6yfFBAkEA6dJpoTMKDlCEMeJWZVUnhL7K/OBphWLO7cZfaxowBeGGXuMBWiaySsdeIDV7S/PDnoHBKwIkSsSfjzWYptuq4QJBAMuRXwoqZHKPHjMTCyz3C7nwFCzgmmKM5PReZU0s4/tdFu/VGOSnVDSzC5JFcY48Cs03TBwZ2wPhv/3r4a7YRL8CQQCxedRTVro7Q0IT2whYwdnNGERazLtLU0RdlkS2tpnc3OFxBDzygIyz1b/MEswTSmMg3LwSOP3zAmtZ+AR2IiYBAkBENgnqlhniaSJtaswr3PwI6fFYuEoDC8MMPzUijxA1ghPVeUpGE+ubXQNbl/lc97GG4iiWofNJcbOrmgadV8pxAkBhsn2T9DSoDdeImRecxs3/Xej4EYQpj/3X436RrFntjT06wD6wF9s5CvPmz/ftUBJ71IVlBQUd3jOgQPRzEhNC"
PubKeyData string = "rsa MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC57qC/BeoYcM6ijazuaCdJkbT8pvPpFEDVzf9ZQ9axswXU3mywSOaR3wflriSjmvRfUNs/BAjshgtJqgviUXx7lE5aG9mcUyvomyFFpfCR2l2Lvow0H8y7JoL6yxMSQf8gpAcaQzPB8dsfGe+DqA+5wjxXPOhC1QUcllt08yBB3wIDAQAB"
Signature string = "lsOsGOrY0rrs4A2CaJ3FzKLU5jx41d/Dw7gxQLUDPC4KMq6Cd3hyjZN6B8BbCDHBcZCFSd+sKvUbmM+ZCM1D6OrqYGvoRLTZJjWqbUsHRS7PkmIUWToxWxe0qo+tq5K/aYoDPJ+o6fRYTnUGILkN5+pQ8NquJqviLPCvBJVpKCo="
)
@@ -62,27 +62,27 @@ func TestValidateSignature(t *testing.T) {
var req Request
var hashData = "foobar data test"
- req = NewRequest("GET", "/", "")
+ req = NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "Bearer "+Signature)
assert.True(t, req.ValidateAuthenticationToken(PubKeyData, hashData))
- req = NewRequest("GET", "/", "")
+ req = NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "Bearer "+Signature)
assert.False(t, req.ValidateAuthenticationToken("false data", hashData))
- req = NewRequest("GET", "/", "")
+ req = NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "Bearer "+Signature)
assert.False(t, req.ValidateAuthenticationToken(PubKeyData, hashData+"falsefalse"))
- req = NewRequest("GET", "/", "")
+ req = NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "ADSAFAFAF")
assert.False(t, req.ValidateAuthenticationToken(PubKeyData, hashData))
- req = NewRequest("GET", "/", "")
+ req = NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "Bearer *&^(&^%(^@%$%)@$%@!$^@$^)@!")
assert.False(t, req.ValidateAuthenticationToken(PubKeyData, hashData))
- req = NewRequest("GET", "/", "")
+ req = NewRequest("GET", "/", "", nil)
req.Headers.Set("authorization", "")
assert.False(t, req.ValidateAuthenticationToken(PubKeyData, hashData))
}
@@ -91,3 +91,10 @@ func TestMain(m *testing.M) {
log.SetOutput(ioutil.Discard)
os.Exit(m.Run())
}
+
+func TestGenerateAuthenticationToken(t *testing.T) {
+ priv, _, _ := testing2.ReadTestKey("../../testdata/key-1.json")
+
+ token := GenerateAuthenticationToken([]byte("secret"), *priv)
+ assert.Equal(t, "RHurMX2K6xiBrfYuyWufGegfrTArrn9Nm/MJaCswwqEpV3HTaQaeEEcQefM5RyzQoF4UIbPvHxRrbjL8u9Nns8GvpZ/ACdDN3MXOX0zVjkydX4Iit0k32PfikzX1kFvM0B7Lak7iNUoq0KMacBJ6ri+v+SCSSwvukB5dO5y4zdIOU1Dfypel62gc58+FWyIDcoVQEjb+hpAs1CVd5wNMR4iMe6sovp2JQ4FMVd0LEJLDOcfGHtv0kg+jikSt+QmR5YuKwIfjxZHA/dPkyL6bMmwizap4CfF/qBbiGADxkPQIxmPxuZ7mSPrtukIJu1DHayhbcp19ikfKvG8fBziLMg==", token)
+}
diff --git a/internal/logo.go b/internal/logo.go
index f0acf2b..a48d7af 100644
--- a/internal/logo.go
+++ b/internal/logo.go
@@ -19,10 +19,10 @@
package internal
-var Version = "0.1.0"
+var GitCommit = "dev"
var Logo = " ____ _ _ __ __ _\n" +
- "| _ \\(_) | | \\/ | | | " + Version + "\n" +
+ "| _ \\(_) | | \\/ | | | " + GitCommit + "\n" +
"| |_) |_| |_| \\ / | __ _ ___| |_ _ _ __ ___\n" +
"| _ <| | __| |\\/| |/ _` |/ _ \\ | | | | '_ ` _ \\\n" +
"| |_) | | |_| | | | (_| | __/ | |_| | | | | | |\n" +
diff --git a/internal/organisation/bolt.go b/internal/organisation/bolt.go
index f8c3555..f195749 100644
--- a/internal/organisation/bolt.go
+++ b/internal/organisation/bolt.go
@@ -24,7 +24,7 @@ import (
"time"
"github.com/bitmaelum/key-resolver-go/internal"
- "github.com/boltdb/bolt"
+ bolt "go.etcd.io/bbolt"
)
type boltResolver struct {
@@ -77,6 +77,8 @@ func (b boltResolver) Create(hash, publicKey, proof string, validations []string
Proof: proof,
Validations: validations,
Serial: uint64(time.Now().UnixNano()),
+ Deleted: false,
+ DeletedAt: time.Time{},
}
buf, err := json.Marshal(rec)
if err != nil {
@@ -113,3 +115,80 @@ func (b boltResolver) Delete(hash string) (bool, error) {
return true, nil
}
+
+func (b boltResolver) SoftDelete(hash string) (bool, error) {
+ err := b.client.Update(func(tx *bolt.Tx) error {
+ bucket := tx.Bucket([]byte(b.bucketName))
+ if bucket == nil {
+ return nil
+ }
+
+ rec, err := getFromBucket(bucket, hash)
+ if err != nil {
+ return ErrNotFound
+ }
+
+ // make record deleted
+ rec.Deleted = true
+ rec.DeletedAt = time.Now()
+
+ // Store
+ buf, err := json.Marshal(rec)
+ if err != nil {
+ return err
+ }
+ return bucket.Put([]byte(hash), buf)
+ })
+
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
+
+func (b boltResolver) SoftUndelete(hash string) (bool, error) {
+ err := b.client.Update(func(tx *bolt.Tx) error {
+ bucket := tx.Bucket([]byte(b.bucketName))
+ if bucket == nil {
+ return nil
+ }
+
+ rec, err := getFromBucket(bucket, hash)
+ if err != nil {
+ return ErrNotFound
+ }
+
+ // undelete
+ rec.Deleted = false
+ rec.DeletedAt = time.Time{}
+
+ // Store
+ buf, err := json.Marshal(rec)
+ if err != nil {
+ return err
+ }
+ return bucket.Put([]byte(hash), buf)
+ })
+
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
+
+func getFromBucket(bucket *bolt.Bucket, hash string) (*ResolveInfoType, error) {
+ data := bucket.Get([]byte(hash))
+ if data == nil {
+ return nil, ErrNotFound
+ }
+
+ rec := &ResolveInfoType{}
+ err := json.Unmarshal(data, &rec)
+ if err != nil {
+ return nil, ErrNotFound
+ }
+
+ return rec, nil
+}
diff --git a/internal/organisation/dynamodb.go b/internal/organisation/dynamodb.go
index 6b7640b..085ecaa 100644
--- a/internal/organisation/dynamodb.go
+++ b/internal/organisation/dynamodb.go
@@ -31,7 +31,7 @@ import (
)
type dynamoDbResolver struct {
- C *dynamodb.DynamoDB
+ Dyna *dynamodb.DynamoDB
TableName string
}
@@ -45,12 +45,14 @@ type Record struct {
Proof string `dynamodbav:"proof"`
Validations []string `dynamodbav:"validations"`
Serial uint64 `dynamodbav:"sn"`
+ Deleted bool `dynamodbav:"deleted"`
+ DeletedAt uint64 `dynamodbav:"deleted_at"`
}
// NewDynamoDBResolver returns a new resolver based on DynamoDB
func NewDynamoDBResolver(client *dynamodb.DynamoDB, tableName string) Repository {
return &dynamoDbResolver{
- C: client,
+ Dyna: client,
TableName: tableName,
}
}
@@ -74,7 +76,7 @@ func (r *dynamoDbResolver) Update(info *ResolveInfoType, publicKey, proof string
},
}
- _, err := r.C.UpdateItem(input)
+ _, err := r.Dyna.UpdateItem(input)
if err != nil {
log.Print(err)
return false, err
@@ -103,12 +105,12 @@ func (r *dynamoDbResolver) Create(hash, publicKey, proof string, validations []s
TableName: aws.String(r.TableName),
}
- _, err = r.C.PutItem(input)
+ _, err = r.Dyna.PutItem(input)
return err == nil, err
}
func (r *dynamoDbResolver) Get(hash string) (*ResolveInfoType, error) {
- result, err := r.C.GetItem(&dynamodb.GetItemInput{
+ result, err := r.Dyna.GetItem(&dynamodb.GetItemInput{
TableName: aws.String(r.TableName),
Key: map[string]*dynamodb.AttributeValue{
"hash": {S: aws.String(hash)},
@@ -132,6 +134,11 @@ func (r *dynamoDbResolver) Get(hash string) (*ResolveInfoType, error) {
return nil, ErrNotFound
}
+ // We would prefer if we didn't retrieve it from the Getitem input
+ if record.Deleted {
+ return nil, ErrNotFound
+ }
+
return &ResolveInfoType{
Hash: record.Hash,
PubKey: record.PublicKey,
@@ -142,7 +149,7 @@ func (r *dynamoDbResolver) Get(hash string) (*ResolveInfoType, error) {
}
func (r *dynamoDbResolver) Delete(hash string) (bool, error) {
- _, err := r.C.DeleteItem(&dynamodb.DeleteItemInput{
+ _, err := r.Dyna.DeleteItem(&dynamodb.DeleteItemInput{
TableName: aws.String(r.TableName),
Key: map[string]*dynamodb.AttributeValue{
"hash": {S: aws.String(hash)},
@@ -157,3 +164,45 @@ func (r *dynamoDbResolver) Delete(hash string) (bool, error) {
return true, nil
}
+
+func (r *dynamoDbResolver) SoftDelete(hash string) (bool, error) {
+ input := &dynamodb.UpdateItemInput{
+ ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
+ ":dt": {N: aws.String(strconv.FormatInt(time.Now().Unix(), 10))},
+ },
+ TableName: aws.String(r.TableName),
+ UpdateExpression: aws.String("SET deleted=1, deleted_at=:dt"),
+ Key: map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String(hash)},
+ },
+ }
+
+ _, err := r.Dyna.UpdateItem(input)
+ if err != nil {
+ log.Print(err)
+ return false, err
+ }
+
+ return true, nil
+}
+
+func (r *dynamoDbResolver) SoftUndelete(hash string) (bool, error) {
+ input := &dynamodb.UpdateItemInput{
+ ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
+ ":dt": {N: aws.String("")},
+ },
+ TableName: aws.String(r.TableName),
+ UpdateExpression: aws.String("SET deleted=0, deleted_at=:dt"),
+ Key: map[string]*dynamodb.AttributeValue{
+ "hash": {S: aws.String(hash)},
+ },
+ }
+
+ _, err := r.Dyna.UpdateItem(input)
+ if err != nil {
+ log.Print(err)
+ return false, err
+ }
+
+ return true, nil
+}
diff --git a/internal/organisation/repository.go b/internal/organisation/repository.go
index b71e503..8114925 100644
--- a/internal/organisation/repository.go
+++ b/internal/organisation/repository.go
@@ -21,6 +21,7 @@ package organisation
import (
"os"
+ "time"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
@@ -33,6 +34,8 @@ type ResolveInfoType struct {
Proof string
Validations []string
Serial uint64
+ Deleted bool
+ DeletedAt time.Time
}
// Repository to resolve records
@@ -40,6 +43,8 @@ type Repository interface {
Get(hash string) (*ResolveInfoType, error)
Create(hash, publicKey, proof string, validations []string) (bool, error)
Update(info *ResolveInfoType, publicKey, proof string, validations []string) (bool, error)
+ SoftDelete(hash string) (bool, error)
+ SoftUndelete(hash string) (bool, error)
Delete(hash string) (bool, error)
}
diff --git a/internal/organisation/sqlite.go b/internal/organisation/sqlite.go
index 2739aff..a42f786 100644
--- a/internal/organisation/sqlite.go
+++ b/internal/organisation/sqlite.go
@@ -38,7 +38,7 @@ type SqliteDbResolver struct {
}
// NewDynamoDBResolver returns a new resolver based on DynamoDB
-func NewSqliteResolver(dsn string) *SqliteDbResolver {
+func NewSqliteResolver(dsn string) Repository {
if !strings.HasPrefix(dsn, "file:") {
if dsn == ":memory:" {
dsn = "file::memory:?mode=memory"
@@ -58,7 +58,7 @@ func NewSqliteResolver(dsn string) *SqliteDbResolver {
TimeNow: time.Now(),
}
- _, _ = db.conn.Exec("CREATE TABLE IF NOT EXISTS mock_organisation (hash VARCHAR(64) PRIMARY KEY, proof TEXT, validations TEXT, pubkey TEXT, serial INTEGER)")
+ _, _ = db.conn.Exec("CREATE TABLE IF NOT EXISTS mock_organisation (hash VARCHAR(64) PRIMARY KEY, proof TEXT, validations TEXT, pubkey TEXT, serial INTEGER, deleted INTEGER, deleted_at INTEGER)")
return db
}
@@ -92,7 +92,7 @@ func (r *SqliteDbResolver) Create(hash, publicKey, proof string, validations []s
return false, err
}
- res, err := r.conn.Exec("INSERT INTO mock_organisation VALUES (?, ?, ?, ?, ?)", hash, proof, string(b), publicKey, newSerial)
+ res, err := r.conn.Exec("INSERT INTO mock_organisation VALUES (?, ?, ?, ?, ?, 0, 0)", hash, proof, string(b), publicKey, newSerial)
if err != nil {
return false, err
}
@@ -140,3 +140,35 @@ func (r *SqliteDbResolver) Delete(hash string) (bool, error) {
count, err := res.RowsAffected()
return count != 0, err
}
+
+func (r *SqliteDbResolver) SoftDelete(hash string) (bool, error) {
+ st, err := r.conn.Prepare("UPDATE mock_organisation SET deleted=1, deleted_at=? WHERE hash=?")
+ if err != nil {
+ return false, err
+ }
+
+ dt := time.Now().Unix()
+ res, err := st.Exec(dt, hash)
+ if err != nil {
+ return false, err
+ }
+
+ count, err := res.RowsAffected()
+ return count != 0, err
+
+}
+
+func (r *SqliteDbResolver) SoftUndelete(hash string) (bool, error) {
+ st, err := r.conn.Prepare("UPDATE mock_organisation SET deleted=0, deleted_at=NULL WHERE hash=?")
+ if err != nil {
+ return false, err
+ }
+
+ res, err := st.Exec(hash)
+ if err != nil {
+ return false, err
+ }
+
+ count, err := res.RowsAffected()
+ return count != 0, err
+}
diff --git a/internal/routing/boltdb.go b/internal/routing/boltdb.go
index 9c89c1f..6232de7 100644
--- a/internal/routing/boltdb.go
+++ b/internal/routing/boltdb.go
@@ -24,7 +24,7 @@ import (
"time"
"github.com/bitmaelum/key-resolver-go/internal"
- "github.com/boltdb/bolt"
+ bolt "go.etcd.io/bbolt"
)
type boltResolver struct {
diff --git a/internal/testing/testing_test.go b/internal/testing/testing_test.go
new file mode 100644
index 0000000..ba776f8
--- /dev/null
+++ b/internal/testing/testing_test.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package testing
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestReadTestKey(t *testing.T) {
+ priv, pub, err := ReadTestKey("../../testdata/key-7.json")
+ assert.NoError(t, err)
+ assert.Equal(t, priv.String(), "ed25519 MC4CAQAwBQYDK2VwBCIEIApsDq5uwKSUNlmw9z3u63CeNdrfDgBOkJRmvM6gvQj3")
+ assert.Equal(t, pub.String(), "ed25519 MCowBQYDK2VwAyEA1xbVcwtwUx9EFnvZltYd7qz1FxwJOOugkkA9vHYxoQM=")
+
+ priv, pub, err = ReadTestKey("../does-not-exist.json")
+ assert.Error(t, err)
+ assert.Nil(t, priv)
+ assert.Nil(t, pub)
+}