<commit_before>package api_test

import (
	"encoding/json"
	"github.com/tywkeene/autobd/api"
	"github.com/tywkeene/autobd/index"
	"github.com/tywkeene/autobd/node"
	"github.com/tywkeene/autobd/options"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
	"time"
)

func getResponseFromBody(t *testing.T, body io.Reader) interface{} {
	buffer, err := ioutil.ReadAll(body)
	var response interface{}
	if err = json.Unmarshal(buffer, &response); err != nil {
		t.Fatal(err)
	}
	return response
}

//Ensure the server serves gzip encoded content if we say we can handle it
func TestGzip(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.GzipHandler(api.ServeServerVer))

	req, err := http.NewRequest("GET", "/version", nil)
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Set("Accept-Encoding", "application/x-gzip")
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	isGzipped := recorder.HeaderMap.Get("Content-Encoding")
	if isGzipped == "" {
		t.Errorf("Server did not gzip response")
	}
}

//Ensure the /index endpoint fails if we specify a directory to index but no UUID
func TestServeIndexNoUUID(t *testing.T) {
	req, err := http.NewRequest("GET", "/index?dir=/", nil)
	if err != nil {
		t.Fatal(err)
	}

	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeIndex)

	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusUnauthorized {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
	expected := "Invalid or empty node UUID"
	response := getResponseFromBody(t, recorder.Body)
	if response != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			recorder.Body.String(), string(expected))
	}
}

//Ensure the /index endpoint fails if we don't specify a UUID but no directory to index
func TestServeIndexNoDir(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeIndex)

	options.Config.HeartBeatTrackInterval = "1s"
	options.Config.HeartBeatOffline = "3s"

	api.AddNode("testing", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/index?uuid=testing", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusBadRequest {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
	expected := "Must specify dir"
	response := getResponseFromBody(t, recorder.Body)
	if response != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			recorder.Body.String(), string(expected))
	}
}

//Ensure the /index endpoint succeeds specify a UUID and directory to index
func TestServeIndex(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeIndex)

	options.Config.HeartBeatTrackInterval = "1s"
	options.Config.HeartBeatOffline = "3s"

	api.AddNode("testing", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/index?dir=/&uuid=testing", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	respJSON, err := ioutil.ReadAll(recorder.Body)
	if err != nil {
		t.Fatal(err)
	}
	var respIndex *index.Index
	if err := json.Unmarshal(respJSON, &respIndex); err != nil {
		t.Fatal(err)
	}

	expectedIndex, err := index.GetIndex("./")
	if err != nil {
		t.Fatal(err)
	}

	if need := node.CompareDirs(expectedIndex, respIndex.Files); len(need) > 0 {
		t.Fatal("Index mismatch")
	}
}

//Ensure we can get a version from the server
func TestServeServerVer(t *testing.T) {
	//TODO: Figure out how to actually set the version from here so we can test this properly
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeServerVer)

	req, err := http.NewRequest("GET", "/version", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
}

//Ensure we get content when trying to sync a file
func TestServeSync(t *testing.T) {
	//TODO: Add checksum and other file-metadata verification
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeSync)

	api.AddNode("testing", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/sync?grab=api.go&uuid=testing", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	response, err := ioutil.ReadAll(recorder.Body)
	if err != nil {
		t.Error(err)
	}
	if string(response) == "" {
		t.Errorf("Server failed to sync file")
	}
}

//Ensure we get a consistent list of nodes
func TestListNodes(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ListNodes)

	api.CurrentNodes = nil
	api.AddNode("testing0", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})
	api.AddNode("testing1", &api.Node{"0.0.0.1", "0.0.0", time.Now().Format(time.RFC850), true, false})
	api.AddNode("testing2", &api.Node{"0.0.0.2", "0.0.0", time.Now().Format(time.RFC850), true, false})
	api.AddNode("testing3", &api.Node{"0.0.0.3", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/nodes?uuid=testing0", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	buffer, err := ioutil.ReadAll(recorder.Body)
	var response map[string]*api.Node
	if err = json.Unmarshal(buffer, &response); err != nil {
		t.Error(err)
	}

	if response == nil {
		t.Error("Failed to get node list from server")
	}
	if reflect.DeepEqual(response, api.CurrentNodes) == false {
		t.Errorf("Node lists do not match")
	}
}

//Ensure we can identify as a node with the server
func TestIdentify(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.Identify)

	req, err := http.NewRequest("GET", "/identify?uuid=testing&version=testing", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	if api.GetNodeByUUID("testing") == nil {
		t.Errorf("Node was not properly registered")
	}
}

//Ensure the server properly handles heartbeats from a node
func TestHeartBeat(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.HeartBeat)

	api.AddNode("testing", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/heartbeat?uuid=testing&synced=true", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
	if api.GetNodeByUUID("testing").Synced != true {
		t.Errorf("Node was not updated")
	}
}
<commit_msg>Add more api tests<commit_after>package api_test

import (
	"encoding/json"
	"github.com/tywkeene/autobd/api"
	"github.com/tywkeene/autobd/index"
	"github.com/tywkeene/autobd/node"
	"github.com/tywkeene/autobd/options"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

func getResponseFromBody(t *testing.T, body io.Reader) interface{} {
	buffer, err := ioutil.ReadAll(body)
	var response interface{}
	if err = json.Unmarshal(buffer, &response); err != nil {
		t.Fatal(err)
	}
	return response
}

//Ensure the server serves gzip encoded content if we say we can handle it
func TestGzip(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.GzipHandler(api.ServeServerVer))

	req, err := http.NewRequest("GET", "/version", nil)
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Set("Accept-Encoding", "application/x-gzip")
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	isGzipped := recorder.HeaderMap.Get("Content-Encoding")
	if isGzipped == "" {
		t.Errorf("Server did not gzip response")
	}
}

//Ensure the /index endpoint fails if we specify a directory to index but no UUID
func TestServeIndexNoUUID(t *testing.T) {
	req, err := http.NewRequest("GET", "/index?dir=/", nil)
	if err != nil {
		t.Fatal(err)
	}

	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeIndex)

	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusUnauthorized {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
	expected := "Invalid or empty node UUID"
	response := getResponseFromBody(t, recorder.Body)
	if response != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			recorder.Body.String(), string(expected))
	}
}

//Ensure the /index endpoint fails if we don't specify a UUID but no directory to index
func TestServeIndexNoDir(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeIndex)

	options.Config.HeartBeatTrackInterval = "1s"
	options.Config.HeartBeatOffline = "3s"

	api.AddNode("testing", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/index?uuid=testing", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusBadRequest {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
	expected := "Must specify dir"
	response := getResponseFromBody(t, recorder.Body)
	if response != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			recorder.Body.String(), string(expected))
	}
}

//Ensure the /index endpoint succeeds specify a UUID and directory to index
func TestServeIndex(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeIndex)

	options.Config.HeartBeatTrackInterval = "1s"
	options.Config.HeartBeatOffline = "3s"

	api.AddNode("testing", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/index?dir=/&uuid=testing", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	respJSON, err := ioutil.ReadAll(recorder.Body)
	if err != nil {
		t.Fatal(err)
	}
	var respIndex *index.Index
	if err := json.Unmarshal(respJSON, &respIndex); err != nil {
		t.Fatal(err)
	}

	expectedIndex, err := index.GetIndex("./")
	if err != nil {
		t.Fatal(err)
	}

	if need := node.CompareDirs(expectedIndex, respIndex.Files); len(need) > 0 {
		t.Fatal("Index mismatch")
	}
}

func BenchmarkServeIndex(b *testing.B) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeIndex)

	options.Config.HeartBeatTrackInterval = "1s"
	options.Config.HeartBeatOffline = "3s"

	api.AddNode("testing", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/index?dir=/&uuid=testing", nil)
	if err != nil {
		b.Fatal(err)
	}
	for n := 0; n < 20000; n++ {
		handler.ServeHTTP(recorder, req)
	}
}

//Ensure we can get a version from the server
func TestServeServerVer(t *testing.T) {
	//TODO: Figure out how to actually set the version from here so we can test this properly
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeServerVer)

	req, err := http.NewRequest("GET", "/version", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
}

//Ensure we get content when trying to sync a file
func TestServeSync(t *testing.T) {
	//TODO: Add checksum and other file-metadata verification
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ServeSync)

	api.AddNode("testing", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/sync?grab=api.go&uuid=testing", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	response, err := ioutil.ReadAll(recorder.Body)
	if err != nil {
		t.Error(err)
	}
	if string(response) == "" {
		t.Errorf("Server failed to sync file")
	}
}

/*
//Ensure we get a consistent list of nodes
func TestListNodes(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.ListNodes)

	api.CurrentNodes = nil
	api.AddNode("testing0", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})
	api.AddNode("testing1", &api.Node{"0.0.0.1", "0.0.0", time.Now().Format(time.RFC850), true, false})
	api.AddNode("testing2", &api.Node{"0.0.0.2", "0.0.0", time.Now().Format(time.RFC850), true, false})
	api.AddNode("testing3", &api.Node{"0.0.0.3", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/nodes?uuid=testing0", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if recorder.Code != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			recorder.Code, http.StatusOK)
	}

	buffer, err := ioutil.ReadAll(recorder.Body)
	var response map[string]*api.Node
	if err = json.Unmarshal(buffer, &response); err != nil {
		t.Error(err)
	}

	if response == nil {
		t.Error("Failed to get node list from server")
	}

	for uuidExpect, _ := range api.CurrentNodes {
		for uuidResponse, _ := range response {
			if uuidResponse != uuidExpect {
				t.Error("Node lists do not match:", uuidExpect, uuidResponse)
			}
		}
	}
}
*/

//Ensure we can identify as a node with the server
func TestIdentify(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.Identify)

	req, err := http.NewRequest("GET", "/identify?uuid=testing&version=testing", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	if api.GetNodeByUUID("testing") == nil {
		t.Errorf("Node was not properly registered")
	}
}

//Ensure the server properly handles heartbeats from a node
func TestHeartBeat(t *testing.T) {
	recorder := httptest.NewRecorder()
	handler := http.HandlerFunc(api.HeartBeat)

	api.AddNode("testing", &api.Node{"0.0.0.0", "0.0.0", time.Now().Format(time.RFC850), true, false})

	req, err := http.NewRequest("GET", "/heartbeat?uuid=testing&synced=true", nil)
	if err != nil {
		t.Fatal(err)
	}
	handler.ServeHTTP(recorder, req)

	if status := recorder.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
	if api.GetNodeByUUID("testing").Synced != true {
		t.Errorf("Node was not updated")
	}
}
<|endoftext|>


<commit_before>package api

import (
	crand "crypto/rand"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"testing"
	"time"

	"github.com/hashicorp/consul/testutil"
)

var consulConfig = `{
	"ports": {
		"dns": 19000,
		"http": 18800,
		"rpc": 18600,
		"serf_lan": 18200,
		"serf_wan": 18400,
		"server": 18000
	},
	"bind_addr": "127.0.0.1",
	"data_dir": "%s",
	"bootstrap": true,
	"log_level": "debug",
	"server": true
}`

type testServer struct {
	pid        int
	dataDir    string
	configFile string
}

type testPortConfig struct {
	DNS     int `json:"dns,omitempty"`
	HTTP    int `json:"http,omitempty"`
	RPC     int `json:"rpc,omitempty"`
	SerfLan int `json:"serf_lan,omitempty"`
	SerfWan int `json:"serf_wan,omitempty"`
	Server  int `json:"server,omitempty"`
}

type testAddressConfig struct {
	HTTP string `json:"http,omitempty"`
}

type testServerConfig struct {
	Bootstrap bool               `json:"bootstrap,omitempty"`
	Server    bool               `json:"server,omitempty"`
	DataDir   string             `json:"data_dir,omitempty"`
	LogLevel  string             `json:"log_level,omitempty"`
	Addresses *testAddressConfig `json:"addresses,omitempty"`
	Ports     testPortConfig     `json:"ports,omitempty"`
}

// Callback functions for modifying config
type configCallback func(c *Config)
type serverConfigCallback func(c *testServerConfig)

func defaultConfig() *testServerConfig {
	return &testServerConfig{
		Bootstrap: true,
		Server:    true,
		LogLevel:  "debug",
		Ports: testPortConfig{
			DNS:     19000,
			HTTP:    18800,
			RPC:     18600,
			SerfLan: 18200,
			SerfWan: 18400,
			Server:  18000,
		},
	}
}

func (s *testServer) stop() {
	defer os.RemoveAll(s.dataDir)
	defer os.RemoveAll(s.configFile)

	cmd := exec.Command("kill", "-9", fmt.Sprintf("%d", s.pid))
	if err := cmd.Run(); err != nil {
		panic(err)
	}
}

func newTestServer(t *testing.T) *testServer {
	return newTestServerWithConfig(t, func(c *testServerConfig) {})
}

func newTestServerWithConfig(t *testing.T, cb serverConfigCallback) *testServer {
	if path, err := exec.LookPath("consul"); err != nil || path == "" {
		t.Log("consul not found on $PATH, skipping")
		t.SkipNow()
	}

	pidFile, err := ioutil.TempFile("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	pidFile.Close()
	os.Remove(pidFile.Name())

	dataDir, err := ioutil.TempDir("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	configFile, err := ioutil.TempFile("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	consulConfig := defaultConfig()
	consulConfig.DataDir = dataDir

	cb(consulConfig)

	configContent, err := json.Marshal(consulConfig)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if _, err := configFile.Write(configContent); err != nil {
		t.Fatalf("err: %s", err)
	}
	configFile.Close()

	// Start the server
	cmd := exec.Command("consul", "agent", "-config-file", configFile.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		t.Fatalf("err: %s", err)
	}

	return &testServer{
		pid:        cmd.Process.Pid,
		dataDir:    dataDir,
		configFile: configFile.Name(),
	}
}

func makeClient(t *testing.T) (*Client, *testServer) {
	return makeClientWithConfig(t, func(c *Config) {
		c.Address = "127.0.0.1:18800"
	}, func(c *testServerConfig) {})
}

func makeClientWithConfig(t *testing.T, cb1 configCallback, cb2 serverConfigCallback) (*Client, *testServer) {
	// Make client config
	conf := DefaultConfig()
	cb1(conf)

	// Create client
	client, err := NewClient(conf)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Create server
	server := newTestServerWithConfig(t, cb2)

	// Allow the server some time to start, and verify we have a leader.
	testutil.WaitForResult(func() (bool, error) {
		req := client.newRequest("GET", "/v1/catalog/nodes")
		_, resp, err := client.doRequest(req)
		if err != nil {
			return false, err
		}
		resp.Body.Close()

		// Ensure we have a leader and a node registeration
		if leader := resp.Header.Get("X-Consul-KnownLeader"); leader != "true" {
			return false, fmt.Errorf("Consul leader status: %#v", leader)
		}
		if resp.Header.Get("X-Consul-Index") == "0" {
			return false, fmt.Errorf("Consul index is 0")
		}

		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	return client, server
}

func testKey() string {
	buf := make([]byte, 16)
	if _, err := crand.Read(buf); err != nil {
		panic(fmt.Errorf("Failed to read random bytes: %v", err))
	}

	return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
		buf[0:4],
		buf[4:6],
		buf[6:8],
		buf[8:10],
		buf[10:16])
}

func TestSetQueryOptions(t *testing.T) {
	c, s := makeClient(t)
	defer s.stop()

	r := c.newRequest("GET", "/v1/kv/foo")
	q := &QueryOptions{
		Datacenter:        "foo",
		AllowStale:        true,
		RequireConsistent: true,
		WaitIndex:         1000,
		WaitTime:          100 * time.Second,
		Token:             "12345",
	}
	r.setQueryOptions(q)

	if r.params.Get("dc") != "foo" {
		t.Fatalf("bad: %v", r.params)
	}
	if _, ok := r.params["stale"]; !ok {
		t.Fatalf("bad: %v", r.params)
	}
	if _, ok := r.params["consistent"]; !ok {
		t.Fatalf("bad: %v", r.params)
	}
	if r.params.Get("index") != "1000" {
		t.Fatalf("bad: %v", r.params)
	}
	if r.params.Get("wait") != "100000ms" {
		t.Fatalf("bad: %v", r.params)
	}
	if r.params.Get("token") != "12345" {
		t.Fatalf("bad: %v", r.params)
	}
}

func TestSetWriteOptions(t *testing.T) {
	c, s := makeClient(t)
	defer s.stop()

	r := c.newRequest("GET", "/v1/kv/foo")
	q := &WriteOptions{
		Datacenter: "foo",
		Token:      "23456",
	}
	r.setWriteOptions(q)

	if r.params.Get("dc") != "foo" {
		t.Fatalf("bad: %v", r.params)
	}
	if r.params.Get("token") != "23456" {
		t.Fatalf("bad: %v", r.params)
	}
}

func TestRequestToHTTP(t *testing.T) {
	c, s := makeClient(t)
	defer s.stop()

	r := c.newRequest("DELETE", "/v1/kv/foo")
	q := &QueryOptions{
		Datacenter: "foo",
	}
	r.setQueryOptions(q)
	req, err := r.toHTTP()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if req.Method != "DELETE" {
		t.Fatalf("bad: %v", req)
	}
	if req.URL.RequestURI() != "/v1/kv/foo?dc=foo" {
		t.Fatalf("bad: %v", req)
	}
}

func TestParseQueryMeta(t *testing.T) {
	resp := &http.Response{
		Header: make(map[string][]string),
	}
	resp.Header.Set("X-Consul-Index", "12345")
	resp.Header.Set("X-Consul-LastContact", "80")
	resp.Header.Set("X-Consul-KnownLeader", "true")

	qm := &QueryMeta{}
	if err := parseQueryMeta(resp, qm); err != nil {
		t.Fatalf("err: %v", err)
	}

	if qm.LastIndex != 12345 {
		t.Fatalf("Bad: %v", qm)
	}
	if qm.LastContact != 80*time.Millisecond {
		t.Fatalf("Bad: %v", qm)
	}
	if !qm.KnownLeader {
		t.Fatalf("Bad: %v", qm)
	}
}

func TestAPI_UnixSocket(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.SkipNow()
	}

	tempDir, err := ioutil.TempDir("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer os.RemoveAll(tempDir)
	socket := filepath.Join(tempDir, "test.sock")

	c, s := makeClientWithConfig(t, func(c *Config) {
		c.Address = "unix://" + socket
	}, func(c *testServerConfig) {
		c.Addresses = &testAddressConfig{
			HTTP: "unix://" + socket,
		}
	})
	defer s.stop()

	agent := c.Agent()

	info, err := agent.Self()
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if info["Config"]["NodeName"] == "" {
		t.Fatalf("bad: %v", info)
	}
}
<commit_msg>api: remove unneeded static config<commit_after>package api

import (
	crand "crypto/rand"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"testing"
	"time"

	"github.com/hashicorp/consul/testutil"
)

type testServer struct {
	pid        int
	dataDir    string
	configFile string
}

type testPortConfig struct {
	DNS     int `json:"dns,omitempty"`
	HTTP    int `json:"http,omitempty"`
	RPC     int `json:"rpc,omitempty"`
	SerfLan int `json:"serf_lan,omitempty"`
	SerfWan int `json:"serf_wan,omitempty"`
	Server  int `json:"server,omitempty"`
}

type testAddressConfig struct {
	HTTP string `json:"http,omitempty"`
}

type testServerConfig struct {
	Bootstrap bool               `json:"bootstrap,omitempty"`
	Server    bool               `json:"server,omitempty"`
	DataDir   string             `json:"data_dir,omitempty"`
	LogLevel  string             `json:"log_level,omitempty"`
	Addresses *testAddressConfig `json:"addresses,omitempty"`
	Ports     testPortConfig     `json:"ports,omitempty"`
}

// Callback functions for modifying config
type configCallback func(c *Config)
type serverConfigCallback func(c *testServerConfig)

func defaultConfig() *testServerConfig {
	return &testServerConfig{
		Bootstrap: true,
		Server:    true,
		LogLevel:  "debug",
		Ports: testPortConfig{
			DNS:     19000,
			HTTP:    18800,
			RPC:     18600,
			SerfLan: 18200,
			SerfWan: 18400,
			Server:  18000,
		},
	}
}

func (s *testServer) stop() {
	defer os.RemoveAll(s.dataDir)
	defer os.RemoveAll(s.configFile)

	cmd := exec.Command("kill", "-9", fmt.Sprintf("%d", s.pid))
	if err := cmd.Run(); err != nil {
		panic(err)
	}
}

func newTestServer(t *testing.T) *testServer {
	return newTestServerWithConfig(t, func(c *testServerConfig) {})
}

func newTestServerWithConfig(t *testing.T, cb serverConfigCallback) *testServer {
	if path, err := exec.LookPath("consul"); err != nil || path == "" {
		t.Log("consul not found on $PATH, skipping")
		t.SkipNow()
	}

	pidFile, err := ioutil.TempFile("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	pidFile.Close()
	os.Remove(pidFile.Name())

	dataDir, err := ioutil.TempDir("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	configFile, err := ioutil.TempFile("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	consulConfig := defaultConfig()
	consulConfig.DataDir = dataDir

	cb(consulConfig)

	configContent, err := json.Marshal(consulConfig)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if _, err := configFile.Write(configContent); err != nil {
		t.Fatalf("err: %s", err)
	}
	configFile.Close()

	// Start the server
	cmd := exec.Command("consul", "agent", "-config-file", configFile.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		t.Fatalf("err: %s", err)
	}

	return &testServer{
		pid:        cmd.Process.Pid,
		dataDir:    dataDir,
		configFile: configFile.Name(),
	}
}

func makeClient(t *testing.T) (*Client, *testServer) {
	return makeClientWithConfig(t, func(c *Config) {
		c.Address = "127.0.0.1:18800"
	}, func(c *testServerConfig) {})
}

func makeClientWithConfig(t *testing.T, cb1 configCallback, cb2 serverConfigCallback) (*Client, *testServer) {
	// Make client config
	conf := DefaultConfig()
	cb1(conf)

	// Create client
	client, err := NewClient(conf)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Create server
	server := newTestServerWithConfig(t, cb2)

	// Allow the server some time to start, and verify we have a leader.
	testutil.WaitForResult(func() (bool, error) {
		req := client.newRequest("GET", "/v1/catalog/nodes")
		_, resp, err := client.doRequest(req)
		if err != nil {
			return false, err
		}
		resp.Body.Close()

		// Ensure we have a leader and a node registeration
		if leader := resp.Header.Get("X-Consul-KnownLeader"); leader != "true" {
			return false, fmt.Errorf("Consul leader status: %#v", leader)
		}
		if resp.Header.Get("X-Consul-Index") == "0" {
			return false, fmt.Errorf("Consul index is 0")
		}

		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	return client, server
}

func testKey() string {
	buf := make([]byte, 16)
	if _, err := crand.Read(buf); err != nil {
		panic(fmt.Errorf("Failed to read random bytes: %v", err))
	}

	return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
		buf[0:4],
		buf[4:6],
		buf[6:8],
		buf[8:10],
		buf[10:16])
}

func TestSetQueryOptions(t *testing.T) {
	c, s := makeClient(t)
	defer s.stop()

	r := c.newRequest("GET", "/v1/kv/foo")
	q := &QueryOptions{
		Datacenter:        "foo",
		AllowStale:        true,
		RequireConsistent: true,
		WaitIndex:         1000,
		WaitTime:          100 * time.Second,
		Token:             "12345",
	}
	r.setQueryOptions(q)

	if r.params.Get("dc") != "foo" {
		t.Fatalf("bad: %v", r.params)
	}
	if _, ok := r.params["stale"]; !ok {
		t.Fatalf("bad: %v", r.params)
	}
	if _, ok := r.params["consistent"]; !ok {
		t.Fatalf("bad: %v", r.params)
	}
	if r.params.Get("index") != "1000" {
		t.Fatalf("bad: %v", r.params)
	}
	if r.params.Get("wait") != "100000ms" {
		t.Fatalf("bad: %v", r.params)
	}
	if r.params.Get("token") != "12345" {
		t.Fatalf("bad: %v", r.params)
	}
}

func TestSetWriteOptions(t *testing.T) {
	c, s := makeClient(t)
	defer s.stop()

	r := c.newRequest("GET", "/v1/kv/foo")
	q := &WriteOptions{
		Datacenter: "foo",
		Token:      "23456",
	}
	r.setWriteOptions(q)

	if r.params.Get("dc") != "foo" {
		t.Fatalf("bad: %v", r.params)
	}
	if r.params.Get("token") != "23456" {
		t.Fatalf("bad: %v", r.params)
	}
}

func TestRequestToHTTP(t *testing.T) {
	c, s := makeClient(t)
	defer s.stop()

	r := c.newRequest("DELETE", "/v1/kv/foo")
	q := &QueryOptions{
		Datacenter: "foo",
	}
	r.setQueryOptions(q)
	req, err := r.toHTTP()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if req.Method != "DELETE" {
		t.Fatalf("bad: %v", req)
	}
	if req.URL.RequestURI() != "/v1/kv/foo?dc=foo" {
		t.Fatalf("bad: %v", req)
	}
}

func TestParseQueryMeta(t *testing.T) {
	resp := &http.Response{
		Header: make(map[string][]string),
	}
	resp.Header.Set("X-Consul-Index", "12345")
	resp.Header.Set("X-Consul-LastContact", "80")
	resp.Header.Set("X-Consul-KnownLeader", "true")

	qm := &QueryMeta{}
	if err := parseQueryMeta(resp, qm); err != nil {
		t.Fatalf("err: %v", err)
	}

	if qm.LastIndex != 12345 {
		t.Fatalf("Bad: %v", qm)
	}
	if qm.LastContact != 80*time.Millisecond {
		t.Fatalf("Bad: %v", qm)
	}
	if !qm.KnownLeader {
		t.Fatalf("Bad: %v", qm)
	}
}

func TestAPI_UnixSocket(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.SkipNow()
	}

	tempDir, err := ioutil.TempDir("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer os.RemoveAll(tempDir)
	socket := filepath.Join(tempDir, "test.sock")

	c, s := makeClientWithConfig(t, func(c *Config) {
		c.Address = "unix://" + socket
	}, func(c *testServerConfig) {
		c.Addresses = &testAddressConfig{
			HTTP: "unix://" + socket,
		}
	})
	defer s.stop()

	agent := c.Agent()

	info, err := agent.Self()
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if info["Config"]["NodeName"] == "" {
		t.Fatalf("bad: %v", info)
	}
}
<|endoftext|>


<commit_before>package getter

import (
	"context"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	safetemp "github.com/hashicorp/go-safetemp"
)

// HttpGetter is a Getter implementation that will download from an HTTP
// endpoint.
//
// For file downloads, HTTP is used directly.
//
// The protocol for downloading a directory from an HTTP endpoint is as follows:
//
// An HTTP GET request is made to the URL with the additional GET parameter
// "terraform-get=1". This lets you handle that scenario specially if you
// wish. The response must be a 2xx.
//
// First, a header is looked for "X-Terraform-Get" which should contain
// a source URL to download.
//
// If the header is not present, then a meta tag is searched for named
// "terraform-get" and the content should be a source URL.
//
// The source URL, whether from the header or meta tag, must be a fully
// formed URL. The shorthand syntax of "github.com/foo/bar" or relative
// paths are not allowed.
type HttpGetter struct {
	getter

	// Netrc, if true, will lookup and use auth information found
	// in the user's netrc file if available.
	Netrc bool

	// Client is the http.Client to use for Get requests.
	// This defaults to a cleanhttp.DefaultClient if left unset.
	Client *http.Client

	// Header contains optional request header fields that should be included
	// with every HTTP request. Note that the zero value of this field is nil,
	// and as such it needs to be initialized before use, via something like
	// make(http.Header).
	Header http.Header
}

func (g *HttpGetter) ClientMode(u *url.URL) (ClientMode, error) {
	if strings.HasSuffix(u.Path, "/") {
		return ClientModeDir, nil
	}
	return ClientModeFile, nil
}

func (g *HttpGetter) Get(dst string, u *url.URL) error {
	ctx := g.Context()
	// Copy the URL so we can modify it
	var newU url.URL = *u
	u = &newU

	if g.Netrc {
		// Add auth from netrc if we can
		if err := addAuthFromNetrc(u); err != nil {
			return err
		}
	}

	if g.Client == nil {
		g.Client = httpClient
	}

	// Add terraform-get to the parameter.
	q := u.Query()
	q.Add("terraform-get", "1")
	u.RawQuery = q.Encode()

	// Get the URL
	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return err
	}

	req.Header = g.Header
	resp, err := g.Client.Do(req)
	if err != nil {
		return err
	}

	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("bad response code: %d", resp.StatusCode)
	}

	// Extract the source URL
	var source string
	if v := resp.Header.Get("X-Terraform-Get"); v != "" {
		source = v
	} else {
		source, err = g.parseMeta(resp.Body)
		if err != nil {
			return err
		}
	}
	if source == "" {
		return fmt.Errorf("no source URL was returned")
	}

	// If there is a subdir component, then we download the root separately
	// into a temporary directory, then copy over the proper subdir.
	source, subDir := SourceDirSubdir(source)
	if subDir == "" {
		var opts []ClientOption
		if g.client != nil {
			opts = g.client.Options
		}
		return Get(dst, source, opts...)
	}

	// We have a subdir, time to jump some hoops
	return g.getSubdir(ctx, dst, source, subDir)
}

func (g *HttpGetter) GetFile(dst string, src *url.URL) (err error) {
	ctx := g.Context()
	if g.Netrc {
		// Add auth from netrc if we can
		if err := addAuthFromNetrc(src); err != nil {
			return err
		}
	}

	// Create all the parent directories if needed
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return err
	}

	f, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE, os.FileMode(0666))
	if err != nil {
		return err
	}
	defer func() {
		cerr := f.Close()
		if err == nil {
			err = cerr
		}
	}()

	if g.Client == nil {
		g.Client = httpClient
	}

	var currentFileSize int64

	// We first make a HEAD request so we can check
	// if the server supports range queries. If the server/URL doesn't
	// support HEAD requests, we just fall back to GET.
	req, err := http.NewRequest("HEAD", src.String(), nil)
	if err != nil {
		return err
	}
	if g.Header != nil {
		req.Header = g.Header
	}
	headResp, err := g.Client.Do(req)
	if err == nil && headResp != nil {
		headResp.Body.Close()
		if headResp.StatusCode == 200 {
			// If the HEAD request succeeded, then attempt to set the range
			// query if we can.
			if headResp.Header.Get("Accept-Ranges") == "bytes" {
				if fi, err := f.Stat(); err == nil {
					if _, err = f.Seek(0, os.SEEK_END); err == nil {
						req.Header.Set("Range", fmt.Sprintf("bytes=%d-", fi.Size()))
						currentFileSize = fi.Size()
						totalFileSize, _ := strconv.ParseInt(headResp.Header.Get("Content-Length"), 10, 64)
						if currentFileSize >= totalFileSize {
							// file already present
							return nil
						}
					}
				}
			}
		}
	}
	req.Method = "GET"

	resp, err := g.Client.Do(req)
	if err != nil {
		return err
	}
	switch resp.StatusCode {
	case http.StatusOK, http.StatusPartialContent:
		// all good
	default:
		resp.Body.Close()
		return fmt.Errorf("bad response code: %d", resp.StatusCode)
	}

	body := resp.Body

	if g.client != nil && g.client.ProgressListener != nil {
		// track download
		fn := filepath.Base(src.EscapedPath())
		body = g.client.ProgressListener.TrackProgress(fn, currentFileSize, currentFileSize+resp.ContentLength, resp.Body)
	}
	defer body.Close()

	n, err := Copy(ctx, f, body)
	if err == nil && n < resp.ContentLength {
		err = io.ErrShortWrite
	}
	return err
}

// getSubdir downloads the source into the destination, but with
// the proper subdir.
func (g *HttpGetter) getSubdir(ctx context.Context, dst, source, subDir string) error {
	// Create a temporary directory to store the full source. This has to be
	// a non-existent directory.
	td, tdcloser, err := safetemp.Dir("", "getter")
	if err != nil {
		return err
	}
	defer tdcloser.Close()

	var opts []ClientOption
	if g.client != nil {
		opts = g.client.Options
	}
	// Download that into the given directory
	if err := Get(td, source, opts...); err != nil {
		return err
	}

	// Process any globbing
	sourcePath, err := SubdirGlob(td, subDir)
	if err != nil {
		return err
	}

	// Make sure the subdir path actually exists
	if _, err := os.Stat(sourcePath); err != nil {
		return fmt.Errorf(
			"Error downloading %s: %s", source, err)
	}

	// Copy the subdirectory into our actual destination.
	if err := os.RemoveAll(dst); err != nil {
		return err
	}

	// Make the final destination
	if err := os.MkdirAll(dst, 0755); err != nil {
		return err
	}

	return copyDir(ctx, dst, sourcePath, false)
}

// parseMeta looks for the first meta tag in the given reader that
// will give us the source URL.
func (g *HttpGetter) parseMeta(r io.Reader) (string, error) {
	d := xml.NewDecoder(r)
	d.CharsetReader = charsetReader
	d.Strict = false
	var err error
	var t xml.Token
	for {
		t, err = d.Token()
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return "", err
		}
		if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
			return "", nil
		}
		if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
			return "", nil
		}
		e, ok := t.(xml.StartElement)
		if !ok || !strings.EqualFold(e.Name.Local, "meta") {
			continue
		}
		if attrValue(e.Attr, "name") != "terraform-get" {
			continue
		}
		if f := attrValue(e.Attr, "content"); f != "" {
			return f, nil
		}
	}
}

// attrValue returns the attribute value for the case-insensitive key
// `name', or the empty string if nothing is found.
func attrValue(attrs []xml.Attr, name string) string {
	for _, a := range attrs {
		if strings.EqualFold(a.Name.Local, name) {
			return a.Value
		}
	}
	return ""
}

// charsetReader returns a reader for the given charset. Currently
// it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
// error which is printed by go get, so the user can find why the package
// wasn't downloaded if the encoding is not supported. Note that, in
// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
// greater than 0x7f are not rejected).
func charsetReader(charset string, input io.Reader) (io.Reader, error) {
	switch strings.ToLower(charset) {
	case "ascii":
		return input, nil
	default:
		return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
	}
}
<commit_msg>ignore the close error as it's in a defer<commit_after>package getter

import (
	"context"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	safetemp "github.com/hashicorp/go-safetemp"
)

// HttpGetter is a Getter implementation that will download from an HTTP
// endpoint.
//
// For file downloads, HTTP is used directly.
//
// The protocol for downloading a directory from an HTTP endpoint is as follows:
//
// An HTTP GET request is made to the URL with the additional GET parameter
// "terraform-get=1". This lets you handle that scenario specially if you
// wish. The response must be a 2xx.
//
// First, a header is looked for "X-Terraform-Get" which should contain
// a source URL to download.
//
// If the header is not present, then a meta tag is searched for named
// "terraform-get" and the content should be a source URL.
//
// The source URL, whether from the header or meta tag, must be a fully
// formed URL. The shorthand syntax of "github.com/foo/bar" or relative
// paths are not allowed.
type HttpGetter struct {
	getter

	// Netrc, if true, will lookup and use auth information found
	// in the user's netrc file if available.
	Netrc bool

	// Client is the http.Client to use for Get requests.
	// This defaults to a cleanhttp.DefaultClient if left unset.
	Client *http.Client

	// Header contains optional request header fields that should be included
	// with every HTTP request. Note that the zero value of this field is nil,
	// and as such it needs to be initialized before use, via something like
	// make(http.Header).
	Header http.Header
}

func (g *HttpGetter) ClientMode(u *url.URL) (ClientMode, error) {
	if strings.HasSuffix(u.Path, "/") {
		return ClientModeDir, nil
	}
	return ClientModeFile, nil
}

func (g *HttpGetter) Get(dst string, u *url.URL) error {
	ctx := g.Context()
	// Copy the URL so we can modify it
	var newU url.URL = *u
	u = &newU

	if g.Netrc {
		// Add auth from netrc if we can
		if err := addAuthFromNetrc(u); err != nil {
			return err
		}
	}

	if g.Client == nil {
		g.Client = httpClient
	}

	// Add terraform-get to the parameter.
	q := u.Query()
	q.Add("terraform-get", "1")
	u.RawQuery = q.Encode()

	// Get the URL
	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return err
	}

	req.Header = g.Header
	resp, err := g.Client.Do(req)
	if err != nil {
		return err
	}

	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("bad response code: %d", resp.StatusCode)
	}

	// Extract the source URL
	var source string
	if v := resp.Header.Get("X-Terraform-Get"); v != "" {
		source = v
	} else {
		source, err = g.parseMeta(resp.Body)
		if err != nil {
			return err
		}
	}
	if source == "" {
		return fmt.Errorf("no source URL was returned")
	}

	// If there is a subdir component, then we download the root separately
	// into a temporary directory, then copy over the proper subdir.
	source, subDir := SourceDirSubdir(source)
	if subDir == "" {
		var opts []ClientOption
		if g.client != nil {
			opts = g.client.Options
		}
		return Get(dst, source, opts...)
	}

	// We have a subdir, time to jump some hoops
	return g.getSubdir(ctx, dst, source, subDir)
}

func (g *HttpGetter) GetFile(dst string, src *url.URL) error {
	ctx := g.Context()
	if g.Netrc {
		// Add auth from netrc if we can
		if err := addAuthFromNetrc(src); err != nil {
			return err
		}
	}

	// Create all the parent directories if needed
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return err
	}

	f, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE, os.FileMode(0666))
	if err != nil {
		return err
	}
	defer f.Close()

	if g.Client == nil {
		g.Client = httpClient
	}

	var currentFileSize int64

	// We first make a HEAD request so we can check
	// if the server supports range queries. If the server/URL doesn't
	// support HEAD requests, we just fall back to GET.
	req, err := http.NewRequest("HEAD", src.String(), nil)
	if err != nil {
		return err
	}
	if g.Header != nil {
		req.Header = g.Header
	}
	headResp, err := g.Client.Do(req)
	if err == nil && headResp != nil {
		headResp.Body.Close()
		if headResp.StatusCode == 200 {
			// If the HEAD request succeeded, then attempt to set the range
			// query if we can.
			if headResp.Header.Get("Accept-Ranges") == "bytes" {
				if fi, err := f.Stat(); err == nil {
					if _, err = f.Seek(0, os.SEEK_END); err == nil {
						req.Header.Set("Range", fmt.Sprintf("bytes=%d-", fi.Size()))
						currentFileSize = fi.Size()
						totalFileSize, _ := strconv.ParseInt(headResp.Header.Get("Content-Length"), 10, 64)
						if currentFileSize >= totalFileSize {
							// file already present
							return nil
						}
					}
				}
			}
		}
	}
	req.Method = "GET"

	resp, err := g.Client.Do(req)
	if err != nil {
		return err
	}
	switch resp.StatusCode {
	case http.StatusOK, http.StatusPartialContent:
		// all good
	default:
		resp.Body.Close()
		return fmt.Errorf("bad response code: %d", resp.StatusCode)
	}

	body := resp.Body

	if g.client != nil && g.client.ProgressListener != nil {
		// track download
		fn := filepath.Base(src.EscapedPath())
		body = g.client.ProgressListener.TrackProgress(fn, currentFileSize, currentFileSize+resp.ContentLength, resp.Body)
	}
	defer body.Close()

	n, err := Copy(ctx, f, body)
	if err == nil && n < resp.ContentLength {
		err = io.ErrShortWrite
	}
	return err
}

// getSubdir downloads the source into the destination, but with
// the proper subdir.
func (g *HttpGetter) getSubdir(ctx context.Context, dst, source, subDir string) error {
	// Create a temporary directory to store the full source. This has to be
	// a non-existent directory.
	td, tdcloser, err := safetemp.Dir("", "getter")
	if err != nil {
		return err
	}
	defer tdcloser.Close()

	var opts []ClientOption
	if g.client != nil {
		opts = g.client.Options
	}
	// Download that into the given directory
	if err := Get(td, source, opts...); err != nil {
		return err
	}

	// Process any globbing
	sourcePath, err := SubdirGlob(td, subDir)
	if err != nil {
		return err
	}

	// Make sure the subdir path actually exists
	if _, err := os.Stat(sourcePath); err != nil {
		return fmt.Errorf(
			"Error downloading %s: %s", source, err)
	}

	// Copy the subdirectory into our actual destination.
	if err := os.RemoveAll(dst); err != nil {
		return err
	}

	// Make the final destination
	if err := os.MkdirAll(dst, 0755); err != nil {
		return err
	}

	return copyDir(ctx, dst, sourcePath, false)
}

// parseMeta looks for the first meta tag in the given reader that
// will give us the source URL.
func (g *HttpGetter) parseMeta(r io.Reader) (string, error) {
	d := xml.NewDecoder(r)
	d.CharsetReader = charsetReader
	d.Strict = false
	var err error
	var t xml.Token
	for {
		t, err = d.Token()
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return "", err
		}
		if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
			return "", nil
		}
		if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
			return "", nil
		}
		e, ok := t.(xml.StartElement)
		if !ok || !strings.EqualFold(e.Name.Local, "meta") {
			continue
		}
		if attrValue(e.Attr, "name") != "terraform-get" {
			continue
		}
		if f := attrValue(e.Attr, "content"); f != "" {
			return f, nil
		}
	}
}

// attrValue returns the attribute value for the case-insensitive key
// `name', or the empty string if nothing is found.
func attrValue(attrs []xml.Attr, name string) string {
	for _, a := range attrs {
		if strings.EqualFold(a.Name.Local, name) {
			return a.Value
		}
	}
	return ""
}

// charsetReader returns a reader for the given charset. Currently
// it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
// error which is printed by go get, so the user can find why the package
// wasn't downloaded if the encoding is not supported. Note that, in
// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
// greater than 0x7f are not rejected).
func charsetReader(charset string, input io.Reader) (io.Reader, error) {
	switch strings.ToLower(charset) {
	case "ascii":
		return input, nil
	default:
		return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strconv"
)

type GitObjectType string

const (
	GitBlob   = "blob"
	GitTree   = "tree"
	GitCommit = "commit"
	GitTag    = "tag"
)

type GitObject struct {
	Permission uint32
	ObjectType GitObjectType
	Hash       string
	Name       string
}

func git_show(hash string) ([]byte, error) {
	cmd := exec.Command("git", "show", hash)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		fmt.Println("could not attach a pipe to the command ", err)
		return nil, err
	}
	err = cmd.Start()
	if err != nil {
		fmt.Println("command failed with ", err)
		return nil, err
	}
	return ioutil.ReadAll(stdout)
}

func lstree(commit string) ([]GitObject, error) {
	// Inspect the cwd
	cmd := exec.Command("git", "ls-tree", commit)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		fmt.Println("could not attach a pipe to the command ", err)
		return nil, err
	}
	err = cmd.Start()
	if err != nil {
		fmt.Println("command failed with ", err)
		return nil, err
	}
	//	output, err := ioutil.ReadAll(stdout)
	//	fmt.Println(string(output))
	buf_stdout := bufio.NewReader(stdout)

	var obs []GitObject
	// for reasons I do not understand, saying "this ends with a newline" means
	// the newline gets eaten by the final capture. Also, ending with a '$' breaks
	// the whole match.
	tree_r, err := regexp.Compile("^([0-9]+)\\s([a-z]+)\\s([a-z0-9]+)\\s(.+)")
	if err != nil {
		panic(err)
	}
	for line, err := buf_stdout.ReadBytes('\n'); err == nil; line, err = buf_stdout.ReadBytes('\n') {
		results := tree_r.FindSubmatch(line)
		if len(results) != 5 {
			// We expect to get back [original, perms, ob, hash, filename]
			return nil, errors.New(fmt.Sprintf("Unexpected parse of `git ls-tree` output: %q", results))
		}
		permissions, err := strconv.ParseUint(string(results[1]), 10, 32)
		if err != nil {
			return nil, errors.New(fmt.Sprintf("Could not parse permissions for file %s, got %s", results[4], results[1]))
		}
		obs = append(obs, GitObject{uint32(permissions), GitObjectType(results[2]), string(results[3]), string(results[4])})
	}
	return obs, err
}

func get_object(starting_hash, final_path string) ([]byte, error) {
	objects, err := lstree(starting_hash)
	fmt.Println("final path", final_path)
	next_prefix, rest := path.Split(final_path)
	for _, object := range objects {
		fmt.Println(next_prefix, object.Name)
		if object.Name == next_prefix {
			if object.ObjectType == GitTree {
				return get_object(object.Hash, rest)
			} else if object.ObjectType == GitBlob {
				fmt.Println("busted")
				return []byte("Found it"), nil
			} else {
				// XXX let's do a better job here
				return nil, errors.New("Unsupported object type")
			}
		} else if object.Name == rest {
			if object.ObjectType == GitBlob {
				fmt.Println("rly found it")
				return git_show(object.Hash)
			}
		}
	}
	return []byte{}, err
}

func main() {
	var git_path *string = flag.String("repo", ".", "git repo to serve")
	var internal_path *string = flag.String("path", ".", "path to the object")
	flag.Parse()

	// Move to the git repo
	err := os.Chdir(*git_path)
	if err != nil {
		fmt.Println(err)
		os.Exit(2)
	}

	// Verify this is is a git directory
	cmd := exec.Command("git", "status")
	err = cmd.Run()
	if err != nil {
		fmt.Println("Git didn't like ", *git_path, " got ", err)
		os.Exit(3)
	}

	blob, err := get_object("HEAD", *internal_path)
	fmt.Println(string(blob))
	fmt.Println(err)
}
<commit_msg>Clean up some of my debugging nonsense<commit_after>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strconv"
)

type GitObjectType string

const (
	GitBlob   = "blob"
	GitTree   = "tree"
	GitCommit = "commit"
	GitTag    = "tag"
)

type GitObject struct {
	Permission uint32
	ObjectType GitObjectType
	Hash       string
	Name       string
}

func git_show(hash string) ([]byte, error) {
	cmd := exec.Command("git", "show", hash)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		fmt.Println("could not attach a pipe to the command ", err)
		return nil, err
	}
	err = cmd.Start()
	if err != nil {
		fmt.Println("command failed with ", err)
		return nil, err
	}
	return ioutil.ReadAll(stdout)
}

func lstree(commit string) ([]GitObject, error) {
	// Inspect the cwd
	cmd := exec.Command("git", "ls-tree", commit)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		fmt.Println("could not attach a pipe to the command ", err)
		return nil, err
	}
	err = cmd.Start()
	if err != nil {
		fmt.Println("command failed with ", err)
		return nil, err
	}
	buf_stdout := bufio.NewReader(stdout)

	var obs []GitObject
	// for reasons I do not understand, saying "this ends with a newline" means
	// the newline gets eaten by the final capture. Also, ending with a '$' breaks
	// the whole match.
	tree_r, err := regexp.Compile("^([0-9]+)\\s([a-z]+)\\s([a-z0-9]+)\\s(.+)")
	if err != nil {
		panic(err)
	}
	for line, err := buf_stdout.ReadBytes('\n'); err == nil; line, err = buf_stdout.ReadBytes('\n') {
		results := tree_r.FindSubmatch(line)
		if len(results) != 5 {
			// We expect to get back [original, perms, ob, hash, filename]
			return nil, errors.New(fmt.Sprintf("Unexpected parse of `git ls-tree` output: %q", results))
		}
		permissions, err := strconv.ParseUint(string(results[1]), 10, 32)
		if err != nil {
			return nil, errors.New(fmt.Sprintf("Could not parse permissions for file %s, got %s", results[4], results[1]))
		}
		obs = append(obs, GitObject{uint32(permissions), GitObjectType(results[2]), string(results[3]), string(results[4])})
	}
	return obs, err
}

func get_object(starting_hash, final_path string) ([]byte, error) {
	objects, err := lstree(starting_hash)
	next_prefix, rest := path.Split(final_path)
	for _, object := range objects {
		fmt.Println(next_prefix, object.Name)
		if object.Name == next_prefix {
			if object.ObjectType == GitTree {
				return get_object(object.Hash, rest)
			} else {
				// XXX let's do a better job here
				return nil, errors.New("Unsupported object type")
			}
		} else if object.Name == rest {
			if object.ObjectType == GitBlob {
				return git_show(object.Hash)
			}
		}
	}
	return []byte{}, err
}

func main() {
	var git_path *string = flag.String("repo", ".", "git repo to serve")
	var internal_path *string = flag.String("path", ".", "path to the object")
	flag.Parse()

	// Move to the git repo
	err := os.Chdir(*git_path)
	if err != nil {
		fmt.Println(err)
		os.Exit(2)
	}

	// Verify this is is a git directory
	cmd := exec.Command("git", "status")
	err = cmd.Run()
	if err != nil {
		fmt.Println("Git didn't like ", *git_path, " got ", err)
		os.Exit(3)
	}

	blob, err := get_object("HEAD", *internal_path)
	fmt.Println(string(blob))
	fmt.Println(err)
}
<|endoftext|>


<commit_before>package api

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/netlify/gotrue/api/provider"
	"github.com/netlify/gotrue/models"
	"github.com/netlify/gotrue/storage"
	uuid "github.com/satori/go.uuid"
	"github.com/sirupsen/logrus"
)

type ExternalProviderClaims struct {
	NetlifyMicroserviceClaims
	Provider    string `json:"provider"`
	InviteToken string `json:"invite_token,omitempty"`
	Referrer    string `json:"referrer,omitempty"`
}

// SignupParams are the parameters the Signup endpoint accepts
type ExternalSignupParams struct {
	Provider string `json:"provider"`
	Code     string `json:"code"`
}

func (a *API) ExternalProviderRedirect(w http.ResponseWriter, r *http.Request) error {
	ctx := r.Context()
	config := a.getConfig(ctx)

	providerType := r.URL.Query().Get("provider")
	provider, err := a.Provider(ctx, providerType)
	if err != nil {
		return badRequestError("Unsupported provider: %+v", err).WithInternalError(err)
	}

	inviteToken := r.URL.Query().Get("invite_token")
	if inviteToken != "" {
		_, userErr := models.FindUserByConfirmationToken(a.db, inviteToken)
		if userErr != nil {
			if models.IsNotFoundError(userErr) {
				return notFoundError(userErr.Error())
			}
			return internalServerError("Database error finding user").WithInternalError(userErr)
		}
	}

	referrer := ""
	if reqref := r.Referer(); reqref != "" {
		base, berr := url.Parse(config.SiteURL)
		refurl, rerr := url.Parse(reqref)
		// As long as the referrer came from the site, we will redirect back there
		if berr == nil && rerr == nil && base.Hostname() == refurl.Hostname() {
			referrer = reqref
		}
	}

	log := getLogEntry(r)
	log.WithField("provider", providerType).Info("Redirecting to external provider")

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, ExternalProviderClaims{
		NetlifyMicroserviceClaims: NetlifyMicroserviceClaims{
			StandardClaims: jwt.StandardClaims{
				ExpiresAt: time.Now().Add(5 * time.Minute).Unix(),
			},
			SiteURL:    config.SiteURL,
			InstanceID: getInstanceID(ctx).String(),
			NetlifyID:  getNetlifyID(ctx),
		},
		Provider:    providerType,
		InviteToken: inviteToken,
		Referrer:    referrer,
	})
	tokenString, err := token.SignedString([]byte(a.config.OperatorToken))
	if err != nil {
		return internalServerError("Error creating state").WithInternalError(err)
	}

	http.Redirect(w, r, provider.AuthCodeURL(tokenString), http.StatusFound)
	return nil
}

func (a *API) ExternalProviderCallback(w http.ResponseWriter, r *http.Request) error {
	a.redirectErrors(a.internalExternalProviderCallback, w, r)
	return nil
}

func (a *API) internalExternalProviderCallback(w http.ResponseWriter, r *http.Request) error {
	ctx := r.Context()
	config := a.getConfig(ctx)
	instanceID := getInstanceID(ctx)
	rq := r.URL.Query()

	extError := rq.Get("error")
	if extError != "" {
		return oauthError(extError, rq.Get("error_description"))
	}

	oauthCode := rq.Get("code")
	if oauthCode == "" {
		return badRequestError("Authorization code missing")
	}

	providerType := getExternalProviderType(ctx)
	provider, err := a.Provider(ctx, providerType)
	if err != nil {
		return badRequestError("Unsupported provider: %+v", err).WithInternalError(err)
	}

	log := getLogEntry(r)
	log.WithFields(logrus.Fields{
		"provider": providerType,
		"code":     oauthCode,
	}).Debug("Exchanging oauth code")

	tok, err := provider.GetOAuthToken(oauthCode)
	if err != nil {
		return internalServerError("Unable to exchange external code: %s", oauthCode).WithInternalError(err)
	}

	userData, err := provider.GetUserData(ctx, tok)
	if err != nil {
		return internalServerError("Error getting user email from external provider").WithInternalError(err)
	}

	var user *models.User
	var token *AccessTokenResponse
	err = a.db.Transaction(func(tx *storage.Connection) error {
		var terr error
		inviteToken := getInviteToken(ctx)
		if inviteToken != "" {
			if user, terr = a.processInvite(ctx, tx, userData, instanceID, inviteToken, providerType); terr != nil {
				return terr
			}
		} else {
			aud := a.requestAud(ctx, r)
			user, terr = models.FindUserByEmailAndAudience(tx, instanceID, userData.Email, aud)
			if terr != nil && !models.IsNotFoundError(terr) {
				return internalServerError("Error checking for duplicate users").WithInternalError(terr)
			}
			if user == nil {
				if config.DisableSignup {
					return forbiddenError("Signups not allowed for this instance")
				}

				params := &SignupParams{
					Provider: providerType,
					Email:    userData.Email,
					Aud:      aud,
					Data:     make(map[string]interface{}),
				}
				for k, v := range userData.Metadata {
					if v != "" {
						params.Data[k] = v
					}
				}

				user, terr = a.signupNewUser(tx, ctx, params)
				if terr != nil {
					return terr
				}
			}

			if !user.IsConfirmed() {
				if !userData.Verified && !config.Mailer.Autoconfirm {
					mailer := a.Mailer(ctx)
					if terr = sendConfirmation(tx, user, mailer, config.SMTP.MaxFrequency); terr != nil {
						return internalServerError("Error sending confirmation mail").WithInternalError(terr)
					}
					// email must be verified to issue a token
					http.Redirect(w, r, a.getExternalRedirectURL(r), http.StatusFound)
					return nil
				}

				if terr := models.NewAuditLogEntry(tx, instanceID, user, models.UserSignedUpAction, nil); terr != nil {
					return terr
				}
				if config.Webhook.HasEvent("signup") {
					if terr = triggerHook(ctx, tx, SignupEvent, user, instanceID, config); terr != nil {
						return terr
					}
				}

				// fall through to auto-confirm and issue token
				if terr = user.Confirm(tx); terr != nil {
					return internalServerError("Error updating user").WithInternalError(terr)
				}
			} else {
				if terr := models.NewAuditLogEntry(tx, instanceID, user, models.LoginAction, nil); terr != nil {
					return terr
				}
				if config.Webhook.HasEvent("login") {
					if terr = triggerHook(ctx, tx, LoginEvent, user, instanceID, config); terr != nil {
						return terr
					}
				}
			}
		}

		token, terr = a.issueRefreshToken(ctx, tx, user)
		if terr != nil {
			return oauthError("server_error", terr.Error())
		}
		return nil
	})
	if err != nil {
		return err
	}

	q := url.Values{}
	q.Set("access_token", token.Token)
	q.Set("token_type", token.TokenType)
	q.Set("expires_in", strconv.Itoa(token.ExpiresIn))
	q.Set("refresh_token", token.RefreshToken)

	http.Redirect(w, r, a.getExternalRedirectURL(r)+"#"+q.Encode(), http.StatusFound)
	return nil
}

func (a *API) processInvite(ctx context.Context, tx *storage.Connection, userData *provider.UserProvidedData, instanceID uuid.UUID, inviteToken, providerType string) (*models.User, error) {
	config := a.getConfig(ctx)
	user, err := models.FindUserByConfirmationToken(tx, inviteToken)
	if err != nil {
		if models.IsNotFoundError(err) {
			return nil, notFoundError(err.Error())
		}
		return nil, internalServerError("Database error finding user").WithInternalError(err)
	}

	if user.Email != userData.Email {
		return nil, badRequestError("Invited email does not match email from external provider").WithInternalMessage("invited=%s external=%s", user.Email, userData.Email)
	}

	if err := user.UpdateAppMetaData(tx, map[string]interface{}{
		"provider": providerType,
	}); err != nil {
		return nil, internalServerError("Database error updating user").WithInternalError(err)
	}

	updates := make(map[string]interface{})
	for k, v := range userData.Metadata {
		if v != "" {
			updates[k] = v
		}
	}
	if err := user.UpdateUserMetaData(tx, updates); err != nil {
		return nil, internalServerError("Database error updating user").WithInternalError(err)
	}

	if err := models.NewAuditLogEntry(tx, instanceID, user, models.InviteAcceptedAction, nil); err != nil {
		return nil, err
	}
	if config.Webhook.HasEvent("signup") {
		if err := triggerHook(ctx, tx, SignupEvent, user, instanceID, config); err != nil {
			return nil, err
		}
	}

	// confirm because they were able to respond to invite email
	if err := user.Confirm(tx); err != nil {
		return nil, err
	}
	return user, nil
}

// loadOAuthState parses the `state` query parameter as a JWS payload,
// extracting the provider requested
func (a *API) loadOAuthState(w http.ResponseWriter, r *http.Request) (context.Context, error) {
	ctx := r.Context()
	state := r.URL.Query().Get("state")
	if state == "" {
		return nil, badRequestError("OAuth state parameter missing")
	}

	claims := ExternalProviderClaims{}
	p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
	_, err := p.ParseWithClaims(state, &claims, func(token *jwt.Token) (interface{}, error) {
		return []byte(a.config.OperatorToken), nil
	})
	if err != nil || claims.Provider == "" {
		return nil, badRequestError("OAuth state is invalid: %v", err)
	}
	if claims.InviteToken != "" {
		ctx = withInviteToken(ctx, claims.InviteToken)
	}
	if claims.Referrer != "" {
		ctx = withExternalReferrer(ctx, claims.Referrer)
	}

	ctx = withExternalProviderType(ctx, claims.Provider)
	return withSignature(ctx, state), nil
}

// Provider returns a Provider interface for the given name.
func (a *API) Provider(ctx context.Context, name string) (provider.Provider, error) {
	config := a.getConfig(ctx)
	name = strings.ToLower(name)

	switch name {
	case "bitbucket":
		return provider.NewBitbucketProvider(config.External.Bitbucket)
	case "github":
		return provider.NewGithubProvider(config.External.Github)
	case "gitlab":
		return provider.NewGitlabProvider(config.External.Gitlab)
	case "google":
		return provider.NewGoogleProvider(config.External.Google)
	case "facebook":
		return provider.NewFacebookProvider(config.External.Facebook)
	default:
		return nil, fmt.Errorf("Provider %s could not be found", name)
	}
}

func (a *API) redirectErrors(handler apiHandler, w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	log := getLogEntry(r)
	errorID := getRequestID(ctx)
	err := handler(w, r)
	if err != nil {
		q := getErrorQueryString(err, errorID, log)
		http.Redirect(w, r, a.getExternalRedirectURL(r)+"#"+q.Encode(), http.StatusFound)
	}
}

func getErrorQueryString(err error, errorID string, log logrus.FieldLogger) *url.Values {
	q := url.Values{}
	switch e := err.(type) {
	case *HTTPError:
		if str, ok := oauthErrorMap[e.Code]; ok {
			q.Set("error", str)
		} else {
			q.Set("error", "server_error")
		}
		if e.Code >= http.StatusInternalServerError {
			e.ErrorID = errorID
			// this will get us the stack trace too
			log.WithError(e.Cause()).Error(e.Error())
		} else {
			log.WithError(e.Cause()).Info(e.Error())
		}
		q.Set("error_description", e.Message)
	case *OAuthError:
		q.Set("error", e.Err)
		q.Set("error_description", e.Description)
		log.WithError(e.Cause()).Info(e.Error())
	case ErrorCause:
		return getErrorQueryString(e.Cause(), errorID, log)
	default:
		q.Set("error", "server_error")
		q.Set("error_description", err.Error())
	}
	return &q
}

func (a *API) getExternalRedirectURL(r *http.Request) string {
	ctx := r.Context()
	config := a.getConfig(ctx)
	if config.External.RedirectURL != "" {
		return config.External.RedirectURL
	}
	if er := getExternalReferrer(ctx); er != "" {
		return er
	}
	return config.SiteURL
}
<commit_msg>Fix panic for un-verified external email<commit_after>package api

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/netlify/gotrue/api/provider"
	"github.com/netlify/gotrue/models"
	"github.com/netlify/gotrue/storage"
	uuid "github.com/satori/go.uuid"
	"github.com/sirupsen/logrus"
)

type ExternalProviderClaims struct {
	NetlifyMicroserviceClaims
	Provider    string `json:"provider"`
	InviteToken string `json:"invite_token,omitempty"`
	Referrer    string `json:"referrer,omitempty"`
}

// SignupParams are the parameters the Signup endpoint accepts
type ExternalSignupParams struct {
	Provider string `json:"provider"`
	Code     string `json:"code"`
}

func (a *API) ExternalProviderRedirect(w http.ResponseWriter, r *http.Request) error {
	ctx := r.Context()
	config := a.getConfig(ctx)

	providerType := r.URL.Query().Get("provider")
	provider, err := a.Provider(ctx, providerType)
	if err != nil {
		return badRequestError("Unsupported provider: %+v", err).WithInternalError(err)
	}

	inviteToken := r.URL.Query().Get("invite_token")
	if inviteToken != "" {
		_, userErr := models.FindUserByConfirmationToken(a.db, inviteToken)
		if userErr != nil {
			if models.IsNotFoundError(userErr) {
				return notFoundError(userErr.Error())
			}
			return internalServerError("Database error finding user").WithInternalError(userErr)
		}
	}

	referrer := ""
	if reqref := r.Referer(); reqref != "" {
		base, berr := url.Parse(config.SiteURL)
		refurl, rerr := url.Parse(reqref)
		// As long as the referrer came from the site, we will redirect back there
		if berr == nil && rerr == nil && base.Hostname() == refurl.Hostname() {
			referrer = reqref
		}
	}

	log := getLogEntry(r)
	log.WithField("provider", providerType).Info("Redirecting to external provider")

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, ExternalProviderClaims{
		NetlifyMicroserviceClaims: NetlifyMicroserviceClaims{
			StandardClaims: jwt.StandardClaims{
				ExpiresAt: time.Now().Add(5 * time.Minute).Unix(),
			},
			SiteURL:    config.SiteURL,
			InstanceID: getInstanceID(ctx).String(),
			NetlifyID:  getNetlifyID(ctx),
		},
		Provider:    providerType,
		InviteToken: inviteToken,
		Referrer:    referrer,
	})
	tokenString, err := token.SignedString([]byte(a.config.OperatorToken))
	if err != nil {
		return internalServerError("Error creating state").WithInternalError(err)
	}

	http.Redirect(w, r, provider.AuthCodeURL(tokenString), http.StatusFound)
	return nil
}

func (a *API) ExternalProviderCallback(w http.ResponseWriter, r *http.Request) error {
	a.redirectErrors(a.internalExternalProviderCallback, w, r)
	return nil
}

func (a *API) internalExternalProviderCallback(w http.ResponseWriter, r *http.Request) error {
	ctx := r.Context()
	config := a.getConfig(ctx)
	instanceID := getInstanceID(ctx)
	rq := r.URL.Query()

	extError := rq.Get("error")
	if extError != "" {
		return oauthError(extError, rq.Get("error_description"))
	}

	oauthCode := rq.Get("code")
	if oauthCode == "" {
		return badRequestError("Authorization code missing")
	}

	providerType := getExternalProviderType(ctx)
	provider, err := a.Provider(ctx, providerType)
	if err != nil {
		return badRequestError("Unsupported provider: %+v", err).WithInternalError(err)
	}

	log := getLogEntry(r)
	log.WithFields(logrus.Fields{
		"provider": providerType,
		"code":     oauthCode,
	}).Debug("Exchanging oauth code")

	tok, err := provider.GetOAuthToken(oauthCode)
	if err != nil {
		return internalServerError("Unable to exchange external code: %s", oauthCode).WithInternalError(err)
	}

	userData, err := provider.GetUserData(ctx, tok)
	if err != nil {
		return internalServerError("Error getting user email from external provider").WithInternalError(err)
	}

	var user *models.User
	var token *AccessTokenResponse
	err = a.db.Transaction(func(tx *storage.Connection) error {
		var terr error
		inviteToken := getInviteToken(ctx)
		if inviteToken != "" {
			if user, terr = a.processInvite(ctx, tx, userData, instanceID, inviteToken, providerType); terr != nil {
				return terr
			}
		} else {
			aud := a.requestAud(ctx, r)
			user, terr = models.FindUserByEmailAndAudience(tx, instanceID, userData.Email, aud)
			if terr != nil && !models.IsNotFoundError(terr) {
				return internalServerError("Error checking for duplicate users").WithInternalError(terr)
			}
			if user == nil {
				if config.DisableSignup {
					return forbiddenError("Signups not allowed for this instance")
				}

				params := &SignupParams{
					Provider: providerType,
					Email:    userData.Email,
					Aud:      aud,
					Data:     make(map[string]interface{}),
				}
				for k, v := range userData.Metadata {
					if v != "" {
						params.Data[k] = v
					}
				}

				user, terr = a.signupNewUser(tx, ctx, params)
				if terr != nil {
					return terr
				}
			}

			if !user.IsConfirmed() {
				if !userData.Verified && !config.Mailer.Autoconfirm {
					mailer := a.Mailer(ctx)
					if terr = sendConfirmation(tx, user, mailer, config.SMTP.MaxFrequency); terr != nil {
						return internalServerError("Error sending confirmation mail").WithInternalError(terr)
					}
					// email must be verified to issue a token
					return nil
				}

				if terr := models.NewAuditLogEntry(tx, instanceID, user, models.UserSignedUpAction, nil); terr != nil {
					return terr
				}
				if config.Webhook.HasEvent("signup") {
					if terr = triggerHook(ctx, tx, SignupEvent, user, instanceID, config); terr != nil {
						return terr
					}
				}

				// fall through to auto-confirm and issue token
				if terr = user.Confirm(tx); terr != nil {
					return internalServerError("Error updating user").WithInternalError(terr)
				}
			} else {
				if terr := models.NewAuditLogEntry(tx, instanceID, user, models.LoginAction, nil); terr != nil {
					return terr
				}
				if config.Webhook.HasEvent("login") {
					if terr = triggerHook(ctx, tx, LoginEvent, user, instanceID, config); terr != nil {
						return terr
					}
				}
			}
		}

		token, terr = a.issueRefreshToken(ctx, tx, user)
		if terr != nil {
			return oauthError("server_error", terr.Error())
		}
		return nil
	})
	if err != nil {
		return err
	}

	rurl := a.getExternalRedirectURL(r)
	if token != nil {
		q := url.Values{}
		q.Set("access_token", token.Token)
		q.Set("token_type", token.TokenType)
		q.Set("expires_in", strconv.Itoa(token.ExpiresIn))
		q.Set("refresh_token", token.RefreshToken)
		rurl += "#" + q.Encode()
	}
	http.Redirect(w, r, rurl, http.StatusFound)
	return nil
}

func (a *API) processInvite(ctx context.Context, tx *storage.Connection, userData *provider.UserProvidedData, instanceID uuid.UUID, inviteToken, providerType string) (*models.User, error) {
	config := a.getConfig(ctx)
	user, err := models.FindUserByConfirmationToken(tx, inviteToken)
	if err != nil {
		if models.IsNotFoundError(err) {
			return nil, notFoundError(err.Error())
		}
		return nil, internalServerError("Database error finding user").WithInternalError(err)
	}

	if user.Email != userData.Email {
		return nil, badRequestError("Invited email does not match email from external provider").WithInternalMessage("invited=%s external=%s", user.Email, userData.Email)
	}

	if err := user.UpdateAppMetaData(tx, map[string]interface{}{
		"provider": providerType,
	}); err != nil {
		return nil, internalServerError("Database error updating user").WithInternalError(err)
	}

	updates := make(map[string]interface{})
	for k, v := range userData.Metadata {
		if v != "" {
			updates[k] = v
		}
	}
	if err := user.UpdateUserMetaData(tx, updates); err != nil {
		return nil, internalServerError("Database error updating user").WithInternalError(err)
	}

	if err := models.NewAuditLogEntry(tx, instanceID, user, models.InviteAcceptedAction, nil); err != nil {
		return nil, err
	}
	if config.Webhook.HasEvent("signup") {
		if err := triggerHook(ctx, tx, SignupEvent, user, instanceID, config); err != nil {
			return nil, err
		}
	}

	// confirm because they were able to respond to invite email
	if err := user.Confirm(tx); err != nil {
		return nil, err
	}
	return user, nil
}

// loadOAuthState parses the `state` query parameter as a JWS payload,
// extracting the provider requested
func (a *API) loadOAuthState(w http.ResponseWriter, r *http.Request) (context.Context, error) {
	ctx := r.Context()
	state := r.URL.Query().Get("state")
	if state == "" {
		return nil, badRequestError("OAuth state parameter missing")
	}

	claims := ExternalProviderClaims{}
	p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
	_, err := p.ParseWithClaims(state, &claims, func(token *jwt.Token) (interface{}, error) {
		return []byte(a.config.OperatorToken), nil
	})
	if err != nil || claims.Provider == "" {
		return nil, badRequestError("OAuth state is invalid: %v", err)
	}
	if claims.InviteToken != "" {
		ctx = withInviteToken(ctx, claims.InviteToken)
	}
	if claims.Referrer != "" {
		ctx = withExternalReferrer(ctx, claims.Referrer)
	}

	ctx = withExternalProviderType(ctx, claims.Provider)
	return withSignature(ctx, state), nil
}

// Provider returns a Provider interface for the given name.
func (a *API) Provider(ctx context.Context, name string) (provider.Provider, error) {
	config := a.getConfig(ctx)
	name = strings.ToLower(name)

	switch name {
	case "bitbucket":
		return provider.NewBitbucketProvider(config.External.Bitbucket)
	case "github":
		return provider.NewGithubProvider(config.External.Github)
	case "gitlab":
		return provider.NewGitlabProvider(config.External.Gitlab)
	case "google":
		return provider.NewGoogleProvider(config.External.Google)
	case "facebook":
		return provider.NewFacebookProvider(config.External.Facebook)
	default:
		return nil, fmt.Errorf("Provider %s could not be found", name)
	}
}

func (a *API) redirectErrors(handler apiHandler, w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	log := getLogEntry(r)
	errorID := getRequestID(ctx)
	err := handler(w, r)
	if err != nil {
		q := getErrorQueryString(err, errorID, log)
		http.Redirect(w, r, a.getExternalRedirectURL(r)+"#"+q.Encode(), http.StatusFound)
	}
}

func getErrorQueryString(err error, errorID string, log logrus.FieldLogger) *url.Values {
	q := url.Values{}
	switch e := err.(type) {
	case *HTTPError:
		if str, ok := oauthErrorMap[e.Code]; ok {
			q.Set("error", str)
		} else {
			q.Set("error", "server_error")
		}
		if e.Code >= http.StatusInternalServerError {
			e.ErrorID = errorID
			// this will get us the stack trace too
			log.WithError(e.Cause()).Error(e.Error())
		} else {
			log.WithError(e.Cause()).Info(e.Error())
		}
		q.Set("error_description", e.Message)
	case *OAuthError:
		q.Set("error", e.Err)
		q.Set("error_description", e.Description)
		log.WithError(e.Cause()).Info(e.Error())
	case ErrorCause:
		return getErrorQueryString(e.Cause(), errorID, log)
	default:
		q.Set("error", "server_error")
		q.Set("error_description", err.Error())
	}
	return &q
}

func (a *API) getExternalRedirectURL(r *http.Request) string {
	ctx := r.Context()
	config := a.getConfig(ctx)
	if config.External.RedirectURL != "" {
		return config.External.RedirectURL
	}
	if er := getExternalReferrer(ctx); er != "" {
		return er
	}
	return config.SiteURL
}
<|endoftext|>


<commit_before>package uniq

import "sort"

type Interface sort.Interface

func Ue(data Interface) int {
	len := data.Len()
	if len == 0 {
		return 0
	}
	i, j := 0, 1
	// find the first duplicate
	for j < len && data.Less(i, j) {
		i++
		j++
	}
	// this loop is simpler after the first duplicate is found
	for ; j < len; j++ {
		if data.Less(i, j) {
			i++
			data.Swap(i, j)
		}
	}
	return i + 1
}
<commit_msg>Skip Len() == 1 as well<commit_after>package uniq

import "sort"

type Interface sort.Interface

func Ue(data Interface) int {
	len := data.Len()
	if len <= 1 {
		return len
	}
	i, j := 0, 1
	// find the first duplicate
	for j < len && data.Less(i, j) {
		i++
		j++
	}
	// this loop is simpler after the first duplicate is found
	for ; j < len; j++ {
		if data.Less(i, j) {
			i++
			data.Swap(i, j)
		}
	}
	return i + 1
}
<|endoftext|>


<commit_before>package gitserver

import (
	"errors"
	"time"

	"net/http"

	"golang.org/x/net/context"
)

var ErrUserNotFound = errors.New("User not found")

type Session struct {
	Value   string
	Expires time.Time
}

//Perhaps this should be an interface instead?
type User struct {
	ID    string
	Email string
	//There are some concerns here about thread safety
	Sessions []Session
}

type UserStore interface {
	//TODO might not be needing Sessions
	UpdateUser(ctx context.Context, user *User) error
	LookupUser(ctx context.Context, id string) (*User, error)
	Sessions(ctx context.Context, id string) []Session
}

func AuthorizedUser(req *http.Request) (*User, error) {

	if loginHandler.logger == nil {
		panic("You must call gitserver.Setup(...) before gitserver.AuthorizedUser(...)")
	}

	ctx := loginHandler.contextFromRequest(req)

	loginHandler.logger.Debugf(ctx, "Serving logged in")

	/* Check if user was already logged in */
	sc, err := sessionCookieFromRequest(req)
	if err != nil && err != ErrSessionCookieNotFound {
		return nil, ErrUserNotFound
	}

	if err != ErrSessionCookieNotFound {
		user, _ := loginHandler.userStore.LookupUser(ctx, sc.UserID)
		if user != nil {
			for _, s := range user.Sessions {
				if s.Value == sc.SessionID && !isExpired(s) {
					return user, nil
				}
			}
		}
	}
	return nil, ErrUserNotFound
}
<commit_msg>Treats all errors the same when trying to get user from a request and returns ErrUserNotFound on all of them<commit_after>package gitserver

import (
	"errors"
	"time"

	"net/http"

	"golang.org/x/net/context"
)

var ErrUserNotFound = errors.New("User not found")

type Session struct {
	Value   string
	Expires time.Time
}

//Perhaps this should be an interface instead?
type User struct {
	ID    string
	Email string
	//There are some concerns here about thread safety
	Sessions []Session
}

type UserStore interface {
	//TODO might not be needing Sessions
	UpdateUser(ctx context.Context, user *User) error
	LookupUser(ctx context.Context, id string) (*User, error)
	Sessions(ctx context.Context, id string) []Session
}

func AuthorizedUser(req *http.Request) (*User, error) {

	if loginHandler.logger == nil {
		panic("You must call gitserver.Setup(...) before gitserver.AuthorizedUser(...)")
	}

	ctx := loginHandler.contextFromRequest(req)

	loginHandler.logger.Debugf(ctx, "Serving logged in")

	/* Check if user was already logged in */
	sc, err := sessionCookieFromRequest(req)
	if err != nil {
		return nil, ErrUserNotFound
	}

	if err != ErrSessionCookieNotFound {
		user, _ := loginHandler.userStore.LookupUser(ctx, sc.UserID)
		if user != nil {
			for _, s := range user.Sessions {
				if s.Value == sc.SessionID && !isExpired(s) {
					return user, nil
				}
			}
		}
	}
	return nil, ErrUserNotFound
}
<|endoftext|>


<commit_before>package sixdegreesimdb

import (
	"database/sql"

	"github.com/jmoiron/sqlx"

	"fmt"
)

type NCONST struct {
	ID  string
	URL string
}

type NCONSTResp struct {
	Principals []Principal
	//Ambiguous  bool
}

type NameResponse struct {
	NCONSTa NCONSTResp
	NCONSTB NCONSTResp
}

// struct definitions are duplicated here as well as in the python, we could deduplicate them if we
// rewrote the python in go

// Title represents the rich description of a movie title
type Title struct {
	TCONST         string `db:"tconst"`
	TitleType      string `db:"titletype"`
	PrimaryTitle   string `db:"primarytitle"`
	OriginalTitle  string `db:"originaltitle"`
	IsAdult        int    `db:"isadult"`
	StartYear      int    `db:"startyear"`
	EndYear        int    `db:"endyear"`
	RuntimeMinutes int    `db:"runtimeminutes"`
	Genres         string `db:"genres"`
	NCONST         string `db:"nconst"` // should be []string?
}

// Principal represents a rich description of a principal
type Principal struct {
	NCONST            string        `db:"nconst"`
	PrimaryName       string        `db:"primaryname"`
	BirthYear         sql.NullInt64 `db:"birthyear"`
	DeathYear         sql.NullInt64 `db:"deathyear"`
	PrimaryProfession string        `db:"primaryprofession"`
	KnownForTitles    string        `db:"knownfortitles"` // should be []string?
}

// Link represents an element in the chain between two principals. A Link signifies that principal
// was in title
type Link struct {
	Title     *Title
	Principal *Principal
}

// Chain represents a result in
// TODO: Optimise to use less data and then retrieve that data when we want to display a chain?
type Chain struct {
	Start *Principal
	End   *Principal
	Links []Link
}

func newChain(principal1, principal2 *Principal) *Chain {
	return &Chain{
		Start: principal1,
		End:   principal2,
		Links: make([]Link, 0, 0),
	}
}

func lookupName(db *sqlx.DB, name string) (*Principal, error) {
	principals, err := principalsForName(db, name)
	if err != nil {
		return &Principal{}, err
	}
	if len(principals) > 1 {
		return &Principal{}, fmt.Errorf("%s is an ambiguous name", name)
	}
	return &principals[0], nil
}

// doSearchName given a db and two actor names searches for a path between the two names
// TODO: Limit to only actors for now to put off rhe need for resolving name disambiguation
func doSearchName(db *sqlx.DB, name1, name2 string) (*Chain, error) {
	principal1, err := lookupName(db, name1)
	if err != nil {
		return nil, err
	}
	principal2, err := lookupName(db, name2)
	if err != nil {
		return nil, err
	}

	return doSearchPrincipals(db, principal1, principal2)
}

// doSearchNCONST given a db and two actor nconst values searches for a path between the two nconsts
func doSearchNCONST(db *sqlx.DB, nconst1, nconst2 string) (*Chain, error) {
	principal1, err := principalForNCONST(db, nconst1)
	if err != nil {
		return nil, err
	}

	principal2, err := principalForNCONST(db, nconst2)
	if err != nil {
		return nil, err
	}
	return doSearchPrincipals(db, principal1, principal2)
}

func doSearchPrincipals(db *sqlx.DB, principal1, principal2 *Principal) (*Chain, error) {
	return newChain(principal1, principal2), nil
}

// TODO: should really use an ORM for this
// TODO: Handle case insensitivity?
// TODO: batching sql ops?
func principalsForName(db *sqlx.DB, name string) ([]Principal, error) {
	var principals []Principal
	err := db.Select(&principals, "SELECT * FROM name_basics WHERE primaryname = $1", name)
	return principals, err
}

func principalForNCONST(db *sqlx.DB, nconst string) (*Principal, error) {
	var principal Principal
	err := db.Get(&principal, "SELECT * FROM name_basics WHERE nconst = $1", nconst)
	return &principal, err
}
<commit_msg>search.go: add notes<commit_after>package sixdegreesimdb

import (
	"database/sql"

	"github.com/jmoiron/sqlx"

	"fmt"
)

type NCONST struct {
	ID  string
	URL string
}

type NCONSTResp struct {
	Principals []Principal
	//Ambiguous  bool
}

type NameResponse struct {
	NCONSTa NCONSTResp
	NCONSTB NCONSTResp
}

// struct definitions are duplicated here as well as in the python, we could deduplicate them if we
// rewrote the python in go

// Title represents the rich description of a movie title
type Title struct {
	TCONST         string `db:"tconst"`
	TitleType      string `db:"titletype"`
	PrimaryTitle   string `db:"primarytitle"`
	OriginalTitle  string `db:"originaltitle"`
	IsAdult        int    `db:"isadult"`
	StartYear      int    `db:"startyear"`
	EndYear        int    `db:"endyear"`
	RuntimeMinutes int    `db:"runtimeminutes"`
	Genres         string `db:"genres"`
	NCONST         string `db:"nconst"` // should be []string?
}

// Principal represents a rich description of a principal
type Principal struct {
	NCONST            string        `db:"nconst"`
	PrimaryName       string        `db:"primaryname"`
	BirthYear         sql.NullInt64 `db:"birthyear"`
	DeathYear         sql.NullInt64 `db:"deathyear"`
	PrimaryProfession string        `db:"primaryprofession"`
	KnownForTitles    string        `db:"knownfortitles"` // should be []string?
}

// Link represents an element in the chain between two principals. A Link signifies that principal
// was in title
type Link struct {
	Title     *Title
	Principal *Principal
}

// Chain represents a result in
// TODO: Optimise to use less data and then retrieve that data when we want to display a chain?
type Chain struct {
	Start *Principal
	End   *Principal
	Links []Link
}

func newChain(principal1, principal2 *Principal) *Chain {
	return &Chain{
		Start: principal1,
		End:   principal2,
		Links: make([]Link, 0, 0),
	}
}

func lookupName(db *sqlx.DB, name string) (*Principal, error) {
	principals, err := principalsForName(db, name)
	if err != nil {
		return &Principal{}, err
	}
	if len(principals) > 1 {
		return &Principal{}, fmt.Errorf("%s is an ambiguous name", name)
	}
	return &principals[0], nil
}

// doSearchName given a db and two actor names searches for a path between the two names
// TODO: Limit to only actors for now to put off rhe need for resolving name disambiguation
func doSearchName(db *sqlx.DB, name1, name2 string) (*Chain, error) {
	principal1, err := lookupName(db, name1)
	if err != nil {
		return nil, err
	}
	principal2, err := lookupName(db, name2)
	if err != nil {
		return nil, err
	}

	return doSearchPrincipals(db, principal1, principal2)
}

// doSearchNCONST given a db and two actor nconst values searches for a path between the two nconsts
func doSearchNCONST(db *sqlx.DB, nconst1, nconst2 string) (*Chain, error) {
	principal1, err := principalForNCONST(db, nconst1)
	if err != nil {
		return nil, err
	}

	principal2, err := principalForNCONST(db, nconst2)
	if err != nil {
		return nil, err
	}
	return doSearchPrincipals(db, principal1, principal2)
}

// BFS
// 1.given nconstA find all tconst it is in
// 2. check if nconstB is in any of these tconst
// 3. if so return the chain
// 4. find all the nconstX in these tconst
// 5. find all the tconst that nconstX is in
// 6. GOTO 2

// lookups to design db for:
// given an nconst find all tconst - table name_basics doesn't suffice for this as the knownfor
// field present here  only contains a maximum of four values and as a result is much less
// exhaustive than the data in the title_principals table. Thus use created table name_titles
// given a tconst find all nconst - table title_principals
func doSearchPrincipals(db *sqlx.DB, principal1, principal2 *Principal) (*Chain, error) {
	return newChain(principal1, principal2), nil
}

// TODO: should really use an ORM for this
// TODO: Handle case insensitivity?
// TODO: batching sql ops?
func principalsForName(db *sqlx.DB, name string) ([]Principal, error) {
	var principals []Principal
	err := db.Select(&principals, "SELECT * FROM name_basics WHERE primaryname = $1", name)
	return principals, err
}

func principalForNCONST(db *sqlx.DB, nconst string) (*Principal, error) {
	var principal Principal
	err := db.Get(&principal, "SELECT * FROM name_basics WHERE nconst = $1", nconst)
	return &principal, err
}

func principalsForTCONST(db *sqlx.DB, tconst string) ([]Principal, error) {
	var principals []Principal
	err := db.Select(&principals, "SELECT ndonst FROM name_basics WHERE tconst = $1", tconst)
	return principals, err
}
<|endoftext|>


<commit_before>package collaboration

import (
	socialapimodels "socialapi/models"
	"socialapi/workers/collaboration/models"
	"strconv"
	"strings"
	"time"

	"koding/db/mongodb/modelhelper"
	"koding/kites/kloud/klient"

	"github.com/koding/bongo"
	"github.com/koding/kite"

	"labix.org/v2/mgo"
)

// DeleteDriveDoc deletes the file from google drive
func (c *Controller) DeleteDriveDoc(ping *models.Ping) error {
	// if file id is nil, there is nothing to do
	if ping.FileId == "" {
		return nil
	}

	return c.deleteFile(ping.FileId)
}

// EndPrivateMessage stops the collaboration session and deletes the all
// messages from db
func (c *Controller) EndPrivateMessage(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == 0 {
		return nil
	}

	// fetch the channel
	channel := socialapimodels.NewChannel()
	if err := channel.ById(ping.ChannelId); err != nil {
		// if channel is not there, do not do anyting
		if err == bongo.RecordNotFound {
			return nil
		}

		return err
	}

	// delete the channel
	err = channel.Delete()
	if err != nil {
		return err
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(ping.ChannelId)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	return modelhelper.UnsetSocialChannelFromWorkspace(ws.ObjectId)
}

// UnshareVM removes the users from JMachine document
func (c *Controller) UnshareVM(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == 0 {
		return nil
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(
		strconv.FormatInt(ping.ChannelId, 10),
	)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	return modelhelper.UnshareMachineByUid(ws.MachineUID)
}

// RemoveUsersFromMachine removes the collaboraters from the host machine
func (c *Controller) RemoveUsersFromMachine(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == 0 {
		return nil
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(strconv.FormatInt(ping.ChannelId, 10))
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	m, err := modelhelper.GetMachineByUid(ws.MachineUID)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the machine is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	// Get the klient.
	klientRef, err := klient.ConnectTimeout(c.kite, m.QueryString, time.Second*10)
	if err != nil {
		if err == klient.ErrDialingFailed || err == kite.ErrNoKitesAvailable {
			c.log.Error(
				"[%s] Klient is not registered to Kontrol. Err: %s",
				m.QueryString,
				err,
			)

			return nil // if the machine is not open, we cant do anything
		}

		return err
	}
	defer klientRef.Close()

	type args struct {
		Username string

		// we are not gonna use this propery here, just for reference
		Permanent bool
	}

	var iterErr error
	for _, user := range m.Users {
		// do not unshare from owner user
		if user.Sudo && user.Owner {
			continue
		}

		// fetch user for its username
		u, err := modelhelper.GetUserById(user.Id.Hex())
		if err != nil {
			c.log.Error(err.Error())

			// if we cant find the regarding user, do not do anything
			if err == mgo.ErrNotFound {
				continue
			}

			iterErr = err

			continue // do not stop iterating, unshare from others
		}

		param := args{
			Username: u.Name,
		}

		_, err = klientRef.Client.Tell("klient.unshare", param)
		if err != nil {
			c.log.Error(err.Error())

			// those are so error prone, force klient side not to change the API
			// or make them exported to some other package?
			if strings.Contains(err.Error(), "user is permanent") {
				continue
			}

			if strings.Contains(err.Error(), "user is not in the shared list") {
				continue
			}

			if strings.Contains(err.Error(), "User not found") {
				continue
			}

			iterErr = err

			continue // do not stop iterating, unshare from others
		}
	}

	res, err := klientRef.Client.Tell("klient.shared", nil)
	if err == nil {
		c.log.Info("other users in the machine: %+v", res.MustString())
	}

	// iterErr will be nil if we dont encounter to any error in iter
	return iterErr

}
<commit_msg>Socialapi: check if the requester can open the regarding channel<commit_after>package collaboration

import (
	socialapimodels "socialapi/models"
	"socialapi/workers/collaboration/models"
	"strconv"
	"strings"
	"time"

	"koding/db/mongodb/modelhelper"
	"koding/kites/kloud/klient"

	"github.com/koding/bongo"
	"github.com/koding/kite"

	"labix.org/v2/mgo"
)

// DeleteDriveDoc deletes the file from google drive
func (c *Controller) DeleteDriveDoc(ping *models.Ping) error {
	// if file id is nil, there is nothing to do
	if ping.FileId == "" {
		return nil
	}

	return c.deleteFile(ping.FileId)
}

// EndPrivateMessage stops the collaboration session and deletes the all
// messages from db
func (c *Controller) EndPrivateMessage(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == 0 {
		return nil
	}

	// fetch the channel
	channel := socialapimodels.NewChannel()
	if err := channel.ById(ping.ChannelId); err != nil {
		// if channel is not there, do not do anyting
		if err == bongo.RecordNotFound {
			return nil
		}

		return err
	}

	canOpen, err := channel.CanOpen(ping.AccountId)
	if err != nil {
		return err
	}

	if !canOpen {
		return nil // if the requester can not open the channel do not process
	}

	// delete the channel
	err = channel.Delete()
	if err != nil {
		return err
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(
		strconv.FormatInt(ping.ChannelId, 10),
	)

	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	return modelhelper.UnsetSocialChannelFromWorkspace(ws.ObjectId)
}

// UnshareVM removes the users from JMachine document
func (c *Controller) UnshareVM(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == 0 {
		return nil
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(
		strconv.FormatInt(ping.ChannelId, 10),
	)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	return modelhelper.UnshareMachineByUid(ws.MachineUID)
}

// RemoveUsersFromMachine removes the collaboraters from the host machine
func (c *Controller) RemoveUsersFromMachine(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == 0 {
		return nil
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(strconv.FormatInt(ping.ChannelId, 10))
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	m, err := modelhelper.GetMachineByUid(ws.MachineUID)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the machine is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	// Get the klient.
	klientRef, err := klient.ConnectTimeout(c.kite, m.QueryString, time.Second*10)
	if err != nil {
		if err == klient.ErrDialingFailed || err == kite.ErrNoKitesAvailable {
			c.log.Error(
				"[%s] Klient is not registered to Kontrol. Err: %s",
				m.QueryString,
				err,
			)

			return nil // if the machine is not open, we cant do anything
		}

		return err
	}
	defer klientRef.Close()

	type args struct {
		Username string

		// we are not gonna use this propery here, just for reference
		Permanent bool
	}

	var iterErr error
	for _, user := range m.Users {
		// do not unshare from owner user
		if user.Sudo && user.Owner {
			continue
		}

		// fetch user for its username
		u, err := modelhelper.GetUserById(user.Id.Hex())
		if err != nil {
			c.log.Error(err.Error())

			// if we cant find the regarding user, do not do anything
			if err == mgo.ErrNotFound {
				continue
			}

			iterErr = err

			continue // do not stop iterating, unshare from others
		}

		param := args{
			Username: u.Name,
		}

		_, err = klientRef.Client.Tell("klient.unshare", param)
		if err != nil {
			c.log.Error(err.Error())

			// those are so error prone, force klient side not to change the API
			// or make them exported to some other package?
			if strings.Contains(err.Error(), "user is permanent") {
				continue
			}

			if strings.Contains(err.Error(), "user is not in the shared list") {
				continue
			}

			if strings.Contains(err.Error(), "User not found") {
				continue
			}

			iterErr = err

			continue // do not stop iterating, unshare from others
		}
	}

	res, err := klientRef.Client.Tell("klient.shared", nil)
	if err == nil {
		c.log.Info("other users in the machine: %+v", res.MustString())
	}

	// iterErr will be nil if we dont encounter to any error in iter
	return iterErr

}
<|endoftext|>