<commit_before>package libkb

import (
	"errors"
	"fmt"

	keybase1 "github.com/keybase/client/go/protocol"
	triplesec "github.com/keybase/go-triplesec"
)

type Account struct {
	Contextified
	secretSyncer *SecretSyncer
	localSession *Session
	loginSession *LoginSession
	streamCache  *PassphraseStreamCache
	skbKeyring   *SKBKeyringFile
	secSigKey    GenericKey // cached secret signing key
	secEncKey    GenericKey // cache secret encryption key
}

func NewAccount(g *GlobalContext) *Account {
	return &Account{
		localSession: newSession(g),
		secretSyncer: NewSecretSyncer(g),
		Contextified: NewContextified(g),
	}
}

func (a *Account) LocalSession() *Session {
	return a.localSession
}

func (a *Account) UnloadLocalSession() {
	a.localSession = newSession(a.G())
}

// LoggedIn returns true if the user is logged in.  It does not
// try to load the session.
func (a *Account) LoggedIn() bool {
	return a.LocalSession().IsLoggedIn()
}

func (a *Account) LoggedInAndProvisioined() bool {
	return a.LocalSession().IsLoggedInAndProvisioned()
}

// LoggedInLoad will load and check the session with the api server if necessary.
func (a *Account) LoggedInLoad() (bool, error) {
	return a.LocalSession().loadAndCheck()
}

func (a *Account) LoggedInProvisionedLoad() (bool, error) {
	return a.LocalSession().loadAndCheckProvisioned()
}

func (a *Account) LoadLoginSession(emailOrUsername string) error {
	if a.LoginSession().ExistsFor(emailOrUsername) {
		return nil
	}

	ls := NewLoginSession(emailOrUsername, a.G())
	if err := ls.Load(); err != nil {
		return err
	}
	a.setLoginSession(ls)
	return nil
}

func (a *Account) CreateLoginSessionWithSalt(emailOrUsername string, salt []byte) error {
	if a.loginSessionExists() {
		return fmt.Errorf("CreateLoginSessionWithSalt called, but Account already has LoginSession")
	}

	ls := NewLoginSessionWithSalt(emailOrUsername, salt, a.G())
	a.setLoginSession(ls)
	return nil
}

func (a *Account) setLoginSession(ls *LoginSession) {
	if a.loginSession != nil {
		// this usually happens in tests that don't call G.Logout() to logout.
		// But it probably signifies an error.
		a.G().Log.Debug("Account: overwriting loginSession")
	}

	a.loginSession = ls
}

func (a *Account) loginSessionExists() bool {
	return a.LoginSession() != nil
}

func (a *Account) LoginSession() *LoginSession {
	return a.loginSession
}

func (a *Account) Logout() error {
	a.ClearStreamCache()

	if err := a.localSession.Logout(); err != nil {
		return err
	}

	a.UnloadLocalSession()
	a.loginSession = nil
	a.skbKeyring = nil

	a.secretSyncer.Clear()
	a.secretSyncer = NewSecretSyncer(a.G())

	a.ClearCachedSecretKeys()

	return nil
}

func (a *Account) CreateStreamCache(tsec *triplesec.Cipher, pps *PassphraseStream) {
	if a.streamCache != nil {
		a.G().Log.Warning("Account.CreateStreamCache overwriting exisitng StreamCache")
	}
	a.streamCache = NewPassphraseStreamCache(tsec, pps)
}

// SetStreamGeneration sets the passphrase generation on the cached stream
// if it exists, and otherwise will wind up warning of a problem.
func (a *Account) SetStreamGeneration(gen PassphraseGeneration) {
	ps := a.PassphraseStreamRef()
	if ps == nil {
		a.G().Log.Warning("Passphrase stream was nil; unexpected")
	} else {
		ps.SetGeneration(gen)
	}
}

// GetStreamGeneration() gets the generation of the currently cached
// passphrase stream
func (a *Account) GetStreamGeneration() (ret PassphraseGeneration) {
	if ps := a.PassphraseStream(); ps != nil {
		ret = ps.Generation()
	}
	return
}

func (a *Account) CreateStreamCacheViaStretch(passphrase string) error {
	if a.streamCache.Valid() {
		return nil
	}

	salt, err := a.loginSession.Salt()
	if err != nil {
		return err
	}

	tsec, pps, err := StretchPassphrase(passphrase, salt)
	if err != nil {
		return err
	}

	a.streamCache = NewPassphraseStreamCache(tsec, pps)

	return nil
}

func (a *Account) PassphraseStreamCache() *PassphraseStreamCache {
	return a.streamCache
}

// PassphraseStream returns a copy of the currently cached passphrase stream,
// or nil if none is there.
func (a *Account) PassphraseStream() *PassphraseStream {
	return a.PassphraseStreamCache().PassphraseStream()
}

// PassphraseStreamRef returns a reference to the actual passphrase stream, or
// nil if none is there.
func (a *Account) PassphraseStreamRef() *PassphraseStream {
	return a.PassphraseStreamCache().PassphraseStreamRef()
}

func (a *Account) ClearStreamCache() {
	a.streamCache.Clear()
	a.streamCache = nil
}

// ClearLoginSession clears out any cached login sessions with the account
// object
func (a *Account) ClearLoginSession() {
	if a.loginSession != nil {
		a.loginSession.Clear()
		a.loginSession = nil
	}
}

func (a *Account) SecretSyncer() *SecretSyncer {
	return a.secretSyncer
}

func (a *Account) RunSecretSyncer(uid keybase1.UID) error {
	return RunSyncer(a.SecretSyncer(), uid, a.LoggedIn(), a.localSession)
}

func (a *Account) Keyring() (*SKBKeyringFile, error) {
	if a.localSession == nil {
		a.G().Log.Warning("local session is nil")
	}
	a.LocalSession().loadAndCheck()
	if a.localSession == nil {
		a.G().Log.Warning("local session after load is nil")
	}
	kr := a.skbKeyring
	if kr != nil {
		return kr, nil
	}

	unp := a.localSession.GetUsername()
	// not sure how this could happen, but just in case:
	if unp == nil {
		return nil, NoUsernameError{}
	}
	kr, err := LoadSKBKeyring(*unp, a.G())
	if err != nil {
		return nil, err
	}
	a.skbKeyring = kr
	return a.skbKeyring, nil
}

func (a *Account) getDeviceKey(ckf *ComputedKeyFamily, secretKeyType SecretKeyType) (GenericKey, error) {
	did := a.G().Env.GetDeviceID()
	if did.IsNil() {
		return nil, errors.New("Could not get device id")
	}

	switch secretKeyType {
	case DeviceSigningKeyType:
		return ckf.GetSibkeyForDevice(did)
	case DeviceEncryptionKeyType:
		return ckf.GetEncryptionSubkeyForDevice(did)
	default:
		return nil, fmt.Errorf("Invalid type %v", secretKeyType)
	}
}

// LockedLocalSecretKey looks in the local keyring to find a key
// for the given user.  Returns non-nil if one was found, and nil
// otherwise.
func (a *Account) LockedLocalSecretKey(ska SecretKeyArg) (*SKB, error) {
	var ret *SKB
	me := ska.Me
	a.EnsureUsername(me.GetNormalizedName())

	keyring, err := a.Keyring()
	if err != nil {
		return nil, err
	}
	if keyring == nil {
		a.G().Log.Debug("| No secret keyring found: %s", err)
		return nil, NoKeyringsError{}
	}

	ckf := me.GetComputedKeyFamily()
	if ckf == nil {
		a.G().Log.Warning("No ComputedKeyFamily found for %s", me.name)
		return nil, KeyFamilyError{Msg: "not found for " + me.name}
	}

	if (ska.KeyType == DeviceSigningKeyType) || (ska.KeyType == DeviceEncryptionKeyType) {
		key, err := a.getDeviceKey(ckf, ska.KeyType)
		if err != nil {
			a.G().Log.Debug("| No key for current device: %s", err)
			return nil, err
		}

		if key == nil {
			a.G().Log.Debug("| Key for current device is nil")
			return nil, NoKeyError{Msg: "Key for current device is nil"}
		}

		kid := key.GetKID()
		a.G().Log.Debug("| Found KID for current device: %s", kid)
		ret = keyring.LookupByKid(kid)
		if ret != nil {
			a.G().Log.Debug("| Using device key: %s", kid)
		}
	} else {
		a.G().Log.Debug("| Looking up secret key in local keychain")
		blocks := keyring.SearchWithComputedKeyFamily(ckf, ska)
		if len(blocks) > 0 {
			ret = blocks[0]
		}
	}

	if ret != nil {
		ret.SetUID(me.GetUID())
	}

	return ret, nil
}

func (a *Account) Shutdown() error {
	return nil
}

func (a *Account) EnsureUsername(username NormalizedUsername) {
	su := a.LocalSession().GetUsername()
	if su == nil {
		a.LocalSession().SetUsername(username)
		return
	}
	if *su != username {
		a.Logout()
		a.LocalSession().SetUsername(username)
	}

}

func (a *Account) UserInfo() (uid keybase1.UID, username NormalizedUsername, token string, deviceSubkey GenericKey, err error) {
	if !a.LoggedIn() {
		err = LoginRequiredError{}
		return
	}

	user, err := LoadMe(LoadUserArg{LoginContext: a, Contextified: NewContextified(a.G())})
	if err != nil {
		return
	}

	deviceSubkey, err = user.GetDeviceSubkey()
	if err != nil {
		return
	}

	uid = user.GetUID()
	username = user.GetNormalizedName()
	token = a.localSession.GetToken()
	return
}

// XXX during xlogin cleanup, should be possible to replace this
// with SaveStateTmp().
func (a *Account) SaveState(sessionID, csrf string, username NormalizedUsername, uid keybase1.UID) error {
	cw := a.G().Env.GetConfigWriter()
	if cw == nil {
		return NoConfigWriterError{}
	}

	if err := a.LoginSession().Clear(); err != nil {
		return err
	}
	salt, err := a.LoginSession().Salt()
	if err != nil {
		return err
	}
	var nilDeviceID keybase1.DeviceID
	if err := cw.SetUserConfig(NewUserConfig(uid, username, salt, nilDeviceID), false); err != nil {
		return err
	}
	if err := cw.Write(); err != nil {
		return err
	}
	if err := a.LocalSession().SetLoggedIn(sessionID, csrf, username, uid, nilDeviceID); err != nil {
		return err
	}

	return nil
}

// SaveStateTmp saves the logins state to memory, and to a
// temporary config file.
func (a *Account) SaveStateTmp(sessionID, csrf string, username NormalizedUsername, uid keybase1.UID, deviceID keybase1.DeviceID) (filename string, err error) {
	cw := a.G().Env.GetConfigWriter()
	if cw == nil {
		return "", NoConfigWriterError{}
	}

	if err := a.LoginSession().Clear(); err != nil {
		return "", err
	}
	salt, err := a.LoginSession().Salt()
	if err != nil {
		return "", err
	}
	if err := cw.SetUserConfig(NewUserConfig(uid, username, salt, deviceID), false); err != nil {
		return "", err
	}
	filename, err = cw.SaveTmp(deviceID.String())
	if err != nil {
		return "", err
	}
	if err := a.LocalSession().SetLoggedIn(sessionID, csrf, username, uid, deviceID); err != nil {
		return "", err
	}

	return filename, nil
}

func (a *Account) Dump() {
	fmt.Printf("Account dump:\n")
	a.loginSession.Dump()
	a.streamCache.Dump()
}

func (a *Account) CachedSecretKey(ska SecretKeyArg) (GenericKey, error) {
	if ska.KeyType == DeviceSigningKeyType {
		if a.secSigKey != nil {
			return a.secSigKey, nil
		}
		return nil, NotFoundError{}
	}
	if ska.KeyType == DeviceEncryptionKeyType {
		if a.secEncKey != nil {
			return a.secEncKey, nil
		}
		return nil, NotFoundError{}
	}
	return nil, fmt.Errorf("invalid key type for cached secret key: %d", ska.KeyType)
}

func (a *Account) SetCachedSecretKey(ska SecretKeyArg, key GenericKey) error {
	if key == nil {
		return errors.New("cache of nil secret key attempted")
	}
	if ska.KeyType == DeviceSigningKeyType {
		a.G().Log.Debug("caching secret key for %d", ska.KeyType)
		a.secSigKey = key
		return nil
	}
	if ska.KeyType == DeviceEncryptionKeyType {
		a.G().Log.Debug("caching secret key for %d", ska.KeyType)
		a.secEncKey = key
		return nil
	}
	return fmt.Errorf("attempt to cache invalid key type: %d", ska.KeyType)
}

func (a *Account) ClearCachedSecretKeys() {
	a.G().Log.Debug("clearing cached secret keys")
	a.secSigKey = nil
	a.secEncKey = nil
}
<commit_msg>Dedup SaveState code<commit_after>package libkb

import (
	"errors"
	"fmt"

	keybase1 "github.com/keybase/client/go/protocol"
	triplesec "github.com/keybase/go-triplesec"
)

type Account struct {
	Contextified
	secretSyncer *SecretSyncer
	localSession *Session
	loginSession *LoginSession
	streamCache  *PassphraseStreamCache
	skbKeyring   *SKBKeyringFile
	secSigKey    GenericKey // cached secret signing key
	secEncKey    GenericKey // cache secret encryption key
}

func NewAccount(g *GlobalContext) *Account {
	return &Account{
		localSession: newSession(g),
		secretSyncer: NewSecretSyncer(g),
		Contextified: NewContextified(g),
	}
}

func (a *Account) LocalSession() *Session {
	return a.localSession
}

func (a *Account) UnloadLocalSession() {
	a.localSession = newSession(a.G())
}

// LoggedIn returns true if the user is logged in.  It does not
// try to load the session.
func (a *Account) LoggedIn() bool {
	return a.LocalSession().IsLoggedIn()
}

func (a *Account) LoggedInAndProvisioined() bool {
	return a.LocalSession().IsLoggedInAndProvisioned()
}

// LoggedInLoad will load and check the session with the api server if necessary.
func (a *Account) LoggedInLoad() (bool, error) {
	return a.LocalSession().loadAndCheck()
}

func (a *Account) LoggedInProvisionedLoad() (bool, error) {
	return a.LocalSession().loadAndCheckProvisioned()
}

func (a *Account) LoadLoginSession(emailOrUsername string) error {
	if a.LoginSession().ExistsFor(emailOrUsername) {
		return nil
	}

	ls := NewLoginSession(emailOrUsername, a.G())
	if err := ls.Load(); err != nil {
		return err
	}
	a.setLoginSession(ls)
	return nil
}

func (a *Account) CreateLoginSessionWithSalt(emailOrUsername string, salt []byte) error {
	if a.loginSessionExists() {
		return fmt.Errorf("CreateLoginSessionWithSalt called, but Account already has LoginSession")
	}

	ls := NewLoginSessionWithSalt(emailOrUsername, salt, a.G())
	a.setLoginSession(ls)
	return nil
}

func (a *Account) setLoginSession(ls *LoginSession) {
	if a.loginSession != nil {
		// this usually happens in tests that don't call G.Logout() to logout.
		// But it probably signifies an error.
		a.G().Log.Debug("Account: overwriting loginSession")
	}

	a.loginSession = ls
}

func (a *Account) loginSessionExists() bool {
	return a.LoginSession() != nil
}

func (a *Account) LoginSession() *LoginSession {
	return a.loginSession
}

func (a *Account) Logout() error {
	a.ClearStreamCache()

	if err := a.localSession.Logout(); err != nil {
		return err
	}

	a.UnloadLocalSession()
	a.loginSession = nil
	a.skbKeyring = nil

	a.secretSyncer.Clear()
	a.secretSyncer = NewSecretSyncer(a.G())

	a.ClearCachedSecretKeys()

	return nil
}

func (a *Account) CreateStreamCache(tsec *triplesec.Cipher, pps *PassphraseStream) {
	if a.streamCache != nil {
		a.G().Log.Warning("Account.CreateStreamCache overwriting exisitng StreamCache")
	}
	a.streamCache = NewPassphraseStreamCache(tsec, pps)
}

// SetStreamGeneration sets the passphrase generation on the cached stream
// if it exists, and otherwise will wind up warning of a problem.
func (a *Account) SetStreamGeneration(gen PassphraseGeneration) {
	ps := a.PassphraseStreamRef()
	if ps == nil {
		a.G().Log.Warning("Passphrase stream was nil; unexpected")
	} else {
		ps.SetGeneration(gen)
	}
}

// GetStreamGeneration() gets the generation of the currently cached
// passphrase stream
func (a *Account) GetStreamGeneration() (ret PassphraseGeneration) {
	if ps := a.PassphraseStream(); ps != nil {
		ret = ps.Generation()
	}
	return
}

func (a *Account) CreateStreamCacheViaStretch(passphrase string) error {
	if a.streamCache.Valid() {
		return nil
	}

	salt, err := a.loginSession.Salt()
	if err != nil {
		return err
	}

	tsec, pps, err := StretchPassphrase(passphrase, salt)
	if err != nil {
		return err
	}

	a.streamCache = NewPassphraseStreamCache(tsec, pps)

	return nil
}

func (a *Account) PassphraseStreamCache() *PassphraseStreamCache {
	return a.streamCache
}

// PassphraseStream returns a copy of the currently cached passphrase stream,
// or nil if none is there.
func (a *Account) PassphraseStream() *PassphraseStream {
	return a.PassphraseStreamCache().PassphraseStream()
}

// PassphraseStreamRef returns a reference to the actual passphrase stream, or
// nil if none is there.
func (a *Account) PassphraseStreamRef() *PassphraseStream {
	return a.PassphraseStreamCache().PassphraseStreamRef()
}

func (a *Account) ClearStreamCache() {
	a.streamCache.Clear()
	a.streamCache = nil
}

// ClearLoginSession clears out any cached login sessions with the account
// object
func (a *Account) ClearLoginSession() {
	if a.loginSession != nil {
		a.loginSession.Clear()
		a.loginSession = nil
	}
}

func (a *Account) SecretSyncer() *SecretSyncer {
	return a.secretSyncer
}

func (a *Account) RunSecretSyncer(uid keybase1.UID) error {
	return RunSyncer(a.SecretSyncer(), uid, a.LoggedIn(), a.localSession)
}

func (a *Account) Keyring() (*SKBKeyringFile, error) {
	if a.localSession == nil {
		a.G().Log.Warning("local session is nil")
	}
	a.LocalSession().loadAndCheck()
	if a.localSession == nil {
		a.G().Log.Warning("local session after load is nil")
	}
	kr := a.skbKeyring
	if kr != nil {
		return kr, nil
	}

	unp := a.localSession.GetUsername()
	// not sure how this could happen, but just in case:
	if unp == nil {
		return nil, NoUsernameError{}
	}
	kr, err := LoadSKBKeyring(*unp, a.G())
	if err != nil {
		return nil, err
	}
	a.skbKeyring = kr
	return a.skbKeyring, nil
}

func (a *Account) getDeviceKey(ckf *ComputedKeyFamily, secretKeyType SecretKeyType) (GenericKey, error) {
	did := a.G().Env.GetDeviceID()
	if did.IsNil() {
		return nil, errors.New("Could not get device id")
	}

	switch secretKeyType {
	case DeviceSigningKeyType:
		return ckf.GetSibkeyForDevice(did)
	case DeviceEncryptionKeyType:
		return ckf.GetEncryptionSubkeyForDevice(did)
	default:
		return nil, fmt.Errorf("Invalid type %v", secretKeyType)
	}
}

// LockedLocalSecretKey looks in the local keyring to find a key
// for the given user.  Returns non-nil if one was found, and nil
// otherwise.
func (a *Account) LockedLocalSecretKey(ska SecretKeyArg) (*SKB, error) {
	var ret *SKB
	me := ska.Me
	a.EnsureUsername(me.GetNormalizedName())

	keyring, err := a.Keyring()
	if err != nil {
		return nil, err
	}
	if keyring == nil {
		a.G().Log.Debug("| No secret keyring found: %s", err)
		return nil, NoKeyringsError{}
	}

	ckf := me.GetComputedKeyFamily()
	if ckf == nil {
		a.G().Log.Warning("No ComputedKeyFamily found for %s", me.name)
		return nil, KeyFamilyError{Msg: "not found for " + me.name}
	}

	if (ska.KeyType == DeviceSigningKeyType) || (ska.KeyType == DeviceEncryptionKeyType) {
		key, err := a.getDeviceKey(ckf, ska.KeyType)
		if err != nil {
			a.G().Log.Debug("| No key for current device: %s", err)
			return nil, err
		}

		if key == nil {
			a.G().Log.Debug("| Key for current device is nil")
			return nil, NoKeyError{Msg: "Key for current device is nil"}
		}

		kid := key.GetKID()
		a.G().Log.Debug("| Found KID for current device: %s", kid)
		ret = keyring.LookupByKid(kid)
		if ret != nil {
			a.G().Log.Debug("| Using device key: %s", kid)
		}
	} else {
		a.G().Log.Debug("| Looking up secret key in local keychain")
		blocks := keyring.SearchWithComputedKeyFamily(ckf, ska)
		if len(blocks) > 0 {
			ret = blocks[0]
		}
	}

	if ret != nil {
		ret.SetUID(me.GetUID())
	}

	return ret, nil
}

func (a *Account) Shutdown() error {
	return nil
}

func (a *Account) EnsureUsername(username NormalizedUsername) {
	su := a.LocalSession().GetUsername()
	if su == nil {
		a.LocalSession().SetUsername(username)
		return
	}
	if *su != username {
		a.Logout()
		a.LocalSession().SetUsername(username)
	}

}

func (a *Account) UserInfo() (uid keybase1.UID, username NormalizedUsername, token string, deviceSubkey GenericKey, err error) {
	if !a.LoggedIn() {
		err = LoginRequiredError{}
		return
	}

	user, err := LoadMe(LoadUserArg{LoginContext: a, Contextified: NewContextified(a.G())})
	if err != nil {
		return
	}

	deviceSubkey, err = user.GetDeviceSubkey()
	if err != nil {
		return
	}

	uid = user.GetUID()
	username = user.GetNormalizedName()
	token = a.localSession.GetToken()
	return
}

// SaveState saves the logins state to memory, and to the user
// config file.
func (a *Account) SaveState(sessionID, csrf string, username NormalizedUsername, uid keybase1.UID) error {
	saver := func(cw ConfigWriter) error {
		return cw.Write()
	}
	var nilDeviceID keybase1.DeviceID
	return a.saveState(sessionID, csrf, username, uid, nilDeviceID, saver)
}

// SaveStateTmp saves the logins state to memory, and to a
// temporary config file.
func (a *Account) SaveStateTmp(sessionID, csrf string, username NormalizedUsername, uid keybase1.UID, deviceID keybase1.DeviceID) (filename string, err error) {
	saver := func(cw ConfigWriter) error {
		var serr error
		filename, serr = cw.SaveTmp(deviceID.String())
		return serr
	}
	if err := a.saveState(sessionID, csrf, username, uid, deviceID, saver); err != nil {
		return "", err
	}
	return filename, nil
}

func (a *Account) saveState(sessionID, csrf string, username NormalizedUsername, uid keybase1.UID, deviceID keybase1.DeviceID, saver func(ConfigWriter) error) error {
	cw, err := a.newUserConfigWriter(username, uid, deviceID)
	if err != nil {
		return err
	}
	if err := saver(cw); err != nil {
		return err
	}
	return a.LocalSession().SetLoggedIn(sessionID, csrf, username, uid, deviceID)
}

func (a *Account) newUserConfigWriter(username NormalizedUsername, uid keybase1.UID, deviceID keybase1.DeviceID) (ConfigWriter, error) {
	cw := a.G().Env.GetConfigWriter()
	if cw == nil {
		return nil, NoConfigWriterError{}
	}

	if err := a.LoginSession().Clear(); err != nil {
		return nil, err
	}
	salt, err := a.LoginSession().Salt()
	if err != nil {
		return nil, err
	}
	if err := cw.SetUserConfig(NewUserConfig(uid, username, salt, deviceID), false); err != nil {
		return nil, err
	}

	return cw, nil
}

func (a *Account) Dump() {
	fmt.Printf("Account dump:\n")
	a.loginSession.Dump()
	a.streamCache.Dump()
}

func (a *Account) CachedSecretKey(ska SecretKeyArg) (GenericKey, error) {
	if ska.KeyType == DeviceSigningKeyType {
		if a.secSigKey != nil {
			return a.secSigKey, nil
		}
		return nil, NotFoundError{}
	}
	if ska.KeyType == DeviceEncryptionKeyType {
		if a.secEncKey != nil {
			return a.secEncKey, nil
		}
		return nil, NotFoundError{}
	}
	return nil, fmt.Errorf("invalid key type for cached secret key: %d", ska.KeyType)
}

func (a *Account) SetCachedSecretKey(ska SecretKeyArg, key GenericKey) error {
	if key == nil {
		return errors.New("cache of nil secret key attempted")
	}
	if ska.KeyType == DeviceSigningKeyType {
		a.G().Log.Debug("caching secret key for %d", ska.KeyType)
		a.secSigKey = key
		return nil
	}
	if ska.KeyType == DeviceEncryptionKeyType {
		a.G().Log.Debug("caching secret key for %d", ska.KeyType)
		a.secEncKey = key
		return nil
	}
	return fmt.Errorf("attempt to cache invalid key type: %d", ska.KeyType)
}

func (a *Account) ClearCachedSecretKeys() {
	a.G().Log.Debug("clearing cached secret keys")
	a.secSigKey = nil
	a.secEncKey = nil
}
<|endoftext|>


<commit_before>package gojpegoptim

import (
	"bytes"
	"image/jpeg"
	"io/ioutil"
	"testing"
)

func TestJpegOptimFromBuffer(t *testing.T) {
	fi, err := ioutil.ReadFile("test.jpg")
	if err != nil {
		t.Fatal(err)
	}
	imgBytes, err := EncodeOptimized(fi, 100)
	if err != nil {
		t.Fatal(err)
	}
	_, err = jpeg.Decode(bytes.NewReader(imgBytes))
	if err != nil {
		t.Fatal(err)
	}

	t.Log("input size", len(fi), "output size", len(imgBytes), "gain", len(fi)-len(imgBytes), (len(fi)-len(imgBytes))*100/len(fi), "%")
}
<commit_msg>more tests<commit_after>package gojpegoptim

import (
	"bytes"
	"image/jpeg"
	"io/ioutil"
	"testing"
)

func TestJpegOptimFromBuffer(t *testing.T) {
	fi, err := ioutil.ReadFile("test.jpg")
	if err != nil {
		t.Fatal(err)
	}
	imgBytes, err := EncodeOptimized(fi, 100)
	if err != nil {
		t.Fatal(err)
	}
	_, err = jpeg.Decode(bytes.NewReader(imgBytes))
	if err != nil {
		t.Fatal(err)
	}
	gain := (len(fi) - len(imgBytes)) * 100 / len(fi)
	t.Log("input size", len(fi), "output size", len(imgBytes), "gain", len(fi)-len(imgBytes), gain, "%")
	if gain != 35 {
		t.Fatal("Optimization failed")
	}
}

func TestJpegOptimBadBuffer(t *testing.T) {
	b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
	_, err := EncodeOptimized(b, 100)
	if err == nil {
		t.Fatal("Should be detected as an error")
	}

}
<|endoftext|>


<commit_before>package client

/*
   Copyright 2016 Alexander I.Grafov <grafov@gmail.com>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

   ॐ तारे तुत्तारे तुरे स्व
*/

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strings"
	"time"

	"github.com/grafov/autograf/grafana"
)

// BoardProperties keeps metadata of a dashboard.
type BoardProperties struct {
	IsStarred  bool      `json:"isStarred,omitempty"`
	IsHome     bool      `json:"isHome,omitempty"`
	IsSnapshot bool      `json:"isSnapshot,omitempty"`
	Type       string    `json:"type,omitempty"`
	CanSave    bool      `json:"canSave"`
	CanEdit    bool      `json:"canEdit"`
	CanStar    bool      `json:"canStar"`
	Slug       string    `json:"slug"`
	Expires    time.Time `json:"expires"`
	Created    time.Time `json:"created"`
	Updated    time.Time `json:"updated"`
	UpdatedBy  string    `json:"updatedBy"`
	CreatedBy  string    `json:"createdBy"`
	Version    int       `json:"version"`
}

// GetDashboard loads a dashboard from Grafana instance along with metadata for a dashboard.
// For dashboards from a filesystem set "file/" prefix for slug. By default dashboards from
// a database assumed. Database dashboards may have "db/" prefix or may have not, it will
// be appended automatically.
func (r *Instance) GetDashboard(slug string) (grafana.Board, BoardProperties, error) {
	var (
		raw    []byte
		result struct {
			Meta  BoardProperties `json:"meta"`
			Board grafana.Board   `json:"dashboard"`
		}
		code int
		err  error
	)
	slug, _ = setPrefix(slug)
	if raw, code, err = r.get(fmt.Sprintf("api/dashboards/%s", slug), nil); err != nil {
		return grafana.Board{}, BoardProperties{}, err
	}
	if code != 200 {
		return grafana.Board{}, BoardProperties{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	if err := dec.Decode(&result); err != nil {
		return grafana.Board{}, BoardProperties{}, fmt.Errorf("unmarshal board with meta: %s\n%s", err, raw)
	}
	return result.Board, result.Meta, err
}

// GetRawDashboard loads a dashboard JSON from Grafana instance along with metadata for a dashboard.
// Contrary to GetDashboard() it not unpack loaded JSON to grafana.Board structure. Instead it
// returns it as byte slice. It guarantee that data of dashboard returned untouched by conversion
// with grafana.Board so no matter how properly fields from a current version of Grafana mapped to
// our grafana.Board fields. It useful for backuping purposes when you want a dashboard exactly with
// same data as it exported by Grafana.
//
// For dashboards from a filesystem set "file/" prefix for slug. By default dashboards from
// a database assumed. Database dashboards may have "db/" prefix or may have not, it will
// be appended automatically.
func (r *Instance) GetRawDashboard(slug string) ([]byte, BoardProperties, error) {
	var (
		raw    []byte
		result struct {
			Meta  BoardProperties `json:"meta"`
			Board json.RawMessage `json:"dashboard"`
		}
		code int
		err  error
	)
	slug, _ = setPrefix(slug)
	if raw, code, err = r.get(fmt.Sprintf("api/dashboards/%s", slug), nil); err != nil {
		return nil, BoardProperties{}, err
	}
	if code != 200 {
		return nil, BoardProperties{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	if err := dec.Decode(&result); err != nil {
		return nil, BoardProperties{}, fmt.Errorf("unmarshal board with meta: %s\n%s", err, raw)
	}
	return []byte(result.Board), result.Meta, err
}

// FoundBoard keeps result of search with metadata of a dashboard.
type FoundBoard struct {
	ID        uint     `json:"id"`
	Title     string   `json:"title"`
	URI       string   `json:"uri"`
	Type      string   `json:"type"`
	Tags      []string `json:"tags"`
	IsStarred bool     `json:"isStarred"`
}

// SearchDashboards search dashboards by substring of their title. It allows restrict the result set with
// only starred dashboards and only for tags (logical OR applied to multiple tags).
func (r *Instance) SearchDashboards(query string, starred bool, tags ...string) ([]FoundBoard, error) {
	var (
		raw    []byte
		boards []FoundBoard
		code   int
		err    error
	)
	u := url.URL{}
	q := u.Query()
	if query != "" {
		q.Set("query", query)
	}
	if starred {
		q.Set("starred", "true")
	}
	for _, tag := range tags {
		q.Add("tag", tag)
	}
	if raw, code, err = r.get("api/search", q); err != nil {
		return nil, err
	}
	if code != 200 {
		return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	err = json.Unmarshal(raw, &boards)
	return boards, err
}

// SetDashboard updates existing dashboard or creates a new one.
// Set dasboard ID to nil to create a new dashboard.
// Set overwrite to true if you want to overwrite existing dashboard with
// newer version or with same dashboard title.
// Grafana only can create or update a dashboard in a database. File dashboards
// may be only loaded with HTTP API but not created or updated.
func (r *Instance) SetDashboard(board grafana.Board, overwrite bool) error {
	var (
		isBoardFromDB bool
		newBoard      struct {
			Dashboard grafana.Board `json:"dashboard"`
			Overwrite bool          `json:"overwrite"`
		}
		raw  []byte
		resp StatusMessage
		code int
		err  error
	)
	if board.Slug, isBoardFromDB = cleanPrefix(board.Slug); !isBoardFromDB {
		return errors.New("only database dashboard (with 'db/' prefix in a slug) can be set")
	}
	newBoard.Dashboard = board
	newBoard.Overwrite = overwrite
	if !overwrite {
		newBoard.Dashboard.ID = 0
	}
	if raw, err = json.Marshal(newBoard); err != nil {
		return err
	}
	if raw, code, err = r.post("api/dashboards/db", nil, raw); err != nil {
		return err
	}
	if err = json.Unmarshal(raw, &resp); err != nil {
		return err
	}
	switch code {
	case 401:
		return fmt.Errorf("%d %s", code, *resp.Message)
	case 412:
		return fmt.Errorf("%d %s", code, *resp.Message)
	}
	return nil
}

// SetRawDashboard updates existing dashboard or creates a new one.
// Contrary to SetDashboard() it accepts raw JSON instead of grafana.Board structure.
// Grafana only can create or update a dashboard in a database. File dashboards
// may be only loaded with HTTP API but not created or updated.
func (r *Instance) SetRawDashboard(raw []byte) error {
	var (
		rawResp []byte
		resp    StatusMessage
		code    int
		err     error
	)
	if rawResp, code, err = r.post("api/dashboards/db", nil, raw); err != nil {
		return err
	}
	if err = json.Unmarshal(rawResp, &resp); err != nil {
		return err
	}
	switch code {
	case 401:
		return fmt.Errorf("%d %s", code, *resp.Message)
	case 412:
		return fmt.Errorf("%d %s", code, *resp.Message)
	}
	return nil
}

// DeleteDashboard deletes dashboard that selected by slug string.
// Grafana only can delete a dashboard in a database. File dashboards
// may be only loaded with HTTP API but not deteled.
func (r *Instance) DeleteDashboard(slug string) (StatusMessage, error) {
	var (
		isBoardFromDB bool
		raw           []byte
		reply         StatusMessage
		err           error
	)
	if slug, isBoardFromDB = cleanPrefix(slug); !isBoardFromDB {
		return StatusMessage{}, errors.New("only database dashboards (with 'db/' prefix in a slug) can be removed")
	}
	if raw, err = r.delete(fmt.Sprintf("api/dashboards/db/%s", slug)); err != nil {
		return StatusMessage{}, err
	}
	err = json.Unmarshal(raw, &reply)
	return reply, err
}

// implicitely use dashboards from Grafana DB not from a file system
func setPrefix(slug string) (string, bool) {
	if strings.HasPrefix(slug, "db") {
		return slug, true
	}
	if strings.HasPrefix(slug, "file") {
		return slug, false
	}
	return fmt.Sprintf("db/%s", slug), true
}

// assume we use database dashboard by default
func cleanPrefix(slug string) (string, bool) {
	if strings.HasPrefix(slug, "db") {
		return slug[3:], true
	}
	if strings.HasPrefix(slug, "file") {
		return slug[3:], false
	}
	return fmt.Sprintf("%s", slug), true
}
<commit_msg>Fix format of the input JSON for SetRawDashboard.<commit_after>package client

/*
   Copyright 2016 Alexander I.Grafov <grafov@gmail.com>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

   ॐ तारे तुत्तारे तुरे स्व
*/

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strings"
	"time"

	"github.com/grafov/autograf/grafana"
)

// BoardProperties keeps metadata of a dashboard.
type BoardProperties struct {
	IsStarred  bool      `json:"isStarred,omitempty"`
	IsHome     bool      `json:"isHome,omitempty"`
	IsSnapshot bool      `json:"isSnapshot,omitempty"`
	Type       string    `json:"type,omitempty"`
	CanSave    bool      `json:"canSave"`
	CanEdit    bool      `json:"canEdit"`
	CanStar    bool      `json:"canStar"`
	Slug       string    `json:"slug"`
	Expires    time.Time `json:"expires"`
	Created    time.Time `json:"created"`
	Updated    time.Time `json:"updated"`
	UpdatedBy  string    `json:"updatedBy"`
	CreatedBy  string    `json:"createdBy"`
	Version    int       `json:"version"`
}

// GetDashboard loads a dashboard from Grafana instance along with metadata for a dashboard.
// For dashboards from a filesystem set "file/" prefix for slug. By default dashboards from
// a database assumed. Database dashboards may have "db/" prefix or may have not, it will
// be appended automatically.
func (r *Instance) GetDashboard(slug string) (grafana.Board, BoardProperties, error) {
	var (
		raw    []byte
		result struct {
			Meta  BoardProperties `json:"meta"`
			Board grafana.Board   `json:"dashboard"`
		}
		code int
		err  error
	)
	slug, _ = setPrefix(slug)
	if raw, code, err = r.get(fmt.Sprintf("api/dashboards/%s", slug), nil); err != nil {
		return grafana.Board{}, BoardProperties{}, err
	}
	if code != 200 {
		return grafana.Board{}, BoardProperties{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	if err := dec.Decode(&result); err != nil {
		return grafana.Board{}, BoardProperties{}, fmt.Errorf("unmarshal board with meta: %s\n%s", err, raw)
	}
	return result.Board, result.Meta, err
}

// GetRawDashboard loads a dashboard JSON from Grafana instance along with metadata for a dashboard.
// Contrary to GetDashboard() it not unpack loaded JSON to grafana.Board structure. Instead it
// returns it as byte slice. It guarantee that data of dashboard returned untouched by conversion
// with grafana.Board so no matter how properly fields from a current version of Grafana mapped to
// our grafana.Board fields. It useful for backuping purposes when you want a dashboard exactly with
// same data as it exported by Grafana.
//
// For dashboards from a filesystem set "file/" prefix for slug. By default dashboards from
// a database assumed. Database dashboards may have "db/" prefix or may have not, it will
// be appended automatically.
func (r *Instance) GetRawDashboard(slug string) ([]byte, BoardProperties, error) {
	var (
		raw    []byte
		result struct {
			Meta  BoardProperties `json:"meta"`
			Board json.RawMessage `json:"dashboard"`
		}
		code int
		err  error
	)
	slug, _ = setPrefix(slug)
	if raw, code, err = r.get(fmt.Sprintf("api/dashboards/%s", slug), nil); err != nil {
		return nil, BoardProperties{}, err
	}
	if code != 200 {
		return nil, BoardProperties{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	if err := dec.Decode(&result); err != nil {
		return nil, BoardProperties{}, fmt.Errorf("unmarshal board with meta: %s\n%s", err, raw)
	}
	return []byte(result.Board), result.Meta, err
}

// FoundBoard keeps result of search with metadata of a dashboard.
type FoundBoard struct {
	ID        uint     `json:"id"`
	Title     string   `json:"title"`
	URI       string   `json:"uri"`
	Type      string   `json:"type"`
	Tags      []string `json:"tags"`
	IsStarred bool     `json:"isStarred"`
}

// SearchDashboards search dashboards by substring of their title. It allows restrict the result set with
// only starred dashboards and only for tags (logical OR applied to multiple tags).
func (r *Instance) SearchDashboards(query string, starred bool, tags ...string) ([]FoundBoard, error) {
	var (
		raw    []byte
		boards []FoundBoard
		code   int
		err    error
	)
	u := url.URL{}
	q := u.Query()
	if query != "" {
		q.Set("query", query)
	}
	if starred {
		q.Set("starred", "true")
	}
	for _, tag := range tags {
		q.Add("tag", tag)
	}
	if raw, code, err = r.get("api/search", q); err != nil {
		return nil, err
	}
	if code != 200 {
		return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	err = json.Unmarshal(raw, &boards)
	return boards, err
}

// SetDashboard updates existing dashboard or creates a new one.
// Set dasboard ID to nil to create a new dashboard.
// Set overwrite to true if you want to overwrite existing dashboard with
// newer version or with same dashboard title.
// Grafana only can create or update a dashboard in a database. File dashboards
// may be only loaded with HTTP API but not created or updated.
func (r *Instance) SetDashboard(board grafana.Board, overwrite bool) error {
	var (
		isBoardFromDB bool
		newBoard      struct {
			Dashboard grafana.Board `json:"dashboard"`
			Overwrite bool          `json:"overwrite"`
		}
		raw  []byte
		resp StatusMessage
		code int
		err  error
	)
	if board.Slug, isBoardFromDB = cleanPrefix(board.Slug); !isBoardFromDB {
		return errors.New("only database dashboard (with 'db/' prefix in a slug) can be set")
	}
	newBoard.Dashboard = board
	newBoard.Overwrite = overwrite
	if !overwrite {
		newBoard.Dashboard.ID = 0
	}
	if raw, err = json.Marshal(newBoard); err != nil {
		return err
	}
	if raw, code, err = r.post("api/dashboards/db", nil, raw); err != nil {
		return err
	}
	if err = json.Unmarshal(raw, &resp); err != nil {
		return err
	}
	switch code {
	case 401:
		return fmt.Errorf("%d %s", code, *resp.Message)
	case 412:
		return fmt.Errorf("%d %s", code, *resp.Message)
	}
	return nil
}

// SetRawDashboard updates existing dashboard or creates a new one.
// Contrary to SetDashboard() it accepts raw JSON instead of grafana.Board structure.
// Grafana only can create or update a dashboard in a database. File dashboards
// may be only loaded with HTTP API but not created or updated.
func (r *Instance) SetRawDashboard(raw []byte) error {
	var (
		rawResp []byte
		resp    StatusMessage
		code    int
		err     error
		buf     bytes.Buffer
	)
	buf.WriteString(`{"dashboard":`)
	buf.Write(raw)
	buf.WriteString(`}`)
	if rawResp, code, err = r.post("api/dashboards/db", nil, buf.Bytes()); err != nil {
		return err
	}
	if err = json.Unmarshal(rawResp, &resp); err != nil {
		return err
	}
	switch code {
	case 401:
		return fmt.Errorf("%d %s", code, *resp.Message)
	case 412:
		return fmt.Errorf("%d %s", code, *resp.Message)
	}
	return nil
}

// DeleteDashboard deletes dashboard that selected by slug string.
// Grafana only can delete a dashboard in a database. File dashboards
// may be only loaded with HTTP API but not deteled.
func (r *Instance) DeleteDashboard(slug string) (StatusMessage, error) {
	var (
		isBoardFromDB bool
		raw           []byte
		reply         StatusMessage
		err           error
	)
	if slug, isBoardFromDB = cleanPrefix(slug); !isBoardFromDB {
		return StatusMessage{}, errors.New("only database dashboards (with 'db/' prefix in a slug) can be removed")
	}
	if raw, err = r.delete(fmt.Sprintf("api/dashboards/db/%s", slug)); err != nil {
		return StatusMessage{}, err
	}
	err = json.Unmarshal(raw, &reply)
	return reply, err
}

// implicitely use dashboards from Grafana DB not from a file system
func setPrefix(slug string) (string, bool) {
	if strings.HasPrefix(slug, "db") {
		return slug, true
	}
	if strings.HasPrefix(slug, "file") {
		return slug, false
	}
	return fmt.Sprintf("db/%s", slug), true
}

// assume we use database dashboard by default
func cleanPrefix(slug string) (string, bool) {
	if strings.HasPrefix(slug, "db") {
		return slug[3:], true
	}
	if strings.HasPrefix(slug, "file") {
		return slug[3:], false
	}
	return fmt.Sprintf("%s", slug), true
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2019 VMware, Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package object

import (
	"context"
	"flag"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"sort"

	"github.com/vmware/govmomi/govc/cli"
	"github.com/vmware/govmomi/govc/flags"
	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/view"
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/methods"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/types"
	"github.com/vmware/govmomi/vim25/xml"
)

type save struct {
	*flags.FolderFlag

	n       int
	dir     string
	force   bool
	verbose bool
	recurse bool
	one     bool
	kind    kinds
	summary map[string]int
}

func init() {
	cli.Register("object.save", &save{})
}

func (cmd *save) Register(ctx context.Context, f *flag.FlagSet) {
	cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx)
	cmd.FolderFlag.Register(ctx, f)

	f.BoolVar(&cmd.one, "1", false, "Save ROOT only, without its children")
	f.StringVar(&cmd.dir, "d", "", "Save objects in directory")
	f.BoolVar(&cmd.force, "f", false, "Remove existing object directory")
	f.BoolVar(&cmd.recurse, "r", true, "Include children of the container view root")
	f.Var(&cmd.kind, "type", "Resource types to save.  Defaults to all types")
	f.BoolVar(&cmd.verbose, "v", false, "Verbose output")
}

func (cmd *save) Usage() string {
	return "[PATH]"
}

func (cmd *save) Description() string {
	return `Save managed objects.

By default, the object tree and all properties are saved, starting at PATH.
PATH defaults to ServiceContent, but can be specified to save a subset of objects.
The primary use case for this command is to save inventory from a live vCenter and
load it into a vcsim instance.

Examples:
  govc object.save -d my-vcenter
  vcsim -load my-vcenter`
}

func (cmd *save) save(content []types.ObjectContent) error {
	for _, x := range content {
		cmd.summary[x.Obj.Type]++
		if cmd.verbose {
			fmt.Printf("Saving %s...", x.Obj)
		}
		name := fmt.Sprintf("%04d-%s-%s", cmd.n, x.Obj.Type, url.QueryEscape(x.Obj.Value))
		cmd.n++
		f, err := os.Create(filepath.Join(cmd.dir, name) + ".xml")
		if err != nil {
			return err
		}
		e := xml.NewEncoder(f)
		e.Indent("", "  ")
		if err = e.Encode(x); err != nil {
			_ = f.Close()
			return err
		}
		if err = f.Close(); err != nil {
			return err
		}
		if cmd.verbose {
			fmt.Println("ok")
		}
	}
	return nil
}

func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
	cmd.summary = make(map[string]int)
	c, err := cmd.Client()
	if err != nil {
		return err
	}
	if cmd.dir == "" {
		u := c.URL()
		name := u.Fragment
		if name == "" {
			name = u.Hostname()
		}
		cmd.dir = "vcsim-" + name
	}
	mkdir := os.Mkdir
	if cmd.force {
		mkdir = os.MkdirAll
	}
	if err := mkdir(cmd.dir, 0755); err != nil {
		return err
	}

	var content []types.ObjectContent
	pc := property.DefaultCollector(c)
	root := vim25.ServiceInstance
	if f.NArg() == 1 {
		root, err = cmd.ManagedObject(ctx, f.Arg(0))
		if err != nil {
			if !root.FromString(f.Arg(0)) {
				return err
			}
		}
		if cmd.one {
			err = pc.RetrieveOne(ctx, root, nil, &content)
			if err != nil {
				return nil
			}
			if err = cmd.save(content); err != nil {
				return err
			}
			return nil
		}
	}

	req := types.RetrievePropertiesEx{
		This:    pc.Reference(),
		Options: types.RetrieveOptions{MaxObjects: 10},
	}

	if root == vim25.ServiceInstance {
		err := pc.RetrieveOne(ctx, root, []string{"content"}, &content)
		if err != nil {
			return nil
		}
		if err = cmd.save(content); err != nil {
			return err
		}
		if cmd.one {
			return nil
		}

		root = c.ServiceContent.RootFolder

		for _, p := range content[0].PropSet {
			if c, ok := p.Val.(types.ServiceContent); ok {
				for _, ref := range mo.References(c) {
					all := types.NewBool(true)
					switch ref.Type {
					case "LicenseManager", "ServiceManager":
						all = nil
					}
					req.SpecSet = append(req.SpecSet, types.PropertyFilterSpec{
						ObjectSet: []types.ObjectSpec{{
							Obj: ref,
						}},
						PropSet: []types.PropertySpec{{
							Type:    ref.Type,
							All:     all,
							PathSet: nil,
						}},
					})
				}
				break
			}
		}
	}

	m := view.NewManager(c)
	v, err := m.CreateContainerView(ctx, root, cmd.kind, cmd.recurse)
	if err != nil {
		return err
	}

	defer func() {
		_ = v.Destroy(ctx)
	}()

	all := types.NewBool(true)
	req.SpecSet = append(req.SpecSet, types.PropertyFilterSpec{
		ObjectSet: []types.ObjectSpec{{
			Obj:  v.Reference(),
			Skip: types.NewBool(false),
			SelectSet: []types.BaseSelectionSpec{
				&types.TraversalSpec{
					Type: v.Reference().Type,
					Path: "view",
					SelectSet: []types.BaseSelectionSpec{
						&types.SelectionSpec{
							Name: "computeTraversalSpec",
						},
						&types.SelectionSpec{
							Name: "datastoreTraversalSpec",
						},
						&types.SelectionSpec{
							Name: "hostNetworkSystemTraversalSpec",
						},
						&types.SelectionSpec{
							Name: "hostVirtualNicManagerTraversalSpec",
						},
						&types.SelectionSpec{
							Name: "entityTraversalSpec",
						},
					},
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "computeTraversalSpec",
					},
					Type: "ComputeResource",
					Path: "environmentBrowser",
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "datastoreTraversalSpec",
					},
					Type: "Datastore",
					Path: "browser",
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "hostNetworkSystemTraversalSpec",
					},
					Type: "HostSystem",
					Path: "configManager.networkSystem",
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "hostVirtualNicManagerTraversalSpec",
					},
					Type: "HostSystem",
					Path: "configManager.virtualNicManager",
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "entityTraversalSpec",
					},
					Type: "ManagedEntity",
					Path: "recentTask",
				},
			},
		}},
		PropSet: []types.PropertySpec{
			{Type: "EnvironmentBrowser", All: all},
			{Type: "HostDatastoreBrowser", All: all},
			{Type: "HostNetworkSystem", All: all},
			{Type: "HostVirtualNicManager", All: all},
			{Type: "ManagedEntity", All: all},
			{Type: "Task", All: all},
		},
	})

	res, err := methods.RetrievePropertiesEx(ctx, c, &req)
	if err != nil {
		return err
	}
	if err = cmd.save(res.Returnval.Objects); err != nil {
		return err
	}

	token := res.Returnval.Token
	for token != "" {
		cres, err := methods.ContinueRetrievePropertiesEx(ctx, c, &types.ContinueRetrievePropertiesEx{
			This:  req.This,
			Token: token,
		})
		if err != nil {
			return err
		}
		token = cres.Returnval.Token
		if err = cmd.save(cres.Returnval.Objects); err != nil {
			return err
		}
	}

	var summary []string
	for k, v := range cmd.summary {
		if v == 1 && !cmd.verbose {
			continue
		}
		summary = append(summary, fmt.Sprintf("%s: %d", k, v))
	}
	sort.Strings(summary)

	s := ", including"
	if cmd.verbose {
		s = ""
	}
	fmt.Printf("Saved %d total objects to %q%s:\n", cmd.n, cmd.dir, s)
	for i := range summary {
		fmt.Println(summary[i])
	}

	return nil
}
<commit_msg>vcsim: traverse configManager.datastoreSystem in object.save<commit_after>/*
Copyright (c) 2019 VMware, Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package object

import (
	"context"
	"flag"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"sort"

	"github.com/vmware/govmomi/govc/cli"
	"github.com/vmware/govmomi/govc/flags"
	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/view"
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/methods"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/types"
	"github.com/vmware/govmomi/vim25/xml"
)

type save struct {
	*flags.FolderFlag

	n       int
	dir     string
	force   bool
	verbose bool
	recurse bool
	one     bool
	kind    kinds
	summary map[string]int
}

func init() {
	cli.Register("object.save", &save{})
}

func (cmd *save) Register(ctx context.Context, f *flag.FlagSet) {
	cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx)
	cmd.FolderFlag.Register(ctx, f)

	f.BoolVar(&cmd.one, "1", false, "Save ROOT only, without its children")
	f.StringVar(&cmd.dir, "d", "", "Save objects in directory")
	f.BoolVar(&cmd.force, "f", false, "Remove existing object directory")
	f.BoolVar(&cmd.recurse, "r", true, "Include children of the container view root")
	f.Var(&cmd.kind, "type", "Resource types to save.  Defaults to all types")
	f.BoolVar(&cmd.verbose, "v", false, "Verbose output")
}

func (cmd *save) Usage() string {
	return "[PATH]"
}

func (cmd *save) Description() string {
	return `Save managed objects.

By default, the object tree and all properties are saved, starting at PATH.
PATH defaults to ServiceContent, but can be specified to save a subset of objects.
The primary use case for this command is to save inventory from a live vCenter and
load it into a vcsim instance.

Examples:
  govc object.save -d my-vcenter
  vcsim -load my-vcenter`
}

func (cmd *save) save(content []types.ObjectContent) error {
	for _, x := range content {
		cmd.summary[x.Obj.Type]++
		if cmd.verbose {
			fmt.Printf("Saving %s...", x.Obj)
		}
		name := fmt.Sprintf("%04d-%s-%s", cmd.n, x.Obj.Type, url.QueryEscape(x.Obj.Value))
		cmd.n++
		f, err := os.Create(filepath.Join(cmd.dir, name) + ".xml")
		if err != nil {
			return err
		}
		e := xml.NewEncoder(f)
		e.Indent("", "  ")
		if err = e.Encode(x); err != nil {
			_ = f.Close()
			return err
		}
		if err = f.Close(); err != nil {
			return err
		}
		if cmd.verbose {
			fmt.Println("ok")
		}
	}
	return nil
}

func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
	cmd.summary = make(map[string]int)
	c, err := cmd.Client()
	if err != nil {
		return err
	}
	if cmd.dir == "" {
		u := c.URL()
		name := u.Fragment
		if name == "" {
			name = u.Hostname()
		}
		cmd.dir = "vcsim-" + name
	}
	mkdir := os.Mkdir
	if cmd.force {
		mkdir = os.MkdirAll
	}
	if err := mkdir(cmd.dir, 0755); err != nil {
		return err
	}

	var content []types.ObjectContent
	pc := property.DefaultCollector(c)
	root := vim25.ServiceInstance
	if f.NArg() == 1 {
		root, err = cmd.ManagedObject(ctx, f.Arg(0))
		if err != nil {
			if !root.FromString(f.Arg(0)) {
				return err
			}
		}
		if cmd.one {
			err = pc.RetrieveOne(ctx, root, nil, &content)
			if err != nil {
				return nil
			}
			if err = cmd.save(content); err != nil {
				return err
			}
			return nil
		}
	}

	req := types.RetrievePropertiesEx{
		This:    pc.Reference(),
		Options: types.RetrieveOptions{MaxObjects: 10},
	}

	if root == vim25.ServiceInstance {
		err := pc.RetrieveOne(ctx, root, []string{"content"}, &content)
		if err != nil {
			return nil
		}
		if err = cmd.save(content); err != nil {
			return err
		}
		if cmd.one {
			return nil
		}

		root = c.ServiceContent.RootFolder

		for _, p := range content[0].PropSet {
			if c, ok := p.Val.(types.ServiceContent); ok {
				for _, ref := range mo.References(c) {
					all := types.NewBool(true)
					switch ref.Type {
					case "LicenseManager", "ServiceManager":
						all = nil
					}
					req.SpecSet = append(req.SpecSet, types.PropertyFilterSpec{
						ObjectSet: []types.ObjectSpec{{
							Obj: ref,
						}},
						PropSet: []types.PropertySpec{{
							Type:    ref.Type,
							All:     all,
							PathSet: nil,
						}},
					})
				}
				break
			}
		}
	}

	m := view.NewManager(c)
	v, err := m.CreateContainerView(ctx, root, cmd.kind, cmd.recurse)
	if err != nil {
		return err
	}

	defer func() {
		_ = v.Destroy(ctx)
	}()

	all := types.NewBool(true)
	req.SpecSet = append(req.SpecSet, types.PropertyFilterSpec{
		ObjectSet: []types.ObjectSpec{{
			Obj:  v.Reference(),
			Skip: types.NewBool(false),
			SelectSet: []types.BaseSelectionSpec{
				&types.TraversalSpec{
					Type: v.Reference().Type,
					Path: "view",
					SelectSet: []types.BaseSelectionSpec{
						&types.SelectionSpec{
							Name: "computeTraversalSpec",
						},
						&types.SelectionSpec{
							Name: "datastoreTraversalSpec",
						},
						&types.SelectionSpec{
							Name: "hostDatastoreSystemTraversalSpec",
						},
						&types.SelectionSpec{
							Name: "hostNetworkSystemTraversalSpec",
						},
						&types.SelectionSpec{
							Name: "hostVirtualNicManagerTraversalSpec",
						},
						&types.SelectionSpec{
							Name: "entityTraversalSpec",
						},
					},
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "computeTraversalSpec",
					},
					Type: "ComputeResource",
					Path: "environmentBrowser",
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "datastoreTraversalSpec",
					},
					Type: "Datastore",
					Path: "browser",
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "hostNetworkSystemTraversalSpec",
					},
					Type: "HostSystem",
					Path: "configManager.networkSystem",
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "hostVirtualNicManagerTraversalSpec",
					},
					Type: "HostSystem",
					Path: "configManager.virtualNicManager",
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "hostDatastoreSystemTraversalSpec",
					},
					Type: "HostSystem",
					Path: "configManager.datastoreSystem",
				},
				&types.TraversalSpec{
					SelectionSpec: types.SelectionSpec{
						Name: "entityTraversalSpec",
					},
					Type: "ManagedEntity",
					Path: "recentTask",
				},
			},
		}},
		PropSet: []types.PropertySpec{
			{Type: "EnvironmentBrowser", All: all},
			{Type: "HostDatastoreBrowser", All: all},
			{Type: "HostDatastoreSystem", All: all},
			{Type: "HostNetworkSystem", All: all},
			{Type: "HostVirtualNicManager", All: all},
			{Type: "ManagedEntity", All: all},
			{Type: "Task", All: all},
		},
	})

	res, err := methods.RetrievePropertiesEx(ctx, c, &req)
	if err != nil {
		return err
	}
	if err = cmd.save(res.Returnval.Objects); err != nil {
		return err
	}

	token := res.Returnval.Token
	for token != "" {
		cres, err := methods.ContinueRetrievePropertiesEx(ctx, c, &types.ContinueRetrievePropertiesEx{
			This:  req.This,
			Token: token,
		})
		if err != nil {
			return err
		}
		token = cres.Returnval.Token
		if err = cmd.save(cres.Returnval.Objects); err != nil {
			return err
		}
	}

	var summary []string
	for k, v := range cmd.summary {
		if v == 1 && !cmd.verbose {
			continue
		}
		summary = append(summary, fmt.Sprintf("%s: %d", k, v))
	}
	sort.Strings(summary)

	s := ", including"
	if cmd.verbose {
		s = ""
	}
	fmt.Printf("Saved %d total objects to %q%s:\n", cmd.n, cmd.dir, s)
	for i := range summary {
		fmt.Println(summary[i])
	}

	return nil
}
<|endoftext|>


<commit_before>// Package client is a CT log client implementation and contains types and code
// for interacting with RFC6962-compliant CT Log instances.
// See http://tools.ietf.org/html/rfc6962 for details
package client

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"time"

	ct "github.com/google/certificate-transparency/go"
	"golang.org/x/net/context"
)

// URI paths for CT Log endpoints
const (
	AddChainPath    = "/ct/v1/add-chain"
	AddPreChainPath = "/ct/v1/add-pre-chain"
	AddJSONPath     = "/ct/v1/add-json"
	GetSTHPath      = "/ct/v1/get-sth"
	GetEntriesPath  = "/ct/v1/get-entries"
)

// LogClient represents a client for a given CT Log instance
type LogClient struct {
	uri        string       // the base URI of the log. e.g. http://ct.googleapis/pilot
	httpClient *http.Client // used to interact with the log via HTTP
}

//////////////////////////////////////////////////////////////////////////////////
// JSON structures follow.
// These represent the structures returned by the CT Log server.
//////////////////////////////////////////////////////////////////////////////////

// addChainRequest represents the JSON request body sent to the add-chain CT
// method.
type addChainRequest struct {
	Chain []string `json:"chain"`
}

// addChainResponse represents the JSON response to the add-chain CT method.
// An SCT represents a Log's promise to integrate a [pre-]certificate into the
// log within a defined period of time.
type addChainResponse struct {
	SCTVersion ct.Version `json:"sct_version"` // SCT structure version
	ID         string     `json:"id"`          // Log ID
	Timestamp  uint64     `json:"timestamp"`   // Timestamp of issuance
	Extensions string     `json:"extensions"`  // Holder for any CT extensions
	Signature  string     `json:"signature"`   // Log signature for this SCT
}

// addJSONRequest represents the JSON request body sent ot the add-json CT
// method.
type addJSONRequest struct {
	Data interface{} `json:"data"`
}

// getSTHResponse respresents the JSON response to the get-sth CT method
type getSTHResponse struct {
	TreeSize          uint64 `json:"tree_size"`           // Number of certs in the current tree
	Timestamp         uint64 `json:"timestamp"`           // Time that the tree was created
	SHA256RootHash    string `json:"sha256_root_hash"`    // Root hash of the tree
	TreeHeadSignature string `json:"tree_head_signature"` // Log signature for this STH
}

// getConsistencyProofResponse represents the JSON response to the CT get-consistency-proof method
type getConsistencyProofResponse struct {
	Consistency []string `json:"consistency"`
}

// getAuditProofResponse represents the JSON response to the CT get-audit-proof method
type getAuditProofResponse struct {
	Hash     []string `json:"hash"`      // the hashes which make up the proof
	TreeSize uint64   `json:"tree_size"` // the tree size against which this proof is constructed
}

// getAcceptedRootsResponse represents the JSON response to the CT get-roots method.
type getAcceptedRootsResponse struct {
	Certificates []string `json:"certificates"`
}

// getEntryAndProodReponse represents the JSON response to the CT get-entry-and-proof method
type getEntryAndProofResponse struct {
	LeafInput string   `json:"leaf_input"` // the entry itself
	ExtraData string   `json:"extra_data"` // any chain provided when the entry was added to the log
	AuditPath []string `json:"audit_path"` // the corresponding proof
}

// New constructs a new LogClient instance.
// |uri| is the base URI of the CT log instance to interact with, e.g.
// http://ct.googleapis.com/pilot
// |hc| is the underlying client to be used for HTTP requests to the CT log.
func New(uri string, hc *http.Client) *LogClient {
	if hc == nil {
		hc = new(http.Client)
	}
	return &LogClient{uri: uri, httpClient: hc}
}

// Makes a HTTP call to |uri|, and attempts to parse the response as a
// JSON representation of the structure in |res|. Uses |ctx| to
// control the HTTP call (so it can have a timeout or be cancelled by
// the caller), and |httpClient| to make the actual HTTP call.
// Returns a non-nil |error| if there was a problem.
func fetchAndParse(ctx context.Context, httpClient *http.Client, uri string, res interface{}) error {
	req, err := http.NewRequest(http.MethodGet, uri, nil)
	if err != nil {
		return err
	}
	req.Cancel = ctx.Done()
	resp, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	// Make sure everything is read, so http.Client can reuse the connection.
	defer ioutil.ReadAll(resp.Body)

	if resp.StatusCode != 200 {
		return fmt.Errorf("got HTTP Status %s", resp.Status)
	}

	if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
		return err
	}

	return nil
}

// Makes a HTTP POST call to |uri|, and attempts to parse the response as a JSON
// representation of the structure in |res|.
// Returns a non-nil |error| if there was a problem.
func (c *LogClient) postAndParse(uri string, req interface{}, res interface{}) (*http.Response, string, error) {
	postBody, err := json.Marshal(req)
	if err != nil {
		return nil, "", err
	}
	httpReq, err := http.NewRequest(http.MethodPost, uri, bytes.NewReader(postBody))
	if err != nil {
		return nil, "", err
	}
	httpReq.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(httpReq)
	// Read all of the body, if there is one, so that the http.Client can do
	// Keep-Alive:
	var body []byte
	if resp != nil {
		body, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
	}
	if err != nil {
		return resp, string(body), err
	}
	if resp.StatusCode == 200 {
		if err != nil {
			return resp, string(body), err
		}
		if err = json.Unmarshal(body, &res); err != nil {
			return resp, string(body), err
		}
	}
	return resp, string(body), nil
}

func backoffForRetry(ctx context.Context, d time.Duration) error {
	backoffTimer := time.NewTimer(d)
	if ctx != nil {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-backoffTimer.C:
		}
	} else {
		<-backoffTimer.C
	}
	return nil
}

// Attempts to add |chain| to the log, using the api end-point specified by
// |path|. If provided context expires before submission is complete an
// error will be returned.
func (c *LogClient) addChainWithRetry(ctx context.Context, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	var resp addChainResponse
	var req addChainRequest
	for _, link := range chain {
		req.Chain = append(req.Chain, base64.StdEncoding.EncodeToString(link))
	}
	httpStatus := "Unknown"
	backoffSeconds := 0
	done := false
	for !done {
		if backoffSeconds > 0 {
			log.Printf("Got %s, backing-off %d seconds", httpStatus, backoffSeconds)
		}
		err := backoffForRetry(ctx, time.Second*time.Duration(backoffSeconds))
		if err != nil {
			return nil, err
		}
		if backoffSeconds > 0 {
			backoffSeconds = 0
		}
		httpResp, errorBody, err := c.postAndParse(c.uri+path, &req, &resp)
		if err != nil {
			backoffSeconds = 10
			continue
		}
		switch {
		case httpResp.StatusCode == 200:
			done = true
		case httpResp.StatusCode == 408:
			// request timeout, retry immediately
		case httpResp.StatusCode == 503:
			// Retry
			backoffSeconds = 10
			if retryAfter := httpResp.Header.Get("Retry-After"); retryAfter != "" {
				if seconds, err := strconv.Atoi(retryAfter); err == nil {
					backoffSeconds = seconds
				}
			}
		default:
			return nil, fmt.Errorf("got HTTP Status %s: %s", httpResp.Status, errorBody)
		}
		httpStatus = httpResp.Status
	}

	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// AddChain adds the (DER represented) X509 |chain| to the log.
func (c *LogClient) AddChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddChainPath, chain)
}

// AddPreChain adds the (DER represented) Precertificate |chain| to the log.
func (c *LogClient) AddPreChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddPreChainPath, chain)
}

// AddChainWithContext adds the (DER represented) X509 |chain| to the log and
// fails if the provided context expires before the chain is submitted.
func (c *LogClient) AddChainWithContext(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(ctx, AddChainPath, chain)
}

func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) {
	req := addJSONRequest{
		Data: data,
	}
	var resp addChainResponse
	_, _, err := c.postAndParse(c.uri+AddJSONPath, &req, &resp)
	if err != nil {
		return nil, err
	}
	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// GetSTH retrieves the current STH from the log.
// Returns a populated SignedTreeHead, or a non-nil error.
func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) {
	var resp getSTHResponse
	if err = fetchAndParse(context.TODO(), c.httpClient, c.uri+GetSTHPath, &resp); err != nil {
		return
	}
	sth = &ct.SignedTreeHead{
		TreeSize:  resp.TreeSize,
		Timestamp: resp.Timestamp,
	}

	rawRootHash, err := base64.StdEncoding.DecodeString(resp.SHA256RootHash)
	if err != nil {
		return nil, fmt.Errorf("invalid base64 encoding in sha256_root_hash: %v", err)
	}
	if len(rawRootHash) != sha256.Size {
		return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(rawRootHash))
	}
	copy(sth.SHA256RootHash[:], rawRootHash)

	rawSignature, err := base64.StdEncoding.DecodeString(resp.TreeHeadSignature)
	if err != nil {
		return nil, errors.New("invalid base64 encoding in tree_head_signature")
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	// TODO(alcutter): Verify signature
	sth.TreeHeadSignature = *ds
	return
}
<commit_msg>Let the JSON decoder deal with base64 fields.<commit_after>// Package client is a CT log client implementation and contains types and code
// for interacting with RFC6962-compliant CT Log instances.
// See http://tools.ietf.org/html/rfc6962 for details
package client

import (
	"bytes"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"time"

	ct "github.com/google/certificate-transparency/go"
	"golang.org/x/net/context"
)

// URI paths for CT Log endpoints
const (
	AddChainPath    = "/ct/v1/add-chain"
	AddPreChainPath = "/ct/v1/add-pre-chain"
	AddJSONPath     = "/ct/v1/add-json"
	GetSTHPath      = "/ct/v1/get-sth"
	GetEntriesPath  = "/ct/v1/get-entries"
)

// LogClient represents a client for a given CT Log instance
type LogClient struct {
	uri        string       // the base URI of the log. e.g. http://ct.googleapis/pilot
	httpClient *http.Client // used to interact with the log via HTTP
}

//////////////////////////////////////////////////////////////////////////////////
// JSON structures follow.
// These represent the structures returned by the CT Log server.
//////////////////////////////////////////////////////////////////////////////////

// addChainRequest represents the JSON request body sent to the add-chain CT
// method.
type addChainRequest struct {
	Chain [][]byte `json:"chain"`
}

// addChainResponse represents the JSON response to the add-chain CT method.
// An SCT represents a Log's promise to integrate a [pre-]certificate into the
// log within a defined period of time.
type addChainResponse struct {
	SCTVersion ct.Version `json:"sct_version"` // SCT structure version
	ID         []byte     `json:"id"`          // Log ID
	Timestamp  uint64     `json:"timestamp"`   // Timestamp of issuance
	Extensions string     `json:"extensions"`  // Holder for any CT extensions
	Signature  []byte     `json:"signature"`   // Log signature for this SCT
}

// addJSONRequest represents the JSON request body sent ot the add-json CT
// method.
type addJSONRequest struct {
	Data interface{} `json:"data"`
}

// getSTHResponse respresents the JSON response to the get-sth CT method
type getSTHResponse struct {
	TreeSize          uint64 `json:"tree_size"`           // Number of certs in the current tree
	Timestamp         uint64 `json:"timestamp"`           // Time that the tree was created
	SHA256RootHash    []byte `json:"sha256_root_hash"`    // Root hash of the tree
	TreeHeadSignature []byte `json:"tree_head_signature"` // Log signature for this STH
}

// getConsistencyProofResponse represents the JSON response to the CT get-consistency-proof method
type getConsistencyProofResponse struct {
	Consistency []string `json:"consistency"`
}

// getAuditProofResponse represents the JSON response to the CT get-audit-proof method
type getAuditProofResponse struct {
	Hash     []string `json:"hash"`      // the hashes which make up the proof
	TreeSize uint64   `json:"tree_size"` // the tree size against which this proof is constructed
}

// getAcceptedRootsResponse represents the JSON response to the CT get-roots method.
type getAcceptedRootsResponse struct {
	Certificates []string `json:"certificates"`
}

// getEntryAndProodReponse represents the JSON response to the CT get-entry-and-proof method
type getEntryAndProofResponse struct {
	LeafInput string   `json:"leaf_input"` // the entry itself
	ExtraData string   `json:"extra_data"` // any chain provided when the entry was added to the log
	AuditPath []string `json:"audit_path"` // the corresponding proof
}

// New constructs a new LogClient instance.
// |uri| is the base URI of the CT log instance to interact with, e.g.
// http://ct.googleapis.com/pilot
// |hc| is the underlying client to be used for HTTP requests to the CT log.
func New(uri string, hc *http.Client) *LogClient {
	if hc == nil {
		hc = new(http.Client)
	}
	return &LogClient{uri: uri, httpClient: hc}
}

// Makes a HTTP call to |uri|, and attempts to parse the response as a
// JSON representation of the structure in |res|. Uses |ctx| to
// control the HTTP call (so it can have a timeout or be cancelled by
// the caller), and |httpClient| to make the actual HTTP call.
// Returns a non-nil |error| if there was a problem.
func fetchAndParse(ctx context.Context, httpClient *http.Client, uri string, res interface{}) error {
	req, err := http.NewRequest(http.MethodGet, uri, nil)
	if err != nil {
		return err
	}
	req.Cancel = ctx.Done()
	resp, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	// Make sure everything is read, so http.Client can reuse the connection.
	defer ioutil.ReadAll(resp.Body)

	if resp.StatusCode != 200 {
		return fmt.Errorf("got HTTP Status %s", resp.Status)
	}

	if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
		return err
	}

	return nil
}

// Makes a HTTP POST call to |uri|, and attempts to parse the response as a JSON
// representation of the structure in |res|.
// Returns a non-nil |error| if there was a problem.
func (c *LogClient) postAndParse(uri string, req interface{}, res interface{}) (*http.Response, string, error) {
	postBody, err := json.Marshal(req)
	if err != nil {
		return nil, "", err
	}
	httpReq, err := http.NewRequest(http.MethodPost, uri, bytes.NewReader(postBody))
	if err != nil {
		return nil, "", err
	}
	httpReq.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(httpReq)
	// Read all of the body, if there is one, so that the http.Client can do
	// Keep-Alive:
	var body []byte
	if resp != nil {
		body, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
	}
	if err != nil {
		return resp, string(body), err
	}
	if resp.StatusCode == 200 {
		if err != nil {
			return resp, string(body), err
		}
		if err = json.Unmarshal(body, &res); err != nil {
			return resp, string(body), err
		}
	}
	return resp, string(body), nil
}

func backoffForRetry(ctx context.Context, d time.Duration) error {
	backoffTimer := time.NewTimer(d)
	if ctx != nil {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-backoffTimer.C:
		}
	} else {
		<-backoffTimer.C
	}
	return nil
}

// Attempts to add |chain| to the log, using the api end-point specified by
// |path|. If provided context expires before submission is complete an
// error will be returned.
func (c *LogClient) addChainWithRetry(ctx context.Context, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	var resp addChainResponse
	var req addChainRequest
	for _, link := range chain {
		req.Chain = append(req.Chain, link)
	}
	httpStatus := "Unknown"
	backoffSeconds := 0
	done := false
	for !done {
		if backoffSeconds > 0 {
			log.Printf("Got %s, backing-off %d seconds", httpStatus, backoffSeconds)
		}
		err := backoffForRetry(ctx, time.Second*time.Duration(backoffSeconds))
		if err != nil {
			return nil, err
		}
		if backoffSeconds > 0 {
			backoffSeconds = 0
		}
		httpResp, errorBody, err := c.postAndParse(c.uri+path, &req, &resp)
		if err != nil {
			backoffSeconds = 10
			continue
		}
		switch {
		case httpResp.StatusCode == 200:
			done = true
		case httpResp.StatusCode == 408:
			// request timeout, retry immediately
		case httpResp.StatusCode == 503:
			// Retry
			backoffSeconds = 10
			if retryAfter := httpResp.Header.Get("Retry-After"); retryAfter != "" {
				if seconds, err := strconv.Atoi(retryAfter); err == nil {
					backoffSeconds = seconds
				}
			}
		default:
			return nil, fmt.Errorf("got HTTP Status %s: %s", httpResp.Status, errorBody)
		}
		httpStatus = httpResp.Status
	}

	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], resp.ID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// AddChain adds the (DER represented) X509 |chain| to the log.
func (c *LogClient) AddChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddChainPath, chain)
}

// AddPreChain adds the (DER represented) Precertificate |chain| to the log.
func (c *LogClient) AddPreChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddPreChainPath, chain)
}

// AddChainWithContext adds the (DER represented) X509 |chain| to the log and
// fails if the provided context expires before the chain is submitted.
func (c *LogClient) AddChainWithContext(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(ctx, AddChainPath, chain)
}

func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) {
	req := addJSONRequest{
		Data: data,
	}
	var resp addChainResponse
	_, _, err := c.postAndParse(c.uri+AddJSONPath, &req, &resp)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], resp.ID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// GetSTH retrieves the current STH from the log.
// Returns a populated SignedTreeHead, or a non-nil error.
func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) {
	var resp getSTHResponse
	if err = fetchAndParse(context.TODO(), c.httpClient, c.uri+GetSTHPath, &resp); err != nil {
		return
	}
	sth = &ct.SignedTreeHead{
		TreeSize:  resp.TreeSize,
		Timestamp: resp.Timestamp,
	}

	if len(resp.SHA256RootHash) != sha256.Size {
		return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(resp.SHA256RootHash))
	}
	copy(sth.SHA256RootHash[:], resp.SHA256RootHash)

	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.TreeHeadSignature))
	if err != nil {
		return nil, err
	}
	// TODO(alcutter): Verify signature
	sth.TreeHeadSignature = *ds
	return
}
<|endoftext|>


<commit_before>// Copyright 2014 The Dename Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
package client

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	. "github.com/andres-erbsen/dename/protocol"
)

const hASH_BYTES = 32

// VerifyResolveAgainstRoot decodes a profile from a resolve structure
// and verifies that it is indeed a part of a mrklemap with rootHash.
// resolve contains nodes of a crit-bit tree, where each pointer has
// been replaced with a cryptographic hash of its target.  As crit-bit
// tree lookup only uses nodes on one branch of the tree, all other nodes
// are omitted. Furthermore, hashes of nodes that /are/ present are
// implicit: if a node's child is nil, we can find its hash by hashing the
// next node in the array. This function assumes that the hash of the root
// has been authenticated externally and verifies that 1) all provided
// nodes indeed lie in their implicitly claimed positions by recomputing
// the hash of the root from the provided nodes and 2) that the chain of
// nodes we received unambiguously determines the result of the lookup we
// requrested by running the crit-bit tree lookup algorithm on these nodes.
func VerifyResolveAgainstRoot(rootHash, name []byte, resolve []*ClientReply_MerklemapNode, testing ...interface{}) (
	profile []byte, err error) {
	if !bytes.Equal(reproduceRootHash(resolve), rootHash) {
		return nil, fmt.Errorf("root hash does not match")
	}
	nameHash := sha256.Sum256(name)
	if len(testing) > 0 {
		copy(nameHash[:], name)
	}
	var requiredPath [hASH_BYTES * 8]bool
	for i := range requiredPath {
		requiredPath[i] = nameHash[i/8]&(1<<(7-uint(i%8))) != 0
	}
	if match, err := compareCritBitPath(resolve, requiredPath[:]); !match {
		return nil, err
	}
	return resolve[len(resolve)-1].Value, nil
}

func reproduceRootHash(resolve []*ClientReply_MerklemapNode) []byte {
	if len(resolve) == 0 {
		return nil
	}
	curHash := hashWireNode(resolve[len(resolve)-1], [hASH_BYTES]byte{}, [hASH_BYTES]byte{})
	for i := len(resolve) - 2; i >= 0; i-- {
		hasLeft, hasRight := resolve[i].LeftChildHash != nil, resolve[i].RightChildHash != nil
		hasValue := resolve[i].Value != nil
		var substituteLeftHash, substituteRightHash [hASH_BYTES]byte
		switch {
		case !hasLeft && hasRight && !hasValue:
			substituteLeftHash = curHash
		case hasLeft && !hasRight && !hasValue:
			substituteRightHash = curHash
		default:
			return nil
		}
		curHash = hashWireNode(resolve[i], substituteLeftHash, substituteRightHash)
	}
	return curHash[:]
}

func compareCritBitPath(resolve []*ClientReply_MerklemapNode, remainingPath []bool) (bool, error) {
	for _, node := range resolve {
		for j := 0; j < len(node.Substring)*8-8+int(*node.SubstringBitsInLastByte); j++ {
			observedBit := (node.Substring[j/8]<<uint(j%8))&(1<<7) != 0
			if len(remainingPath) == 0 {
				return false, fmt.Errorf("merklemap path too long")
			}
			var requiredBit bool
			requiredBit, remainingPath = remainingPath[0], remainingPath[1:]
			if observedBit != requiredBit {
				return false, nil // this path is not present in the merklemap
			}
		}
		if node.Value == nil {
			if node.LeftChildHash != nil && node.RightChildHash != nil {
				return false, fmt.Errorf("merklemap node with both children given")
			}
			if node.LeftChildHash == nil && node.RightChildHash == nil {
				return false, fmt.Errorf("merklemap node with no children given")
			}
			descendingRight := node.RightChildHash == nil
			if descendingRight != remainingPath[0] {
				return false, fmt.Errorf("merklemap wrong branch taken")
			}
			remainingPath = remainingPath[1:]
		} else {
			if len(remainingPath) > 0 {
				return false, fmt.Errorf("merklemap truncated hash chain")
			}
			return true, nil
		}
	}
	return false, fmt.Errorf("merklemap too long hash chain")
}

func hashWireNode(wireNode *ClientReply_MerklemapNode, substituteLeftHash,
	substituteRightHash [hASH_BYTES]byte) [hASH_BYTES]byte {
	hash := sha256.New()
	hash.Write([]byte{byte(len(wireNode.Substring)), byte(*wireNode.SubstringBitsInLastByte)})
	hash.Write(wireNode.Substring)
	if wireNode.LeftChildHash != nil {
		hash.Write(wireNode.LeftChildHash)
	} else {
		hash.Write(substituteLeftHash[:])
	}
	if wireNode.RightChildHash != nil {
		hash.Write(wireNode.RightChildHash)
	} else {
		hash.Write(substituteRightHash[:])
	}
	hash.Write(wireNode.Value)
	var ret [hASH_BYTES]byte
	hash.Sum(ret[:0])
	return ret
}
<commit_msg>fix potential out-of-bounds error in client/merklemap; refactor<commit_after>// Copyright 2014 The Dename Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
package client

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	. "github.com/andres-erbsen/dename/protocol"
)

const hASH_BYTES = 32

// VerifyResolveAgainstRoot decodes a profile from a resolve structure and
// verifies that it is indeed a part of a mrklemap with rootHash.  resolve
// contains nodes of a binary trie, except that each pointer has been replaced
// with a cryptographic hash of its target. As a binary trie lookup only uses
// nodes on one branch of the tree, all other nodes are omitted. Furthermore,
// hashes of nodes that /are/ present are implicit: if a node's child hash is
// nil, we can find it by hashing the next given node. This function assumes
// that the hash of the root has been authenticated externally and verifies that
// 1) all provided nodes indeed lie in their claimed positions in the trie by
// recomputing the hash of the root from the provided nodes and 2) that the
// parts of the trie contained in resolve unambiguously determine the result of
// the lookup of name by running the binary trie tree lookup algorithm on these
// nodes.
func VerifyResolveAgainstRoot(rootHash, name []byte, resolve []*ClientReply_MerklemapNode, testing ...interface{}) (
	profile []byte, err error) {
	if !bytes.Equal(reproduceRootHash(resolve), rootHash) {
		return nil, fmt.Errorf("root hash does not match")
	}
	nameHash := sha256.Sum256(name)
	if len(testing) > 0 {
		copy(nameHash[:], name)
	}
	var requiredPath [hASH_BYTES * 8]bool
	for i := range requiredPath {
		requiredPath[i] = nameHash[i/8]&(0x80>>uint(i%8)) != 0
	}
	return reproduceBinaryTrieLookup(resolve, requiredPath[:])
}

func reproduceRootHash(resolve []*ClientReply_MerklemapNode) []byte {
	if len(resolve) == 0 {
		return nil
	}
	curHash := hashWireNode(resolve[len(resolve)-1], [hASH_BYTES]byte{}, [hASH_BYTES]byte{})
	for i := len(resolve) - 2; i >= 0; i-- {
		hasLeft, hasRight := resolve[i].LeftChildHash != nil, resolve[i].RightChildHash != nil
		hasValue := resolve[i].Value != nil
		var substituteLeftHash, substituteRightHash [hASH_BYTES]byte
		switch {
		case !hasLeft && hasRight && !hasValue:
			substituteLeftHash = curHash
		case hasLeft && !hasRight && !hasValue:
			substituteRightHash = curHash
		default:
			return nil
		}
		curHash = hashWireNode(resolve[i], substituteLeftHash, substituteRightHash)
	}
	return curHash[:]
}

func reproduceBinaryTrieLookup(resolve []*ClientReply_MerklemapNode, remainingPath []bool) ([]byte, error) {
	for _, node := range resolve {
		if *node.SubstringBitsInLastByte > 8 {
			return nil, fmt.Errorf("invalid node: bitsInLastByte: %d", *node.SubstringBitsInLastByte)
		}
		for i := 0; i < len(node.Substring)*8-8+int(*node.SubstringBitsInLastByte); i++ {
			if len(remainingPath) == 0 {
				return nil, fmt.Errorf("merklemap path too long")
			}
			var requiredBit bool
			requiredBit, remainingPath = remainingPath[0], remainingPath[1:]
			observedBit := node.Substring[i/8]&(0x80>>uint(i%8)) != 0
			if observedBit != requiredBit {
				return nil, nil // the required path is not present in the merklemap
			}
		}
		if node.Value == nil {
			if node.LeftChildHash != nil && node.RightChildHash != nil {
				return nil, fmt.Errorf("merklemap node with both children given")
			}
			if node.LeftChildHash == nil && node.RightChildHash == nil {
				return nil, fmt.Errorf("merklemap node with no children given")
			}
			descendingRight := node.RightChildHash == nil
			if descendingRight != remainingPath[0] {
				return nil, fmt.Errorf("merklemap wrong branch taken")
			}
			remainingPath = remainingPath[1:]
		} else {
			if len(remainingPath) > 0 {
				return nil, fmt.Errorf("merklemap: value too high in the tree")
			}
			return node.Value, nil
		}
	}
	return nil, fmt.Errorf("merklemap: no value or contradiction given")
}

func hashWireNode(wireNode *ClientReply_MerklemapNode, substituteLeftHash,
	substituteRightHash [hASH_BYTES]byte) [hASH_BYTES]byte {
	hash := sha256.New()
	hash.Write([]byte{byte(len(wireNode.Substring)), byte(*wireNode.SubstringBitsInLastByte)})
	hash.Write(wireNode.Substring)
	if wireNode.LeftChildHash != nil {
		hash.Write(wireNode.LeftChildHash)
	} else {
		hash.Write(substituteLeftHash[:])
	}
	if wireNode.RightChildHash != nil {
		hash.Write(wireNode.RightChildHash)
	} else {
		hash.Write(substituteRightHash[:])
	}
	hash.Write(wireNode.Value)
	var ret [hASH_BYTES]byte
	hash.Sum(ret[:0])
	return ret
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package flag

import (
	"errors"
	"flag"
	"strings"
)

// NamedCertKey is a flag value parsing "certfile,keyfile" and "certfile,keyfile:name,name,name".
type NamedCertKey struct {
	Names             []string
	CertFile, KeyFile string
}

var _ flag.Value = &NamedCertKey{}

func (nkc *NamedCertKey) String() string {
	s := nkc.CertFile + "," + nkc.KeyFile
	if len(nkc.Names) > 0 {
		s = s + ":" + strings.Join(nkc.Names, ",")
	}
	return s
}

func (nkc *NamedCertKey) Set(value string) error {
	cs := strings.SplitN(value, ":", 2)
	var keycert string
	if len(cs) == 2 {
		var names string
		keycert, names = strings.TrimSpace(cs[0]), strings.TrimSpace(cs[1])
		if names == "" {
			return errors.New("empty names list is not allowed")
		}
		nkc.Names = nil
		for _, name := range strings.Split(names, ",") {
			nkc.Names = append(nkc.Names, strings.TrimSpace(name))
		}
	} else {
		nkc.Names = nil
		keycert = strings.TrimSpace(cs[0])
	}
	cs = strings.Split(keycert, ",")
	if len(cs) != 2 {
		return errors.New("expected comma separated certificate and key file paths")
	}
	nkc.CertFile = strings.TrimSpace(cs[0])
	nkc.KeyFile = strings.TrimSpace(cs[1])
	return nil
}

func (*NamedCertKey) Type() string {
	return "namedCertKey"
}

// NamedCertKeyArray is a flag value parsing NamedCertKeys, each passed with its own
// flag instance (in contrast to comma separated slices).
type NamedCertKeyArray struct {
	value   *[]NamedCertKey
	changed bool
}

var _ flag.Value = &NamedCertKey{}

// NewNamedKeyCertArray creates a new NamedCertKeyArray with the internal value
// pointing to p.
func NewNamedCertKeyArray(p *[]NamedCertKey) *NamedCertKeyArray {
	return &NamedCertKeyArray{
		value: p,
	}
}

func (a *NamedCertKeyArray) Set(val string) error {
	nkc := NamedCertKey{}
	err := nkc.Set(val)
	if err != nil {
		return err
	}
	if !a.changed {
		*a.value = []NamedCertKey{nkc}
		a.changed = true
	} else {
		*a.value = append(*a.value, nkc)
	}
	return nil
}

func (a *NamedCertKeyArray) Type() string {
	return "namedCertKey"
}

func (a *NamedCertKeyArray) String() string {
	nkcs := make([]string, 0, len(*a.value))
	for i := range *a.value {
		nkcs = append(nkcs, (*a.value)[i].String())
	}
	return "[" + strings.Join(nkcs, ";") + "]"
}
<commit_msg>Ensure NamedCertKeyArray implements flag.Value<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package flag

import (
	"errors"
	"flag"
	"strings"
)

// NamedCertKey is a flag value parsing "certfile,keyfile" and "certfile,keyfile:name,name,name".
type NamedCertKey struct {
	Names             []string
	CertFile, KeyFile string
}

var _ flag.Value = &NamedCertKey{}

func (nkc *NamedCertKey) String() string {
	s := nkc.CertFile + "," + nkc.KeyFile
	if len(nkc.Names) > 0 {
		s = s + ":" + strings.Join(nkc.Names, ",")
	}
	return s
}

func (nkc *NamedCertKey) Set(value string) error {
	cs := strings.SplitN(value, ":", 2)
	var keycert string
	if len(cs) == 2 {
		var names string
		keycert, names = strings.TrimSpace(cs[0]), strings.TrimSpace(cs[1])
		if names == "" {
			return errors.New("empty names list is not allowed")
		}
		nkc.Names = nil
		for _, name := range strings.Split(names, ",") {
			nkc.Names = append(nkc.Names, strings.TrimSpace(name))
		}
	} else {
		nkc.Names = nil
		keycert = strings.TrimSpace(cs[0])
	}
	cs = strings.Split(keycert, ",")
	if len(cs) != 2 {
		return errors.New("expected comma separated certificate and key file paths")
	}
	nkc.CertFile = strings.TrimSpace(cs[0])
	nkc.KeyFile = strings.TrimSpace(cs[1])
	return nil
}

func (*NamedCertKey) Type() string {
	return "namedCertKey"
}

// NamedCertKeyArray is a flag value parsing NamedCertKeys, each passed with its own
// flag instance (in contrast to comma separated slices).
type NamedCertKeyArray struct {
	value   *[]NamedCertKey
	changed bool
}

var _ flag.Value = &NamedCertKeyArray{}

// NewNamedKeyCertArray creates a new NamedCertKeyArray with the internal value
// pointing to p.
func NewNamedCertKeyArray(p *[]NamedCertKey) *NamedCertKeyArray {
	return &NamedCertKeyArray{
		value: p,
	}
}

func (a *NamedCertKeyArray) Set(val string) error {
	nkc := NamedCertKey{}
	err := nkc.Set(val)
	if err != nil {
		return err
	}
	if !a.changed {
		*a.value = []NamedCertKey{nkc}
		a.changed = true
	} else {
		*a.value = append(*a.value, nkc)
	}
	return nil
}

func (a *NamedCertKeyArray) Type() string {
	return "namedCertKey"
}

func (a *NamedCertKeyArray) String() string {
	nkcs := make([]string, 0, len(*a.value))
	for i := range *a.value {
		nkcs = append(nkcs, (*a.value)[i].String())
	}
	return "[" + strings.Join(nkcs, ";") + "]"
}
<|endoftext|>


<commit_before>/*
 * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @Copyright 	2017-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @license 	Apache-2.0
 */

package client

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"

	"github.com/ory/fosite"
	"github.com/ory/go-convenience/stringslice"
	"github.com/pkg/errors"
)

type DynamicValidator struct {
	c *http.Client
}

func NewDynamicValidator() *DynamicValidator {
	return &DynamicValidator{
		c: http.DefaultClient,
	}
}

func (v *DynamicValidator) Validate(c *Client) error {
	if len(c.SectorIdentifierURI) > 0 {
		if err := v.validateSectorIdentifierURL(c.SectorIdentifierURI, c.GetRedirectURIs()); err != nil {
			return err
		}
	}

	return nil
}

func (v *DynamicValidator) validateSectorIdentifierURL(location string, redirectURIs []string) error {
	l, err := url.Parse(location)
	if err != nil {
		return errors.WithStack(fosite.ErrInvalidRequest.WithDebug(fmt.Sprintf("Value of sector_identifier_uri could not be parsed: %s", err)))
	}

	if l.Scheme != "https" {
		return errors.WithStack(fosite.ErrInvalidRequest.WithDebug("Value sector_identifier_uri must be an HTTPS URL but it is not."))
	}

	response, err := v.c.Get(location)
	if err != nil {
		return errors.WithStack(fosite.ErrInvalidRequest.WithDebug(fmt.Sprintf("Unable to connect to URL set by sector_identifier_uri: %s", err)))
	}
	defer response.Body.Close()

	var urls []string
	if err := json.NewDecoder(response.Body).Decode(&urls); err != nil {
		return errors.WithStack(fosite.ErrInvalidRequest.WithDebug(fmt.Sprintf("Unable to decode values from sector_identifier_uri: %s", err)))
	}

	if len(urls) == 0 {
		return errors.WithStack(fosite.ErrInvalidRequest.WithDebug("Array from sector_identifier_uri contains no items"))
	}

	for _, r := range redirectURIs {
		if !stringslice.Has(urls, r) {
			return errors.WithStack(fosite.ErrInvalidRequest.WithDebug(fmt.Sprintf("Redirect URL \"%s\" does not match values from sector_identifier_uri.", r)))
		}
	}

	return nil
}
<commit_msg>client: Disallow fragments in client's redirect uri<commit_after>/*
 * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @Copyright 	2017-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @license 	Apache-2.0
 */

package client

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/ory/fosite"
	"github.com/ory/go-convenience/stringslice"
	"github.com/pkg/errors"
)

type DynamicValidator struct {
	c *http.Client
}

func NewDynamicValidator() *DynamicValidator {
	return &DynamicValidator{
		c: http.DefaultClient,
	}
}

func (v *DynamicValidator) Validate(c *Client) error {
	if len(c.SectorIdentifierURI) > 0 {
		if err := v.validateSectorIdentifierURL(c.SectorIdentifierURI, c.GetRedirectURIs()); err != nil {
			return err
		}
	}

	for _, r := range c.RedirectURIs {
		if strings.Contains(r, "#") {
			return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Redirect URIs must not contain fragments (#)"))
		}
	}

	return nil
}

func (v *DynamicValidator) validateSectorIdentifierURL(location string, redirectURIs []string) error {
	l, err := url.Parse(location)
	if err != nil {
		return errors.WithStack(fosite.ErrInvalidRequest.WithHint(fmt.Sprintf("Value of sector_identifier_uri could not be parsed: %s", err)))
	}

	if l.Scheme != "https" {
		return errors.WithStack(fosite.ErrInvalidRequest.WithDebug("Value sector_identifier_uri must be an HTTPS URL but it is not."))
	}

	response, err := v.c.Get(location)client: Disallow fragments in client's redirect uri
	if err != nil {
		return errors.WithStack(fosite.ErrInvalidRequest.WithDebug(fmt.Sprintf("Unable to connect to URL set by sector_identifier_uri: %s", err)))
	}
	defer response.Body.Close()

	var urls []string
	if err := json.NewDecoder(response.Body).Decode(&urls); err != nil {
		return errors.WithStack(fosite.ErrInvalidRequest.WithDebug(fmt.Sprintf("Unable to decode values from sector_identifier_uri: %s", err)))
	}

	if len(urls) == 0 {
		return errors.WithStack(fosite.ErrInvalidRequest.WithDebug("Array from sector_identifier_uri contains no items"))
	}

	for _, r := range redirectURIs {
		if !stringslice.Has(urls, r) {
			return errors.WithStack(fosite.ErrInvalidRequest.WithDebug(fmt.Sprintf("Redirect URL \"%s\" does not match values from sector_identifier_uri.", r)))
		}
	}

	return nil
}
<|endoftext|>