Skip to content

Commit

Permalink
Import/Export Db (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
vikkio88 authored May 22, 2023
1 parent 05a45c6 commit 3400409
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 5 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
main
bin/
muscurdi_db/
muscurdi_db/
*.gob
*.migbak
70 changes: 70 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package db

import (
"encoding/gob"
"errors"
"fmt"
"muscurdig/libs"
"muscurdig/models"
"os"
"path/filepath"
"time"

c "github.com/ostafen/clover/v2"
)
Expand Down Expand Up @@ -45,6 +49,8 @@ func (db *Db) Drop() {
db.clover.DropCollection(passwordEntryCollection)
db.clover.DropCollection(masterPasswordCollection)
db.cache = map[string]any{}
// this is to avoid fatal errors in case of drop then continuing
setupCollections(db.clover)
}

func (db *Db) SaveMasterPassword(mp models.MasterPassword) (models.MasterPassword, error) {
Expand Down Expand Up @@ -139,10 +145,64 @@ func (db *Db) FilterPasswords(search string) []models.PasswordEntry {
}

func (db *Db) GetAllPasswords() []models.PasswordEntry {
//TODO: maybe I need to check that the collection exists
pwDtosDocs, _ := db.clover.FindAll(c.NewQuery(passwordEntryCollection))
return db.loadManyPasswordEntry(pwDtosDocs)
}

func (db *Db) GenerateDump(baseFolder string) (string, error) {
dumpDate := time.Now().Format("200601021504")
fileName := filepath.Join(baseFolder, fmt.Sprintf("pwds_%s.%s", dumpDate, DumpFileExtension))
f, err := os.Create(fileName)
if err != nil {
return "", err
}
defer f.Close()

mp, _ := db.GetMasterPassword()
pwDtosDocs, _ := db.clover.FindAll(c.NewQuery(passwordEntryCollection))
pwds := loadManyPasswordEntryDto(pwDtosDocs)
data := DbDump{
mp.Value,
pwds,
}

encoder := gob.NewEncoder(f)
errEncoding := encoder.Encode(data)

return fileName, errEncoding
}

func (db *Db) ImportDump(password string, dumpFileLocation string) error {
file, err := os.Open(dumpFileLocation)
if err != nil {
return err
}
defer file.Close()

decoder := gob.NewDecoder(file)
var importedDump DbDump
err = decoder.Decode(&importedDump)
if err != nil {
return errors.New("Cannot read data dump.")
}

mp := models.NewMasterPasswordFromB64(importedDump.Mp)
if !mp.Check(password) {
return errors.New("Invalid dump password.")
}

newMp := models.NewMasterPassword(password)
cryptoImport := newMp.GetCrypto()

for _, dto := range importedDump.Pwds {
pe := dto.ToPasswordEntry(&cryptoImport)
db.InsertPasswordEntry(pe)
}

return nil
}

func (db *Db) loadManyPasswordEntry(docs []*c.Document) []models.PasswordEntry {
crypto := db.GetCryptoInstance()
result := make([]models.PasswordEntry, len(docs))
Expand All @@ -153,6 +213,16 @@ func (db *Db) loadManyPasswordEntry(docs []*c.Document) []models.PasswordEntry {

return result
}

func loadManyPasswordEntryDto(docs []*c.Document) []models.PasswordEntryDto {
result := make([]models.PasswordEntryDto, len(docs))
for i, doc := range docs {
result[i] = *loadPasswordEntryDto(doc)
}

return result
}

func loadPasswordEntryDto(doc *c.Document) *models.PasswordEntryDto {
var dto models.PasswordEntryDto
doc.Unmarshal(&dto)
Expand Down
10 changes: 10 additions & 0 deletions db/db_dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package db

import "muscurdig/models"

const DumpFileExtension = "migbak"

type DbDump struct {
Mp string
Pwds []models.PasswordEntryDto
}
84 changes: 84 additions & 0 deletions db/db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package db_test

import (
d "muscurdig/db"
"muscurdig/models"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

const (
testing_db_folder = "testing_db"
master_password = "mp"
dump_file_renamed = "testing_dump.migbak"
)

type DbIntegrationTestSuite struct {
suite.Suite
}

func (suite *DbIntegrationTestSuite) TearDownSuite() {
if _, err := os.Stat(testing_db_folder); !os.IsNotExist(err) {
os.RemoveAll(testing_db_folder)
}
if _, err := os.Stat(dump_file_renamed); !os.IsNotExist(err) {
os.RemoveAll(dump_file_renamed)
}
}

func (suite *DbIntegrationTestSuite) TestDbIntegrationWorkflow() {
t := suite.T()

db := d.NewDb(testing_db_folder)
mp := models.NewMasterPassword(master_password)
db.SaveMasterPassword(mp)
db.InsertPasswordEntry(models.NewPasswordEntry("bla", "bla", "bla"))
db.InsertPasswordEntry(models.NewPasswordEntry("blip", "blop", "blup"))

pwds := db.GetAllPasswords()
assert.Len(t, pwds, 2)

pwds = db.FilterPasswords("bla")
assert.Len(t, pwds, 1)

pwds = db.FilterPasswords("BlOp")
assert.Len(t, pwds, 1)

pwds = db.FilterPasswords("blu")
assert.Len(t, pwds, 0)

dumpFile, err := db.GenerateDump("")
assert.Nil(t, err)
assert.FileExists(t, dumpFile)

os.Rename(dumpFile, dump_file_renamed)
assert.FileExists(t, dump_file_renamed)
db.Drop()
db.Close()

db = d.NewDb(testing_db_folder)
defer db.Close()

db.SaveMasterPassword(mp)
db.InsertPasswordEntry(models.NewPasswordEntry("flippity", "flop", "password"))
pwds = db.GetAllPasswords()
assert.Len(t, pwds, 1)

errImport := db.ImportDump("wrongPassword", dump_file_renamed)
assert.NotNil(t, errImport)
errImport2 := db.ImportDump(master_password, dump_file_renamed)
assert.Nil(t, errImport2)
pwds = db.GetAllPasswords()
assert.Len(t, pwds, 3)

pwds = db.FilterPasswords("bla")
assert.Len(t, pwds, 1)
assert.Equal(t, "bla", pwds[0].Password)
}

func TestDbIntegrationTests(t *testing.T) {
suite.Run(t, new(DbIntegrationTestSuite))
}
14 changes: 14 additions & 0 deletions libs/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,19 @@ func TestCryptoCodeDecode(t *testing.T) {
t1, err1 := libs.Decrypt(c, key)
assert.Nil(t, err1)
assert.Equal(t, "some stuff", t1)
}

func TestCryptoDecryptWithTwoDifferentInstances(t *testing.T) {
key := "pass"
content := "somestuff"
c := libs.NewCrypto(key)
enc, err := c.EncryptB64(content)
assert.Nil(t, err)
assert.NotEmpty(t, enc)

c2 := libs.NewCrypto(key)
content2, err2 := c2.DecryptB64(enc)
assert.Nil(t, err2)
assert.Equal(t, content, content2)

}
5 changes: 4 additions & 1 deletion models/password_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ type PasswordEntryDto struct {
}

func (p *PasswordEntryDto) ToPasswordEntry(crypto *libs.Crypto) PasswordEntry {
clear, _ := crypto.DecryptB64(p.Password)
clear, err := crypto.DecryptB64(p.Password)
if err != nil {
return PasswordEntry{}
}
return NewPasswordEntryWithId(p.Id, p.Website, p.Username, clear)
}
21 changes: 21 additions & 0 deletions ui/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
Expand Down Expand Up @@ -83,3 +84,23 @@ func topAligned(object fyne.CanvasObject) *fyne.Container {
func bottomAligned(object fyne.CanvasObject) *fyne.Container {
return container.NewBorder(nil, object, nil, nil)
}

func showPasswordDialog(title, placeHolder string, callback func(pwd string), window fyne.Window) {
pwdEntry := widget.NewPasswordEntry()
pwdEntry.PlaceHolder = placeHolder
var d dialog.Dialog
okBtn := widget.NewButton("Ok", func() {
callback(pwdEntry.Text)
d.Hide()
})
okBtn.Disable()
pwdEntry.OnChanged = func(s string) {
okBtn.Disable()
if len(s) > 2 {
okBtn.Enable()
}
}
d = dialog.NewCustom(title, "Cancel", container.NewBorder(nil, nil, nil, okBtn, pwdEntry), window)

d.Show()
}
37 changes: 34 additions & 3 deletions ui/settings_view.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ui

import (
"fmt"
c "muscurdig/context"

"fyne.io/fyne/v2"
Expand All @@ -11,6 +12,7 @@ import (
)

func GetSettingsView(ctx *c.AppContext) *fyne.Container {
message := newFlashTxtPlaceholder()
deleteAllBtn := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() {
dialog.ShowConfirm("Confirmation", "Are you sure you want to remove all the saved password?",
func(b bool) {
Expand All @@ -26,18 +28,47 @@ func GetSettingsView(ctx *c.AppContext) *fyne.Container {
})

exportPassBtn := widget.NewButtonWithIcon("", theme.DownloadIcon(), func() {
dialog.ShowInformation("Coming soon!", "This featute is coming soon", ctx.GetWindow())
dialog.ShowFolderOpen(func(lu fyne.ListableURI, err error) {
path := lu.Path()
path, err1 := ctx.Db.GenerateDump(path)
if err1 != nil {
errorMessage("Could not create dump file!", message)
} else {
successMessage(fmt.Sprintf("Saved as: %s", path), message)
}

}, ctx.GetWindow())
})

importPassBtn := widget.NewButtonWithIcon("", theme.UploadIcon(), func() {
dialog.ShowInformation("Coming soon!", "This featute is coming soon", ctx.GetWindow())
dialog.ShowFileOpen(func(uc fyne.URIReadCloser, err error) {
uc.Close()
path := uc.URI().Path()
showPasswordDialog(
"Insert Dump Master Password", "Password...",
func(p string) {
err := ctx.Db.ImportDump(p, path)
if err != nil {
errorMessage(fmt.Sprintf("Error: %s", err), message)
} else {
successMessage("Dump file imported!", message)
}

},
ctx.GetWindow())

}, ctx.GetWindow())
})

return container.NewMax(
container.NewBorder(
centered(h1("Settings")),
leftAligned(
container.NewBorder(
nil,
nil,
backButton(ctx, c.List),
nil,
centered(message),
),
nil,
nil,
Expand Down

0 comments on commit 3400409

Please sign in to comment.