cchat-gtk/internal/keyring/driver/json/json.go

110 lines
2.4 KiB
Go

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 %04o", 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 '_'
}