<commit_before>package pluginproxy

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/grafana/grafana/pkg/api/datasource"
	glog "github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/services/oauthtoken"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
	"github.com/grafana/grafana/pkg/util/proxyutil"
	"github.com/opentracing/opentracing-go"
)

var (
	logger = glog.New("data-proxy-log")
	client = newHTTPClient()
)

type DataSourceProxy struct {
	ds        *models.DataSource
	ctx       *models.ReqContext
	targetUrl *url.URL
	proxyPath string
	route     *plugins.AppPluginRoute
	plugin    *plugins.DataSourcePlugin
	cfg       *setting.Cfg
}

type handleResponseTransport struct {
	transport http.RoundTripper
}

func (t *handleResponseTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	res, err := t.transport.RoundTrip(req)
	if err != nil {
		return nil, err
	}
	res.Header.Del("Set-Cookie")
	return res, nil
}

type httpClient interface {
	Do(req *http.Request) (*http.Response, error)
}

type logWrapper struct {
	logger glog.Logger
}

// Write writes log messages as bytes from proxy
func (lw *logWrapper) Write(p []byte) (n int, err error) {
	withoutNewline := strings.TrimSuffix(string(p), "\n")
	lw.logger.Error("Data proxy error", "error", withoutNewline)
	return len(p), nil
}

// NewDataSourceProxy creates a new Datasource proxy
func NewDataSourceProxy(ds *models.DataSource, plugin *plugins.DataSourcePlugin, ctx *models.ReqContext,
	proxyPath string, cfg *setting.Cfg) (*DataSourceProxy, error) {
	targetURL, err := datasource.ValidateURL(ds.Type, ds.Url)
	if err != nil {
		return nil, err
	}

	return &DataSourceProxy{
		ds:        ds,
		plugin:    plugin,
		ctx:       ctx,
		proxyPath: proxyPath,
		targetUrl: targetURL,
		cfg:       cfg,
	}, nil
}

func newHTTPClient() httpClient {
	return &http.Client{
		Timeout:   30 * time.Second,
		Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
	}
}

func (proxy *DataSourceProxy) HandleRequest() {
	if err := proxy.validateRequest(); err != nil {
		proxy.ctx.JsonApiErr(403, err.Error(), nil)
		return
	}

	proxyErrorLogger := logger.New("userId", proxy.ctx.UserId, "orgId", proxy.ctx.OrgId, "uname", proxy.ctx.Login,
		"path", proxy.ctx.Req.URL.Path, "remote_addr", proxy.ctx.RemoteAddr(), "referer", proxy.ctx.Req.Referer())

	transport, err := proxy.ds.GetHttpTransport()
	if err != nil {
		proxy.ctx.JsonApiErr(400, "Unable to load TLS certificate", err)
		return
	}

	reverseProxy := &httputil.ReverseProxy{
		Director:      proxy.director,
		FlushInterval: time.Millisecond * 200,
		ErrorLog:      log.New(&logWrapper{logger: proxyErrorLogger}, "", 0),
		Transport: &handleResponseTransport{
			transport: transport,
		},
		ModifyResponse: func(resp *http.Response) error {
			if resp.StatusCode == 401 {
				// The data source rejected the request as unauthorized, convert to 400 (bad request)
				body, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					return fmt.Errorf("failed to read data source response body: %w", err)
				}
				_ = resp.Body.Close()

				proxyErrorLogger.Info("Authentication to data source failed", "body", string(body), "statusCode",
					resp.StatusCode)
				msg := "Authentication to data source failed"
				*resp = http.Response{
					StatusCode:    400,
					Status:        "Bad Request",
					Body:          ioutil.NopCloser(strings.NewReader(msg)),
					ContentLength: int64(len(msg)),
				}
			}
			return nil
		},
	}

	proxy.logRequest()

	span, ctx := opentracing.StartSpanFromContext(proxy.ctx.Req.Context(), "datasource reverse proxy")
	defer span.Finish()

	proxy.ctx.Req.Request = proxy.ctx.Req.WithContext(ctx)

	span.SetTag("datasource_id", proxy.ds.Id)
	span.SetTag("datasource_type", proxy.ds.Type)
	span.SetTag("user_id", proxy.ctx.SignedInUser.UserId)
	span.SetTag("org_id", proxy.ctx.SignedInUser.OrgId)

	proxy.addTraceFromHeaderValue(span, "X-Panel-Id", "panel_id")
	proxy.addTraceFromHeaderValue(span, "X-Dashboard-Id", "dashboard_id")

	if err := opentracing.GlobalTracer().Inject(
		span.Context(),
		opentracing.HTTPHeaders,
		opentracing.HTTPHeadersCarrier(proxy.ctx.Req.Request.Header)); err != nil {
		logger.Error("Failed to inject span context instance", "err", err)
	}

	reverseProxy.ServeHTTP(proxy.ctx.Resp, proxy.ctx.Req.Request)
}

func (proxy *DataSourceProxy) addTraceFromHeaderValue(span opentracing.Span, headerName string, tagName string) {
	panelId := proxy.ctx.Req.Header.Get(headerName)
	dashId, err := strconv.Atoi(panelId)
	if err == nil {
		span.SetTag(tagName, dashId)
	}
}

func (proxy *DataSourceProxy) director(req *http.Request) {
	req.URL.Scheme = proxy.targetUrl.Scheme
	req.URL.Host = proxy.targetUrl.Host
	req.Host = proxy.targetUrl.Host

	reqQueryVals := req.URL.Query()

	switch proxy.ds.Type {
	case models.DS_INFLUXDB_08:
		req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath)
		reqQueryVals.Add("u", proxy.ds.User)
		reqQueryVals.Add("p", proxy.ds.DecryptedPassword())
		req.URL.RawQuery = reqQueryVals.Encode()
	case models.DS_INFLUXDB:
		req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
		req.URL.RawQuery = reqQueryVals.Encode()
		if !proxy.ds.BasicAuth {
			req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.DecryptedPassword()))
		}
	default:
		req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
	}

	if proxy.ds.BasicAuth {
		req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser,
			proxy.ds.DecryptedBasicAuthPassword()))
	}

	dsAuth := req.Header.Get("X-DS-Authorization")
	if len(dsAuth) > 0 {
		req.Header.Del("X-DS-Authorization")
		req.Header.Set("Authorization", dsAuth)
	}

	applyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser)

	keepCookieNames := []string{}
	if proxy.ds.JsonData != nil {
		if keepCookies := proxy.ds.JsonData.Get("keepCookies"); keepCookies != nil {
			keepCookieNames = keepCookies.MustStringArray()
		}
	}

	proxyutil.ClearCookieHeader(req, keepCookieNames)
	proxyutil.PrepareProxyRequest(req)

	req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))

	// Clear Origin and Referer to avoir CORS issues
	req.Header.Del("Origin")
	req.Header.Del("Referer")

	if proxy.route != nil {
		ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
	}

	if oauthtoken.IsOAuthPassThruEnabled(proxy.ds) {
		if token := oauthtoken.GetCurrentOAuthToken(proxy.ctx.Req.Context(), proxy.ctx.SignedInUser); token != nil {
			req.Header.Set("Authorization", fmt.Sprintf("%s %s", token.Type(), token.AccessToken))
		}
	}
}

func (proxy *DataSourceProxy) validateRequest() error {
	if !checkWhiteList(proxy.ctx, proxy.targetUrl.Host) {
		return errors.New("target URL is not a valid target")
	}

	if proxy.ds.Type == models.DS_PROMETHEUS {
		if proxy.ctx.Req.Request.Method == "DELETE" {
			return errors.New("deletes not allowed on proxied Prometheus datasource")
		}
		if proxy.ctx.Req.Request.Method == "PUT" {
			return errors.New("puts not allowed on proxied Prometheus datasource")
		}
		if proxy.ctx.Req.Request.Method == "POST" && !(proxy.proxyPath == "api/v1/query" || proxy.proxyPath == "api/v1/query_range") {
			return errors.New("posts not allowed on proxied Prometheus datasource except on /query and /query_range")
		}
	}

	if proxy.ds.Type == models.DS_ES {
		if proxy.ctx.Req.Request.Method == "DELETE" {
			return errors.New("deletes not allowed on proxied Elasticsearch datasource")
		}
		if proxy.ctx.Req.Request.Method == "PUT" {
			return errors.New("puts not allowed on proxied Elasticsearch datasource")
		}
		if proxy.ctx.Req.Request.Method == "POST" && proxy.proxyPath != "_msearch" {
			return errors.New("posts not allowed on proxied Elasticsearch datasource except on /_msearch")
		}
	}

	// found route if there are any
	if len(proxy.plugin.Routes) > 0 {
		for _, route := range proxy.plugin.Routes {
			// method match
			if route.Method != "" && route.Method != "*" && route.Method != proxy.ctx.Req.Method {
				continue
			}

			if route.ReqRole.IsValid() {
				if !proxy.ctx.HasUserRole(route.ReqRole) {
					return errors.New("plugin proxy route access denied")
				}
			}

			if strings.HasPrefix(proxy.proxyPath, route.Path) {
				proxy.route = route
				break
			}
		}
	}

	return nil
}

func (proxy *DataSourceProxy) logRequest() {
	if !setting.DataProxyLogging {
		return
	}

	var body string
	if proxy.ctx.Req.Request.Body != nil {
		buffer, err := ioutil.ReadAll(proxy.ctx.Req.Request.Body)
		if err == nil {
			proxy.ctx.Req.Request.Body = ioutil.NopCloser(bytes.NewBuffer(buffer))
			body = string(buffer)
		}
	}

	logger.Info("Proxying incoming request",
		"userid", proxy.ctx.UserId,
		"orgid", proxy.ctx.OrgId,
		"username", proxy.ctx.Login,
		"datasource", proxy.ds.Type,
		"uri", proxy.ctx.Req.RequestURI,
		"method", proxy.ctx.Req.Request.Method,
		"body", body)
}

func checkWhiteList(c *models.ReqContext, host string) bool {
	if host != "" && len(setting.DataProxyWhiteList) > 0 {
		if _, exists := setting.DataProxyWhiteList[host]; !exists {
			c.JsonApiErr(403, "Data proxy hostname and ip are not included in whitelist", nil)
			return false
		}
	}

	return true
}
<commit_msg>trace user login and datasource name instead of id (#29183)<commit_after>package pluginproxy

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/grafana/grafana/pkg/api/datasource"
	glog "github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/services/oauthtoken"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
	"github.com/grafana/grafana/pkg/util/proxyutil"
	"github.com/opentracing/opentracing-go"
)

var (
	logger = glog.New("data-proxy-log")
	client = newHTTPClient()
)

type DataSourceProxy struct {
	ds        *models.DataSource
	ctx       *models.ReqContext
	targetUrl *url.URL
	proxyPath string
	route     *plugins.AppPluginRoute
	plugin    *plugins.DataSourcePlugin
	cfg       *setting.Cfg
}

type handleResponseTransport struct {
	transport http.RoundTripper
}

func (t *handleResponseTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	res, err := t.transport.RoundTrip(req)
	if err != nil {
		return nil, err
	}
	res.Header.Del("Set-Cookie")
	return res, nil
}

type httpClient interface {
	Do(req *http.Request) (*http.Response, error)
}

type logWrapper struct {
	logger glog.Logger
}

// Write writes log messages as bytes from proxy
func (lw *logWrapper) Write(p []byte) (n int, err error) {
	withoutNewline := strings.TrimSuffix(string(p), "\n")
	lw.logger.Error("Data proxy error", "error", withoutNewline)
	return len(p), nil
}

// NewDataSourceProxy creates a new Datasource proxy
func NewDataSourceProxy(ds *models.DataSource, plugin *plugins.DataSourcePlugin, ctx *models.ReqContext,
	proxyPath string, cfg *setting.Cfg) (*DataSourceProxy, error) {
	targetURL, err := datasource.ValidateURL(ds.Type, ds.Url)
	if err != nil {
		return nil, err
	}

	return &DataSourceProxy{
		ds:        ds,
		plugin:    plugin,
		ctx:       ctx,
		proxyPath: proxyPath,
		targetUrl: targetURL,
		cfg:       cfg,
	}, nil
}

func newHTTPClient() httpClient {
	return &http.Client{
		Timeout:   30 * time.Second,
		Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
	}
}

func (proxy *DataSourceProxy) HandleRequest() {
	if err := proxy.validateRequest(); err != nil {
		proxy.ctx.JsonApiErr(403, err.Error(), nil)
		return
	}

	proxyErrorLogger := logger.New("userId", proxy.ctx.UserId, "orgId", proxy.ctx.OrgId, "uname", proxy.ctx.Login,
		"path", proxy.ctx.Req.URL.Path, "remote_addr", proxy.ctx.RemoteAddr(), "referer", proxy.ctx.Req.Referer())

	transport, err := proxy.ds.GetHttpTransport()
	if err != nil {
		proxy.ctx.JsonApiErr(400, "Unable to load TLS certificate", err)
		return
	}

	reverseProxy := &httputil.ReverseProxy{
		Director:      proxy.director,
		FlushInterval: time.Millisecond * 200,
		ErrorLog:      log.New(&logWrapper{logger: proxyErrorLogger}, "", 0),
		Transport: &handleResponseTransport{
			transport: transport,
		},
		ModifyResponse: func(resp *http.Response) error {
			if resp.StatusCode == 401 {
				// The data source rejected the request as unauthorized, convert to 400 (bad request)
				body, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					return fmt.Errorf("failed to read data source response body: %w", err)
				}
				_ = resp.Body.Close()

				proxyErrorLogger.Info("Authentication to data source failed", "body", string(body), "statusCode",
					resp.StatusCode)
				msg := "Authentication to data source failed"
				*resp = http.Response{
					StatusCode:    400,
					Status:        "Bad Request",
					Body:          ioutil.NopCloser(strings.NewReader(msg)),
					ContentLength: int64(len(msg)),
				}
			}
			return nil
		},
	}

	proxy.logRequest()

	span, ctx := opentracing.StartSpanFromContext(proxy.ctx.Req.Context(), "datasource reverse proxy")
	defer span.Finish()

	proxy.ctx.Req.Request = proxy.ctx.Req.WithContext(ctx)

	span.SetTag("datasource_name", proxy.ds.Name)
	span.SetTag("datasource_type", proxy.ds.Type)
	span.SetTag("user", proxy.ctx.SignedInUser.Login)
	span.SetTag("org_id", proxy.ctx.SignedInUser.OrgId)

	proxy.addTraceFromHeaderValue(span, "X-Panel-Id", "panel_id")
	proxy.addTraceFromHeaderValue(span, "X-Dashboard-Id", "dashboard_id")

	if err := opentracing.GlobalTracer().Inject(
		span.Context(),
		opentracing.HTTPHeaders,
		opentracing.HTTPHeadersCarrier(proxy.ctx.Req.Request.Header)); err != nil {
		logger.Error("Failed to inject span context instance", "err", err)
	}

	reverseProxy.ServeHTTP(proxy.ctx.Resp, proxy.ctx.Req.Request)
}

func (proxy *DataSourceProxy) addTraceFromHeaderValue(span opentracing.Span, headerName string, tagName string) {
	panelId := proxy.ctx.Req.Header.Get(headerName)
	dashId, err := strconv.Atoi(panelId)
	if err == nil {
		span.SetTag(tagName, dashId)
	}
}

func (proxy *DataSourceProxy) director(req *http.Request) {
	req.URL.Scheme = proxy.targetUrl.Scheme
	req.URL.Host = proxy.targetUrl.Host
	req.Host = proxy.targetUrl.Host

	reqQueryVals := req.URL.Query()

	switch proxy.ds.Type {
	case models.DS_INFLUXDB_08:
		req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath)
		reqQueryVals.Add("u", proxy.ds.User)
		reqQueryVals.Add("p", proxy.ds.DecryptedPassword())
		req.URL.RawQuery = reqQueryVals.Encode()
	case models.DS_INFLUXDB:
		req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
		req.URL.RawQuery = reqQueryVals.Encode()
		if !proxy.ds.BasicAuth {
			req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.DecryptedPassword()))
		}
	default:
		req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
	}

	if proxy.ds.BasicAuth {
		req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser,
			proxy.ds.DecryptedBasicAuthPassword()))
	}

	dsAuth := req.Header.Get("X-DS-Authorization")
	if len(dsAuth) > 0 {
		req.Header.Del("X-DS-Authorization")
		req.Header.Set("Authorization", dsAuth)
	}

	applyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser)

	keepCookieNames := []string{}
	if proxy.ds.JsonData != nil {
		if keepCookies := proxy.ds.JsonData.Get("keepCookies"); keepCookies != nil {
			keepCookieNames = keepCookies.MustStringArray()
		}
	}

	proxyutil.ClearCookieHeader(req, keepCookieNames)
	proxyutil.PrepareProxyRequest(req)

	req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))

	// Clear Origin and Referer to avoir CORS issues
	req.Header.Del("Origin")
	req.Header.Del("Referer")

	if proxy.route != nil {
		ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
	}

	if oauthtoken.IsOAuthPassThruEnabled(proxy.ds) {
		if token := oauthtoken.GetCurrentOAuthToken(proxy.ctx.Req.Context(), proxy.ctx.SignedInUser); token != nil {
			req.Header.Set("Authorization", fmt.Sprintf("%s %s", token.Type(), token.AccessToken))
		}
	}
}

func (proxy *DataSourceProxy) validateRequest() error {
	if !checkWhiteList(proxy.ctx, proxy.targetUrl.Host) {
		return errors.New("target URL is not a valid target")
	}

	if proxy.ds.Type == models.DS_PROMETHEUS {
		if proxy.ctx.Req.Request.Method == "DELETE" {
			return errors.New("deletes not allowed on proxied Prometheus datasource")
		}
		if proxy.ctx.Req.Request.Method == "PUT" {
			return errors.New("puts not allowed on proxied Prometheus datasource")
		}
		if proxy.ctx.Req.Request.Method == "POST" && !(proxy.proxyPath == "api/v1/query" || proxy.proxyPath == "api/v1/query_range") {
			return errors.New("posts not allowed on proxied Prometheus datasource except on /query and /query_range")
		}
	}

	if proxy.ds.Type == models.DS_ES {
		if proxy.ctx.Req.Request.Method == "DELETE" {
			return errors.New("deletes not allowed on proxied Elasticsearch datasource")
		}
		if proxy.ctx.Req.Request.Method == "PUT" {
			return errors.New("puts not allowed on proxied Elasticsearch datasource")
		}
		if proxy.ctx.Req.Request.Method == "POST" && proxy.proxyPath != "_msearch" {
			return errors.New("posts not allowed on proxied Elasticsearch datasource except on /_msearch")
		}
	}

	// found route if there are any
	if len(proxy.plugin.Routes) > 0 {
		for _, route := range proxy.plugin.Routes {
			// method match
			if route.Method != "" && route.Method != "*" && route.Method != proxy.ctx.Req.Method {
				continue
			}

			if route.ReqRole.IsValid() {
				if !proxy.ctx.HasUserRole(route.ReqRole) {
					return errors.New("plugin proxy route access denied")
				}
			}

			if strings.HasPrefix(proxy.proxyPath, route.Path) {
				proxy.route = route
				break
			}
		}
	}

	return nil
}

func (proxy *DataSourceProxy) logRequest() {
	if !setting.DataProxyLogging {
		return
	}

	var body string
	if proxy.ctx.Req.Request.Body != nil {
		buffer, err := ioutil.ReadAll(proxy.ctx.Req.Request.Body)
		if err == nil {
			proxy.ctx.Req.Request.Body = ioutil.NopCloser(bytes.NewBuffer(buffer))
			body = string(buffer)
		}
	}

	logger.Info("Proxying incoming request",
		"userid", proxy.ctx.UserId,
		"orgid", proxy.ctx.OrgId,
		"username", proxy.ctx.Login,
		"datasource", proxy.ds.Type,
		"uri", proxy.ctx.Req.RequestURI,
		"method", proxy.ctx.Req.Request.Method,
		"body", body)
}

func checkWhiteList(c *models.ReqContext, host string) bool {
	if host != "" && len(setting.DataProxyWhiteList) > 0 {
		if _, exists := setting.DataProxyWhiteList[host]; !exists {
			c.JsonApiErr(403, "Data proxy hostname and ip are not included in whitelist", nil)
			return false
		}
	}

	return true
}
<|endoftext|>


<commit_before>package v1

import (
    "github.com/emicklei/go-restful"
    "github.com/nebtex/menshend/pkg/config"
    mutils "github.com/nebtex/menshend/pkg/utils"
)

//FlashResource  this allow to store some error messages in the cookie
//only useful in the browser
type FlashResource struct {
    Flashes []string `json:"flashes"`
}

//Register ...
func (f *FlashResource) Register(container *restful.Container) {
    ws := new(restful.WebService).
        Consumes(restful.MIME_JSON).
        Produces(restful.MIME_JSON)
    ws.Path("/v1/flashes").
        Doc("get the flases, this only works on browsers")
    ws.Route(ws.GET("").To(f.get).
        Doc("list current flashes and delete them").
        Operation("getFlashes").
        Writes(FlashResource{}))
    container.Add(ws)
}

func (f *FlashResource) get(request *restful.Request, response *restful.Response) {
    session, err := config.FlashStore.Get(request.Request, "flashes")
    mutils.HttpCheckPanic(err, mutils.InternalError)
    flashes := []string{}
    for _, v := range session.Flashes() {
        flash := v.(string)
        flashes = append(flashes, flash)
    }
    fn := &FlashResource{}
    fn.Flashes = flashes
    mutils.HttpCheckPanic(response.WriteEntity(fn), mutils.InternalError)
}
<commit_msg>delete flashes when they are read<commit_after>package v1

import (
    "github.com/emicklei/go-restful"
    "github.com/nebtex/menshend/pkg/config"
    mutils "github.com/nebtex/menshend/pkg/utils"
)

//FlashResource  this allow to store some error messages in the cookie
//only useful in the browser
type FlashResource struct {
    Flashes []string `json:"flashes"`
}

//Register ...
func (f *FlashResource) Register(container *restful.Container) {
    ws := new(restful.WebService).
        Consumes(restful.MIME_JSON).
        Produces(restful.MIME_JSON)
    ws.Path("/v1/flashes").
        Doc("get the flases, this only works on browsers")
    ws.Route(ws.GET("").To(f.get).
        Doc("list current flashes and delete them").
        Operation("getFlashes").
        Writes(FlashResource{}))
    container.Add(ws)
}

func (f *FlashResource) get(request *restful.Request, response *restful.Response) {
    session, err := config.FlashStore.Get(request.Request, "flashes")
    mutils.HttpCheckPanic(err, mutils.InternalError)
    flashes := []string{}
    for _, v := range session.Flashes() {
        flash := v.(string)
        flashes = append(flashes, flash)
    }
    fn := &FlashResource{}
    fn.Flashes = flashes
    session.Save(request.Request, response.ResponseWriter)
    mutils.HttpCheckPanic(response.WriteEntity(fn), mutils.InternalError)
}
<|endoftext|>


<commit_before>package docstore

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/url"
	"os"
	"path/filepath"
	"time"
	// "reflect"
	"strconv"

	"github.com/tsileo/blobstash/pkg/client/clientutil"
)

var ErrIDNotFound = errors.New("ID doest not exist")

var defaultServerAddr = "http://localhost:8050"
var defaultUserAgent = "DocStore Go client v1"

type ID struct {
	data []byte
	hash string
}

// Hash returns the hash of the JSON blob
func (id *ID) Hash() string {
	return id.hash
}

// ETag returns the ETag of the current document for future conditional requests
func (id *ID) ETag() string {
	return id.hash
}

// String implements Stringer interface
func (id *ID) String() string {
	return hex.EncodeToString(id.data)
}

// Ts returns the timestamp component
func (id *ID) Time() time.Time {
	return time.Unix(int64(binary.BigEndian.Uint32(id.data[0:4])), 0)
}

// FromHex build an `ID` from an hex encoded string
func IDFromHex(data string) (*ID, error) {
	if len(data) != 24 {
		return nil, fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	b, err := hex.DecodeString(data)
	if err != nil {
		return nil, fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	return &ID{data: b}, err
}

// Q is a wrapper of `map[string]interface{} for syntactic sugar
type Q map[string]interface{}
type M map[string]interface{}

type DocStore struct {
	client *clientutil.Client
}

// Collection represents a collection of documents
type Collection struct {
	docstore *DocStore
	col      string
}

// InsertOpts defines the options for the `Insert` operation
type InsertOpts struct {
	Indexed bool
}

// DefaultInsertOpts initializes a new `InsertOpts` with sane default
func DefaultInsertOpts() *InsertOpts {
	return &InsertOpts{
		Indexed: false,
	}
}

func DefaultOpts() *clientutil.Opts {
	return &clientutil.Opts{
		SnappyCompression: true,
		Host:              defaultServerAddr,
		UserAgent:         defaultUserAgent,
		EnableHTTP2:       true,
	}
}

// serverAddr should't have a trailing space
func New(opts *clientutil.Opts) *DocStore {
	if opts == nil {
		opts = DefaultOpts()
	}
	return &DocStore{
		client: clientutil.New(opts),
	}
}

func (docstore *DocStore) Col(collection string) *Collection {
	return &Collection{
		docstore: docstore,
		col:      collection,
	}
}

func (col *Collection) Insert(idoc interface{}, opts *InsertOpts) (*ID, error) {
	if opts == nil {
		opts = DefaultInsertOpts()
	}
	var js []byte
	var err error
	var payload io.Reader
	switch doc := idoc.(type) {
	case io.Reader:
		payload = doc
	case []byte:
		payload = bytes.NewReader(doc)
	default:
		if js, err = json.Marshal(doc); err != nil {
			return nil, err
		}
		payload = bytes.NewReader(js)
	}
	headers := map[string]string{}
	// if opts.Indexed {
	// 	headers["BlobStash-DocStore-IndexFullText"] = "1"
	// }
	resp, err := col.docstore.client.DoReq("POST", fmt.Sprintf("/api/docstore/%s", col.col), headers, payload)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 204, 200, 201:
		_id, err := IDFromHex(resp.Header.Get("BlobStash-DocStore-Doc-Id"))
		if err != nil {
			return nil, err
		}
		_id.hash = resp.Header.Get("BlobStash-DocStore-Doc-Hash")
		return _id, nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return nil, fmt.Errorf("failed to insert doc (%d): %v", resp.StatusCode, body.String())
	}
}

// Update the whole document
func (col *Collection) UpdateID(id string, doc interface{}) error {
	js, err := json.Marshal(doc)
	if err != nil {
		return err
	}
	resp, err := col.docstore.client.DoReq("POST", fmt.Sprintf("/api/docstore/%s/%s", col.col, id), nil, bytes.NewReader(js))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		return nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return fmt.Errorf("failed to insert doc: %v", body.String())
	}
}

// Get retrieve the document, `doc` must a map[string]interface{} or a struct pointer.
func (col *Collection) GetID(id string, doc interface{}) error {
	resp, err := col.docstore.client.DoReq("GET", fmt.Sprintf("/api/docstore/%s/%s", col.col, id), nil, nil)
	if err != nil {
		return err
	}

	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		respBody := clientutil.NewSnappyResponseReader(resp)
		defer respBody.Close()
		if err := json.NewDecoder(respBody).Decode(doc); err != nil {
			return err
		}
		return nil
	case 404:
		return ErrIDNotFound
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return fmt.Errorf("failed to insert doc: %v", body.String())
	}
}

type Iter struct {
	col   *Collection
	query *Query

	Opts     *IterOpts // Contains the current `IterOpts`
	LatestID string    // Needed for the subsequent API calls
	cursor   string

	closed bool
	err    error
}

func (iter *Iter) Cursor() string {
	return iter.cursor
}

func (iter *Iter) Close() error {
	iter.closed = true
	return iter.err
}

func (iter *Iter) Err() error {
	return iter.err
}

// Next unmarshall the request into the given slice,
// returns false when there's no more data
func (iter *Iter) Next(res interface{}) bool {
	if iter.closed {
		return false
	}
	u := fmt.Sprintf("/api/docstore/%s", iter.col.col)
	qcnt := 0
	if iter.cursor != "" {
		u = u + "?cursor=" + iter.cursor
		qcnt = 1
	}
	qqs := iter.query.ToQueryString()
	if qqs != "" {
		if qcnt == 0 {
			u = u + "?" + qqs
		} else {
			u = u + "&" + qqs
		}
	}
	resp, err := iter.col.docstore.client.DoReq("GET", u, nil, nil)
	if err != nil {
		iter.err = err
		return false
	}
	defer resp.Body.Close()
	switch {
	case resp.StatusCode == 200:
		respBody := clientutil.NewSnappyResponseReader(resp)
		defer respBody.Close()
		if err := json.NewDecoder(respBody).Decode(res); err != nil {
			iter.err = err
			return false
		}
		iter.cursor = resp.Header.Get("BlobStash-DocStore-Iter-Cursor")
		hasMore, _ := strconv.ParseBool(resp.Header.Get("BlobStash-DocStore-Iter-Has-More"))
		if !hasMore {
			iter.closed = true // Next call will return false
		}
		return true
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		iter.err = fmt.Errorf("failed to insert doc: %v", body.String())
		return false
	}
}

type IterOpts struct {
	Limit int
}

func DefaultIterOtps() *IterOpts {
	return &IterOpts{
		Limit: 50, // TODO(tsileo): tweak this
	}
}

type Query struct {
	StoredQuery     string
	StoredQueryArgs interface{}

	Query string

	Script string
}

func (q *Query) ToQueryString() string {
	if q.Query != "" {
		return fmt.Sprintf("query=%s", url.QueryEscape(q.Query))
	}
	if q.Script != "" {
		return fmt.Sprintf("script=%s", url.QueryEscape(q.Script))
	}
	if q.StoredQueryArgs != nil {
		js, err := json.Marshal(q.StoredQueryArgs)
		if err != nil {
			panic(err)
		}
		return fmt.Sprintf("stored_query=%s&stored_query_args=%s", q.StoredQuery, url.QueryEscape(string(js)))
	}
	return ""
}

func (col *Collection) Iter(query *Query, opts *IterOpts) (*Iter, error) {
	if opts == nil {
		opts = DefaultIterOtps()
	}
	if query == nil {
		query = &Query{}
	}
	return &Iter{
		col:   col,
		query: query,
		Opts:  opts,
	}, nil
}

type collectionResp struct {
	Collections []string `json:"collections"`
}

func (docstore *DocStore) DownloadAttachment(ref, path string) error {
	resp, err := docstore.client.DoReq("GET", "/api/filetree/file/"+ref, nil, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		output, err := os.Create(path)
		if err != nil {
			return err
		}
		defer output.Close()
		if _, err := io.Copy(output, resp.Body); err != nil {
			return err
		}
		return nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return fmt.Errorf("failed to insert doc: %v", body.String())
	}
}

func (docstore *DocStore) UploadAttachment(path string) (string, error) {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)
	fileWriter, err := bodyWriter.CreateFormFile("file", filepath.Base(path))
	if err != nil {
		return "", err
	}

	fh, err := os.Open(path)
	if err != nil {
		return "", err
	}

	if _, err := io.Copy(fileWriter, fh); err != nil {
		return "", err
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()
	resp, err := docstore.client.DoReq("POST", "/api/filetree/upload", map[string]string{
		"Content-Type": contentType,
	}, bodyBuf)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		res := map[string]interface{}{}
		if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
			return "", err
		}
		return res["ref"].(string), nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return "", fmt.Errorf("failed to insert doc: %v", body.String())
	}
}

func (docstore *DocStore) Collections() ([]string, error) {
	resp, err := docstore.client.DoReq("GET", "/api/docstore/", nil, nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	switch {
	case resp.StatusCode == 200:
		respBody := clientutil.NewSnappyResponseReader(resp)
		defer respBody.Close()
		colResp := &collectionResp{}
		if err := json.NewDecoder(respBody).Decode(colResp); err != nil {
			return nil, err
		}
		return colResp.Collections, nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return nil, fmt.Errorf("failed to insert doc: %v", body.String())
	}
}
<commit_msg>client/docstore: returns and takes `io.Reader` instead of path<commit_after>package docstore

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/url"
	"time"
	// "reflect"
	"strconv"

	"github.com/tsileo/blobstash/pkg/client/clientutil"
)

var ErrIDNotFound = errors.New("ID doest not exist")

var defaultServerAddr = "http://localhost:8050"
var defaultUserAgent = "DocStore Go client v1"

type ID struct {
	data []byte
	hash string
}

// Hash returns the hash of the JSON blob
func (id *ID) Hash() string {
	return id.hash
}

// ETag returns the ETag of the current document for future conditional requests
func (id *ID) ETag() string {
	return id.hash
}

// String implements Stringer interface
func (id *ID) String() string {
	return hex.EncodeToString(id.data)
}

// Ts returns the timestamp component
func (id *ID) Time() time.Time {
	return time.Unix(int64(binary.BigEndian.Uint32(id.data[0:4])), 0)
}

// FromHex build an `ID` from an hex encoded string
func IDFromHex(data string) (*ID, error) {
	if len(data) != 24 {
		return nil, fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	b, err := hex.DecodeString(data)
	if err != nil {
		return nil, fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	return &ID{data: b}, err
}

// Q is a wrapper of `map[string]interface{} for syntactic sugar
type Q map[string]interface{}
type M map[string]interface{}

type DocStore struct {
	client *clientutil.Client
}

// Collection represents a collection of documents
type Collection struct {
	docstore *DocStore
	col      string
}

// InsertOpts defines the options for the `Insert` operation
type InsertOpts struct {
	Indexed bool
}

// DefaultInsertOpts initializes a new `InsertOpts` with sane default
func DefaultInsertOpts() *InsertOpts {
	return &InsertOpts{
		Indexed: false,
	}
}

func DefaultOpts() *clientutil.Opts {
	return &clientutil.Opts{
		SnappyCompression: true,
		Host:              defaultServerAddr,
		UserAgent:         defaultUserAgent,
		EnableHTTP2:       true,
	}
}

// serverAddr should't have a trailing space
func New(opts *clientutil.Opts) *DocStore {
	if opts == nil {
		opts = DefaultOpts()
	}
	return &DocStore{
		client: clientutil.New(opts),
	}
}

func (docstore *DocStore) Col(collection string) *Collection {
	return &Collection{
		docstore: docstore,
		col:      collection,
	}
}

func (col *Collection) Insert(idoc interface{}, opts *InsertOpts) (*ID, error) {
	if opts == nil {
		opts = DefaultInsertOpts()
	}
	var js []byte
	var err error
	var payload io.Reader
	switch doc := idoc.(type) {
	case io.Reader:
		payload = doc
	case []byte:
		payload = bytes.NewReader(doc)
	default:
		if js, err = json.Marshal(doc); err != nil {
			return nil, err
		}
		payload = bytes.NewReader(js)
	}
	headers := map[string]string{}
	// if opts.Indexed {
	// 	headers["BlobStash-DocStore-IndexFullText"] = "1"
	// }
	resp, err := col.docstore.client.DoReq("POST", fmt.Sprintf("/api/docstore/%s", col.col), headers, payload)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 204, 200, 201:
		_id, err := IDFromHex(resp.Header.Get("BlobStash-DocStore-Doc-Id"))
		if err != nil {
			return nil, err
		}
		_id.hash = resp.Header.Get("BlobStash-DocStore-Doc-Hash")
		return _id, nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return nil, fmt.Errorf("failed to insert doc (%d): %v", resp.StatusCode, body.String())
	}
}

// Update the whole document
func (col *Collection) UpdateID(id string, doc interface{}) error {
	js, err := json.Marshal(doc)
	if err != nil {
		return err
	}
	resp, err := col.docstore.client.DoReq("POST", fmt.Sprintf("/api/docstore/%s/%s", col.col, id), nil, bytes.NewReader(js))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		return nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return fmt.Errorf("failed to insert doc: %v", body.String())
	}
}

// Get retrieve the document, `doc` must a map[string]interface{} or a struct pointer.
func (col *Collection) GetID(id string, doc interface{}) error {
	resp, err := col.docstore.client.DoReq("GET", fmt.Sprintf("/api/docstore/%s/%s", col.col, id), nil, nil)
	if err != nil {
		return err
	}

	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		respBody := clientutil.NewSnappyResponseReader(resp)
		defer respBody.Close()
		if err := json.NewDecoder(respBody).Decode(doc); err != nil {
			return err
		}
		return nil
	case 404:
		return ErrIDNotFound
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return fmt.Errorf("failed to insert doc: %v", body.String())
	}
}

type Iter struct {
	col   *Collection
	query *Query

	Opts     *IterOpts // Contains the current `IterOpts`
	LatestID string    // Needed for the subsequent API calls
	cursor   string

	closed bool
	err    error
}

func (iter *Iter) Cursor() string {
	return iter.cursor
}

func (iter *Iter) Close() error {
	iter.closed = true
	return iter.err
}

func (iter *Iter) Err() error {
	return iter.err
}

// Next unmarshall the request into the given slice,
// returns false when there's no more data
func (iter *Iter) Next(res interface{}) bool {
	if iter.closed {
		return false
	}
	u := fmt.Sprintf("/api/docstore/%s", iter.col.col)
	qcnt := 0
	if iter.cursor != "" {
		u = u + "?cursor=" + iter.cursor
		qcnt = 1
	}
	qqs := iter.query.ToQueryString()
	if qqs != "" {
		if qcnt == 0 {
			u = u + "?" + qqs
		} else {
			u = u + "&" + qqs
		}
	}
	resp, err := iter.col.docstore.client.DoReq("GET", u, nil, nil)
	if err != nil {
		iter.err = err
		return false
	}
	defer resp.Body.Close()
	switch {
	case resp.StatusCode == 200:
		respBody := clientutil.NewSnappyResponseReader(resp)
		defer respBody.Close()
		if err := json.NewDecoder(respBody).Decode(res); err != nil {
			iter.err = err
			return false
		}
		iter.cursor = resp.Header.Get("BlobStash-DocStore-Iter-Cursor")
		hasMore, _ := strconv.ParseBool(resp.Header.Get("BlobStash-DocStore-Iter-Has-More"))
		if !hasMore {
			iter.closed = true // Next call will return false
		}
		return true
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		iter.err = fmt.Errorf("failed to insert doc: %v", body.String())
		return false
	}
}

type IterOpts struct {
	Limit int
}

func DefaultIterOtps() *IterOpts {
	return &IterOpts{
		Limit: 50, // TODO(tsileo): tweak this
	}
}

type Query struct {
	StoredQuery     string
	StoredQueryArgs interface{}

	Query string

	Script string
}

func (q *Query) ToQueryString() string {
	if q.Query != "" {
		return fmt.Sprintf("query=%s", url.QueryEscape(q.Query))
	}
	if q.Script != "" {
		return fmt.Sprintf("script=%s", url.QueryEscape(q.Script))
	}
	if q.StoredQueryArgs != nil {
		js, err := json.Marshal(q.StoredQueryArgs)
		if err != nil {
			panic(err)
		}
		return fmt.Sprintf("stored_query=%s&stored_query_args=%s", q.StoredQuery, url.QueryEscape(string(js)))
	}
	return ""
}

func (col *Collection) Iter(query *Query, opts *IterOpts) (*Iter, error) {
	if opts == nil {
		opts = DefaultIterOtps()
	}
	if query == nil {
		query = &Query{}
	}
	return &Iter{
		col:   col,
		query: query,
		Opts:  opts,
	}, nil
}

type collectionResp struct {
	Collections []string `json:"collections"`
}

func (docstore *DocStore) DownloadAttachment(ref string) (io.ReadCloser, error) {
	resp, err := docstore.client.DoReq("GET", "/api/filetree/file/"+ref, nil, nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		return resp.Body, nil
		// output, err := os.Create(path)
		// if err != nil {
		// 	return err
		// }
		// defer output.Close()
		// if _, err := io.Copy(output, resp.Body); err != nil {
		// 	return err
		// }
		// return nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return nil, fmt.Errorf("failed to insert doc: %v", body.String())
	}
}

func (docstore *DocStore) UploadAttachment(name string, r io.Reader) (string, error) {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)
	fileWriter, err := bodyWriter.CreateFormFile("file", name)
	if err != nil {
		return "", err
	}

	if _, err := io.Copy(fileWriter, r); err != nil {
		return "", err
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()
	resp, err := docstore.client.DoReq("POST", "/api/filetree/upload", map[string]string{
		"Content-Type": contentType,
	}, bodyBuf)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		res := map[string]interface{}{}
		if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
			return "", err
		}
		return res["ref"].(string), nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return "", fmt.Errorf("failed to insert doc: %v", body.String())
	}
}

func (docstore *DocStore) Collections() ([]string, error) {
	resp, err := docstore.client.DoReq("GET", "/api/docstore/", nil, nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	switch {
	case resp.StatusCode == 200:
		respBody := clientutil.NewSnappyResponseReader(resp)
		defer respBody.Close()
		colResp := &collectionResp{}
		if err := json.NewDecoder(respBody).Decode(colResp); err != nil {
			return nil, err
		}
		return colResp.Collections, nil
	default:
		var body bytes.Buffer
		body.ReadFrom(resp.Body)
		return nil, fmt.Errorf("failed to insert doc: %v", body.String())
	}
}
<|endoftext|>


<commit_before>package datalayer

import (
	"testing"
)

func TestValidVolumeName(t *testing.T) {
	supposedBad := []string{"£", "-", "-a", "1", "",
		// 41 characters, more than 40
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
	supposedGood := []string{"a", "abc-123", "a12345", "abcde", "AbCdE"}
	for _, bad := range supposedBad {
		if ValidVolumeName(bad) {
			t.Error(bad + " is not a valid volume name, but it passed ValidVolumeName")
		}
	}
	for _, good := range supposedGood {
		if !ValidVolumeName(good) {
			t.Error(good + " is a valid volume name, but it failed ValidVolumeName")
		}
	}
}

func TestSwitchVolume(t *testing.T) {
	currentVolume := "foo"
	basePath := "somethingtemporary"
	err := SwitchVolume(basePath, currentVolume)
	if err != nil {
		t.Error("SwitchVolume failed: %s\n", err)
	}
	activeVolume, err := ActiveVolume()
	if err != nil {
		t.Error("Could not find ActiveVolume")
	}
	if activeVolume != "foo" {
		t.Error(activeVolume + " is not equal to 'foo'")
	}
}
<commit_msg>Make SwitchVolume test work<commit_after>package datalayer

import (
	"io/ioutil"
	"testing"
)

func TestValidVolumeName(t *testing.T) {
	supposedBad := []string{"£", "-", "-a", "1", "",
		// 41 characters, more than 40
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
	supposedGood := []string{"a", "abc-123", "a12345", "abcde", "AbCdE"}
	for _, bad := range supposedBad {
		if ValidVolumeName(bad) {
			t.Error(bad + " is not a valid volume name, but it passed ValidVolumeName")
		}
	}
	for _, good := range supposedGood {
		if !ValidVolumeName(good) {
			t.Error(good + " is a valid volume name, but it failed ValidVolumeName")
		}
	}
}

func TestSwitchVolume(t *testing.T) {
	currentVolume := "foo"
	basePath, err := ioutil.TempDir("", "switch")
	if err != nil {
		t.Errorf("Could not create TempDir: %s\n", err)
	}
	err := SwitchVolume(basePath, currentVolume)
	if err != nil {
		t.Errorf("SwitchVolume failed: %s\n", err)
	}
	activeVolume, err := ActiveVolume()
	if err != nil {
		t.Error("Could not find ActiveVolume")
	}
	if activeVolume != "foo" {
		t.Errorf("%s is not equal to 'foo'", activeVolume)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google 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 util

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/merge"
	"github.com/golang/glog"
	"github.com/spf13/cobra"
)

func checkErr(err error) {
	if err != nil {
		glog.FatalDepth(1, err.Error())
	}
}

func usageError(cmd *cobra.Command, format string, args ...interface{}) {
	glog.Errorf(format, args...)
	glog.Errorf("See '%s -h' for help.", cmd.CommandPath())
	os.Exit(1)
}

func GetFlagString(cmd *cobra.Command, flag string) string {
	f := cmd.Flags().Lookup(flag)
	if f == nil {
		glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
	}
	return f.Value.String()
}

func GetFlagBool(cmd *cobra.Command, flag string) bool {
	f := cmd.Flags().Lookup(flag)
	if f == nil {
		glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
	}
	// Caseless compare.
	if strings.ToLower(f.Value.String()) == "true" {
		return true
	}
	return false
}

// Returns nil if the flag wasn't set.
func GetFlagBoolPtr(cmd *cobra.Command, flag string) *bool {
	f := cmd.Flags().Lookup(flag)
	if f == nil {
		glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
	}
	// Check if flag was not set at all.
	if !f.Changed && f.DefValue == f.Value.String() {
		return nil
	}
	var ret bool
	// Caseless compare.
	if strings.ToLower(f.Value.String()) == "true" {
		ret = true
	} else {
		ret = false
	}
	return &ret
}

// Assumes the flag has a default value.
func GetFlagInt(cmd *cobra.Command, flag string) int {
	f := cmd.Flags().Lookup(flag)
	if f == nil {
		glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
	}
	v, err := strconv.Atoi(f.Value.String())
	// This is likely not a sufficiently friendly error message, but cobra
	// should prevent non-integer values from reaching here.
	checkErr(err)
	return v
}

func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
	f := cmd.Flags().Lookup(flag)
	if f == nil {
		glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
	}
	v, err := time.ParseDuration(f.Value.String())
	checkErr(err)
	return v
}

// Returns the first non-empty string out of the ones provided. If all
// strings are empty, returns an empty string.
func FirstNonEmptyString(args ...string) string {
	for _, s := range args {
		if len(s) > 0 {
			return s
		}
	}
	return ""
}

// Return a list of file names of a certain type within a given directory.
// TODO: replace with resource.Builder
func GetFilesFromDir(directory string, fileType string) []string {
	files := []string{}

	err := filepath.Walk(directory, func(path string, f os.FileInfo, err error) error {
		if filepath.Ext(path) == fileType {
			files = append(files, path)
		}
		return err
	})

	checkErr(err)
	return files
}

// ReadConfigData reads the bytes from the specified filesytem or network
// location or from stdin if location == "-".
// TODO: replace with resource.Builder
func ReadConfigData(location string) ([]byte, error) {
	if len(location) == 0 {
		return nil, fmt.Errorf("location given but empty")
	}

	if location == "-" {
		// Read from stdin.
		data, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			return nil, err
		}

		if len(data) == 0 {
			return nil, fmt.Errorf(`Read from stdin specified ("-") but no data found`)
		}

		return data, nil
	}

	// Use the location as a file path or URL.
	return ReadConfigDataFromLocation(location)
}

// TODO: replace with resource.Builder
func ReadConfigDataFromLocation(location string) ([]byte, error) {
	// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
	if strings.Index(location, "http://") == 0 || strings.Index(location, "https://") == 0 {
		resp, err := http.Get(location)
		if err != nil {
			return nil, fmt.Errorf("unable to access URL %s: %v\n", location, err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != 200 {
			return nil, fmt.Errorf("unable to read URL, server reported %d %s", resp.StatusCode, resp.Status)
		}
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, fmt.Errorf("unable to read URL %s: %v\n", location, err)
		}
		return data, nil
	} else {
		data, err := ioutil.ReadFile(location)
		if err != nil {
			return nil, fmt.Errorf("unable to read %s: %v\n", location, err)
		}
		return data, nil
	}
}

func Merge(dst runtime.Object, fragment, kind string) (runtime.Object, error) {
	// Ok, this is a little hairy, we'd rather not force the user to specify a kind for their JSON
	// So we pull it into a map, add the Kind field, and then reserialize.
	// We also pull the apiVersion for proper parsing
	var intermediate interface{}
	if err := json.Unmarshal([]byte(fragment), &intermediate); err != nil {
		return nil, err
	}
	dataMap, ok := intermediate.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("Expected a map, found something else: %s", fragment)
	}
	version, found := dataMap["apiVersion"]
	if !found {
		return nil, fmt.Errorf("Inline JSON requires an apiVersion field")
	}
	versionString, ok := version.(string)
	if !ok {
		return nil, fmt.Errorf("apiVersion must be a string")
	}

	codec := runtime.CodecFor(api.Scheme, versionString)
	// encode dst into versioned json and apply fragment directly too it
	target, err := codec.Encode(dst)
	if err != nil {
		return nil, err
	}
	patched, err := merge.MergeJSON(target, []byte(fragment))
	if err != nil {
		return nil, err
	}
	out, err := codec.Decode(patched)
	if err != nil {
		return nil, err
	}
	return out, nil
}
<commit_msg>Remove unused flag helpers<commit_after>/*
Copyright 2014 Google 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 util

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

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/merge"
	"github.com/golang/glog"
	"github.com/spf13/cobra"
)

func checkErr(err error) {
	if err != nil {
		glog.FatalDepth(1, err.Error())
	}
}

func usageError(cmd *cobra.Command, format string, args ...interface{}) {
	glog.Errorf(format, args...)
	glog.Errorf("See '%s -h' for help.", cmd.CommandPath())
	os.Exit(1)
}

func GetFlagString(cmd *cobra.Command, flag string) string {
	f := cmd.Flags().Lookup(flag)
	if f == nil {
		glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
	}
	return f.Value.String()
}

func GetFlagBool(cmd *cobra.Command, flag string) bool {
	f := cmd.Flags().Lookup(flag)
	if f == nil {
		glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
	}
	// Caseless compare.
	if strings.ToLower(f.Value.String()) == "true" {
		return true
	}
	return false
}

// Assumes the flag has a default value.
func GetFlagInt(cmd *cobra.Command, flag string) int {
	f := cmd.Flags().Lookup(flag)
	if f == nil {
		glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
	}
	v, err := strconv.Atoi(f.Value.String())
	// This is likely not a sufficiently friendly error message, but cobra
	// should prevent non-integer values from reaching here.
	checkErr(err)
	return v
}

func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
	f := cmd.Flags().Lookup(flag)
	if f == nil {
		glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
	}
	v, err := time.ParseDuration(f.Value.String())
	checkErr(err)
	return v
}

// ReadConfigData reads the bytes from the specified filesytem or network
// location or from stdin if location == "-".
// TODO: replace with resource.Builder
func ReadConfigData(location string) ([]byte, error) {
	if len(location) == 0 {
		return nil, fmt.Errorf("location given but empty")
	}

	if location == "-" {
		// Read from stdin.
		data, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			return nil, err
		}

		if len(data) == 0 {
			return nil, fmt.Errorf(`Read from stdin specified ("-") but no data found`)
		}

		return data, nil
	}

	// Use the location as a file path or URL.
	return ReadConfigDataFromLocation(location)
}

// TODO: replace with resource.Builder
func ReadConfigDataFromLocation(location string) ([]byte, error) {
	// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
	if strings.Index(location, "http://") == 0 || strings.Index(location, "https://") == 0 {
		resp, err := http.Get(location)
		if err != nil {
			return nil, fmt.Errorf("unable to access URL %s: %v\n", location, err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != 200 {
			return nil, fmt.Errorf("unable to read URL, server reported %d %s", resp.StatusCode, resp.Status)
		}
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, fmt.Errorf("unable to read URL %s: %v\n", location, err)
		}
		return data, nil
	} else {
		data, err := ioutil.ReadFile(location)
		if err != nil {
			return nil, fmt.Errorf("unable to read %s: %v\n", location, err)
		}
		return data, nil
	}
}

func Merge(dst runtime.Object, fragment, kind string) (runtime.Object, error) {
	// Ok, this is a little hairy, we'd rather not force the user to specify a kind for their JSON
	// So we pull it into a map, add the Kind field, and then reserialize.
	// We also pull the apiVersion for proper parsing
	var intermediate interface{}
	if err := json.Unmarshal([]byte(fragment), &intermediate); err != nil {
		return nil, err
	}
	dataMap, ok := intermediate.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("Expected a map, found something else: %s", fragment)
	}
	version, found := dataMap["apiVersion"]
	if !found {
		return nil, fmt.Errorf("Inline JSON requires an apiVersion field")
	}
	versionString, ok := version.(string)
	if !ok {
		return nil, fmt.Errorf("apiVersion must be a string")
	}

	codec := runtime.CodecFor(api.Scheme, versionString)
	// encode dst into versioned json and apply fragment directly too it
	target, err := codec.Encode(dst)
	if err != nil {
		return nil, err
	}
	patched, err := merge.MergeJSON(target, []byte(fragment))
	if err != nil {
		return nil, err
	}
	out, err := codec.Decode(patched)
	if err != nil {
		return nil, err
	}
	return out, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors 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 service

import (
	"bytes"
	"fmt"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/docker/machine/libmachine"
	"github.com/pkg/browser"
	"github.com/pkg/errors"
	"k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/tools/clientcmd"

	"text/template"

	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/minikube/pkg/minikube/cluster"
	"k8s.io/minikube/pkg/util"
)

type K8sClient interface {
	GetCoreClient() (corev1.CoreV1Interface, error)
	GetClientset() (*kubernetes.Clientset, error)
}

type K8sClientGetter struct{}

var K8s K8sClient

func init() {
	K8s = &K8sClientGetter{}
}

func (k *K8sClientGetter) GetCoreClient() (corev1.CoreV1Interface, error) {
	client, err := k.GetClientset()
	if err != nil {
		return nil, errors.Wrap(err, "getting clientset")
	}
	return client.Core(), nil
}

func (*K8sClientGetter) GetClientset() (*kubernetes.Clientset, error) {
	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
	configOverrides := &clientcmd.ConfigOverrides{}
	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
	config, err := kubeConfig.ClientConfig()
	if err != nil {
		return nil, fmt.Errorf("Error creating kubeConfig: %s", err)
	}
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "Error creating new client from kubeConfig.ClientConfig()")
	}

	return client, nil
}

type ServiceURL struct {
	Namespace string
	Name      string
	URLs      []string
}

type ServiceURLs []ServiceURL

// Returns all the node port URLs for every service in a particular namespace
// Accepts a template for formating
func GetServiceURLs(api libmachine.API, namespace string, t *template.Template) (ServiceURLs, error) {
	host, err := cluster.CheckIfApiExistsAndLoad(api)
	if err != nil {
		return nil, err
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		return nil, err
	}

	client, err := K8s.GetCoreClient()
	if err != nil {
		return nil, err
	}

	serviceInterface := client.Services(namespace)

	svcs, err := serviceInterface.List(meta_v1.ListOptions{})
	if err != nil {
		return nil, err
	}

	var serviceURLs []ServiceURL
	for _, svc := range svcs.Items {
		urls, err := printURLsForService(client, ip, svc.Name, svc.Namespace, t)
		if err != nil {
			return nil, err
		}
		serviceURLs = append(serviceURLs, ServiceURL{Namespace: svc.Namespace, Name: svc.Name, URLs: urls})
	}

	return serviceURLs, nil
}

// Returns all the node ports for a service in a namespace
// with optional formatting
func GetServiceURLsForService(api libmachine.API, namespace, service string, t *template.Template) ([]string, error) {
	host, err := cluster.CheckIfApiExistsAndLoad(api)
	if err != nil {
		return nil, errors.Wrap(err, "Error checking if api exist and loading it")
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		return nil, errors.Wrap(err, "Error getting ip from host")
	}

	client, err := K8s.GetCoreClient()
	if err != nil {
		return nil, err
	}

	return printURLsForService(client, ip, service, namespace, t)
}

func printURLsForService(c corev1.CoreV1Interface, ip, service, namespace string, t *template.Template) ([]string, error) {
	if t == nil {
		return nil, errors.New("Error, attempted to generate service url with nil --format template")
	}

	s := c.Services(namespace)
	svc, err := s.Get(service, meta_v1.GetOptions{})
	if err != nil {
		return nil, errors.Wrapf(err, "service '%s' could not be found running", service)
	}
	var nodePorts []int32
	if len(svc.Spec.Ports) > 0 {
		for _, port := range svc.Spec.Ports {
			if port.NodePort > 0 {
				nodePorts = append(nodePorts, port.NodePort)
			}
		}
	}
	urls := []string{}
	for _, port := range nodePorts {
		var doc bytes.Buffer
		err = t.Execute(&doc, struct {
			IP   string
			Port int32
		}{
			ip,
			port,
		})
		if err != nil {
			return nil, err
		}

		u, err := url.Parse(doc.String())
		if err != nil {
			return nil, err
		}

		urls = append(urls, u.String())
	}
	return urls, nil
}

// CheckService waits for the specified service to be ready by returning an error until the service is up
// The check is done by polling the endpoint associated with the service and when the endpoint exists, returning no error->service-online
func CheckService(namespace string, service string) error {
	client, err := K8s.GetCoreClient()
	if err != nil {
		return errors.Wrap(err, "Error getting kubernetes client")
	}
	services := client.Services(namespace)
	err = validateService(services, service)
	if err != nil {
		return errors.Wrap(err, "Error validating service")
	}
	endpoints := client.Endpoints(namespace)
	return checkEndpointReady(endpoints, service)
}

func validateService(s corev1.ServiceInterface, service string) error {
	if _, err := s.Get(service, meta_v1.GetOptions{}); err != nil {
		return errors.Wrapf(err, "Error getting service %s", service)
	}
	return nil
}

func checkEndpointReady(endpoints corev1.EndpointsInterface, service string) error {
	endpoint, err := endpoints.Get(service, meta_v1.GetOptions{})
	if err != nil {
		return &util.RetriableError{Err: errors.Errorf("Error getting endpoints for service %s", service)}
	}
	const notReadyMsg = "Waiting, endpoint for service is not ready yet...\n"
	if len(endpoint.Subsets) == 0 {
		fmt.Fprintf(os.Stderr, notReadyMsg)
		return &util.RetriableError{Err: errors.New("Endpoint for service is not ready yet")}
	}
	for _, subset := range endpoint.Subsets {
		if len(subset.Addresses) == 0 {
			fmt.Fprintf(os.Stderr, notReadyMsg)
			return &util.RetriableError{Err: errors.New("No endpoints for service are ready yet")}
		}
	}
	return nil
}

func WaitAndMaybeOpenService(api libmachine.API, namespace string, service string, urlTemplate *template.Template, urlMode bool, https bool,
	wait int, interval int) error {
	if err := util.RetryAfter(wait, func() error { return CheckService(namespace, service) }, time.Duration(interval)*time.Second); err != nil {
		return errors.Wrapf(err, "Could not find finalized endpoint being pointed to by %s", service)
	}

	urls, err := GetServiceURLsForService(api, namespace, service, urlTemplate)
	if err != nil {
		return errors.Wrap(err, "Check that minikube is running and that you have specified the correct namespace")
	}
	for _, url := range urls {
		if https {
			url = strings.Replace(url, "http", "https", 1)
		}
		if urlMode || !strings.HasPrefix(url, "http") {
			fmt.Fprintln(os.Stdout, url)
		} else {
			fmt.Fprintln(os.Stderr, "Opening kubernetes service "+namespace+"/"+service+" in default browser...")
			browser.OpenURL(url)
		}
	}
	return nil
}

func GetServiceListByLabel(namespace string, key string, value string) (*v1.ServiceList, error) {
	client, err := K8s.GetCoreClient()
	if err != nil {
		return &v1.ServiceList{}, &util.RetriableError{Err: err}
	}
	services := client.Services(namespace)
	if err != nil {
		return &v1.ServiceList{}, &util.RetriableError{Err: err}
	}
	return getServiceListFromServicesByLabel(services, key, value)
}

func getServiceListFromServicesByLabel(services corev1.ServiceInterface, key string, value string) (*v1.ServiceList, error) {
	selector := labels.SelectorFromSet(labels.Set(map[string]string{key: value}))
	serviceList, err := services.List(meta_v1.ListOptions{LabelSelector: selector.String()})
	if err != nil {
		return &v1.ServiceList{}, &util.RetriableError{Err: err}
	}

	return serviceList, nil
}

// CreateSecret creates or modifies secrets
func CreateSecret(namespace, name string, dataValues map[string]string, labels map[string]string) error {
	client, err := K8s.GetCoreClient()
	if err != nil {
		return &util.RetriableError{Err: err}
	}
	secrets := client.Secrets(namespace)
	if err != nil {
		return &util.RetriableError{Err: err}
	}

	secret, _ := secrets.Get(name, meta_v1.GetOptions{})

	// Delete existing secret
	if len(secret.Name) > 0 {
		err = DeleteSecret(namespace, name)
		if err != nil {
			return &util.RetriableError{Err: err}
		}
	}

	// convert strings to data secrets
	data := map[string][]byte{}
	for key, value := range dataValues {
		data[key] = []byte(value)
	}

	// Create Secret
	secretObj := &v1.Secret{
		ObjectMeta: meta_v1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Data: data,
		Type: v1.SecretTypeOpaque,
	}

	_, err = secrets.Create(secretObj)
	if err != nil {
		fmt.Println("err: ", err)
		return &util.RetriableError{Err: err}
	}

	return nil
}

// DeleteSecret deletes a secret from a namespace
func DeleteSecret(namespace, name string) error {
	client, err := K8s.GetCoreClient()
	if err != nil {
		return &util.RetriableError{Err: err}
	}

	secrets := client.Secrets(namespace)
	if err != nil {
		return &util.RetriableError{Err: err}
	}

	err = secrets.Delete(name, &meta_v1.DeleteOptions{})
	if err != nil {
		return &util.RetriableError{Err: err}
	}

	return nil
}
<commit_msg>Use minikube context instead of relying on current kubcetl context<commit_after>/*
Copyright 2016 The Kubernetes Authors 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 service

import (
	"bytes"
	"fmt"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/docker/machine/libmachine"
	"github.com/pkg/browser"
	"github.com/pkg/errors"
	"k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/tools/clientcmd"

	"text/template"

	"k8s.io/apimachinery/pkg/labels"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
	"k8s.io/minikube/pkg/minikube/cluster"
	"k8s.io/minikube/pkg/util"
)

type K8sClient interface {
	GetCoreClient() (corev1.CoreV1Interface, error)
	GetClientset() (*kubernetes.Clientset, error)
}

type K8sClientGetter struct{}

var K8s K8sClient

func init() {
	K8s = &K8sClientGetter{}
}

func (k *K8sClientGetter) GetCoreClient() (corev1.CoreV1Interface, error) {
	client, err := k.GetClientset()
	if err != nil {
		return nil, errors.Wrap(err, "getting clientset")
	}
	return client.Core(), nil
}

func (*K8sClientGetter) GetClientset() (*kubernetes.Clientset, error) {
	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
	configOverrides := &clientcmd.ConfigOverrides{
		Context: clientcmdapi.Context{
			Cluster:  "minikube",
			AuthInfo: "minikube",
		},
	}
	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
	config, err := kubeConfig.ClientConfig()
	if err != nil {
		return nil, fmt.Errorf("Error creating kubeConfig: %s", err)
	}
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "Error creating new client from kubeConfig.ClientConfig()")
	}

	return client, nil
}

type ServiceURL struct {
	Namespace string
	Name      string
	URLs      []string
}

type ServiceURLs []ServiceURL

// Returns all the node port URLs for every service in a particular namespace
// Accepts a template for formating
func GetServiceURLs(api libmachine.API, namespace string, t *template.Template) (ServiceURLs, error) {
	host, err := cluster.CheckIfApiExistsAndLoad(api)
	if err != nil {
		return nil, err
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		return nil, err
	}

	client, err := K8s.GetCoreClient()
	if err != nil {
		return nil, err
	}

	serviceInterface := client.Services(namespace)

	svcs, err := serviceInterface.List(meta_v1.ListOptions{})
	if err != nil {
		return nil, err
	}

	var serviceURLs []ServiceURL
	for _, svc := range svcs.Items {
		urls, err := printURLsForService(client, ip, svc.Name, svc.Namespace, t)
		if err != nil {
			return nil, err
		}
		serviceURLs = append(serviceURLs, ServiceURL{Namespace: svc.Namespace, Name: svc.Name, URLs: urls})
	}

	return serviceURLs, nil
}

// Returns all the node ports for a service in a namespace
// with optional formatting
func GetServiceURLsForService(api libmachine.API, namespace, service string, t *template.Template) ([]string, error) {
	host, err := cluster.CheckIfApiExistsAndLoad(api)
	if err != nil {
		return nil, errors.Wrap(err, "Error checking if api exist and loading it")
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		return nil, errors.Wrap(err, "Error getting ip from host")
	}

	client, err := K8s.GetCoreClient()
	if err != nil {
		return nil, err
	}

	return printURLsForService(client, ip, service, namespace, t)
}

func printURLsForService(c corev1.CoreV1Interface, ip, service, namespace string, t *template.Template) ([]string, error) {
	if t == nil {
		return nil, errors.New("Error, attempted to generate service url with nil --format template")
	}

	s := c.Services(namespace)
	svc, err := s.Get(service, meta_v1.GetOptions{})
	if err != nil {
		return nil, errors.Wrapf(err, "service '%s' could not be found running", service)
	}
	var nodePorts []int32
	if len(svc.Spec.Ports) > 0 {
		for _, port := range svc.Spec.Ports {
			if port.NodePort > 0 {
				nodePorts = append(nodePorts, port.NodePort)
			}
		}
	}
	urls := []string{}
	for _, port := range nodePorts {
		var doc bytes.Buffer
		err = t.Execute(&doc, struct {
			IP   string
			Port int32
		}{
			ip,
			port,
		})
		if err != nil {
			return nil, err
		}

		u, err := url.Parse(doc.String())
		if err != nil {
			return nil, err
		}

		urls = append(urls, u.String())
	}
	return urls, nil
}

// CheckService waits for the specified service to be ready by returning an error until the service is up
// The check is done by polling the endpoint associated with the service and when the endpoint exists, returning no error->service-online
func CheckService(namespace string, service string) error {
	client, err := K8s.GetCoreClient()
	if err != nil {
		return errors.Wrap(err, "Error getting kubernetes client")
	}
	services := client.Services(namespace)
	err = validateService(services, service)
	if err != nil {
		return errors.Wrap(err, "Error validating service")
	}
	endpoints := client.Endpoints(namespace)
	return checkEndpointReady(endpoints, service)
}

func validateService(s corev1.ServiceInterface, service string) error {
	if _, err := s.Get(service, meta_v1.GetOptions{}); err != nil {
		return errors.Wrapf(err, "Error getting service %s", service)
	}
	return nil
}

func checkEndpointReady(endpoints corev1.EndpointsInterface, service string) error {
	endpoint, err := endpoints.Get(service, meta_v1.GetOptions{})
	if err != nil {
		return &util.RetriableError{Err: errors.Errorf("Error getting endpoints for service %s", service)}
	}
	const notReadyMsg = "Waiting, endpoint for service is not ready yet...\n"
	if len(endpoint.Subsets) == 0 {
		fmt.Fprintf(os.Stderr, notReadyMsg)
		return &util.RetriableError{Err: errors.New("Endpoint for service is not ready yet")}
	}
	for _, subset := range endpoint.Subsets {
		if len(subset.Addresses) == 0 {
			fmt.Fprintf(os.Stderr, notReadyMsg)
			return &util.RetriableError{Err: errors.New("No endpoints for service are ready yet")}
		}
	}
	return nil
}

func WaitAndMaybeOpenService(api libmachine.API, namespace string, service string, urlTemplate *template.Template, urlMode bool, https bool,
	wait int, interval int) error {
	if err := util.RetryAfter(wait, func() error { return CheckService(namespace, service) }, time.Duration(interval)*time.Second); err != nil {
		return errors.Wrapf(err, "Could not find finalized endpoint being pointed to by %s", service)
	}

	urls, err := GetServiceURLsForService(api, namespace, service, urlTemplate)
	if err != nil {
		return errors.Wrap(err, "Check that minikube is running and that you have specified the correct namespace")
	}
	for _, url := range urls {
		if https {
			url = strings.Replace(url, "http", "https", 1)
		}
		if urlMode || !strings.HasPrefix(url, "http") {
			fmt.Fprintln(os.Stdout, url)
		} else {
			fmt.Fprintln(os.Stderr, "Opening kubernetes service "+namespace+"/"+service+" in default browser...")
			browser.OpenURL(url)
		}
	}
	return nil
}

func GetServiceListByLabel(namespace string, key string, value string) (*v1.ServiceList, error) {
	client, err := K8s.GetCoreClient()
	if err != nil {
		return &v1.ServiceList{}, &util.RetriableError{Err: err}
	}
	services := client.Services(namespace)
	if err != nil {
		return &v1.ServiceList{}, &util.RetriableError{Err: err}
	}
	return getServiceListFromServicesByLabel(services, key, value)
}

func getServiceListFromServicesByLabel(services corev1.ServiceInterface, key string, value string) (*v1.ServiceList, error) {
	selector := labels.SelectorFromSet(labels.Set(map[string]string{key: value}))
	serviceList, err := services.List(meta_v1.ListOptions{LabelSelector: selector.String()})
	if err != nil {
		return &v1.ServiceList{}, &util.RetriableError{Err: err}
	}

	return serviceList, nil
}

// CreateSecret creates or modifies secrets
func CreateSecret(namespace, name string, dataValues map[string]string, labels map[string]string) error {
	client, err := K8s.GetCoreClient()
	if err != nil {
		return &util.RetriableError{Err: err}
	}
	secrets := client.Secrets(namespace)
	if err != nil {
		return &util.RetriableError{Err: err}
	}

	secret, _ := secrets.Get(name, meta_v1.GetOptions{})

	// Delete existing secret
	if len(secret.Name) > 0 {
		err = DeleteSecret(namespace, name)
		if err != nil {
			return &util.RetriableError{Err: err}
		}
	}

	// convert strings to data secrets
	data := map[string][]byte{}
	for key, value := range dataValues {
		data[key] = []byte(value)
	}

	// Create Secret
	secretObj := &v1.Secret{
		ObjectMeta: meta_v1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Data: data,
		Type: v1.SecretTypeOpaque,
	}

	_, err = secrets.Create(secretObj)
	if err != nil {
		fmt.Println("err: ", err)
		return &util.RetriableError{Err: err}
	}

	return nil
}

// DeleteSecret deletes a secret from a namespace
func DeleteSecret(namespace, name string) error {
	client, err := K8s.GetCoreClient()
	if err != nil {
		return &util.RetriableError{Err: err}
	}

	secrets := client.Secrets(namespace)
	if err != nil {
		return &util.RetriableError{Err: err}
	}

	err = secrets.Delete(name, &meta_v1.DeleteOptions{})
	if err != nil {
		return &util.RetriableError{Err: err}
	}

	return nil
}
<|endoftext|>


<commit_before>package pingone

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
	"encoding/base64"

	"github.com/PuerkitoBio/goquery"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/tidwall/gjson"
	"github.com/versent/saml2aws/pkg/cfg"
	"github.com/versent/saml2aws/pkg/creds"
	"github.com/versent/saml2aws/pkg/page"
	"github.com/versent/saml2aws/pkg/prompter"
	"github.com/versent/saml2aws/pkg/provider"
)

var logger = logrus.WithField("provider", "pingone")

// Client wrapper around PingOne + PingId enabling authentication and retrieval of assertions
type Client struct {
	client     *provider.HTTPClient
	idpAccount *cfg.IDPAccount
}

// New create a new PingOne client
func New(idpAccount *cfg.IDPAccount) (*Client, error) {

	tr := provider.NewDefaultTransport(idpAccount.SkipVerify)

	client, err := provider.NewHTTPClient(tr)
	if err != nil {
		return nil, errors.Wrap(err, "error building http client")
	}

	// assign a response validator to ensure all responses are either success or a redirect
	// this is to avoid have explicit checks for every single response
	client.CheckResponseStatus = provider.SuccessOrRedirectResponseValidator

	return &Client{
		client:     client,
		idpAccount: idpAccount,
	}, nil
}

type ctxKey string

// Authenticate Authenticate to PingOne and return the data from the body of the SAML assertion.
func (ac *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
	req, err := http.NewRequest("GET", loginDetails.URL, nil)
	if err != nil {
		return "", errors.Wrap(err, "error building request")
	}
	ctx := context.WithValue(context.Background(), ctxKey("login"), loginDetails)
	return ac.follow(ctx, req)
}

func (ac *Client) follow(ctx context.Context, req *http.Request) (string, error) {
	res, err := ac.client.Do(req)
	if err != nil {
		return "", errors.Wrap(err, "error following")
	}

	doc, err := goquery.NewDocumentFromResponse(res)
	if err != nil {
		return "", errors.Wrap(err, "failed to build document from response")
	}

	var handler func(context.Context, *goquery.Document, *http.Response) (context.Context, *http.Request, error)

	if docIsFormRedirectToAWS(doc) {
		logger.WithField("type", "saml-response-to-aws").Debug("doc detect")
		if samlResponse, ok := extractSAMLResponse(doc); ok {
			decodedSamlResponse, err := base64.StdEncoding.DecodeString(samlResponse)
			if err != nil {
				return "", errors.Wrap(err, "failed to decode saml-response")
			}
			logger.WithField("type", "saml-response").WithField("saml-response", string(decodedSamlResponse)).Debug("doc detect")
			return samlResponse, nil
		}
	} else if docIsFormSamlRequest(doc) {
		logger.WithField("type", "saml-request").Debug("doc detect")
		handler = ac.handleFormRedirect
	} else if docIsFormResume(doc) {
		logger.WithField("type", "resume").Debug("doc detect")
		handler = ac.handleFormRedirect
	} else if docIsLogin(doc) {
		logger.WithField("type", "login").Debug("doc detect")
		handler = ac.handleLogin
	} else if docIsOTP(doc) {
		logger.WithField("type", "otp").Debug("doc detect")
		handler = ac.handleOTP
	} else if docIsSwipe(doc) {
		logger.WithField("type", "swipe").Debug("doc detect")
		handler = ac.handleSwipe
	} else if docIsFormRedirect(doc) {
		logger.WithField("type", "form-redirect").Debug("doc detect")
		handler = ac.handleFormRedirect
	}
	if handler == nil {
		html, _ := doc.Selection.Html()
		logger.WithField("doc", html).Debug("Unknown document type")
		return "", fmt.Errorf("Unknown document type")
	}

	ctx, req, err = handler(ctx, doc, res)
	if err != nil {
		return "", err
	}
	return ac.follow(ctx, req)
}

func (ac *Client) handleLogin(ctx context.Context, doc *goquery.Document, res *http.Response) (context.Context, *http.Request, error) {
	loginDetails, ok := ctx.Value(ctxKey("login")).(*creds.LoginDetails)
	if !ok {
		return ctx, nil, fmt.Errorf("no context value for 'login'")
	}

	form, err := page.NewFormFromDocument(doc, "form")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting login form")
	}

	baseURL := makeBaseURL(res.Request.URL)
	logger.WithField("baseURL", baseURL).Debug("base url")

	form.Values.Set("pf.username", loginDetails.Username)
	form.Values.Set("pf.pass", loginDetails.Password)
	form.URL = makeAbsoluteURL(form.URL, baseURL)

	req, err := form.BuildRequest()
	return ctx, req, err
}

func (ac *Client) handleOTP(ctx context.Context, doc *goquery.Document, _ *http.Response) (context.Context, *http.Request, error) {
	form, err := page.NewFormFromDocument(doc, "#otp-form")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting OTP form")
	}

	token := prompter.StringRequired("Enter passcode")
	form.Values.Set("otp", token)
	req, err := form.BuildRequest()
	return ctx, req, err
}

func (ac *Client) handleSwipe(ctx context.Context, doc *goquery.Document, _ *http.Response) (context.Context, *http.Request, error) {
	form, err := page.NewFormFromDocument(doc, "#form1")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting swipe status form")
	}

	// poll status. request must specifically be a GET
	form.Method = "GET"
	req, err := form.BuildRequest()
	if err != nil {
		return ctx, nil, err
	}

	for {
		time.Sleep(3 * time.Second)

		res, err := ac.client.Do(req)
		if err != nil {
			return ctx, nil, errors.Wrap(err, "error polling swipe status")
		}

		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return ctx, nil, errors.Wrap(err, "error parsing body from swipe status response")
		}

		resp := string(body)

		pingfedMFAStatusResponse := gjson.Get(resp, "status").String()

		//ASYNC_AUTH_WAIT indicates we keep going
		//OK indicates someone swiped
		//DEVICE_CLAIM_TIMEOUT indicates nobody swiped
		//otherwise loop forever?

		if pingfedMFAStatusResponse == "OK" || pingfedMFAStatusResponse == "DEVICE_CLAIM_TIMEOUT" || pingfedMFAStatusResponse == "TIMEOUT" {
			break
		}
	}

	// now build a request for getting response of MFA
	form, err = page.NewFormFromDocument(doc, "#reponseView")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting swipe response form")
	}
	req, err = form.BuildRequest()
	return ctx, req, err
}

func (ac *Client) handleFormRedirect(ctx context.Context, doc *goquery.Document, _ *http.Response) (context.Context, *http.Request, error) {
	form, err := page.NewFormFromDocument(doc, "")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting redirect form")
	}
	req, err := form.BuildRequest()
	return ctx, req, err
}

func (ac *Client) handleFormSamlRequest(ctx context.Context, doc *goquery.Document, _ *http.Response) (context.Context, *http.Request, error) {
	form, err := page.NewFormFromDocument(doc, "")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting samlrequest form")
	}
	req, err := form.BuildRequest()
	return ctx, req, err
}

func docIsLogin(doc *goquery.Document) bool {
	return doc.Has("input[name=\"pf.pass\"]").Size() == 1
}

func docIsOTP(doc *goquery.Document) bool {
	return doc.Has("form#otp-form").Size() == 1
}

func docIsSwipe(doc *goquery.Document) bool {
	return doc.Has("form#form1").Size() == 1 && doc.Has("form#reponseView").Size() == 1
}

func docIsFormRedirect(doc *goquery.Document) bool {
	return doc.Has("input[name=\"ppm_request\"]").Size() == 1
}

func docIsFormSamlRequest(doc *goquery.Document) bool {
	return doc.Find("input[name=\"SAMLRequest\"]").Size() == 1
}

func docIsFormResume(doc *goquery.Document) bool {
	return doc.Find("input[name=\"RelayState\"]").Size() == 1
}

func docIsFormRedirectToAWS(doc *goquery.Document) bool {
	return doc.Find("form[action=\"https://signin.aws.amazon.com/saml\"]").Size() == 1
}

func extractSAMLResponse(doc *goquery.Document) (v string, ok bool) {
	return doc.Find("input[name=\"SAMLResponse\"]").Attr("value")
}

func makeBaseURL(url *url.URL) string {
	return url.Scheme + "://" + url.Hostname()
}

// ensures given url is an absolute URL. if not, it will be combined with the base URL
func makeAbsoluteURL(v string, base string) string {
	logger.WithField("base", base).WithField("v", v).Debug("make absolute url")
	if u, err := url.ParseRequestURI(v); err == nil && !u.IsAbs() {
		return fmt.Sprintf("%s%s", base, v)
	}
	return v
}

<commit_msg>saml2aws is unsupport WebAuthn.<commit_after>package pingone

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
	"encoding/base64"

	"github.com/PuerkitoBio/goquery"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/tidwall/gjson"
	"github.com/versent/saml2aws/pkg/cfg"
	"github.com/versent/saml2aws/pkg/creds"
	"github.com/versent/saml2aws/pkg/page"
	"github.com/versent/saml2aws/pkg/prompter"
	"github.com/versent/saml2aws/pkg/provider"
)

var logger = logrus.WithField("provider", "pingone")

// Client wrapper around PingOne + PingId enabling authentication and retrieval of assertions
type Client struct {
	client     *provider.HTTPClient
	idpAccount *cfg.IDPAccount
}

// New create a new PingOne client
func New(idpAccount *cfg.IDPAccount) (*Client, error) {

	tr := provider.NewDefaultTransport(idpAccount.SkipVerify)

	client, err := provider.NewHTTPClient(tr)
	if err != nil {
		return nil, errors.Wrap(err, "error building http client")
	}

	// assign a response validator to ensure all responses are either success or a redirect
	// this is to avoid have explicit checks for every single response
	client.CheckResponseStatus = provider.SuccessOrRedirectResponseValidator

	return &Client{
		client:     client,
		idpAccount: idpAccount,
	}, nil
}

type ctxKey string

// Authenticate Authenticate to PingOne and return the data from the body of the SAML assertion.
func (ac *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
	req, err := http.NewRequest("GET", loginDetails.URL, nil)
	if err != nil {
		return "", errors.Wrap(err, "error building request")
	}
	ctx := context.WithValue(context.Background(), ctxKey("login"), loginDetails)
	return ac.follow(ctx, req)
}

func (ac *Client) follow(ctx context.Context, req *http.Request) (string, error) {
	res, err := ac.client.Do(req)
	if err != nil {
		return "", errors.Wrap(err, "error following")
	}

	doc, err := goquery.NewDocumentFromResponse(res)
	if err != nil {
		return "", errors.Wrap(err, "failed to build document from response")
	}

	var handler func(context.Context, *goquery.Document, *http.Response) (context.Context, *http.Request, error)

	if docIsFormRedirectToAWS(doc) {
		logger.WithField("type", "saml-response-to-aws").Debug("doc detect")
		if samlResponse, ok := extractSAMLResponse(doc); ok {
			decodedSamlResponse, err := base64.StdEncoding.DecodeString(samlResponse)
			if err != nil {
				return "", errors.Wrap(err, "failed to decode saml-response")
			}
			logger.WithField("type", "saml-response").WithField("saml-response", string(decodedSamlResponse)).Debug("doc detect")
			return samlResponse, nil
		}
	} else if docIsFormSamlRequest(doc) {
		logger.WithField("type", "saml-request").Debug("doc detect")
		handler = ac.handleFormRedirect
	} else if docIsFormResume(doc) {
		logger.WithField("type", "resume").Debug("doc detect")
		handler = ac.handleFormRedirect
	} else if docIsLogin(doc) {
		logger.WithField("type", "login").Debug("doc detect")
		handler = ac.handleLogin
	} else if docIsCheckWebAuthn(doc) {
		logger.WithField("type", "check-webauthn").Debug("doc detect")
		handler = ac.handleCheckWebAuthn
	} else if docIsOTP(doc) {
		logger.WithField("type", "otp").Debug("doc detect")
		handler = ac.handleOTP
	} else if docIsSwipe(doc) {
		logger.WithField("type", "swipe").Debug("doc detect")
		handler = ac.handleSwipe
	} else if docIsFormRedirect(doc) {
		logger.WithField("type", "form-redirect").Debug("doc detect")
		handler = ac.handleFormRedirect
	}
	if handler == nil {
		html, _ := doc.Selection.Html()
		logger.WithField("doc", html).Debug("Unknown document type")
		return "", fmt.Errorf("Unknown document type")
	}

	ctx, req, err = handler(ctx, doc, res)
	if err != nil {
		return "", err
	}
	return ac.follow(ctx, req)
}

func (ac *Client) handleLogin(ctx context.Context, doc *goquery.Document, res *http.Response) (context.Context, *http.Request, error) {
	loginDetails, ok := ctx.Value(ctxKey("login")).(*creds.LoginDetails)
	if !ok {
		return ctx, nil, fmt.Errorf("no context value for 'login'")
	}

	form, err := page.NewFormFromDocument(doc, "form")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting login form")
	}

	baseURL := makeBaseURL(res.Request.URL)
	logger.WithField("baseURL", baseURL).Debug("base url")

	form.Values.Set("pf.username", loginDetails.Username)
	form.Values.Set("pf.pass", loginDetails.Password)
	form.URL = makeAbsoluteURL(form.URL, baseURL)

	req, err := form.BuildRequest()
	return ctx, req, err
}

func (ac *Client) handleCheckWebAuthn(ctx context.Context, doc *goquery.Document, res *http.Response) (context.Context, *http.Request, error) {
	form, err := page.NewFormFromDocument(doc, "form")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting login form")
	}

	form.Values.Set("isWebAuthnSupportedByBrowser", "false")

	req, err := form.BuildRequest()
	return ctx, req, err
}

func (ac *Client) handleOTP(ctx context.Context, doc *goquery.Document, _ *http.Response) (context.Context, *http.Request, error) {
	form, err := page.NewFormFromDocument(doc, "#otp-form")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting OTP form")
	}

	token := prompter.StringRequired("Enter passcode")
	form.Values.Set("otp", token)
	req, err := form.BuildRequest()
	return ctx, req, err
}

func (ac *Client) handleSwipe(ctx context.Context, doc *goquery.Document, _ *http.Response) (context.Context, *http.Request, error) {
	form, err := page.NewFormFromDocument(doc, "#form1")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting swipe status form")
	}

	// poll status. request must specifically be a GET
	form.Method = "GET"
	req, err := form.BuildRequest()
	if err != nil {
		return ctx, nil, err
	}

	for {
		time.Sleep(3 * time.Second)

		res, err := ac.client.Do(req)
		if err != nil {
			return ctx, nil, errors.Wrap(err, "error polling swipe status")
		}

		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return ctx, nil, errors.Wrap(err, "error parsing body from swipe status response")
		}

		resp := string(body)

		pingfedMFAStatusResponse := gjson.Get(resp, "status").String()

		//ASYNC_AUTH_WAIT indicates we keep going
		//OK indicates someone swiped
		//DEVICE_CLAIM_TIMEOUT indicates nobody swiped
		//otherwise loop forever?

		if pingfedMFAStatusResponse == "OK" || pingfedMFAStatusResponse == "DEVICE_CLAIM_TIMEOUT" || pingfedMFAStatusResponse == "TIMEOUT" {
			break
		}
	}

	// now build a request for getting response of MFA
	form, err = page.NewFormFromDocument(doc, "#reponseView")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting swipe response form")
	}
	req, err = form.BuildRequest()
	return ctx, req, err
}

func (ac *Client) handleFormRedirect(ctx context.Context, doc *goquery.Document, _ *http.Response) (context.Context, *http.Request, error) {
	form, err := page.NewFormFromDocument(doc, "")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting redirect form")
	}
	req, err := form.BuildRequest()
	return ctx, req, err
}

func (ac *Client) handleFormSamlRequest(ctx context.Context, doc *goquery.Document, _ *http.Response) (context.Context, *http.Request, error) {
	form, err := page.NewFormFromDocument(doc, "")
	if err != nil {
		return ctx, nil, errors.Wrap(err, "error extracting samlrequest form")
	}
	req, err := form.BuildRequest()
	return ctx, req, err
}

func docIsLogin(doc *goquery.Document) bool {
	return doc.Has("input[name=\"pf.pass\"]").Size() == 1
}

func docIsOTP(doc *goquery.Document) bool {
	return doc.Has("form#otp-form").Size() == 1
}

func docIsCheckWebAuthn(doc *goquery.Document) bool {
	return doc.Has("input[name=\"isWebAuthnSupportedByBrowser\"]").Size() == 1
}

func docIsSwipe(doc *goquery.Document) bool {
	return doc.Has("form#form1").Size() == 1 && doc.Has("form#reponseView").Size() == 1
}

func docIsFormRedirect(doc *goquery.Document) bool {
	return doc.Has("input[name=\"ppm_request\"]").Size() == 1
}

func docIsFormSamlRequest(doc *goquery.Document) bool {
	return doc.Find("input[name=\"SAMLRequest\"]").Size() == 1
}

func docIsFormResume(doc *goquery.Document) bool {
	return doc.Find("input[name=\"RelayState\"]").Size() == 1
}

func docIsFormRedirectToAWS(doc *goquery.Document) bool {
	return doc.Find("form[action=\"https://signin.aws.amazon.com/saml\"]").Size() == 1
}

func extractSAMLResponse(doc *goquery.Document) (v string, ok bool) {
	return doc.Find("input[name=\"SAMLResponse\"]").Attr("value")
}

func makeBaseURL(url *url.URL) string {
	return url.Scheme + "://" + url.Hostname()
}

// ensures given url is an absolute URL. if not, it will be combined with the base URL
func makeAbsoluteURL(v string, base string) string {
	logger.WithField("base", base).WithField("v", v).Debug("make absolute url")
	if u, err := url.ParseRequestURI(v); err == nil && !u.IsAbs() {
		return fmt.Sprintf("%s%s", base, v)
	}
	return v
}

<|endoftext|>