Golang BoltDB Delete Key Seemingly Not Working - go

CentOS 7, Github boltdb/bolt version 1.3.1, go version go1.17.7 linux/amd64
This issue may go to a misunderstanding of how BoltDB works, or maybe I have a bug, or maybe there is an issue. I've used BoltDB before, and have had very good results. Though, I didn't explicly look for this issue. What I'm seeing is that I try to delete a key from a bucket, and the key and its value are deleted in the active db.Update, but it's still there after that db.Update is ended. Looking for any explanation of what might be going on. Seems like this functionality couldn't possibly be broken.
I am using a BoltDB bucket for storing a temporary token associated with an email address for creating a new account. Want to be tidy and clean up old data right away (expired tokens, misused tokens, etc). Pretty standard stuff. The structure for the temporary token is (the key is the temporary token, a 10 digit random character string):
(Temporary Token is the Bucket key)
type tempTokenStruct struct {
EmailAddress string `json:"emailaddress"` // Email Address to be changed
TokenExpiryTime int64 `json:"tokenexpirytime"` // Expiry Time for token in Epoch time
}
The user enters an email address in a web form and hits 'submit'. That creates a call to the REST service that creates an entry in the temporary token table, like:
"BpLnfgDsc2" => foo#bar.com, 1645650084
The service emails a URL that has the temporary token embedded, and that link takes the user to a form that allows them to put in their email address (again to verify) and new password (twice). Hitting Submit then results in the following code being called from within a web handler:
func checkTokenValid(emailAddress string, tempToken string) error {
var tempTokenData tempTokenStruct
var tempTokenBytes []byte
tempTokenBytes = []byte(tempToken)
db, err := bolt.Open(USER_DB_NAME, 0644, nil)
if err != nil {
return err
}
defer db.Close()
err = db.Update(func(tx *bolt.Tx) error {
tempTokenBucket := tx.Bucket([]byte("temptokens"))
// The bucket hasn't been created, so there are no stored tokens
if tempTokenBucket == nil {
return errors.New("Not Authorized (1): Please request a new password new/change email from the login page.")
}
// There is no matching token stored in the bucket, so this is an invalid request
tempTokenJSON := tempTokenBucket.Get(tempTokenBytes)
//[I've put a printf here: A]
if tempTokenJSON == nil {
return errors.New("Not Authorized (2): Please request a new password new/change email from the login page.")
}
jsonConvertErr := json.Unmarshal(tempTokenJSON, &tempTokenData)
if jsonConvertErr != nil {
tempTokenBucket.Delete(tempTokenBytes)
return errors.New("Not Authorized (3): Please request a new password new/change email from the login page.")
}
// Check to see if the time is expired, if so, remove the key and indicate error
if tempTokenData.TokenExpiryTime < time.Now().Unix() {
tempTokenBucket.Delete(tempTokenBytes)
//[I've put a printf here: B]
return errors.New("Not Authorized (4): Please request a new password new/change email from the login page.")
}
// Check to see if the email addresses match
if emailAddress != tempTokenData.EmailAddress {
tempTokenBucket.Delete(tempTokenBytes)
return errors.New("Not Authorized (5): Please request a new password new/change email from the login page.")
}
tempTokenBucket.Delete(tempTokenBytes)
return nil
})
// This is test code to see if the key was in fact deleted
db.Update(func(tx *bolt.Tx) error {
tempTokenBucket := tx.Bucket([]byte("temptokens"))
tempTokenJSON := tempTokenBucket.Get(tempTokenBytes)
// [I've put a printf here: C]
return nil
})
return err
}
I'm testing with a timed-out token (4), so the idea is that when it encounters that timed out token, it wants to delete this now invalid token from the bucket.
At the A location, it prints:
First Get call token BpLnfgDsc2 is {"emailaddress":"foo#bar.com","tokenexpirytime":1645650084}
At the B location I put code in that does a .Get, it prints out (looks to be deleted):
Before the DB Close (4), after deleting, token BpLnfgDsc2 is
At the C location, it prints (looks to be back):
After the DB Close, token BpLnfgDsc2 is {"emailaddress":"foo#bar.com","tokenexpirytime":1645650084}
There are no errors returned for anything. I've repeated this many times, putting fmt.Printfs everywhere to see what's going on. The results are the same, the key doesn't seem to be getting deleted. After this sits, I 'vi -b' the DB file, and the key, value is still there. Running after it sits, it still sees the key value there. I'm confused, and any pointers will be appreciated.
Update: The basic bolt functionality of Put/Get/Delete/Get works as per this test code (should be obvious):
package main
import "fmt"
import "encoding/json"
import "github.com/boltdb/bolt"
type tempTokenStruct struct {
EmailAddress string `json:"emailaddress"` // Email Address to be changed (Temporary Token is the DB key)
TokenExpiryTime int64 `json:"tokenexpirytime"` // Expiry Time for token in Epoch time
}
func main() {
var tempToken tempTokenStruct
tempToken.EmailAddress = "foo#bar.com"
tempToken.TokenExpiryTime = 1234567890
tempTokenDataJSON, jsonMarshalError := json.Marshal(tempToken)
if jsonMarshalError != nil {
fmt.Printf("JSON Marshal Error: %s\n", jsonMarshalError.Error())
return
}
tempTokenKey := []byte("foo")
db, err := bolt.Open("test.db", 0644, nil)
if err != nil {
fmt.Printf("Error opening Database\n")
return
}
defer db.Close()
// Put a key in the table
err = db.Update(func(tx *bolt.Tx) error {
tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))
if err != nil {
return err
}
dbPutError := tempTokenBucket.Put(tempTokenKey, []byte(tempTokenDataJSON))
return dbPutError
})
if err != nil {
fmt.Printf("Error putting key value pair into table: %s\n", err.Error())
}
// Check if the key/value is there after putting it in
err = db.Update(func(tx *bolt.Tx) error {
tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))
if err != nil {
return err
}
valueGet := tempTokenBucket.Get(tempTokenKey)
fmt.Printf("Value for Token: \"%s\" is \"%s\" just after putting it in there\n", tempTokenKey, valueGet)
return nil
})
// Delete that key from the table
err = db.Update(func(tx *bolt.Tx) error {
tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))
if err != nil {
return err
}
dbDeleteError := tempTokenBucket.Delete(tempTokenKey)
return dbDeleteError
})
if err != nil {
fmt.Printf("Error Deleting key from bucket: %s\n", err.Error())
}
// Check if the key/value is there after deleting it
err = db.Update(func(tx *bolt.Tx) error {
tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))
if err != nil {
return err
}
valueGet := tempTokenBucket.Get(tempTokenKey)
fmt.Printf("Value for Token: \"%s\" is \"%s\" after the delete\n", tempTokenKey, valueGet)
return nil
})
if err != nil {
fmt.Printf("Error getting key from table: %s\n", err.Error())
}
}
Prints out:
Value for Token: "foo" is "{"emailaddress":"foo#bar.com","tokenexpirytime":1234567890}" just after putting it in there
Value for Token: "foo" is "" after the delete
So, not sure why the other code doesn't work. Almost as if the delete is using a different key, but the key is the same across the other code.

I believe that the behaviour of db.Update with a non-nil return value is the confusion here. As per the docs
Inside the closure, you have a consistent view of the database. You commit the transaction by returning nil at the end. You can also rollback the transaction at any point by returning an error.
You are returning an error with:
return errors.New("Not Authorized (4): Please request a new password new/change email from the login page.")
This means that all operations within that db.Update( are rolled back. This can be replicated in your simple example with a small change (return fmt.Errorf("RETURNING ERROR HERE")):
package main
import "fmt"
import "encoding/json"
import "github.com/boltdb/bolt"
type tempTokenStruct struct {
EmailAddress string `json:"emailaddress"` // Email Address to be changed (Temporary Token is the DB key)
TokenExpiryTime int64 `json:"tokenexpirytime"` // Expiry Time for token in Epoch time
}
func main() {
var tempToken tempTokenStruct
tempToken.EmailAddress = "foo#bar.com"
tempToken.TokenExpiryTime = 1234567890
tempTokenDataJSON, jsonMarshalError := json.Marshal(tempToken)
if jsonMarshalError != nil {
fmt.Printf("JSON Marshal Error: %s\n", jsonMarshalError.Error())
return
}
tempTokenKey := []byte("foo")
db, err := bolt.Open("test.db", 0644, nil)
if err != nil {
fmt.Printf("Error opening Database\n")
return
}
defer db.Close()
// Put a key in the table
err = db.Update(func(tx *bolt.Tx) error {
tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))
if err != nil {
return err
}
dbPutError := tempTokenBucket.Put(tempTokenKey, []byte(tempTokenDataJSON))
return dbPutError
})
if err != nil {
fmt.Printf("Error putting key value pair into table: %s\n", err.Error())
}
// Check if the key/value is there after putting it in
err = db.Update(func(tx *bolt.Tx) error {
tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))
if err != nil {
return err
}
valueGet := tempTokenBucket.Get(tempTokenKey)
fmt.Printf("Value for Token: \"%s\" is \"%s\" just after putting it in there\n", tempTokenKey, valueGet)
return nil
})
// Delete that key from the table
err = db.Update(func(tx *bolt.Tx) error {
tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))
if err != nil {
return err
}
tempTokenBucket.Delete(tempTokenKey)
return fmt.Errorf("RETURNING ERROR HERE") // CHANGED HERE
})
if err != nil {
fmt.Printf("Error Deleting key from bucket: %s\n", err.Error())
}
// Check if the key/value is there after deleting it
err = db.Update(func(tx *bolt.Tx) error {
tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))
if err != nil {
return err
}
valueGet := tempTokenBucket.Get(tempTokenKey)
fmt.Printf("Value for Token: \"%s\" is \"%s\" after the delete\n", tempTokenKey, valueGet)
return nil
})
if err != nil {
fmt.Printf("Error getting key from table: %s\n", err.Error())
}
}
The output is now:
Value for Token: "foo" is "{"emailaddress":"foo#bar.com","tokenexpirytime":1234567890}" just after putting it in there
Error Deleting key from bucket: RETURNING ERROR HERE
Value for Token: "foo" is "{"emailaddress":"foo#bar.com","tokenexpirytime":1234567890}" after the delete
This appears to match what you are seeing in your main code. The fix is relatively simple - don't return an error if you want changes to be committed.

Related

Hyperledger Fabric: Using GetHistoryFromKey with Golang

I was trying to implement GetHistoryFromKey() in my Golang Chaincode, but I just get the current token state all the time (and not historic).
My Hyperledger Fabric network (latest version) is very basic for this issue. I am using the fabcar chaincode from fabric-samples in Golang and SDK Node.
After calling InitLedger, I was creating a new car with CreateCar(CarNumber: "121", ...). CarNumber is key in this situation.
Next step is a transaction with ChangeCarOwner("121", "NewOwner")
Now I want to get the history of this car. Therefore I was implementing following function.
func (s *SmartContract) GetHistoryForKeyAccIDUUIDAMO(ctx contractapi.TransactionContextInterface, carNumber string) (string, error) {
historyIter, err := ctx.GetStub().GetHistoryForKey(carNumber)
if err != nil {
return "0", fmt.Errorf("Error in loading History by Key", carNumber)
}
if historyIter.HasNext() {
modification, err := historyIter.Next()
if err != nil {
return "0", fmt.Errorf("Error in getting History by Key in Iteration", carNumber)
}
return string(modification.Value), nil
} else {
return "0", fmt.Errorf("Error in getting HistorybyKey", carNumber)
}
}
Result: {"make":"Toyota","model":"Rav4","colour":"Grey","owner":"NewOwner"}
This is not a historic car token, but the car after changing the owner of Car121 to PersonX
How can I get historic transaction data from the creation of car?
EDIT:
Here is my new function, but it is still the same behaviour.
func (s *SmartContract) GetAssetHistory(ctx contractapi.TransactionContextInterface, account string, carNumber uint64) ([]HistoryQueryResult, error) {
historyKey, err := ctx.GetStub().CreateCompositeKey(Prefix, []string{owner, strconv.FormatUint(carNumber, 10)})
resultsIterator, err := ctx.GetStub().GetHistoryForKey(historyKey)
defer resultsIterator.Close()
var records []HistoryQueryResult
for resultsIterator.HasNext() {
response, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset Asset
if len(response.Value) > 0 {
err = json.Unmarshal(response.Value, &asset)
if err != nil {
return nil, err
}
} else {
asset = Asset{Account: account}
}
timestamp, err := ptypes.Timestamp(response.Timestamp)
if err != nil {
return nil, err
}
record := HistoryQueryResult{
TxId: response.TxId,
Timestamp: timestamp,
Record: &asset,
IsDelete: response.IsDelete,
}
records = append(records, record)
}
return records, nil
}
You are just reading the first value from the history iterator so you are seeing just one of the values that has been associated with that key. To see all the historic values you need to iterate over all of them, perhaps collecting them into a slice. See here for an example:
https://github.com/hyperledger/fabric-samples/blob/8ca50df4ffec311e59451c2a7ebe210d9e6f0004/asset-transfer-ledger-queries/chaincode-go/asset_transfer_ledger_chaincode.go#L389-L433

Reconcile triggers again on creating a secret owned by custom resource

I used the operator-sdk to create a custom resource DatabaseService. Creation a DatabaseService CR should trigger the Reconcile function that would create a secret in the CR namespace after getting it from a third party.
I set the CR as the owner of the secret so that whenever the secret is manually deleted, the reconcile function will trigger again and recreate the secret.
Here is the code:
func (r *DatabaseServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
requeueResult := ctrl.Result{Requeue: true, RequeueAfter: time.Minute}
emptyResult := ctrl.Result{Requeue: false, RequeueAfter: 0}
ds := &operatorsv1alpha1.DatabaseService{}
if err := r.Client.Get(context.Background(), req.NamespacedName, ds); err != nil {
if misc.IsNotFound(err) {
return emptyResult, nil
})
return requeueResult, err
}
secret, err := getSecretFromThirdParty(ds)
if err != nil {
return requeueResult, err
}
if err := controllerutil.SetControllerReference(ds, secret, r.Scheme); err != nil {
logger.Error("failed to set controller reference for the secret", zap.Error(err))
return requeueResult, err
}
if err := r.createOrUpdateSecret(secret, logger); err != nil {
return requeueResult, err
}
return emptyResult, nil
}
func (r *DatabaseServiceReconciler) createOrUpdateSecret(secret *corev1.Secret) error {
if err := r.createNamespaceIfNotExist(secret.Namespace, logger); err != nil {
return err
}
if err := r.Client.Create(context.TODO(), secret); err == nil {
return nil
}
if !apierrors.IsAlreadyExists(err) {
return err
}
if err := r.Client.Update(context.TODO(), secret); err != nil {
return err
}
return nil
}
I am observing that if I set the CR as the owner of the secret before calling createOrUpdateSecret - Reconcile function will be triggered again, because something in the owned object (the secret) has changed.
My Reconcile logic is idempotent so it is not a big problem. However, I've no need for Reconcile to run again after changes to the owned object that took place from inside Reconcile. Right now, every time Reconcile creates / updates a secret it would run again. This behavior seems a bit clunky and results in extra work for the operator and extra calls to the third party.
Is there a way to bypass the re-activation of Reconcile creation / update of owned object from inside Reconcile? Or is it not recommended and I should allow reconcile run repeatedly until nothing is changed?

Unauthorized when using gocosmos to create a document

I got go-sql-driver for Azure CosmosDB from https://github.com/btnguyen2k/gocosmos.
It goes well when i call gocosmos.NewRestClient to get a rest client, CreateDatabase() to create database and CreateCollection() to create collection.
The problem is when i use CreateDocument(), i get response with statuscode 401 and body like this
{"code":"Unauthorized","message":"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'post\ndocs\ndbs/ToDoList/colls/Items\nmon, 31 may 2021 13:31:44 gmt\n\n'\r\nActivityId: a9bbd729-3495-400f-9d79-ddec3737aa92, Microsoft.Azure.Documents.Common/2.11.0"}
i've tried all the solutions I've seen, but i haven't solved the problem.
I followed this tutorial and with this sample code, I can successfully create database, collection and document. Here's my testing result, can it help you?
// connects to MongoDB
func connect() *mongo.Client {
mongoDBConnectionString := os.Getenv(mongoDBConnectionStringEnvVarName)
if mongoDBConnectionString == "" {
log.Fatal("missing environment variable: ", mongoDBConnectionStringEnvVarName)
}
database = os.Getenv(mongoDBDatabaseEnvVarName)
if database == "" {
log.Fatal("missing environment variable: ", mongoDBDatabaseEnvVarName)
}
collection = os.Getenv(mongoDBCollectionEnvVarName)
if collection == "" {
log.Fatal("missing environment variable: ", mongoDBCollectionEnvVarName)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
clientOptions := options.Client().ApplyURI(mongoDBConnectionString).SetDirect(true)
c, err := mongo.NewClient(clientOptions)
err = c.Connect(ctx)
if err != nil {
log.Fatalf("unable to initialize connection %v", err)
}
err = c.Ping(ctx, nil)
if err != nil {
log.Fatalf("unable to connect %v", err)
}
return c
}
// creates a todo
func create(desc string) {
c := connect()
ctx := context.Background()
defer c.Disconnect(ctx)
todoCollection := c.Database(database).Collection(collection)
r, err := todoCollection.InsertOne(ctx, Todo{Description: desc, Status: statusPending})
if err != nil {
log.Fatalf("failed to add todo %v", err)
}
fmt.Println("added todo", r.InsertedID)
}

invalid memory address or nil pointer dereference on QueryRow

I'm new to go and trying to create a signin function, When trying to query row from the database I get this error: runtime error: invalid memory address or nil pointer dere
ference
The crash caused by this line:
result := db.QueryRow("SELECT password FROM users WHERE email=$1", credentials.Email)
This is the code:
type Credentials struct {
Email string `json:"email", db:"email"`
Password string `json:"password", db:"password"`
}
func SignIn(w http.ResponseWriter, r *http.Request) {
credentials := &Credentials{}
err := json.NewDecoder(r.Body).Decode(credentials)
if err != nil {
// If there is something wrong with the request body, return a 400 status
w.WriteHeader(http.StatusBadRequest)
return
}
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+"dbname=%s sslmode=disable", dbInfo.Host, dbInfo.Port, dbInfo.User, dbInfo.DBname)
db, err := sql.Open("postgres", psqlInfo)
println(credentials.Email)
result := db.QueryRow("SELECT password FROM users WHERE email=$1", credentials.Email)
defer db.Close()
if err != nil {
//If there is an issue with the database, return a 500 error.
w.WriteHeader(http.StatusInternalServerError)
return
}
//We create another instance of 'Credentials' t store the credentials we get from the database
storedCreds := &Credentials{}
// Store the obtained password in `storedCreds`
err = result.Scan(&storedCreds.Password)
if err != nil {
// If an entry with the email does not exist, send an "Unauthorized"(401) status
if err == sql.ErrNoRows {
w.WriteHeader(http.StatusUnauthorized)
return
}
//If the error is of any other type, send a 500 status
w.WriteHeader(http.StatusInternalServerError)
return
}
if credentials.Password != storedCreds.Password {
//The two passwords does not match, return a 401 status
w.WriteHeader(http.StatusUnauthorized)
}
}
I checked and credentials.Email is not null and I can't understand what causes this error.
db, err := sql.Open("postgres", psqlInfo)
// ...
result := db.QueryRow(...)
db is probably nil because the sql.Open failed.
You're not checking err != nil like you should always do when a function returns an error type.

How to properly read errors from golang oauth2

token, err := googleOauthConfig.Exchange(context.Background(), code)
if err != nil {
fmt.Fprintf(w, "Err: %+v", err)
}
The output of the fprintf is:
Err: oauth2: cannot fetch token: 401 Unauthorized
Response: {"error":"code_already_used","error_description":"code_already_used"}
I want to check if "error" = "code_already_used". For the life of me, I can't sort out how.
How do I check/return/read "error" or "error_description" of err?
I've looked at the oauth2 code and it's a bit above me.
// retrieveToken takes a *Config and uses that to retrieve an *internal.Token.
// This token is then mapped from *internal.Token into an *oauth2.Token which is returned along
// with an error..
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v)
if err != nil {
if rErr, ok := err.(*internal.RetrieveError); ok {
return nil, (*RetrieveError)(rErr)
}
return nil, err
}
return tokenFromInternal(tk), nil
}
How guess I'm trying to see the (*RetrieveError) part. Right?
THANK YOU!
The expression:
(*RetrieveError)(rErr)
converts therErr's type from *internal.RetrieveError to *RetrieveError. And since RetrieveError is declared in the oauth2 package, you can type assert the error you receive to *oauth2.RetrieveError to get the details. The details are contained in that type's Body field as a slice of bytes.
Since a slice of bytes is not the best format to be inspected and in your case it seems like the bytes contain a json object you can make your life easier by predefining a type into which you can unmarshal those details.
That is:
type ErrorDetails struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
token, err := googleOauthConfig.Exchange(context.Background(), code)
if err != nil {
fmt.Fprintf(w, "Err: %+v", err)
if rErr, ok := err.(*oauth2.RetrieveError); ok {
details := new(ErrorDetails)
if err := json.Unmarshal(rErr.Body, details); err != nil {
panic(err)
}
fmt.Println(details.Error, details.ErrorDescription)
}
}
Can do like this.
arr := strings.Split(err.Error(), "\n")
str := strings.Replace(arr[1], "Response: ", "", 1)
var details ErrorDetails
var json = jsoniter.ConfigCompatibleWithStandardLibrary
err := json.Unmarshal([]byte(str), &details)
if err == nil {
beego.Debug(details.Error)
beego.Debug(details.ErrorDescription)
}

Resources