1
0
Fork 0
mirror of https://github.com/diamondburned/cchat-gtk.git synced 2025-11-05 18:34:55 +00:00

Added a fallback keyring driver; fixed minor bugs

This commit is contained in:
diamondburned 2020-07-09 00:19:18 -07:00
parent f2f59b3c2b
commit 394535faff
8 changed files with 234 additions and 34 deletions

View file

@ -0,0 +1,42 @@
package driver
import (
"fmt"
"github.com/diamondburned/cchat-gtk/internal/log"
)
type Provider interface {
Get(service string, v interface{}) error
Set(service string, v interface{}) error
}
type Store struct {
providers []Provider
}
func NewStore(providers ...Provider) Store {
return Store{providers}
}
func (s Store) Get(service string, v interface{}) error {
for _, provider := range s.providers {
if err := provider.Get(service, v); err == nil {
return nil
} else {
log.Info(err)
}
}
return fmt.Errorf("service %s not found in keyring services.", service)
}
func (s Store) Set(service string, v interface{}) error {
for _, provider := range s.providers {
if err := provider.Set(service, v); err == nil {
return nil
} else {
log.Info(err)
}
}
return fmt.Errorf("failed to set keyring for service %s", service)
}

View file

@ -0,0 +1,109 @@
package json
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"unicode"
"github.com/pkg/errors"
)
var ErrUnsafePermission = errors.New("secrets.json file has unsafe permission")
type Provider struct {
dir string
}
func NewProvider(dir string) Provider {
return Provider{dir}
}
func (p Provider) open(service string, write bool) (*os.File, error) {
var flags int
if write {
// If file does not exist, then create a new one. Else, erase the file.
// The file can only be written onto.
flags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
} else {
// If file does not exist, then error out. The file can only be read
// from.
flags = os.O_RDONLY
}
// Make a filename using the given service.
var filename = fmt.Sprintf("%s_secret.json", SanitizeName(service))
f, err := os.OpenFile(filepath.Join(p.dir, filename), flags, 0600)
if err != nil {
return nil, errors.Wrap(err, "Failed to open file")
}
// Stat the file and verify that the permissions are safe.
s, err := f.Stat()
if err != nil {
return nil, errors.Wrap(err, "Failed to stat file")
}
if m := s.Mode(); m != 0600 {
return nil, fmt.Errorf("secrets.json file has unsafe permission %06o", m)
}
return f, nil
}
// Get unmarshals the service from the JSON secret file. It errors out if the
// secret file does not exist.
func (p Provider) Get(service string, v interface{}) error {
f, err := p.open(service, false)
if err != nil {
return err
}
defer f.Close()
if err := json.NewDecoder(f).Decode(v); err != nil {
return errors.Wrap(err, "Failed to decode JSON")
}
return nil
}
// Set writes the service's data into a JSON secret file.
func (p Provider) Set(service string, v interface{}) error {
f, err := p.open(service, true)
if err != nil {
return err
}
defer f.Close()
// Create a new formatted encoder.
enc := json.NewEncoder(f)
enc.SetIndent("", "\n")
// Encode using created encoder.
if err := enc.Encode(v); err != nil {
return errors.Wrap(err, "Failed to encode JSON")
}
return nil
}
// SanitizeName sanitizes the name so that it's safe to use as a filename.
func SanitizeName(name string) string {
// Escape all weird characters in a filename.
name = strings.Map(underscoreNonAlphanum, name)
// Lower-case everything.
name = strings.ToLower(name)
return name
}
func underscoreNonAlphanum(r rune) rune {
// especially does not cover slashes
if unicode.IsLetter(r) || unicode.IsNumber(r) {
return r
}
return '_'
}

View file

@ -0,0 +1,36 @@
package keyring
import (
"bytes"
"encoding/gob"
"strings"
"github.com/diamondburned/cchat-gtk/internal/keyring/driver"
"github.com/zalando/go-keyring"
)
type Provider struct{}
var _ driver.Provider = (*Provider)(nil)
func NewProvider() driver.Provider {
return Provider{}
}
func (Provider) Get(service string, v interface{}) error {
s, err := keyring.Get("cchat-gtk", service)
if err != nil {
return err
}
return gob.NewDecoder(strings.NewReader(s)).Decode(v)
}
func (Provider) Set(service string, v interface{}) error {
var b bytes.Buffer
if err := gob.NewEncoder(&b).Encode(v); err != nil {
return err
}
return keyring.Set("cchat-gtk", service, b.String())
}

View file

@ -1,37 +1,21 @@
package keyring
import (
"bytes"
"encoding/gob"
"strings"
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/keyring/driver"
"github.com/diamondburned/cchat-gtk/internal/keyring/driver/json"
"github.com/diamondburned/cchat-gtk/internal/keyring/driver/keyring"
"github.com/diamondburned/cchat-gtk/internal/log"
"github.com/diamondburned/cchat-gtk/internal/ui/config"
"github.com/diamondburned/cchat/text"
"github.com/pkg/errors"
"github.com/zalando/go-keyring"
)
func get(service string, v interface{}) error {
s, err := keyring.Get("cchat-gtk", service)
if err != nil {
return err
}
// Deleting immediately does not work on a successful start-up.
// keyring.Delete("cchat-gtk", service)
return gob.NewDecoder(strings.NewReader(s)).Decode(v)
}
func set(service string, v interface{}) error {
var b bytes.Buffer
if err := gob.NewEncoder(&b).Encode(v); err != nil {
return err
}
return keyring.Set("cchat-gtk", service, b.String())
}
// Declare a keyring store with fallbacks.
var store = driver.NewStore(
keyring.NewProvider(),
json.NewProvider(config.DirPath()), // fallback
)
type Session struct {
ID string
@ -65,7 +49,7 @@ func ConvertSession(ses cchat.Session, name string) *Session {
}
func SaveSessions(serviceName text.Rich, sessions []Session) {
if err := set(serviceName.Content, sessions); err != nil {
if err := store.Set(serviceName.Content, sessions); err != nil {
log.Warn(errors.Wrap(err, "Error saving session"))
}
}
@ -74,7 +58,7 @@ func SaveSessions(serviceName text.Rich, sessions []Session) {
// calls the auth callback inside the Gtk main thread.
func RestoreSessions(serviceName text.Rich) (sessions []Session) {
// Ignore the error, it's not important.
if err := get(serviceName.Content, &sessions); err != nil {
if err := store.Get(serviceName.Content, &sessions); err != nil {
log.Warn(err)
}
return

View file

@ -50,8 +50,13 @@ func Error(err error) {
Write("Error: " + err.Error())
}
// Warn calls Info().
func Warn(err error) {
Write("Warn: " + err.Error())
Info(err)
}
func Info(err error) {
Write("Info: " + err.Error())
}
func Write(msg string) {

View file

@ -84,10 +84,13 @@ func Sections() (sects [sectionLen][]Entry) {
return
}
// Save the global config.
func Save() error {
return MarshalToFile(ConfigFile, sections)
}
// Restore the global config. IsNotExist is not an error and will not be
// returned.
func Restore() error {
return UnmarshalFromFile(ConfigFile, &sections)
}

View file

@ -5,13 +5,19 @@ import (
"log"
"os"
"path/filepath"
"sync"
"github.com/pkg/errors"
)
var DirPath string
// dirPath indicates the path to the config. This variable is created when
// __init is called.
var dirPath string
func init() {
// Singleton to initialize the config directories once.
var __initonce sync.Once
func __init() {
// Load the config dir:
d, err := os.UserConfigDir()
if err != nil {
@ -19,16 +25,26 @@ func init() {
}
// Fill Path:
DirPath = filepath.Join(d, "cchat-gtk")
dirPath = filepath.Join(d, "cchat-gtk")
// Ensure it exists:
if err := os.Mkdir(DirPath, 0755|os.ModeDir); err != nil && !os.IsExist(err) {
if err := os.Mkdir(dirPath, 0755|os.ModeDir); err != nil && !os.IsExist(err) {
log.Fatalln("Failed to make config dir:", err)
}
}
// DirPath returns the config directory.
func DirPath() string {
// Ensure that files and folders are initialized.
__initonce.Do(__init)
return dirPath
}
// MarshalToFile marshals the given interface into the given filename. The
// filename will be prepended with the config directory.
func MarshalToFile(file string, from interface{}) error {
file = filepath.Join(DirPath, file)
file = filepath.Join(DirPath(), file)
f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_SYNC|os.O_TRUNC, 0644)
if err != nil {
@ -46,8 +62,11 @@ func MarshalToFile(file string, from interface{}) error {
return nil
}
// UnmarshalFromFile unmarshals the given filename to the given interface. The
// filename will be prepended with the config directory. IsNotExist errors are
// ignored.
func UnmarshalFromFile(file string, to interface{}) error {
file = filepath.Join(DirPath, file)
file = filepath.Join(DirPath(), file)
f, err := os.OpenFile(file, os.O_RDONLY, 0644)
if err != nil {

View file

@ -107,6 +107,8 @@ func NewEmptyContainer() *GenericContainer {
content.SetSelectable(true)
content.Show()
// Causes bugs with selections.
// content.Connect("grab-notify", func(l *gtk.Label, grabbed bool) {
// if grabbed {
// // Hack to stop the label from selecting everything after being