<commit_before>package totp

import (
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	duoapi "github.com/duosecurity/duo_api_golang"
	"github.com/duosecurity/duo_api_golang/authapi"
	"github.com/uva-its/gopherbot/bot"
)

var botHandler bot.Handler
var timeoutLock sync.RWMutex
var lastElevate map[string]time.Time
var auth *authapi.AuthApi

type timeoutType int

const (
	idle timeoutType = iota
	absolute
)

type config struct {
	TimeoutSeconds int
	tf64           float64
	TimeoutType    string // TimeoutType - one of idle, absolute
	tt             timeoutType
	DuoIKey        string
	DuoSKey        string
	DuoHost        string
	DuoUserString  string // DuoUserType - one of handle, email, emailUser
}

var cfg config

func authduo(r *bot.Robot, immediate bool, user string, res *authapi.PreauthResult) bool {
	dm := ""
	if r.Channel != "" {
		dm = " - I'll message you directly"
	}
	if immediate {
		r.Say("This command requires immediate elevation" + dm)
	} else {
		r.Say("This command requires elevation" + dm)
	}
	r.Pause(1)

	prompted := false
	var devnum, method int
	var msg []string
	var ret bot.RetVal
	var rep string

	if len(res.Response.Devices) > 1 {
		msg = make([]string, 10)
		for d, dev := range res.Response.Devices {
			if d == 10 {
				break
			}
			switch dev.Type {
			case "phone":
				msg[d] = fmt.Sprintf("Device %d: %s - %s", d, dev.Type, dev.Number)
			case "token":
				msg[d] = fmt.Sprintf("Device %d: %s (%s)", d, dev.Type, dev.Name)
			}
		}
		r.Direct().Say(fmt.Sprintf("Duo devices:\n%s", strings.Join(msg, "\n")))
		r.Direct().Say("Which device # do you want to use?")
		rep, ret = r.Direct().WaitForReplyRegex(`\d`, 10)
		if ret != bot.Ok {
			r.Direct().Say("Try again? I need a single-digit device #")
			rep, ret = r.Direct().WaitForReplyRegex(`\d`, 10)
		}
		if ret != bot.Ok {
			return false
		}
		devnum, _ = strconv.Atoi(rep)
		if devnum < 0 || devnum >= len(res.Response.Devices) {
			r.Direct().Say("Invalid device number")
			return false
		}
	}
	autoProvided := false
	for _, method := range res.Response.Devices[devnum].Capabilities {
		if method == "auto" {
			autoProvided = true
			break
		}
	}
	if len(res.Response.Devices[devnum].Capabilities) == 1 || (autoProvided && len(res.Response.Devices[devnum].Capabilities) == 2) {
		ret = bot.Ok
	} else {
		prompted = true
		msg = make([]string, 10)
		for m, method := range res.Response.Devices[devnum].Capabilities {
			if m == 10 {
				break
			}
			msg[m] = fmt.Sprintf("Method %d: %s", m, method)
		}
		r.Direct().Say(fmt.Sprintf("Duo methods available for your device:\n%s", strings.Join(msg, "\n")))
		r.Direct().Say("Which method # do you want to use?")
		rep, ret := r.Direct().WaitForReplyRegex(`\d`, 10)
		if ret != bot.Ok {
			r.Direct().Say("Try again? I need a single-digit method #")
			rep, ret = r.Direct().WaitForReplyRegex(`\d`, 10)
		}
		method, _ = strconv.Atoi(rep)
		if method < 0 || method >= len(res.Response.Devices[devnum].Capabilities) {
			r.Direct().Say("Invalid method number")
			return false
		}
	}
	if !prompted {
		r.Direct().Say("(no prompting required for your Duo device)")
	}
	if ret == bot.Ok {
		nameattr := r.GetBotAttribute("name")
		botname := nameattr.Attribute
		if botname == "" {
			botname = "Gopherbot"
		} else {
			botname += " - Gopherbot"
		}
		var authres *authapi.AuthResult
		var err error
		factor := res.Response.Devices[devnum].Capabilities[method]
		switch factor {
		case "push":
			authres, err = auth.Auth(factor,
				authapi.AuthUsername(user),
				authapi.AuthDevice(res.Response.Devices[devnum].Device),
				authapi.AuthDisplayUsername(user),
				authapi.AuthType(botname),
			)
		default:
			authres, err = auth.Auth(factor,
				authapi.AuthUsername(user),
				authapi.AuthDevice(res.Response.Devices[devnum].Device),
			)
		}
		if err != nil {
			r.Log(bot.Error, fmt.Sprintf("Error during Duo auth for user %s (%s): %s", user, r.User, err))
			r.Direct().Say("Sorry, there was an error while, trying to authenticate you - ask an admin to check the log")
			return false
		}
		if authres.Response.Result != "allow" {
			r.Log(bot.Error, fmt.Sprintf("Duo auth failed for user %s (%s) - result: %s, status: %s, message: %s", user, r.User, authres.Response.Result, authres.Response.Status, authres.Response.Status_Msg))
			r.Direct().Say("Duo authentication failed")
			return false
		}
		return true
	}
	return false
}

func elevate(r *bot.Robot, immediate bool) bool {
	var duouser string

	switch cfg.DuoUserString {
	case "handle":
		duouser = r.User
	case "email":
		duouser = r.GetSenderAttribute("email").Attribute
	case "emailUser", "emailuser":
		mailattr := r.GetSenderAttribute("email")
		email := mailattr.Attribute
		duouser = strings.Split(email, "@")[0]
	default:
		r.Log(bot.Error, "No DuoUserString configured for Duo elevator plugin")
	}
	if len(duouser) == 0 {
		r.Log(bot.Error, fmt.Sprintf("Couldn't extract a Duo user name for %s with DuoUserString: %s", r.User, cfg.DuoUserString))
		r.Say("This command requires elevation and I couldn't determine your Duo username, sorry")
		return false
	}
	res, err := auth.Preauth(authapi.PreauthUsername(duouser))
	if err != nil {
		r.Log(bot.Error, fmt.Sprintf("Duo preauthentication error for Duo user %s (%s): %s", duouser, r.User, err))
		r.Say("This command requires elevation, but there was an error during preauth")
		return false
	}
	if res.Response.Result == "deny" {
		r.Log(bot.Error, fmt.Sprintf("Received \"deny\" during Duo preauth for Duo user %s (%s)", duouser, r.User))
		r.Say("This command requires elevation, but I received a \"deny\" response during preauth")
		return false
	}

	allowed := false
	now := time.Now().UTC()
	if immediate {
		allowed = authduo(r, immediate, duouser, res)
	} else {
		timeoutLock.RLock()
		le, ok := lastElevate[r.User]
		timeoutLock.RUnlock()
		ask := false
		if ok {
			diff := now.Sub(le)
			if diff.Seconds() > cfg.tf64 {
				ask = true
			} else {
				allowed = true
			}
		} else {
			ask = true
		}
		if ask {
			allowed = authduo(r, immediate, duouser, res)
		}
	}
	if allowed && cfg.tt == idle {
		timeoutLock.Lock()
		lastElevate[r.User] = now
		timeoutLock.Unlock()
	}
	return allowed
}

func provider(r bot.Handler) bot.Elevate {
	botHandler = r
	botHandler.GetElevateConfig(&cfg)
	if cfg.TimeoutType == "absolute" {
		cfg.tt = absolute
	}
	cfg.tf64 = float64(cfg.TimeoutSeconds)
	duo := duoapi.NewDuoApi(cfg.DuoIKey, cfg.DuoSKey, cfg.DuoHost, "Gopherbot", duoapi.SetTimeout(10*time.Second))
	auth = authapi.NewAuthApi(*duo)
	return elevate
}

func init() {
	bot.RegisterElevator("duo", provider)
	lastElevate = make(map[string]time.Time)
}
<commit_msg>Eliminate direct message when Duo doesn't require prompting<commit_after>package totp

import (
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	duoapi "github.com/duosecurity/duo_api_golang"
	"github.com/duosecurity/duo_api_golang/authapi"
	"github.com/uva-its/gopherbot/bot"
)

var botHandler bot.Handler
var timeoutLock sync.RWMutex
var lastElevate map[string]time.Time
var auth *authapi.AuthApi

type timeoutType int

const (
	idle timeoutType = iota
	absolute
)

type config struct {
	TimeoutSeconds int
	tf64           float64
	TimeoutType    string // TimeoutType - one of idle, absolute
	tt             timeoutType
	DuoIKey        string
	DuoSKey        string
	DuoHost        string
	DuoUserString  string // DuoUserType - one of handle, email, emailUser
}

var cfg config

func authduo(r *bot.Robot, immediate bool, user string, res *authapi.PreauthResult) bool {
	dm := ""
	if r.Channel != "" {
		dm = " - I'll message you directly"
	}

	prompted := false
	var devnum, method int
	var msg []string
	var ret bot.RetVal
	var rep string

	if len(res.Response.Devices) > 1 {
		if immediate {
			r.Say("This command requires immediate elevation" + dm)
		} else {
			r.Say("This command requires elevation" + dm)
		}
		r.Pause(1)
		prompted = true

		msg = make([]string, 10)
		for d, dev := range res.Response.Devices {
			if d == 10 {
				break
			}
			switch dev.Type {
			case "phone":
				msg[d] = fmt.Sprintf("Device %d: %s - %s", d, dev.Type, dev.Number)
			case "token":
				msg[d] = fmt.Sprintf("Device %d: %s (%s)", d, dev.Type, dev.Name)
			}
		}
		r.Direct().Say(fmt.Sprintf("Duo devices:\n%s", strings.Join(msg, "\n")))
		r.Direct().Say("Which device # do you want to use?")
		rep, ret = r.Direct().WaitForReplyRegex(`\d`, 10)
		if ret != bot.Ok {
			r.Direct().Say("Try again? I need a single-digit device #")
			rep, ret = r.Direct().WaitForReplyRegex(`\d`, 10)
		}
		if ret != bot.Ok {
			return false
		}
		devnum, _ = strconv.Atoi(rep)
		if devnum < 0 || devnum >= len(res.Response.Devices) {
			r.Direct().Say("Invalid device number")
			return false
		}
	}
	autoProvided := false
	for _, method := range res.Response.Devices[devnum].Capabilities {
		if method == "auto" {
			autoProvided = true
			break
		}
	}
	if len(res.Response.Devices[devnum].Capabilities) == 1 || (autoProvided && len(res.Response.Devices[devnum].Capabilities) == 2) {
		ret = bot.Ok
	} else {
		if !prompted {
			if immediate {
				r.Say("This command requires immediate elevation" + dm)
			} else {
				r.Say("This command requires elevation" + dm)
			}
			r.Pause(1)
		}
		msg = make([]string, 10)
		for m, method := range res.Response.Devices[devnum].Capabilities {
			if m == 10 {
				break
			}
			msg[m] = fmt.Sprintf("Method %d: %s", m, method)
		}
		r.Direct().Say(fmt.Sprintf("Duo methods available for your device:\n%s", strings.Join(msg, "\n")))
		r.Direct().Say("Which method # do you want to use?")
		rep, ret := r.Direct().WaitForReplyRegex(`\d`, 10)
		if ret != bot.Ok {
			r.Direct().Say("Try again? I need a single-digit method #")
			rep, ret = r.Direct().WaitForReplyRegex(`\d`, 10)
		}
		method, _ = strconv.Atoi(rep)
		if method < 0 || method >= len(res.Response.Devices[devnum].Capabilities) {
			r.Direct().Say("Invalid method number")
			return false
		}
	}
	if ret == bot.Ok {
		nameattr := r.GetBotAttribute("name")
		botname := nameattr.Attribute
		if botname == "" {
			botname = "Gopherbot"
		} else {
			botname += " - Gopherbot"
		}
		var authres *authapi.AuthResult
		var err error
		factor := res.Response.Devices[devnum].Capabilities[method]
		switch factor {
		case "push":
			authres, err = auth.Auth(factor,
				authapi.AuthUsername(user),
				authapi.AuthDevice(res.Response.Devices[devnum].Device),
				authapi.AuthDisplayUsername(user),
				authapi.AuthType(botname),
			)
		default:
			authres, err = auth.Auth(factor,
				authapi.AuthUsername(user),
				authapi.AuthDevice(res.Response.Devices[devnum].Device),
			)
		}
		if err != nil {
			r.Log(bot.Error, fmt.Sprintf("Error during Duo auth for user %s (%s): %s", user, r.User, err))
			r.Direct().Say("Sorry, there was an error while, trying to authenticate you - ask an admin to check the log")
			return false
		}
		if authres.Response.Result != "allow" {
			r.Log(bot.Error, fmt.Sprintf("Duo auth failed for user %s (%s) - result: %s, status: %s, message: %s", user, r.User, authres.Response.Result, authres.Response.Status, authres.Response.Status_Msg))
			r.Direct().Say("Duo authentication failed")
			return false
		}
		return true
	}
	return false
}

func elevate(r *bot.Robot, immediate bool) bool {
	var duouser string

	switch cfg.DuoUserString {
	case "handle":
		duouser = r.User
	case "email":
		duouser = r.GetSenderAttribute("email").Attribute
	case "emailUser", "emailuser":
		mailattr := r.GetSenderAttribute("email")
		email := mailattr.Attribute
		duouser = strings.Split(email, "@")[0]
	default:
		r.Log(bot.Error, "No DuoUserString configured for Duo elevator plugin")
	}
	if len(duouser) == 0 {
		r.Log(bot.Error, fmt.Sprintf("Couldn't extract a Duo user name for %s with DuoUserString: %s", r.User, cfg.DuoUserString))
		r.Say("This command requires elevation and I couldn't determine your Duo username, sorry")
		return false
	}
	res, err := auth.Preauth(authapi.PreauthUsername(duouser))
	if err != nil {
		r.Log(bot.Error, fmt.Sprintf("Duo preauthentication error for Duo user %s (%s): %s", duouser, r.User, err))
		r.Say("This command requires elevation, but there was an error during preauth")
		return false
	}
	if res.Response.Result == "deny" {
		r.Log(bot.Error, fmt.Sprintf("Received \"deny\" during Duo preauth for Duo user %s (%s)", duouser, r.User))
		r.Say("This command requires elevation, but I received a \"deny\" response during preauth")
		return false
	}

	allowed := false
	now := time.Now().UTC()
	if immediate {
		allowed = authduo(r, immediate, duouser, res)
	} else {
		timeoutLock.RLock()
		le, ok := lastElevate[r.User]
		timeoutLock.RUnlock()
		ask := false
		if ok {
			diff := now.Sub(le)
			if diff.Seconds() > cfg.tf64 {
				ask = true
			} else {
				allowed = true
			}
		} else {
			ask = true
		}
		if ask {
			allowed = authduo(r, immediate, duouser, res)
		}
	}
	if allowed && cfg.tt == idle {
		timeoutLock.Lock()
		lastElevate[r.User] = now
		timeoutLock.Unlock()
	}
	return allowed
}

func provider(r bot.Handler) bot.Elevate {
	botHandler = r
	botHandler.GetElevateConfig(&cfg)
	if cfg.TimeoutType == "absolute" {
		cfg.tt = absolute
	}
	cfg.tf64 = float64(cfg.TimeoutSeconds)
	duo := duoapi.NewDuoApi(cfg.DuoIKey, cfg.DuoSKey, cfg.DuoHost, "Gopherbot", duoapi.SetTimeout(10*time.Second))
	auth = authapi.NewAuthApi(*duo)
	return elevate
}

func init() {
	bot.RegisterElevator("duo", provider)
	lastElevate = make(map[string]time.Time)
}
<|endoftext|>


<commit_before>// Copyright 2015 The appc 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 lib

import (
	"fmt"
	"os"
	"path"

	"github.com/appc/spec/aci"
	"github.com/coreos/rkt/pkg/fileutil"
	"github.com/coreos/rkt/pkg/user"

	"github.com/containers/build/lib/oci"
	"github.com/containers/build/util"
)

// CopyToDir will copy all elements specified in the froms slice into the
// directory inside the current ACI specified by the to string.
func (a *ACBuild) CopyToDir(froms []string, to string) (err error) {
	if err = a.lock(); err != nil {
		return err
	}
	defer func() {
		if err1 := a.unlock(); err == nil {
			err = err1
		}
	}()

	switch a.Mode {
	case BuildModeAppC:
		return a.copyToDirAppC(froms, to)
	case BuildModeOCI:
		return a.copyToDirOCI(froms, to)
	}
	return fmt.Errorf("unknown build mode: %s", a.Mode)
}

func (a *ACBuild) copyToDirAppC(froms []string, to string) error {
	target := path.Join(a.CurrentImagePath, aci.RootfsDir, to)

	targetInfo, err := os.Stat(target)
	switch {
	case os.IsNotExist(err):
		err := os.MkdirAll(target, 0755)
		if err != nil {
			return err
		}
	case err != nil:
		return err
	case !targetInfo.IsDir():
		return fmt.Errorf("target %q is not a directory", to)
	}

	for _, from := range froms {
		_, file := path.Split(from)
		tmptarget := path.Join(target, file)
		err := fileutil.CopyTree(from, tmptarget, user.NewBlankUidRange())
		if err != nil {
			return err
		}
	}
	return nil
}

func (a *ACBuild) expandTopOCILayer() (string, error) {
	var topLayerID string
	switch ociMan := a.man.(type) {
	case *oci.Image:
		layerIDs := ociMan.GetLayerHashes()
		if len(layerIDs) > 0 {
			topLayerID = layerIDs[len(layerIDs)-1]
		}
	default:
		return "", fmt.Errorf("internal error: mismatched manifest type and build mode???")
	}

	var targetPath string
	if topLayerID == "" {
		var err error
		targetPath, err = util.OCINewExpandedLayer(a.OCIExpandedBlobsPath)
		if err != nil {
			return "", err
		}
	} else {
		topLayerAlgo, topLayerHash, err := util.SplitOCILayerID(topLayerID)
		if err != nil {
			return "", err
		}

		err = util.OCIExtractLayers([]string{topLayerID}, a.CurrentImagePath, a.OCIExpandedBlobsPath)
		if err != nil {
			return "", err
		}
		targetPath = path.Join(a.OCIExpandedBlobsPath, topLayerAlgo, topLayerHash)
	}
	return targetPath, nil
}

func (a *ACBuild) copyToDirOCI(froms []string, to string) error {
	targetPath, err := a.expandTopOCILayer()
	if err != nil {
		return err
	}
	for _, from := range froms {
		_, file := path.Split(from)
		tmptarget := path.Join(targetPath, file)
		err := fileutil.CopyTree(from, tmptarget, user.NewBlankUidRange())
		if err != nil {
			return err
		}
	}

	return a.rehashAndStoreOCIBlob(targetPath, false)
}

// CopyToTarget will copy a single file/directory from the from string to the
// path specified by the to string inside the current ACI.
func (a *ACBuild) CopyToTarget(from string, to string) (err error) {
	if err = a.lock(); err != nil {
		return err
	}
	defer func() {
		if err1 := a.unlock(); err == nil {
			err = err1
		}
	}()

	switch a.Mode {
	case BuildModeAppC:
		return a.copyToTargetAppC(from, to)
	case BuildModeOCI:
		return a.copyToTargetOCI(from, to)
	}
	return fmt.Errorf("unknown build mode: %s", a.Mode)
}

func (a *ACBuild) copyToTargetAppC(from string, to string) error {
	target := path.Join(a.CurrentImagePath, aci.RootfsDir, to)

	dir, _ := path.Split(target)
	if dir != "" {
		err := os.MkdirAll(dir, 0755)
		if err != nil {
			return err
		}
	}

	return fileutil.CopyTree(from, target, user.NewBlankUidRange())
}

func (a *ACBuild) copyToTargetOCI(from string, to string) error {
	targetPath, err := a.expandTopOCILayer()
	if err != nil {
		return err
	}
	target := path.Join(targetPath, to)

	dir, _ := path.Split(target)
	if dir != "" {
		err := os.MkdirAll(dir, 0755)
		if err != nil {
			return err
		}
	}

	err = fileutil.CopyTree(from, target, user.NewBlankUidRange())
	if err != nil {
		return err
	}

	return a.rehashAndStoreOCIBlob(targetPath, false)

}
<commit_msg>oci: fix copy-to-dir destination dir<commit_after>// Copyright 2015 The appc 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 lib

import (
	"fmt"
	"os"
	"path"

	"github.com/appc/spec/aci"
	"github.com/coreos/rkt/pkg/fileutil"
	"github.com/coreos/rkt/pkg/user"

	"github.com/containers/build/lib/oci"
	"github.com/containers/build/util"
)

// CopyToDir will copy all elements specified in the froms slice into the
// directory inside the current ACI specified by the to string.
func (a *ACBuild) CopyToDir(froms []string, to string) (err error) {
	if err = a.lock(); err != nil {
		return err
	}
	defer func() {
		if err1 := a.unlock(); err == nil {
			err = err1
		}
	}()

	switch a.Mode {
	case BuildModeAppC:
		return a.copyToDirAppC(froms, to)
	case BuildModeOCI:
		return a.copyToDirOCI(froms, to)
	}
	return fmt.Errorf("unknown build mode: %s", a.Mode)
}

func (a *ACBuild) copyToDirAppC(froms []string, to string) error {
	target := path.Join(a.CurrentImagePath, aci.RootfsDir, to)

	targetInfo, err := os.Stat(target)
	switch {
	case os.IsNotExist(err):
		err := os.MkdirAll(target, 0755)
		if err != nil {
			return err
		}
	case err != nil:
		return err
	case !targetInfo.IsDir():
		return fmt.Errorf("target %q is not a directory", to)
	}

	for _, from := range froms {
		_, file := path.Split(from)
		tmptarget := path.Join(target, file)
		err := fileutil.CopyTree(from, tmptarget, user.NewBlankUidRange())
		if err != nil {
			return err
		}
	}
	return nil
}

func (a *ACBuild) expandTopOCILayer() (string, error) {
	var topLayerID string
	switch ociMan := a.man.(type) {
	case *oci.Image:
		layerIDs := ociMan.GetLayerHashes()
		if len(layerIDs) > 0 {
			topLayerID = layerIDs[len(layerIDs)-1]
		}
	default:
		return "", fmt.Errorf("internal error: mismatched manifest type and build mode???")
	}

	var targetPath string
	if topLayerID == "" {
		var err error
		targetPath, err = util.OCINewExpandedLayer(a.OCIExpandedBlobsPath)
		if err != nil {
			return "", err
		}
	} else {
		topLayerAlgo, topLayerHash, err := util.SplitOCILayerID(topLayerID)
		if err != nil {
			return "", err
		}

		err = util.OCIExtractLayers([]string{topLayerID}, a.CurrentImagePath, a.OCIExpandedBlobsPath)
		if err != nil {
			return "", err
		}
		targetPath = path.Join(a.OCIExpandedBlobsPath, topLayerAlgo, topLayerHash)
	}
	return targetPath, nil
}

func (a *ACBuild) copyToDirOCI(froms []string, to string) error {
	currentLayer, err := a.expandTopOCILayer()
	if err != nil {
		return err
	}
	targetPath := path.Join(currentLayer, to)

	targetInfo, err := os.Stat(targetPath)
	switch {
	case os.IsNotExist(err):
		err := os.MkdirAll(targetPath, 0755)
		if err != nil {
			return err
		}
	case err != nil:
		return err
	case !targetInfo.IsDir():
		return fmt.Errorf("target %q is not a directory", to)
	}

	for _, from := range froms {
		_, file := path.Split(from)
		tmptarget := path.Join(targetPath, file)
		err := fileutil.CopyTree(from, tmptarget, user.NewBlankUidRange())
		if err != nil {
			return err
		}
	}

	return a.rehashAndStoreOCIBlob(currentLayer, false)
}

// CopyToTarget will copy a single file/directory from the from string to the
// path specified by the to string inside the current ACI.
func (a *ACBuild) CopyToTarget(from string, to string) (err error) {
	if err = a.lock(); err != nil {
		return err
	}
	defer func() {
		if err1 := a.unlock(); err == nil {
			err = err1
		}
	}()

	switch a.Mode {
	case BuildModeAppC:
		return a.copyToTargetAppC(from, to)
	case BuildModeOCI:
		return a.copyToTargetOCI(from, to)
	}
	return fmt.Errorf("unknown build mode: %s", a.Mode)
}

func (a *ACBuild) copyToTargetAppC(from string, to string) error {
	target := path.Join(a.CurrentImagePath, aci.RootfsDir, to)

	dir, _ := path.Split(target)
	if dir != "" {
		err := os.MkdirAll(dir, 0755)
		if err != nil {
			return err
		}
	}

	return fileutil.CopyTree(from, target, user.NewBlankUidRange())
}

func (a *ACBuild) copyToTargetOCI(from string, to string) error {
	targetPath, err := a.expandTopOCILayer()
	if err != nil {
		return err
	}
	target := path.Join(targetPath, to)

	dir, _ := path.Split(target)
	if dir != "" {
		err := os.MkdirAll(dir, 0755)
		if err != nil {
			return err
		}
	}

	err = fileutil.CopyTree(from, target, user.NewBlankUidRange())
	if err != nil {
		return err
	}

	return a.rehashAndStoreOCIBlob(targetPath, false)

}
<|endoftext|>


<commit_before>// Copyright 2018 Drone.IO Inc.
//
// 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 drone

type (
	// User represents a user account.
	User struct {
		ID        int64  `json:"id"`
		Login     string `json:"login"`
		Email     string `json:"email"`
		Avatar    string `json:"avatar_url"`
		Active    bool   `json:"active"`
		Admin     bool   `json:"admin"`
		Machine   bool   `json:"machine"`
		Syncing   bool   `json:"syncing"`
		Synced    int64  `json:"synced"`
		Created   int64  `json:"created"`
		Updated   int64  `json:"updated"`
		LastLogin int64  `json:"last_login"`
		Token     string `json:"token"`
	}

	// UserPatch defines a user patch request.
	UserPatch struct {
		Active  *bool   `json:"active,omitempty"`
		Admin   *bool   `json:"admin,omitempty"`
		Machine *bool   `json:"machine,omitempty"`
		Token   *string `json:"token,omitempty"`
	}

	// Repo represents a repository.
	Repo struct {
		ID          int64  `json:"id"`
		UID         string `json:"uid"`
		UserID      int64  `json:"user_id"`
		Namespace   string `json:"namespace"`
		Name        string `json:"name"`
		Slug        string `json:"slug"`
		SCM         string `json:"scm"`
		HTTPURL     string `json:"git_http_url"`
		SSHURL      string `json:"git_ssh_url"`
		Link        string `json:"link"`
		Branch      string `json:"default_branch"`
		Private     bool   `json:"private"`
		Visibility  string `json:"visibility"`
		Active      bool   `json:"active"`
		Config      string `json:"config_path"`
		Trusted     bool   `json:"trusted"`
		Protected   bool   `json:"protected"`
		IgnoreForks bool   `json:"ignore_forks"`
		IgnorePulls bool   `json:"ignore_pull_requests"`
		CancelPulls bool   `json:"auto_cancel_pull_requests"`
		CancelPush  bool   `json:"auto_cancel_pushes"`
		Timeout     int64  `json:"timeout"`
		Counter     int64  `json:"counter"`
		Synced      int64  `json:"synced"`
		Created     int64  `json:"created"`
		Updated     int64  `json:"updated"`
		Version     int64  `json:"version"`
		Signer      string `json:"signer,omitempty"`
		Secret      string `json:"secret,omitempty"`
		Build       Build  `json:"build,omitempty"`
	}

	// RepoPatch defines a repository patch request.
	RepoPatch struct {
		Config      *string `json:"config_path,omitempty"`
		Protected   *bool   `json:"protected,omitempty"`
		Trusted     *bool   `json:"trusted,omitempty"`
		Timeout     *int64  `json:"timeout,omitempty"`
		Visibility  *string `json:"visibility,omitempty"`
		IgnoreForks *bool   `json:"ignore_forks"`
		IgnorePulls *bool   `json:"ignore_pull_requests"`
		CancelPulls *bool   `json:"auto_cancel_pull_requests"`
		CancelPush  *bool   `json:"auto_cancel_pushes"`
		Counter     *int    `json:"counter,omitempty"`
	}

	// Build defines a build object.
	Build struct {
		ID           int64             `json:"id"`
		RepoID       int64             `json:"repo_id"`
		Trigger      string            `json:"trigger"`
		Number       int64             `json:"number"`
		Parent       int64             `json:"parent,omitempty"`
		Status       string            `json:"status"`
		Error        string            `json:"error,omitempty"`
		Event        string            `json:"event"`
		Action       string            `json:"action"`
		Link         string            `json:"link"`
		Timestamp    int64             `json:"timestamp"`
		Title        string            `json:"title,omitempty"`
		Message      string            `json:"message"`
		Before       string            `json:"before"`
		After        string            `json:"after"`
		Ref          string            `json:"ref"`
		Fork         string            `json:"source_repo"`
		Source       string            `json:"source"`
		Target       string            `json:"target"`
		Author       string            `json:"author_login"`
		AuthorName   string            `json:"author_name"`
		AuthorEmail  string            `json:"author_email"`
		AuthorAvatar string            `json:"author_avatar"`
		Sender       string            `json:"sender"`
		Params       map[string]string `json:"params,omitempty"`
		Cron         string            `json:"cron,omitempty"`
		Deploy       string            `json:"deploy_to,omitempty"`
		DeployID     int64             `json:"deploy_id,omitempty"`
		Started      int64             `json:"started"`
		Finished     int64             `json:"finished"`
		Created      int64             `json:"created"`
		Updated      int64             `json:"updated"`
		Version      int64             `json:"version"`
		Stages       []*Stage          `json:"stages,omitempty"`
	}

	// Stage represents a stage of build execution.
	Stage struct {
		ID        int64             `json:"id"`
		BuildID   int64             `json:"build_id"`
		Number    int               `json:"number"`
		Name      string            `json:"name"`
		Kind      string            `json:"kind,omitempty"`
		Type      string            `json:"type,omitempty"`
		Status    string            `json:"status"`
		Error     string            `json:"error,omitempty"`
		ErrIgnore bool              `json:"errignore"`
		ExitCode  int               `json:"exit_code"`
		Machine   string            `json:"machine,omitempty"`
		OS        string            `json:"os"`
		Arch      string            `json:"arch"`
		Variant   string            `json:"variant,omitempty"`
		Kernel    string            `json:"kernel,omitempty"`
		Limit     int               `json:"limit,omitempty"`
		Started   int64             `json:"started"`
		Stopped   int64             `json:"stopped"`
		Created   int64             `json:"created"`
		Updated   int64             `json:"updated"`
		Version   int64             `json:"version"`
		OnSuccess bool              `json:"on_success"`
		OnFailure bool              `json:"on_failure"`
		DependsOn []string          `json:"depends_on,omitempty"`
		Labels    map[string]string `json:"labels,omitempty"`
		Steps     []*Step           `json:"steps,omitempty"`
	}

	// Step represents an individual step in the stage.
	Step struct {
		ID        int64  `json:"id"`
		StageID   int64  `json:"step_id"`
		Number    int    `json:"number"`
		Name      string `json:"name"`
		Status    string `json:"status"`
		Error     string `json:"error,omitempty"`
		ErrIgnore bool   `json:"errignore,omitempty"`
		ExitCode  int    `json:"exit_code"`
		Started   int64  `json:"started,omitempty"`
		Stopped   int64  `json:"stopped,omitempty"`
		Version   int64  `json:"version"`
	}

	// Registry represents a docker registry with credentials.
	// DEPRECATED
	Registry struct {
		Address  string `json:"address"`
		Username string `json:"username"`
		Password string `json:"password,omitempty"`
		Email    string `json:"email"`
		Token    string `json:"token"`
		Policy   string `json:"policy,omitempty"`
	}

	// Secret represents a secret variable, such as a password or token.
	Secret struct {
		Namespace       string `json:"namespace,omitempty"`
		Name            string `json:"name,omitempty"`
		Data            string `json:"data,omitempty"`
		PullRequest     bool   `json:"pull_request,omitempty"`
		PullRequestPush bool   `json:"pull_request_push,omitempty"`

		// Deprecated.
		Pull bool `json:"pull,omitempty"`
		Fork bool `json:"fork,omitempty"`
	}

	// Server represents a server node.
	Server struct {
		ID       string `json:"id"`
		Provider string `json:"provider"`
		State    string `json:"state"`
		Name     string `json:"name"`
		Image    string `json:"image"`
		Region   string `json:"region"`
		Size     string `json:"size"`
		Address  string `json:"address"`
		Capacity int    `json:"capacity"`
		Secret   string `json:"secret"`
		Error    string `json:"error"`
		CAKey    []byte `json:"ca_key"`
		CACert   []byte `json:"ca_cert"`
		TLSKey   []byte `json:"tls_key"`
		TLSCert  []byte `json:"tls_cert"`
		Created  int64  `json:"created"`
		Updated  int64  `json:"updated"`
		Started  int64  `json:"started"`
		Stopped  int64  `json:"stopped"`
	}

	// Cron represents a cron job.
	Cron struct {
		ID       int64  `json:"id"`
		RepoID   int64  `json:"repo_id"`
		Name     string `json:"name"`
		Expr     string `json:"expr"`
		Next     int64  `json:"next"`
		Prev     int64  `json:"prev"`
		Event    string `json:"event"`
		Branch   string `json:"branch"`
		Target   string `json:"target"`
		Disabled bool   `json:"disabled"`
		Created  int64  `json:"created"`
		Updated  int64  `json:"updated"`
	}

	// CronPatch defines a cron patch request.
	CronPatch struct {
		Event    *string `json:"event"`
		Branch   *string `json:"branch"`
		Target   *string `json:"target"`
		Disabled *bool   `json:"disabled"`
	}

	// Line represents a line of container logs.
	Line struct {
		Number    int    `json:"pos"`
		Message   string `json:"out"`
		Timestamp int64  `json:"time"`
	}

	// Config represents a config file.
	Config struct {
		Data string `json:"data"`
		Kind string `json:"kind"`
	}

	// Version provides system version details.
	Version struct {
		Source  string `json:"source,omitempty"`
		Version string `json:"version,omitempty"`
		Commit  string `json:"commit,omitempty"`
	}

	// System stores system information.
	System struct {
		Proto   string `json:"proto,omitempty"`
		Host    string `json:"host,omitempty"`
		Link    string `json:"link,omitempty"`
		Version string `json:"version,omitempty"`
	}

	// Node provides node details.
	Node struct {
		ID        int64             `json:"id"`
		UID       string            `json:"uid"`
		Provider  string            `json:"provider"`
		State     string            `json:"state"`
		Name      string            `json:"name"`
		Image     string            `json:"image"`
		Region    string            `json:"region"`
		Size      string            `json:"size"`
		OS        string            `json:"os"`
		Arch      string            `json:"arch"`
		Kernel    string            `json:"kernel"`
		Variant   string            `json:"variant"`
		Address   string            `json:"address"`
		Capacity  int               `json:"capacity"`
		Filters   []string          `json:"filters"`
		Labels    map[string]string `json:"labels"`
		Error     string            `json:"error"`
		CAKey     []byte            `json:"ca_key"`
		CACert    []byte            `json:"ca_cert"`
		TLSKey    []byte            `json:"tls_key"`
		TLSCert   []byte            `json:"tls_cert"`
		TLSName   string            `json:"tls_name"`
		Paused    bool              `json:"paused"`
		Protected bool              `json:"protected"`
		Created   int64             `json:"created"`
		Updated   int64             `json:"updated"`
	}

	// NodePatch defines a node patch request.
	NodePatch struct {
		UID       *string            `json:"uid"`
		Provider  *string            `json:"provider"`
		State     *string            `json:"state"`
		Image     *string            `json:"image"`
		Region    *string            `json:"region"`
		Size      *string            `json:"size"`
		Address   *string            `json:"address"`
		Capacity  *int               `json:"capacity"`
		Filters   *[]string          `json:"filters"`
		Labels    *map[string]string `json:"labels"`
		Error     *string            `json:"error"`
		CAKey     *[]byte            `json:"ca_key"`
		CACert    *[]byte            `json:"ca_cert"`
		TLSKey    *[]byte            `json:"tls_key"`
		TLSCert   *[]byte            `json:"tls_cert"`
		Paused    *bool              `json:"paused"`
		Protected *bool              `json:"protected"`
	}

	// Netrc contains login and initialization information used
	// by an automated login process.
	Netrc struct {
		Machine  string `json:"machine"`
		Login    string `json:"login"`
		Password string `json:"password"`
	}
)

// Error represents a json-encoded API error.
type Error struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

func (e *Error) Error() string {
	return e.Message
}
<commit_msg>fix type to patch repo counter<commit_after>// Copyright 2018 Drone.IO Inc.
//
// 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 drone

type (
	// User represents a user account.
	User struct {
		ID        int64  `json:"id"`
		Login     string `json:"login"`
		Email     string `json:"email"`
		Avatar    string `json:"avatar_url"`
		Active    bool   `json:"active"`
		Admin     bool   `json:"admin"`
		Machine   bool   `json:"machine"`
		Syncing   bool   `json:"syncing"`
		Synced    int64  `json:"synced"`
		Created   int64  `json:"created"`
		Updated   int64  `json:"updated"`
		LastLogin int64  `json:"last_login"`
		Token     string `json:"token"`
	}

	// UserPatch defines a user patch request.
	UserPatch struct {
		Active  *bool   `json:"active,omitempty"`
		Admin   *bool   `json:"admin,omitempty"`
		Machine *bool   `json:"machine,omitempty"`
		Token   *string `json:"token,omitempty"`
	}

	// Repo represents a repository.
	Repo struct {
		ID          int64  `json:"id"`
		UID         string `json:"uid"`
		UserID      int64  `json:"user_id"`
		Namespace   string `json:"namespace"`
		Name        string `json:"name"`
		Slug        string `json:"slug"`
		SCM         string `json:"scm"`
		HTTPURL     string `json:"git_http_url"`
		SSHURL      string `json:"git_ssh_url"`
		Link        string `json:"link"`
		Branch      string `json:"default_branch"`
		Private     bool   `json:"private"`
		Visibility  string `json:"visibility"`
		Active      bool   `json:"active"`
		Config      string `json:"config_path"`
		Trusted     bool   `json:"trusted"`
		Protected   bool   `json:"protected"`
		IgnoreForks bool   `json:"ignore_forks"`
		IgnorePulls bool   `json:"ignore_pull_requests"`
		CancelPulls bool   `json:"auto_cancel_pull_requests"`
		CancelPush  bool   `json:"auto_cancel_pushes"`
		Timeout     int64  `json:"timeout"`
		Counter     int64  `json:"counter"`
		Synced      int64  `json:"synced"`
		Created     int64  `json:"created"`
		Updated     int64  `json:"updated"`
		Version     int64  `json:"version"`
		Signer      string `json:"signer,omitempty"`
		Secret      string `json:"secret,omitempty"`
		Build       Build  `json:"build,omitempty"`
	}

	// RepoPatch defines a repository patch request.
	RepoPatch struct {
		Config      *string `json:"config_path,omitempty"`
		Protected   *bool   `json:"protected,omitempty"`
		Trusted     *bool   `json:"trusted,omitempty"`
		Timeout     *int64  `json:"timeout,omitempty"`
		Visibility  *string `json:"visibility,omitempty"`
		IgnoreForks *bool   `json:"ignore_forks"`
		IgnorePulls *bool   `json:"ignore_pull_requests"`
		CancelPulls *bool   `json:"auto_cancel_pull_requests"`
		CancelPush  *bool   `json:"auto_cancel_pushes"`
		Counter     *int64  `json:"counter,omitempty"`
	}

	// Build defines a build object.
	Build struct {
		ID           int64             `json:"id"`
		RepoID       int64             `json:"repo_id"`
		Trigger      string            `json:"trigger"`
		Number       int64             `json:"number"`
		Parent       int64             `json:"parent,omitempty"`
		Status       string            `json:"status"`
		Error        string            `json:"error,omitempty"`
		Event        string            `json:"event"`
		Action       string            `json:"action"`
		Link         string            `json:"link"`
		Timestamp    int64             `json:"timestamp"`
		Title        string            `json:"title,omitempty"`
		Message      string            `json:"message"`
		Before       string            `json:"before"`
		After        string            `json:"after"`
		Ref          string            `json:"ref"`
		Fork         string            `json:"source_repo"`
		Source       string            `json:"source"`
		Target       string            `json:"target"`
		Author       string            `json:"author_login"`
		AuthorName   string            `json:"author_name"`
		AuthorEmail  string            `json:"author_email"`
		AuthorAvatar string            `json:"author_avatar"`
		Sender       string            `json:"sender"`
		Params       map[string]string `json:"params,omitempty"`
		Cron         string            `json:"cron,omitempty"`
		Deploy       string            `json:"deploy_to,omitempty"`
		DeployID     int64             `json:"deploy_id,omitempty"`
		Started      int64             `json:"started"`
		Finished     int64             `json:"finished"`
		Created      int64             `json:"created"`
		Updated      int64             `json:"updated"`
		Version      int64             `json:"version"`
		Stages       []*Stage          `json:"stages,omitempty"`
	}

	// Stage represents a stage of build execution.
	Stage struct {
		ID        int64             `json:"id"`
		BuildID   int64             `json:"build_id"`
		Number    int               `json:"number"`
		Name      string            `json:"name"`
		Kind      string            `json:"kind,omitempty"`
		Type      string            `json:"type,omitempty"`
		Status    string            `json:"status"`
		Error     string            `json:"error,omitempty"`
		ErrIgnore bool              `json:"errignore"`
		ExitCode  int               `json:"exit_code"`
		Machine   string            `json:"machine,omitempty"`
		OS        string            `json:"os"`
		Arch      string            `json:"arch"`
		Variant   string            `json:"variant,omitempty"`
		Kernel    string            `json:"kernel,omitempty"`
		Limit     int               `json:"limit,omitempty"`
		Started   int64             `json:"started"`
		Stopped   int64             `json:"stopped"`
		Created   int64             `json:"created"`
		Updated   int64             `json:"updated"`
		Version   int64             `json:"version"`
		OnSuccess bool              `json:"on_success"`
		OnFailure bool              `json:"on_failure"`
		DependsOn []string          `json:"depends_on,omitempty"`
		Labels    map[string]string `json:"labels,omitempty"`
		Steps     []*Step           `json:"steps,omitempty"`
	}

	// Step represents an individual step in the stage.
	Step struct {
		ID        int64  `json:"id"`
		StageID   int64  `json:"step_id"`
		Number    int    `json:"number"`
		Name      string `json:"name"`
		Status    string `json:"status"`
		Error     string `json:"error,omitempty"`
		ErrIgnore bool   `json:"errignore,omitempty"`
		ExitCode  int    `json:"exit_code"`
		Started   int64  `json:"started,omitempty"`
		Stopped   int64  `json:"stopped,omitempty"`
		Version   int64  `json:"version"`
	}

	// Registry represents a docker registry with credentials.
	// DEPRECATED
	Registry struct {
		Address  string `json:"address"`
		Username string `json:"username"`
		Password string `json:"password,omitempty"`
		Email    string `json:"email"`
		Token    string `json:"token"`
		Policy   string `json:"policy,omitempty"`
	}

	// Secret represents a secret variable, such as a password or token.
	Secret struct {
		Namespace       string `json:"namespace,omitempty"`
		Name            string `json:"name,omitempty"`
		Data            string `json:"data,omitempty"`
		PullRequest     bool   `json:"pull_request,omitempty"`
		PullRequestPush bool   `json:"pull_request_push,omitempty"`

		// Deprecated.
		Pull bool `json:"pull,omitempty"`
		Fork bool `json:"fork,omitempty"`
	}

	// Server represents a server node.
	Server struct {
		ID       string `json:"id"`
		Provider string `json:"provider"`
		State    string `json:"state"`
		Name     string `json:"name"`
		Image    string `json:"image"`
		Region   string `json:"region"`
		Size     string `json:"size"`
		Address  string `json:"address"`
		Capacity int    `json:"capacity"`
		Secret   string `json:"secret"`
		Error    string `json:"error"`
		CAKey    []byte `json:"ca_key"`
		CACert   []byte `json:"ca_cert"`
		TLSKey   []byte `json:"tls_key"`
		TLSCert  []byte `json:"tls_cert"`
		Created  int64  `json:"created"`
		Updated  int64  `json:"updated"`
		Started  int64  `json:"started"`
		Stopped  int64  `json:"stopped"`
	}

	// Cron represents a cron job.
	Cron struct {
		ID       int64  `json:"id"`
		RepoID   int64  `json:"repo_id"`
		Name     string `json:"name"`
		Expr     string `json:"expr"`
		Next     int64  `json:"next"`
		Prev     int64  `json:"prev"`
		Event    string `json:"event"`
		Branch   string `json:"branch"`
		Target   string `json:"target"`
		Disabled bool   `json:"disabled"`
		Created  int64  `json:"created"`
		Updated  int64  `json:"updated"`
	}

	// CronPatch defines a cron patch request.
	CronPatch struct {
		Event    *string `json:"event"`
		Branch   *string `json:"branch"`
		Target   *string `json:"target"`
		Disabled *bool   `json:"disabled"`
	}

	// Line represents a line of container logs.
	Line struct {
		Number    int    `json:"pos"`
		Message   string `json:"out"`
		Timestamp int64  `json:"time"`
	}

	// Config represents a config file.
	Config struct {
		Data string `json:"data"`
		Kind string `json:"kind"`
	}

	// Version provides system version details.
	Version struct {
		Source  string `json:"source,omitempty"`
		Version string `json:"version,omitempty"`
		Commit  string `json:"commit,omitempty"`
	}

	// System stores system information.
	System struct {
		Proto   string `json:"proto,omitempty"`
		Host    string `json:"host,omitempty"`
		Link    string `json:"link,omitempty"`
		Version string `json:"version,omitempty"`
	}

	// Node provides node details.
	Node struct {
		ID        int64             `json:"id"`
		UID       string            `json:"uid"`
		Provider  string            `json:"provider"`
		State     string            `json:"state"`
		Name      string            `json:"name"`
		Image     string            `json:"image"`
		Region    string            `json:"region"`
		Size      string            `json:"size"`
		OS        string            `json:"os"`
		Arch      string            `json:"arch"`
		Kernel    string            `json:"kernel"`
		Variant   string            `json:"variant"`
		Address   string            `json:"address"`
		Capacity  int               `json:"capacity"`
		Filters   []string          `json:"filters"`
		Labels    map[string]string `json:"labels"`
		Error     string            `json:"error"`
		CAKey     []byte            `json:"ca_key"`
		CACert    []byte            `json:"ca_cert"`
		TLSKey    []byte            `json:"tls_key"`
		TLSCert   []byte            `json:"tls_cert"`
		TLSName   string            `json:"tls_name"`
		Paused    bool              `json:"paused"`
		Protected bool              `json:"protected"`
		Created   int64             `json:"created"`
		Updated   int64             `json:"updated"`
	}

	// NodePatch defines a node patch request.
	NodePatch struct {
		UID       *string            `json:"uid"`
		Provider  *string            `json:"provider"`
		State     *string            `json:"state"`
		Image     *string            `json:"image"`
		Region    *string            `json:"region"`
		Size      *string            `json:"size"`
		Address   *string            `json:"address"`
		Capacity  *int               `json:"capacity"`
		Filters   *[]string          `json:"filters"`
		Labels    *map[string]string `json:"labels"`
		Error     *string            `json:"error"`
		CAKey     *[]byte            `json:"ca_key"`
		CACert    *[]byte            `json:"ca_cert"`
		TLSKey    *[]byte            `json:"tls_key"`
		TLSCert   *[]byte            `json:"tls_cert"`
		Paused    *bool              `json:"paused"`
		Protected *bool              `json:"protected"`
	}

	// Netrc contains login and initialization information used
	// by an automated login process.
	Netrc struct {
		Machine  string `json:"machine"`
		Login    string `json:"login"`
		Password string `json:"password"`
	}
)

// Error represents a json-encoded API error.
type Error struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

func (e *Error) Error() string {
	return e.Message
}
<|endoftext|>


<commit_before>// Copyright © 2016 Joseph Wright <rjosephwright@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package lib

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/kms"
	"github.com/aws/aws-sdk-go/service/s3"
)

const CiphertextLength = 204

type BossVaultClient struct {
	kms *kmsClient
	s3  *s3Client
}

func NewBossVaultClient() *BossVaultClient {
	s := session.New()
	return &BossVaultClient{
		kms: &kmsClient{kms.New(s)},
		s3:  &s3Client{s3.New(s)},
	}
}

func (c *BossVaultClient) EncryptAndStore(bucket, artifact, content string) error {
	namespace, err := nsFrom(artifact)
	if err != nil {
		return err
	}

	plaintext, err := contentBytes(content)
	if err != nil {
		return err
	}

	keyId, err := c.kms.keyIdForAlias(namespace)
	if err != nil {
		return err
	}

	dk, err := c.kms.dataKey(keyId)
	if err != nil {
		return err
	}

	encrypted, err := encrypt(plaintext, dk.Plaintext)
	if err != nil {
		return err
	}

	return c.s3.store(artifact, bucket, encrypted, dk.CiphertextBlob)
}

func (c *BossVaultClient) RetrieveAndDecrypt(bucket, artifact string) ([]byte, error) {
	obj, err := c.s3.GetObject(
		&s3.GetObjectInput{
			Bucket: &bucket,
			Key:    &artifact,
		},
	)
	if err != nil {
		return nil, err
	}

	content, err := ioutil.ReadAll(obj.Body)
	if err != nil {
		return nil, err
	}

	encryptedKey := content[:CiphertextLength]
	payload := content[CiphertextLength:]

	dk, err := c.kms.Decrypt(
		&kms.DecryptInput{
			CiphertextBlob: encryptedKey,
		},
	)
	if err != nil {
		return nil, err
	}

	decrypted, err := decrypt(payload, dk.Plaintext)
	if err != nil {
		return nil, err
	}

	return decrypted, nil
}

type kmsClient struct {
	*kms.KMS
}

type s3Client struct {
	*s3.S3
}

func (kc *kmsClient) dataKey(keyId string) (out *kms.GenerateDataKeyOutput, err error) {
	keySpec := "AES_256"
	out, err = kc.GenerateDataKey(&kms.GenerateDataKeyInput{
		KeyId:   &keyId,
		KeySpec: &keySpec,
	})
	if err != nil {
		return
	}
	return
}

func (kc *kmsClient) keyIdForAlias(alias string) (string, error) {
	var keyId string
	var err error

	fullAlias := fmt.Sprintf("alias/%s", alias)

	err = kc.ListAliasesPages(
		&kms.ListAliasesInput{},
		func(out *kms.ListAliasesOutput, lastPage bool) bool {
			for _, a := range out.Aliases {
				if *a.AliasName == fullAlias {
					keyId = *a.TargetKeyId
					return true
				}
			}
			return lastPage
		},
	)
	if err != nil {
		return keyId, err
	}

	if keyId == "" {
		err = fmt.Errorf("No master key found with alias %s", alias)
	}

	return keyId, err
}

func (sc *s3Client) store(artifact, bucket string, encrypted, encryptedKey []byte) error {
	sse := "aws:kms"
	payload := append(encryptedKey, encrypted...)
	body := bytes.NewReader(payload)
	_, err := sc.PutObject(
		&s3.PutObjectInput{
			Bucket:               &bucket,
			Key:                  &artifact,
			Body:                 body,
			ServerSideEncryption: &sse,
		},
	)
	return err
}

func randomBytes(n int) ([]byte, error) {
	buf := make([]byte, n)
	if _, err := io.ReadFull(rand.Reader, buf); err != nil {
		return nil, err
	}
	return buf, nil
}

func contentBytes(content string) ([]byte, error) {
	var buf []byte
	var err error
	parts := strings.Split(content, "@")
	if len(parts) > 1 && parts[0] == "" && len(parts[1]) > 0 {
		path := parts[1]
		if file, err := os.Open(path); err != nil {
			return nil, err
		} else {
			defer file.Close()
			if buf, err = ioutil.ReadAll(file); err != nil {
				return nil, err
			}
		}
	} else {
		buf = []byte(content)
	}
	return buf, err
}

func nsFrom(artifact string) (ns string, err error) {
	parts := strings.Split(artifact, "/")
	if len(parts) == 1 {
		err = fmt.Errorf("Invalid artifact name")
	} else {
		ns = parts[0]
	}
	return ns, err
}

func encrypt(plaintext, key []byte) ([]byte, error) {
	var block cipher.Block
	var err error
	var iv []byte

	if block, err = aes.NewCipher(key); err != nil {
		return nil, err
	}

	if iv, err = randomBytes(aes.BlockSize); err != nil {
		return nil, err
	}

	ciphertext := make([]byte, len(plaintext))
	cfb := cipher.NewCFBEncrypter(block, iv)
	cfb.XORKeyStream(ciphertext, plaintext)

	return append(iv, ciphertext...), nil
}

func decrypt(ciphertext, key []byte) ([]byte, error) {
	iv := ciphertext[:aes.BlockSize]
	payload := ciphertext[aes.BlockSize:]
	decrypted := make([]byte, len(payload))

	if block, err := aes.NewCipher(key); err != nil {
		return nil, err
	} else {
		cfb := cipher.NewCFBDecrypter(block, iv)
		cfb.XORKeyStream(decrypted, payload)
	}

	return decrypted, nil
}
<commit_msg>Remove redundant code<commit_after>// Copyright © 2016 Joseph Wright <rjosephwright@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package lib

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/kms"
	"github.com/aws/aws-sdk-go/service/s3"
)

const CiphertextLength = 204

type BossVaultClient struct {
	kms *kmsClient
	s3  *s3Client
}

func NewBossVaultClient() *BossVaultClient {
	s := session.New()
	return &BossVaultClient{
		kms: &kmsClient{kms.New(s)},
		s3:  &s3Client{s3.New(s)},
	}
}

func (c *BossVaultClient) EncryptAndStore(bucket, artifact, content string) error {
	namespace, err := nsFrom(artifact)
	if err != nil {
		return err
	}

	plaintext, err := contentBytes(content)
	if err != nil {
		return err
	}

	keyId, err := c.kms.keyIdForAlias(namespace)
	if err != nil {
		return err
	}

	dk, err := c.kms.dataKey(keyId)
	if err != nil {
		return err
	}

	encrypted, err := encrypt(plaintext, dk.Plaintext)
	if err != nil {
		return err
	}

	return c.s3.store(artifact, bucket, encrypted, dk.CiphertextBlob)
}

func (c *BossVaultClient) RetrieveAndDecrypt(bucket, artifact string) ([]byte, error) {
	obj, err := c.s3.GetObject(
		&s3.GetObjectInput{
			Bucket: &bucket,
			Key:    &artifact,
		},
	)
	if err != nil {
		return nil, err
	}

	content, err := ioutil.ReadAll(obj.Body)
	if err != nil {
		return nil, err
	}

	encryptedKey := content[:CiphertextLength]
	payload := content[CiphertextLength:]

	dk, err := c.kms.Decrypt(
		&kms.DecryptInput{
			CiphertextBlob: encryptedKey,
		},
	)
	if err != nil {
		return nil, err
	}

	return decrypt(payload, dk.Plaintext)
}

type kmsClient struct {
	*kms.KMS
}

type s3Client struct {
	*s3.S3
}

func (kc *kmsClient) dataKey(keyId string) (out *kms.GenerateDataKeyOutput, err error) {
	keySpec := "AES_256"
	out, err = kc.GenerateDataKey(&kms.GenerateDataKeyInput{
		KeyId:   &keyId,
		KeySpec: &keySpec,
	})
	if err != nil {
		return
	}
	return
}

func (kc *kmsClient) keyIdForAlias(alias string) (string, error) {
	var keyId string
	var err error

	fullAlias := fmt.Sprintf("alias/%s", alias)

	err = kc.ListAliasesPages(
		&kms.ListAliasesInput{},
		func(out *kms.ListAliasesOutput, lastPage bool) bool {
			for _, a := range out.Aliases {
				if *a.AliasName == fullAlias {
					keyId = *a.TargetKeyId
					return true
				}
			}
			return lastPage
		},
	)
	if err != nil {
		return keyId, err
	}

	if keyId == "" {
		err = fmt.Errorf("No master key found with alias %s", alias)
	}

	return keyId, err
}

func (sc *s3Client) store(artifact, bucket string, encrypted, encryptedKey []byte) error {
	sse := "aws:kms"
	payload := append(encryptedKey, encrypted...)
	body := bytes.NewReader(payload)
	_, err := sc.PutObject(
		&s3.PutObjectInput{
			Bucket:               &bucket,
			Key:                  &artifact,
			Body:                 body,
			ServerSideEncryption: &sse,
		},
	)
	return err
}

func randomBytes(n int) ([]byte, error) {
	buf := make([]byte, n)
	if _, err := io.ReadFull(rand.Reader, buf); err != nil {
		return nil, err
	}
	return buf, nil
}

func contentBytes(content string) ([]byte, error) {
	var buf []byte
	var err error
	parts := strings.Split(content, "@")
	if len(parts) > 1 && parts[0] == "" && len(parts[1]) > 0 {
		path := parts[1]
		if file, err := os.Open(path); err != nil {
			return nil, err
		} else {
			defer file.Close()
			if buf, err = ioutil.ReadAll(file); err != nil {
				return nil, err
			}
		}
	} else {
		buf = []byte(content)
	}
	return buf, err
}

func nsFrom(artifact string) (ns string, err error) {
	parts := strings.Split(artifact, "/")
	if len(parts) == 1 {
		err = fmt.Errorf("Invalid artifact name")
	} else {
		ns = parts[0]
	}
	return ns, err
}

func encrypt(plaintext, key []byte) ([]byte, error) {
	var block cipher.Block
	var err error
	var iv []byte

	if block, err = aes.NewCipher(key); err != nil {
		return nil, err
	}

	if iv, err = randomBytes(aes.BlockSize); err != nil {
		return nil, err
	}

	ciphertext := make([]byte, len(plaintext))
	cfb := cipher.NewCFBEncrypter(block, iv)
	cfb.XORKeyStream(ciphertext, plaintext)

	return append(iv, ciphertext...), nil
}

func decrypt(ciphertext, key []byte) ([]byte, error) {
	iv := ciphertext[:aes.BlockSize]
	payload := ciphertext[aes.BlockSize:]
	decrypted := make([]byte, len(payload))

	if block, err := aes.NewCipher(key); err != nil {
		return nil, err
	} else {
		cfb := cipher.NewCFBDecrypter(block, iv)
		cfb.XORKeyStream(decrypted, payload)
	}

	return decrypted, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 The Prometheus 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 frankenstein

import (
	"bytes"
	"fmt"
	"net/http"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/prometheus/common/expfmt"
	"github.com/prometheus/common/model"
	"golang.org/x/net/context"

	"github.com/prometheus/prometheus/storage/metric"
	"github.com/prometheus/prometheus/storage/remote/generic"
)

type IngesterClient struct {
	hostname string
	client   http.Client
}

// NewIngesterClient makes a new IngesterClient.  This client is careful to
// propagate the user ID from Distributor -> Ingestor.
func NewIngesterClient(hostname string, timeout time.Duration) *IngesterClient {
	client := http.Client{
		Timeout: timeout,
	}
	return &IngesterClient{
		hostname: hostname,
		client:   client,
	}
}

func (c *IngesterClient) Append(ctx context.Context, samples []*model.Sample) error {
	userID, err := userID(ctx)
	if err != nil {
		return err
	}
	req := &generic.GenericWriteRequest{}
	for _, s := range samples {
		ts := &generic.TimeSeries{
			Name: proto.String(string(s.Metric[model.MetricNameLabel])),
		}
		for k, v := range s.Metric {
			if k != model.MetricNameLabel {
				ts.Labels = append(ts.Labels,
					&generic.LabelPair{
						Name:  proto.String(string(k)),
						Value: proto.String(string(v)),
					})
			}
		}
		ts.Samples = []*generic.Sample{
			&generic.Sample{
				Value:       proto.Float64(float64(s.Value)),
				TimestampMs: proto.Int64(int64(s.Timestamp)),
			},
		}
		req.Timeseries = append(req.Timeseries, ts)
	}
	data, err := proto.Marshal(req)
	if err != nil {
		return err
	}
	buf := bytes.NewBuffer(data)
	httpReq, err := http.NewRequest("POST", fmt.Sprintf("http://%s/port", c.hostname), buf)
	if err != nil {
		return err
	}
	httpReq.Header.Add(userIDHeaderName, userID)
	httpReq.Header.Set("Content-Type", string(expfmt.FmtProtoDelim))
	_, err = c.client.Do(httpReq)
	return err
}

// Query implements Querier.
func (c *IngesterClient) Query(ctx context.Context, from, to model.Time, matchers ...*metric.LabelMatcher) (model.Matrix, error) {
	userID, err := userID(ctx)
	if err != nil {
		return nil, err
	}
	req := &generic.GenericReadRequest{
		StartTimestampMs: proto.Int64(int64(from)),
		EndTimestampMs:   proto.Int64(int64(to)),
	}
	for _, matcher := range matchers {
		var mType generic.MatchType
		switch matcher.Type {
		case metric.Equal:
			mType = generic.MatchType_EQUAL
		case metric.NotEqual:
			mType = generic.MatchType_NOT_EQUAL
		case metric.RegexMatch:
			mType = generic.MatchType_REGEX_MATCH
		case metric.RegexNoMatch:
			mType = generic.MatchType_REGEX_NO_MATCH
		default:
			panic("invalid matcher type")
		}
		req.Matchers = append(req.Matchers, &generic.LabelMatcher{
			Type:  &mType,
			Name:  proto.String(string(matcher.Name)),
			Value: proto.String(string(matcher.Value)),
		})
	}

	data, err := proto.Marshal(req)
	if err != nil {
		return nil, err
	}
	buf := bytes.NewBuffer(data)

	// TODO: This isn't actually the correct Content-type.
	httpReq, err := http.NewRequest("POST", fmt.Sprintf("http://%s/query", c.hostname), buf)
	if err != nil {
		return nil, err
	}
	httpReq.Header.Add(userIDHeaderName, userID)
	httpReq.Header.Set("Content-Type", string(expfmt.FmtProtoDelim))
	resp, err := c.client.Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("server returned HTTP status %s", resp.Status)
	}

	r := &generic.GenericReadResponse{}
	buf.Reset()
	_, err = buf.ReadFrom(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("unable to read response body: %s", err)
	}
	err = proto.Unmarshal(buf.Bytes(), r)
	if err != nil {
		return nil, fmt.Errorf("unable to unmarshal response body: %s", err)
	}

	m := make(model.Matrix, 0, len(r.Timeseries))
	for _, ts := range r.Timeseries {
		var ss model.SampleStream
		ss.Metric = model.Metric{}
		if ts.Name != nil {
			ss.Metric[model.MetricNameLabel] = model.LabelValue(ts.GetName())
		}
		for _, l := range ts.Labels {
			ss.Metric[model.LabelName(l.GetName())] = model.LabelValue(l.GetValue())
		}

		ss.Values = make([]model.SamplePair, 0, len(ts.Samples))
		for _, s := range ts.Samples {
			ss.Values = append(ss.Values, model.SamplePair{
				Value:     model.SampleValue(s.GetValue()),
				Timestamp: model.Time(s.GetTimestampMs()),
			})
		}
		m = append(m, &ss)
	}

	return m, nil
}
<commit_msg>Review feedback<commit_after>// Copyright 2016 The Prometheus 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 frankenstein

import (
	"bytes"
	"fmt"
	"net/http"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/prometheus/common/expfmt"
	"github.com/prometheus/common/model"
	"golang.org/x/net/context"

	"github.com/prometheus/prometheus/storage/metric"
	"github.com/prometheus/prometheus/storage/remote/generic"
)

type IngesterClient struct {
	hostname string
	client   http.Client
}

// NewIngesterClient makes a new IngesterClient.  This client is careful to
// propagate the user ID from Distributor -> Ingestor.
func NewIngesterClient(hostname string, timeout time.Duration) *IngesterClient {
	client := http.Client{
		Timeout: timeout,
	}
	return &IngesterClient{
		hostname: hostname,
		client:   client,
	}
}

func (c *IngesterClient) Append(ctx context.Context, samples []*model.Sample) error {
	userID, err := userID(ctx)
	if err != nil {
		return err
	}
	req := &generic.GenericWriteRequest{}
	for _, s := range samples {
		ts := &generic.TimeSeries{
			Name: proto.String(string(s.Metric[model.MetricNameLabel])),
		}
		for k, v := range s.Metric {
			if k != model.MetricNameLabel {
				ts.Labels = append(ts.Labels,
					&generic.LabelPair{
						Name:  proto.String(string(k)),
						Value: proto.String(string(v)),
					})
			}
		}
		ts.Samples = []*generic.Sample{
			&generic.Sample{
				Value:       proto.Float64(float64(s.Value)),
				TimestampMs: proto.Int64(int64(s.Timestamp)),
			},
		}
		req.Timeseries = append(req.Timeseries, ts)
	}
	data, err := proto.Marshal(req)
	if err != nil {
		return err
	}
	buf := bytes.NewBuffer(data)
	httpReq, err := http.NewRequest("POST", fmt.Sprintf("http://%s/push", c.hostname), buf)
	if err != nil {
		return err
	}
	httpReq.Header.Add(userIDHeaderName, userID)
	httpReq.Header.Set("Content-Type", string(expfmt.FmtProtoDelim))
	_, err = c.client.Do(httpReq)
	return err
}

// Query implements Querier.
func (c *IngesterClient) Query(ctx context.Context, from, to model.Time, matchers ...*metric.LabelMatcher) (model.Matrix, error) {
	userID, err := userID(ctx)
	if err != nil {
		return nil, err
	}
	req := &generic.GenericReadRequest{
		StartTimestampMs: proto.Int64(int64(from)),
		EndTimestampMs:   proto.Int64(int64(to)),
	}
	for _, matcher := range matchers {
		var mType generic.MatchType
		switch matcher.Type {
		case metric.Equal:
			mType = generic.MatchType_EQUAL
		case metric.NotEqual:
			mType = generic.MatchType_NOT_EQUAL
		case metric.RegexMatch:
			mType = generic.MatchType_REGEX_MATCH
		case metric.RegexNoMatch:
			mType = generic.MatchType_REGEX_NO_MATCH
		default:
			panic("invalid matcher type")
		}
		req.Matchers = append(req.Matchers, &generic.LabelMatcher{
			Type:  &mType,
			Name:  proto.String(string(matcher.Name)),
			Value: proto.String(string(matcher.Value)),
		})
	}

	data, err := proto.Marshal(req)
	if err != nil {
		return nil, err
	}
	buf := bytes.NewBuffer(data)

	// TODO: This isn't actually the correct Content-type.
	httpReq, err := http.NewRequest("POST", fmt.Sprintf("http://%s/query", c.hostname), buf)
	if err != nil {
		return nil, err
	}
	httpReq.Header.Add(userIDHeaderName, userID)
	httpReq.Header.Set("Content-Type", string(expfmt.FmtProtoDelim))
	resp, err := c.client.Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("server returned HTTP status %s", resp.Status)
	}

	r := &generic.GenericReadResponse{}
	buf.Reset()
	_, err = buf.ReadFrom(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("unable to read response body: %s", err)
	}
	err = proto.Unmarshal(buf.Bytes(), r)
	if err != nil {
		return nil, fmt.Errorf("unable to unmarshal response body: %s", err)
	}

	m := make(model.Matrix, 0, len(r.Timeseries))
	for _, ts := range r.Timeseries {
		var ss model.SampleStream
		ss.Metric = model.Metric{}
		if ts.Name != nil {
			ss.Metric[model.MetricNameLabel] = model.LabelValue(ts.GetName())
		}
		for _, l := range ts.Labels {
			ss.Metric[model.LabelName(l.GetName())] = model.LabelValue(l.GetValue())
		}

		ss.Values = make([]model.SamplePair, 0, len(ts.Samples))
		for _, s := range ts.Samples {
			ss.Values = append(ss.Values, model.SamplePair{
				Value:     model.SampleValue(s.GetValue()),
				Timestamp: model.Time(s.GetTimestampMs()),
			})
		}
		m = append(m, &ss)
	}

	return m, nil
}
<|endoftext|>


<commit_before>/*
Elastic Guardian is a tiny reverse proxy that can offer authentication (using HTTP Basic Auth)
as well as authorization.

While it was originally meant as a thin layer between Elasticsearch (which has no builtin
authentication/authorization) and the World, there is nothing specific to Elasticsearch (other
than a few defaults which can be changed via command line flags).

The generic use case for Elastic Guardian is to restrict access to a HTTP API with HTTP
Basic Auth and authorization rules.

It currently offers:
	authentication (using HTTP Basic Auth);
	authorization (based on the {user, HTTP verb, HTTP path}).

It currently supports loading the authentication and authorization data from two different backends:
	inline variables (see settings.go) or
	external files (filenames passed via commandline flags)

Whether the external files are used or not can be controled (at compile time) via AllowAuthFromFiles
constant. See that constant definition for further details.

Please see authentication and authorization packages for further details.

Commandline help can be accessed with:
	elastic_guardian -h

That will also display the default values for all flags. Log output will go to console (stdout)
by default.
*/
package main

import (
	"flag"
	"fmt"
	aa "github.com/alexaandru/elastic_guardian/authentication"
	az "github.com/alexaandru/elastic_guardian/authorization"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
)

// AllowAuthFromFiles controls whether the files specified via command lien flags for
// authentication and authorization will actually be used. Can be used to lock down
// access to only the credentials stored at compile time (effectively disallow overriding
// them at runtime). May come in handy in some scenarios.
const AllowAuthFromFiles = true

// handlerWrapper captures the signature of a http.Handler wrapper function.
type handlerWrapper func(http.Handler) http.Handler

// BackendURL points to the target of the reverse proxy.
var BackendURL string

// FrontendURL points to the URL the proxy will accept incoming requests on.
var FrontendURL string

// Realm holds the Basic Auth realm.
var Realm string

// LogPath holds the path to the logfile.
var LogPath string

// CredentialsPath holds the path to the credentials file.
var CredentialsPath string

// AuthorizationsPath holds the path to the authorizations file.
var AuthorizationsPath string

func initReverseProxy(uri *url.URL, handlers ...handlerWrapper) (rp http.Handler) {
	rp = httputil.NewSingleHostReverseProxy(uri)
	for _, handler := range handlers {
		rp = handler(rp)
	}

	return
}

func wrapAuthentication(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		status, user := aa.BasicAuthPassed(r.Header.Get("Authorization"))
		if status == aa.Passed {
			r.Header.Set("X-Authenticated-User", user)
			h.ServeHTTP(w, r)
		} else if status == aa.NotAttempted {
			go logPrint(r, "401 Unauthorized")
			w.Header().Set("WWW-Authenticate", "Basic realm=\""+Realm+"\"")
			http.Error(w, "401 Unauthorized", http.StatusUnauthorized)
		} else {
			go logPrint(r, "403 Forbidden (authentication)")
			http.Error(w, "403 Forbidden (authentication)", http.StatusForbidden)
		}
	})
}

func wrapAuthorization(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if az.AuthorizationPassed(r.Header.Get("X-Authenticated-User"), r.Method, r.URL.Path) {
			go logPrint(r, "202 Accepted")
			h.ServeHTTP(w, r)
		} else {
			go logPrint(r, "403 Forbidden (authorization)")
			http.Error(w, "403 Forbidden (authorization)", http.StatusForbidden)
		}
	})
}

func processCmdLineFlags() {
	flag.StringVar(&BackendURL, "backend", "http://localhost:9200", "Backend URL (where to proxy requests to)")
	flag.StringVar(&FrontendURL, "frontend", ":9600", "Frontend URL (where to expose the proxied backend)")
	flag.StringVar(&Realm, "realm", "Elasticsearch", "HTTP Basic Auth realm")
	flag.StringVar(&LogPath, "logpath", "", "Path to the logfile (if not set, will dump to stdout)")
	flag.StringVar(&CredentialsPath, "cpath", "", "Path to the credentials file")
	flag.StringVar(&AuthorizationsPath, "apath", "", "Path to the authorizations file")
	flag.Parse()
}

func redirectLogsToFile(path string) (f *os.File, err error) {
	if path == "" {
		return
	}

	f, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0660)
	if err != nil {
		return
	}

	log.SetOutput(f)

	return
}

func logPrint(r *http.Request, msg string) {
	tokens := strings.Split(r.RemoteAddr, ":")
	log.Println(fmt.Sprintf("%s \"%s %s %s\" %s", tokens[0], r.Method, r.URL.Path, r.Proto, msg))
}

func main() {
	processCmdLineFlags()

	if !AllowAuthFromFiles || CredentialsPath == "" {
		aa.LoadCredentials(inlineCredentials)
	} else if err := aa.LoadCredentials(CredentialsPath); err != nil {
		log.Fatal("Cannot open the credentials file:", err)
	}

	if !AllowAuthFromFiles || AuthorizationsPath == "" {
		az.LoadAuthorizations(inlineAuthorizations)
	} else if err := az.LoadAuthorizations(AuthorizationsPath); err != nil {
		log.Fatal("Cannot open the authorizations file:", err)
	}

	if f, err := redirectLogsToFile(LogPath); err != nil {
		log.Fatalf("Error opening logfile: %v", err)
	} else if f != nil {
		defer f.Close()
	}

	uri, err := url.Parse(BackendURL)
	if err != nil {
		log.Fatal(err)
	}

	reverseProxy := initReverseProxy(uri, wrapAuthorization, wrapAuthentication)
	http.Handle("/", reverseProxy)
	if err = http.ListenAndServe(FrontendURL, nil); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Enabled use of multiple CPUs<commit_after>/*
Elastic Guardian is a tiny reverse proxy that can offer authentication (using HTTP Basic Auth)
as well as authorization.

While it was originally meant as a thin layer between Elasticsearch (which has no builtin
authentication/authorization) and the World, there is nothing specific to Elasticsearch (other
than a few defaults which can be changed via command line flags).

The generic use case for Elastic Guardian is to restrict access to a HTTP API with HTTP
Basic Auth and authorization rules.

It currently offers:
	authentication (using HTTP Basic Auth);
	authorization (based on the {user, HTTP verb, HTTP path}).

It currently supports loading the authentication and authorization data from two different backends:
	inline variables (see settings.go) or
	external files (filenames passed via commandline flags)

Whether the external files are used or not can be controled (at compile time) via AllowAuthFromFiles
constant. See that constant definition for further details.

Please see authentication and authorization packages for further details.

Commandline help can be accessed with:
	elastic_guardian -h

That will also display the default values for all flags. Log output will go to console (stdout)
by default.
*/
package main

import (
	"flag"
	"fmt"
	aa "github.com/alexaandru/elastic_guardian/authentication"
	az "github.com/alexaandru/elastic_guardian/authorization"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"runtime"
	"strings"
)

// AllowAuthFromFiles controls whether the files specified via command lien flags for
// authentication and authorization will actually be used. Can be used to lock down
// access to only the credentials stored at compile time (effectively disallow overriding
// them at runtime). May come in handy in some scenarios.
const AllowAuthFromFiles = true

// handlerWrapper captures the signature of a http.Handler wrapper function.
type handlerWrapper func(http.Handler) http.Handler

// BackendURL points to the target of the reverse proxy.
var BackendURL string

// FrontendURL points to the URL the proxy will accept incoming requests on.
var FrontendURL string

// Realm holds the Basic Auth realm.
var Realm string

// LogPath holds the path to the logfile.
var LogPath string

// CredentialsPath holds the path to the credentials file.
var CredentialsPath string

// AuthorizationsPath holds the path to the authorizations file.
var AuthorizationsPath string

func initReverseProxy(uri *url.URL, handlers ...handlerWrapper) (rp http.Handler) {
	rp = httputil.NewSingleHostReverseProxy(uri)
	for _, handler := range handlers {
		rp = handler(rp)
	}

	return
}

func wrapAuthentication(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		status, user := aa.BasicAuthPassed(r.Header.Get("Authorization"))
		if status == aa.Passed {
			r.Header.Set("X-Authenticated-User", user)
			h.ServeHTTP(w, r)
		} else if status == aa.NotAttempted {
			go logPrint(r, "401 Unauthorized")
			w.Header().Set("WWW-Authenticate", "Basic realm=\""+Realm+"\"")
			http.Error(w, "401 Unauthorized", http.StatusUnauthorized)
		} else {
			go logPrint(r, "403 Forbidden (authentication)")
			http.Error(w, "403 Forbidden (authentication)", http.StatusForbidden)
		}
	})
}

func wrapAuthorization(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if az.AuthorizationPassed(r.Header.Get("X-Authenticated-User"), r.Method, r.URL.Path) {
			go logPrint(r, "202 Accepted")
			h.ServeHTTP(w, r)
		} else {
			go logPrint(r, "403 Forbidden (authorization)")
			http.Error(w, "403 Forbidden (authorization)", http.StatusForbidden)
		}
	})
}

func processCmdLineFlags() {
	flag.StringVar(&BackendURL, "backend", "http://localhost:9200", "Backend URL (where to proxy requests to)")
	flag.StringVar(&FrontendURL, "frontend", ":9600", "Frontend URL (where to expose the proxied backend)")
	flag.StringVar(&Realm, "realm", "Elasticsearch", "HTTP Basic Auth realm")
	flag.StringVar(&LogPath, "logpath", "", "Path to the logfile (if not set, will dump to stdout)")
	flag.StringVar(&CredentialsPath, "cpath", "", "Path to the credentials file")
	flag.StringVar(&AuthorizationsPath, "apath", "", "Path to the authorizations file")
	flag.Parse()
}

func redirectLogsToFile(path string) (f *os.File, err error) {
	if path == "" {
		return
	}

	f, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0660)
	if err != nil {
		return
	}

	log.SetOutput(f)

	return
}

func logPrint(r *http.Request, msg string) {
	tokens := strings.Split(r.RemoteAddr, ":")
	log.Println(fmt.Sprintf("%s \"%s %s %s\" %s", tokens[0], r.Method, r.URL.Path, r.Proto, msg))
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	processCmdLineFlags()

	if !AllowAuthFromFiles || CredentialsPath == "" {
		aa.LoadCredentials(inlineCredentials)
	} else if err := aa.LoadCredentials(CredentialsPath); err != nil {
		log.Fatal("Cannot open the credentials file:", err)
	}

	if !AllowAuthFromFiles || AuthorizationsPath == "" {
		az.LoadAuthorizations(inlineAuthorizations)
	} else if err := az.LoadAuthorizations(AuthorizationsPath); err != nil {
		log.Fatal("Cannot open the authorizations file:", err)
	}

	if f, err := redirectLogsToFile(LogPath); err != nil {
		log.Fatalf("Error opening logfile: %v", err)
	} else if f != nil {
		defer f.Close()
	}

	uri, err := url.Parse(BackendURL)
	if err != nil {
		log.Fatal(err)
	}

	reverseProxy := initReverseProxy(uri, wrapAuthorization, wrapAuthentication)
	http.Handle("/", reverseProxy)
	if err = http.ListenAndServe(FrontendURL, nil); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>// possum & gorbac example
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"time"

	"github.com/mikespook/gorbac"
	"github.com/mikespook/possum"
	"github.com/mikespook/possum/router"
	"github.com/mikespook/possum/view"
)

const addr = "127.0.0.1:12345"

var rbac = gorbac.New()

func postHandler(ctx *possum.Context) error {
	body, err := ioutil.ReadAll(ctx.Request.Body)
	if err != nil {
		return err
	}
	defer ctx.Request.Body.Close()
	var m gorbac.Map
	if err := json.Unmarshal(body, &m); err != nil {
		return err
	}
	rbac = gorbac.Restore(m)
	ctx.Response.Data = rbac.Dump()
	return nil
}

func getHandler(ctx *possum.Context) error {
	name := ctx.Request.Form.Get("name")
	if name == "" {
		ctx.Response.Data = rbac.Dump()
	} else {
		ctx.Response.Data = gorbac.RoleToMap(rbac.Get(name))
	}
	return nil
}

func putHandler(ctx *possum.Context) error {
	name := ctx.Request.Form.Get("name")
	permissions := ctx.Request.Form["permissions"]
	parents := ctx.Request.Form["parents"]
	rbac.Set(name, permissions, parents)
	ctx.Response.Data = gorbac.RoleToMap(rbac.Get(name))
	return nil
}

func deleteHandler(ctx *possum.Context) error {
	name := ctx.Request.Form.Get("name")
	role := rbac.Get(name)
	rbac.Remove(name)
	ctx.Response.Data = gorbac.RoleToMap(role)
	return nil
}

func patchHandler(ctx *possum.Context) error {
	name := ctx.Request.Form.Get("name")
	permissions := ctx.Request.Form["permissions"]
	parents := ctx.Request.Form["parents"]
	rbac.Add(name, permissions, parents)
	ctx.Response.Data = gorbac.RoleToMap(rbac.Get(name))
	return nil
}

func rbacHandler(ctx *possum.Context) error {
	switch ctx.Request.Method {
	case "PATCH":
		return patchHandler(ctx)
	case "GET":
		return getHandler(ctx)
	case "POST":
		return postHandler(ctx)
	case "DELETE":
		return deleteHandler(ctx)
	case "PUT":
		return putHandler(ctx)
	}
	return nil
}

func isGrantedHandler(ctx *possum.Context) error {
	ctx.Response.Header().Set("Test", "Hello world")
	return nil
	name := ctx.Request.Form.Get("name")
	permission := ctx.Request.Form.Get("permission")
	if rbac.IsGranted(name, permission, nil) {
		ctx.Response.Status = http.StatusOK
		ctx.Response.Data = true
		return nil
	}
	ctx.Response.Status = http.StatusForbidden
	ctx.Response.Data = false
	return nil
}

func main() {
	mux := possum.NewServerMux()

	mux.PreRequest = func(ctx *possum.Context) error {
		host, _, err := net.SplitHostPort(ctx.Request.RemoteAddr)
		if err != nil {
			return err
		}
		if host != "127.0.0.1" {
			return possum.NewError(http.StatusForbidden, "Localhost only")
		}
		return nil
	}

	mux.PostResponse = func(ctx *possum.Context) error {
		fmt.Printf("[%d] %s:%s \"%s\"", ctx.Response.Status,
			ctx.Request.RemoteAddr, ctx.Request.Method,
			ctx.Request.URL.String())
		return nil
	}
	mux.HandleFunc(router.Simple("/rbac"), rbacHandler, view.Json())
	mux.HandleFunc(router.Simple("/isgranted"), isGrantedHandler, view.Json())
	fmt.Printf("[%s] %s\n", time.Now(), addr)
	if err := http.ListenAndServe(addr, mux); err != nil {
		fmt.Println(err)
		return
	}
}
<commit_msg>upgrading the interface of possum<commit_after>// possum & gorbac example
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"time"

	"github.com/mikespook/gorbac"
	"github.com/mikespook/possum"
	"github.com/mikespook/possum/router"
	"github.com/mikespook/possum/view"
)

const addr = "127.0.0.1:12345"

var rbac = gorbac.New()

func postHandler(ctx *possum.Context) error {
	body, err := ioutil.ReadAll(ctx.Request.Body)
	if err != nil {
		return err
	}
	defer ctx.Request.Body.Close()
	var m gorbac.Map
	if err := json.Unmarshal(body, &m); err != nil {
		return err
	}
	rbac = gorbac.Restore(m)
	ctx.Response.Data = rbac.Dump()
	return nil
}

func getHandler(ctx *possum.Context) error {
	name := ctx.Request.Form.Get("name")
	if name == "" {
		ctx.Response.Data = rbac.Dump()
	} else {
		ctx.Response.Data = gorbac.RoleToMap(rbac.Get(name))
	}
	return nil
}

func putHandler(ctx *possum.Context) error {
	name := ctx.Request.Form.Get("name")
	permissions := ctx.Request.Form["permissions"]
	parents := ctx.Request.Form["parents"]
	rbac.Set(name, permissions, parents)
	ctx.Response.Data = gorbac.RoleToMap(rbac.Get(name))
	return nil
}

func deleteHandler(ctx *possum.Context) error {
	name := ctx.Request.Form.Get("name")
	role := rbac.Get(name)
	rbac.Remove(name)
	ctx.Response.Data = gorbac.RoleToMap(role)
	return nil
}

func patchHandler(ctx *possum.Context) error {
	name := ctx.Request.Form.Get("name")
	permissions := ctx.Request.Form["permissions"]
	parents := ctx.Request.Form["parents"]
	rbac.Add(name, permissions, parents)
	ctx.Response.Data = gorbac.RoleToMap(rbac.Get(name))
	return nil
}

func rbacHandler(ctx *possum.Context) error {
	switch ctx.Request.Method {
	case "PATCH":
		return patchHandler(ctx)
	case "GET":
		return getHandler(ctx)
	case "POST":
		return postHandler(ctx)
	case "DELETE":
		return deleteHandler(ctx)
	case "PUT":
		return putHandler(ctx)
	}
	return nil
}

func isGrantedHandler(ctx *possum.Context) error {
	ctx.Response.Header().Set("Test", "Hello world")
	return nil
	name := ctx.Request.Form.Get("name")
	permission := ctx.Request.Form.Get("permission")
	if rbac.IsGranted(name, permission, nil) {
		ctx.Response.Status = http.StatusOK
		ctx.Response.Data = true
		return nil
	}
	ctx.Response.Status = http.StatusForbidden
	ctx.Response.Data = false
	return nil
}

func main() {
	mux := possum.NewServerMux()

	mux.PreRequest = func(ctx *possum.Context) error {
		host, _, err := net.SplitHostPort(ctx.Request.RemoteAddr)
		if err != nil {
			return err
		}
		if host != "127.0.0.1" {
			return possum.NewError(http.StatusForbidden, "Localhost only")
		}
		return nil
	}

	mux.PostResponse = func(ctx *possum.Context) error {
		fmt.Printf("[%d] %s:%s \"%s\"", ctx.Response.Status,
			ctx.Request.RemoteAddr, ctx.Request.Method,
			ctx.Request.URL.String())
		return nil
	}
	mux.HandleFunc(router.Simple("/rbac"), rbacHandler, view.Json(view.CharSetUTF8))
	mux.HandleFunc(router.Simple("/isgranted"), isGrantedHandler, view.Json(view.CharSetUTF8))
	fmt.Printf("[%s] %s\n", time.Now(), addr)
	if err := http.ListenAndServe(addr, mux); err != nil {
		fmt.Println(err)
		return
	}
}
<|endoftext|>