mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-22 06:03:29 +02:00
rewrite checkpoint
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
||||
Copyright (c) 2013, Travis Cline <travis.cline@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# keyring provides cross-platform keychain access
|
||||
|
||||
http://godoc.org/github.com/tmc/keyring
|
||||
|
||||
Keyring provides a common interface to keyring/keychain tools.
|
||||
|
||||
License: ISC
|
||||
|
||||
Currently implemented:
|
||||
- OSX
|
||||
- SecretService
|
||||
- gnome-keychain (via "gnome_keyring" build flag)
|
||||
|
||||
Contributions welcome!
|
||||
|
||||
Usage example:
|
||||
|
||||
```go
|
||||
err := keyring.Set("libraryFoo", "jack", "sacrifice")
|
||||
password, err := keyring.Get("libraryFoo", "jack")
|
||||
fmt.Println(password) //Output: sacrifice
|
||||
```
|
||||
|
||||
## Linux
|
||||
|
||||
Linux requirements:
|
||||
|
||||
### SecretService provider
|
||||
|
||||
- dbus
|
||||
|
||||
### gnome-keychain provider
|
||||
|
||||
- gnome-keychain headers
|
||||
- Ubuntu/Debian: `libsecret-dev`
|
||||
- Fedora: `libsecret-devel`
|
||||
- Archlinux: `libsecret`
|
||||
|
||||
Tests on Linux:
|
||||
```sh
|
||||
$ go test github.com/tmc/keyring
|
||||
$ # for gnome-keyring provider
|
||||
$ go test -tags gnome_keyring github.com/tmc/keyring
|
||||
```
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Package keyring provides a cross-platform interface to keychains for
|
||||
password management
|
||||
|
||||
Currently implemented:
|
||||
|
||||
* OSX
|
||||
* SecretService
|
||||
* gnome-keychain (via "gnome_keyring" build flag)
|
||||
|
||||
|
||||
Usage
|
||||
|
||||
Example usage:
|
||||
|
||||
|
||||
err := keyring.Set("libraryFoo", "jack", "sacrifice")
|
||||
password, err := keyring.Get("libraryFoo", "jack")
|
||||
fmt.Println(password)
|
||||
Output: sacrifice
|
||||
|
||||
|
||||
TODO
|
||||
|
||||
* Write Windows provider
|
||||
*/
|
||||
package keyring
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// +build gnome_keyring
|
||||
|
||||
package keyring
|
||||
|
||||
/*
|
||||
#cgo pkg-config: libsecret-1 glib-2.0
|
||||
#include <stdlib.h>
|
||||
#include "libsecret/secret.h"
|
||||
|
||||
SecretSchema keyring_schema =
|
||||
{
|
||||
"org.github.tmc.keyring.Password",
|
||||
SECRET_SCHEMA_NONE,
|
||||
{
|
||||
{ "username", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
{ "service", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
{ NULL, 0 },
|
||||
}
|
||||
};
|
||||
|
||||
// wrap the gnome calls because cgo can't deal with vararg functions
|
||||
|
||||
gboolean gkr_set_password(gchar *description, gchar *service, gchar *username, gchar *password, GError **err) {
|
||||
return secret_password_store_sync(
|
||||
&keyring_schema,
|
||||
NULL,
|
||||
description,
|
||||
password,
|
||||
NULL,
|
||||
err,
|
||||
"service", service,
|
||||
"username", username,
|
||||
NULL);
|
||||
}
|
||||
|
||||
gchar * gkr_get_password(gchar *service, gchar *username, GError **err) {
|
||||
return secret_password_lookup_sync(
|
||||
&keyring_schema,
|
||||
NULL,
|
||||
err,
|
||||
"service", service,
|
||||
"username", username,
|
||||
NULL);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type gnomeKeyring struct{}
|
||||
|
||||
func (p gnomeKeyring) Set(Service, Username, Password string) error {
|
||||
desc := (*C.gchar)(C.CString("Username and password for " + Service))
|
||||
username := (*C.gchar)(C.CString(Username))
|
||||
service := (*C.gchar)(C.CString(Service))
|
||||
password := (*C.gchar)(C.CString(Password))
|
||||
defer C.free(unsafe.Pointer(desc))
|
||||
defer C.free(unsafe.Pointer(username))
|
||||
defer C.free(unsafe.Pointer(service))
|
||||
defer C.free(unsafe.Pointer(password))
|
||||
|
||||
var gerr *C.GError
|
||||
result := C.gkr_set_password(desc, service, username, password, &gerr)
|
||||
defer C.free(unsafe.Pointer(gerr))
|
||||
|
||||
if result == 0 {
|
||||
return fmt.Errorf("Gnome-keyring error: %+v", gerr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p gnomeKeyring) Get(Service string, Username string) (string, error) {
|
||||
var gerr *C.GError
|
||||
var pw *C.gchar
|
||||
|
||||
username := (*C.gchar)(C.CString(Username))
|
||||
service := (*C.gchar)(C.CString(Service))
|
||||
defer C.free(unsafe.Pointer(username))
|
||||
defer C.free(unsafe.Pointer(service))
|
||||
|
||||
pw = C.gkr_get_password(service, username, &gerr)
|
||||
defer C.free(unsafe.Pointer(gerr))
|
||||
defer C.secret_password_free((*C.gchar)(pw))
|
||||
|
||||
if pw == nil {
|
||||
return "", fmt.Errorf("Gnome-keyring error: %+v", gerr)
|
||||
}
|
||||
return C.GoString((*C.char)(pw)), nil
|
||||
}
|
||||
|
||||
func initializeProvider() (provider, error) {
|
||||
return gnomeKeyring{}, nil
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Shows example use of the keyring package
|
||||
//
|
||||
// May need to be built with a platform-specific build flag to specify a
|
||||
// provider. See keyring documentation for details.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/tmc/keyring"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if pw, err := keyring.Get("keyring_example", "jack"); err == nil {
|
||||
fmt.Println("current stored password:", pw)
|
||||
} else if err == keyring.ErrNotFound {
|
||||
fmt.Println("no password stored yet")
|
||||
} else {
|
||||
fmt.Println("got unexpected error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("enter new password: ")
|
||||
pw, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("setting keyring_example/jack to..", pw)
|
||||
err = keyring.Set("keyring_example", "jack", string(pw))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("fetching keyring_example/jack..")
|
||||
if pw, err := keyring.Get("keyring_example", "jack"); err == nil {
|
||||
fmt.Println("got", pw)
|
||||
} else {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package keyring
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound means the requested password was not found
|
||||
ErrNotFound = errors.New("keyring: Password not found")
|
||||
// ErrNoDefault means that no default keyring provider has been found
|
||||
ErrNoDefault = errors.New("keyring: No suitable keyring provider found (check your build flags)")
|
||||
|
||||
providerInitOnce sync.Once
|
||||
defaultProvider provider
|
||||
providerInitError error
|
||||
)
|
||||
|
||||
// provider provides a simple interface to keychain sevice
|
||||
type provider interface {
|
||||
Get(service, username string) (string, error)
|
||||
Set(service, username, password string) error
|
||||
}
|
||||
|
||||
func setupProvider() (provider, error) {
|
||||
providerInitOnce.Do(func() {
|
||||
defaultProvider, providerInitError = initializeProvider()
|
||||
})
|
||||
|
||||
if providerInitError != nil {
|
||||
return nil, providerInitError
|
||||
} else if defaultProvider == nil {
|
||||
return nil, ErrNoDefault
|
||||
}
|
||||
return defaultProvider, nil
|
||||
}
|
||||
|
||||
// Get gets the password for a paricular Service and Username using the
|
||||
// default keyring provider.
|
||||
func Get(service, username string) (string, error) {
|
||||
p, err := setupProvider()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return p.Get(service, username)
|
||||
}
|
||||
|
||||
// Set sets the password for a particular Service and Username using the
|
||||
// default keyring provider.
|
||||
func Set(service, username, password string) error {
|
||||
p, err := setupProvider()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.Set(service, username, password)
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package keyring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type osxProvider struct {
|
||||
}
|
||||
|
||||
var pwRe = regexp.MustCompile("password: \"(.+)\"")
|
||||
|
||||
func (p osxProvider) Get(Service, Username string) (string, error) {
|
||||
args := []string{"find-generic-password",
|
||||
"-s", Service,
|
||||
"-a", Username,
|
||||
"-g"}
|
||||
c := exec.Command("/usr/bin/security", args...)
|
||||
o, err := c.CombinedOutput()
|
||||
if err != nil {
|
||||
exitCode := c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
// check particular exit code
|
||||
if exitCode == 44 {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
return "", fmt.Errorf("/usr/bin/security: %s", err)
|
||||
}
|
||||
matches := pwRe.FindStringSubmatch(string(o))
|
||||
if len(matches) != 2 {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
return matches[1], nil
|
||||
}
|
||||
|
||||
func (p osxProvider) Set(Service, Username, Password string) error {
|
||||
args := []string{"add-generic-password",
|
||||
"-s", Service,
|
||||
"-a", Username,
|
||||
"-w", Password,
|
||||
"-U"}
|
||||
c := exec.Command("/usr/bin/security", args...)
|
||||
err := c.Run()
|
||||
if err != nil {
|
||||
o, _ := c.CombinedOutput()
|
||||
return fmt.Errorf(string(o))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeProvider() (provider, error) {
|
||||
return osxProvider{}, nil
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
// +build !gnome_keyring
|
||||
|
||||
package keyring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
dbus "github.com/guelfey/go.dbus"
|
||||
)
|
||||
|
||||
const (
|
||||
ssServiceName = "org.freedesktop.secrets"
|
||||
ssServicePath = "/org/freedesktop/secrets"
|
||||
ssCollectionPath = "/org/freedesktop/secrets/collection/Default"
|
||||
ssServiceIface = "org.freedesktop.Secret.Service."
|
||||
ssSessionIface = "org.freedesktop.Secret.Session."
|
||||
ssCollectionIface = "org.freedesktop.Secret.Collection."
|
||||
ssItemIface = "org.freedesktop.Secret.Item."
|
||||
ssPromptIface = "org.freedesktop.Secret.Prompt."
|
||||
)
|
||||
|
||||
// ssSecret corresponds to org.freedesktop.Secret.Item
|
||||
// Note: Order is important
|
||||
type ssSecret struct {
|
||||
Session dbus.ObjectPath
|
||||
Parameters []byte
|
||||
Value []byte
|
||||
ContentType string `dbus:"content_type"`
|
||||
}
|
||||
|
||||
// newSSSecret prepares an ssSecret for use
|
||||
// Uses text/plain as the Content-type which may need to change in the future
|
||||
func newSSSecret(session dbus.ObjectPath, secret string) (s ssSecret) {
|
||||
s = ssSecret{
|
||||
ContentType: "text/plain; charset=utf8",
|
||||
Parameters: []byte{},
|
||||
Session: session,
|
||||
Value: []byte(secret),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ssProvider implements the provider interface freedesktop SecretService
|
||||
type ssProvider struct {
|
||||
*dbus.Conn
|
||||
srv *dbus.Object
|
||||
}
|
||||
|
||||
// This is used to open a seassion for every get/set. Alternative might be to
|
||||
// defer() the call to close when constructing the ssProvider
|
||||
func (s *ssProvider) openSession() (*dbus.Object, error) {
|
||||
var disregard dbus.Variant
|
||||
var sessionPath dbus.ObjectPath
|
||||
method := fmt.Sprint(ssServiceIface, "OpenSession")
|
||||
err := s.srv.Call(method, 0, "plain", dbus.MakeVariant("")).Store(&disregard, &sessionPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.Object(ssServiceName, sessionPath), nil
|
||||
}
|
||||
|
||||
// Unsure how the .Prompt call surfaces, it hasn't come up.
|
||||
func (s *ssProvider) unlock(p dbus.ObjectPath) error {
|
||||
var unlocked []dbus.ObjectPath
|
||||
var prompt dbus.ObjectPath
|
||||
method := fmt.Sprint(ssServiceIface, "Unlock")
|
||||
err := s.srv.Call(method, 0, []dbus.ObjectPath{p}).Store(&unlocked, &prompt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("keyring/dbus: Unlock error: %s", err)
|
||||
}
|
||||
if prompt != dbus.ObjectPath("/") {
|
||||
method = fmt.Sprint(ssPromptIface, "Prompt")
|
||||
call := s.Object(ssServiceName, prompt).Call(method, 0, "unlock")
|
||||
return call.Err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ssProvider) Get(c, u string) (string, error) {
|
||||
results := []dbus.ObjectPath{}
|
||||
var secret ssSecret
|
||||
search := map[string]string{
|
||||
"username": u,
|
||||
"service": c,
|
||||
}
|
||||
|
||||
session, err := s.openSession()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer session.Call(fmt.Sprint(ssSessionIface, "Close"), 0)
|
||||
s.unlock(ssCollectionPath)
|
||||
collection := s.Object(ssServiceName, ssCollectionPath)
|
||||
|
||||
method := fmt.Sprint(ssCollectionIface, "SearchItems")
|
||||
call := collection.Call(method, 0, search)
|
||||
err = call.Store(&results)
|
||||
if call.Err != nil {
|
||||
return "", call.Err
|
||||
}
|
||||
// results is a slice. Just grab the first one.
|
||||
if len(results) == 0 {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
|
||||
method = fmt.Sprint(ssItemIface, "GetSecret")
|
||||
err = s.Object(ssServiceName, results[0]).Call(method, 0, session.Path()).Store(&secret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(secret.Value), nil
|
||||
}
|
||||
|
||||
func (s *ssProvider) Set(c, u, p string) error {
|
||||
var item, prompt dbus.ObjectPath
|
||||
properties := map[string]dbus.Variant{
|
||||
"org.freedesktop.Secret.Item.Label": dbus.MakeVariant(fmt.Sprintf("%s - %s", u, c)),
|
||||
"org.freedesktop.Secret.Item.Attributes": dbus.MakeVariant(map[string]string{
|
||||
"username": u,
|
||||
"service": c,
|
||||
}),
|
||||
}
|
||||
|
||||
session, err := s.openSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer session.Call(fmt.Sprint(ssSessionIface, "Close"), 0)
|
||||
s.unlock(ssCollectionPath)
|
||||
collection := s.Object(ssServiceName, ssCollectionPath)
|
||||
|
||||
secret := newSSSecret(session.Path(), p)
|
||||
// the bool is "replace"
|
||||
err = collection.Call(fmt.Sprint(ssCollectionIface, "CreateItem"), 0, properties, secret, true).Store(&item, &prompt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("keyring/dbus: CreateItem error: %s", err)
|
||||
}
|
||||
if prompt != "/" {
|
||||
s.Object(ssServiceName, prompt).Call(fmt.Sprint(ssPromptIface, "Prompt"), 0, "unlock")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeProvider() (provider, error) {
|
||||
conn, err := dbus.SessionBus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv := conn.Object(ssServiceName, ssServicePath)
|
||||
p := &ssProvider{conn, srv}
|
||||
return p, nil
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package keyring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBasicSetGet(t *testing.T) {
|
||||
var (
|
||||
pw string
|
||||
err error
|
||||
)
|
||||
pw, err = Get("keyring-test", "jack")
|
||||
if err != nil {
|
||||
// ok on initial invokation
|
||||
fmt.Println("Get() error:", err)
|
||||
}
|
||||
err = Set("keyring-test", "jack", "test")
|
||||
if err != nil {
|
||||
t.Error("Set() error:", err)
|
||||
}
|
||||
pw, err = Get("keyring-test", "jack")
|
||||
if err != nil {
|
||||
t.Error("Get() error:", err)
|
||||
}
|
||||
|
||||
if pw != "test" {
|
||||
fmt.Errorf("expected 'test', got '%s'", pw)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user