<commit_before>package captain // import "github.com/harbur/captain"

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

// Test Fixtures
var validApp = App{
	Image: "",
	Pre:   []string{"echo running pre"},
	Post:  []string{"echo running post"},
}

var invalidApp = App{
	Pre:  []string{"nonexistingPreCommand"},
	Post: []string{"nonexistingPostCommand"},
}

// Pre Command
func TestPre(t *testing.T) {
	res := Pre(validApp)
	assert.Nil(t, res, "No error returned")
}

func TestPreFail(t *testing.T) {
	res := Pre(invalidApp)
	assert.NotNil(t, res, "Error returned")
}

// Post Command
func TestPost(t *testing.T) {
	res := Post(validApp)
	assert.Nil(t, res, "No error returned")
}

func TestPostFail(t *testing.T) {
	res := Post(invalidApp)
	assert.NotNil(t, res, "Error returned")
}

// Build Command
func TestBuild(t *testing.T) {
	var testConfig = readConfig(configFile(basedir + "/test/Simple/captain.yml"))

	var buildOpts = BuildOptions{
		Config: testConfig,
	}

	Build(buildOpts)
}

// Pull Command
func TestPullNoBranchTags(t *testing.T) {
	var testConfig = readConfig(configFile(basedir + "/test/alpine/captain.yml"))

	var buildOpts = BuildOptions{
		Config:      testConfig,
		Branch_tags: false,
	}
	Pull(buildOpts)
}

// Purge Command
func TestPurge(t *testing.T) {
	var testConfig = readConfig(configFile(basedir + "/test/alpine/captain.yml"))

	var buildOpts = BuildOptions{
		Config: testConfig,
	}
	Purge(buildOpts)
}

// SelfUpdate Command
func TestSelfUpdate(t *testing.T) {
	// First Time Self update
	SelfUpdate()
	// Already Installed last version
	SelfUpdate()
}

func TestDownloadFile(t *testing.T) {
	res := downloadFile("/tmp/captain.html", "https://github.com/harbur/captain")
	assert.Nil(t, res, "captain")
}

func TestFindLastVersion(t *testing.T) {
	res := findLastVersion()
	assert.NotNil(t, res, "Last version exists")
}
<commit_msg>Added captain.Test tests<commit_after>package captain // import "github.com/harbur/captain"

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

// Test Fixtures
var validApp = App{
	Image: "",
	Pre:   []string{"echo running pre"},
	Post:  []string{"echo running post"},
}

var invalidApp = App{
	Pre:  []string{"nonexistingPreCommand"},
	Post: []string{"nonexistingPostCommand"},
}

// Pre Command
func TestPre(t *testing.T) {
	res := Pre(validApp)
	assert.Nil(t, res, "No error returned")
}

func TestPreFail(t *testing.T) {
	res := Pre(invalidApp)
	assert.NotNil(t, res, "Error returned")
}

// Post Command
func TestPost(t *testing.T) {
	res := Post(validApp)
	assert.Nil(t, res, "No error returned")
}

func TestPostFail(t *testing.T) {
	res := Post(invalidApp)
	assert.NotNil(t, res, "Error returned")
}

// Build Command
func TestBuild(t *testing.T) {
	var testConfig = readConfig(configFile(basedir + "/test/Simple/captain.yml"))

	var buildOpts = BuildOptions{
		Config: testConfig,
	}

	Build(buildOpts)
}

// Test Command
func TestTest(t *testing.T) {
	var testConfig = readConfig(configFile(basedir + "/test/Simple/captain.yml"))

	var buildOpts = BuildOptions{
		Config: testConfig,
	}

	Test(buildOpts)
}

// Pull Command
func TestPullNoBranchTags(t *testing.T) {
	var testConfig = readConfig(configFile(basedir + "/test/alpine/captain.yml"))

	var buildOpts = BuildOptions{
		Config:      testConfig,
		Branch_tags: false,
	}
	Pull(buildOpts)
}

// Purge Command
func TestPurge(t *testing.T) {
	var testConfig = readConfig(configFile(basedir + "/test/alpine/captain.yml"))

	var buildOpts = BuildOptions{
		Config: testConfig,
	}
	Purge(buildOpts)
}

// SelfUpdate Command
func TestSelfUpdate(t *testing.T) {
	// First Time Self update
	SelfUpdate()
	// Already Installed last version
	SelfUpdate()
}

func TestDownloadFile(t *testing.T) {
	res := downloadFile("/tmp/captain.html", "https://github.com/harbur/captain")
	assert.Nil(t, res, "captain")
}

func TestFindLastVersion(t *testing.T) {
	res := findLastVersion()
	assert.NotNil(t, res, "Last version exists")
}
<|endoftext|>


<commit_before>/*
 Copyright 2017 Crunchy Data Solutions, Inc.
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

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

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

// Crunchy Container Test
package cct

import (
    "context"
    "database/sql"
    "fmt"
    "path"
    "strings"
    "time"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/client"

    _ "github.com/lib/pq"
)

// returns the HostIP and Port reported by the service on 5432/tcp, which should always be postgresql
func pgHostFromContainer(
    docker *client.Client,
    containerId string) (host string, port string, err error) {

    inspect, err := docker.ContainerInspect(
        context.Background(), containerId)
    if err != nil {
        return
    }
    pb := inspect.HostConfig.PortBindings
    if pb == nil || len(pb) == 0 {
        err = fmt.Errorf(
            "Container network in an incomplete state; no PortBindings\n%#v\n",
            inspect.HostConfig)
        return
    }
    binding := inspect.HostConfig.PortBindings["5432/tcp"][0]

    host, port = binding.HostIP, binding.HostPort
    return
}

func isShutdownErr(err error) bool {
    return strings.HasPrefix(string(err.Error()),
        "pq: the database system is shutting down")
}

func isPasswordErr(err error) bool {
    return strings.HasPrefix(string(err.Error()),
        "pq: password authentication failed")
}

func isAcceptingConnectionString(conStr string) (ok bool, err error) {
    ok = false
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    err = pg.Ping()
    if err != nil {
        switch {
        case isShutdownErr(err):
            return false, nil
        case isPasswordErr(err):
            return false, nil
        default:
            err := fmt.Errorf("Database Ping error\n%s\n", err.Error())
            return false, err
        }
    }

    ok = true
    return
}

// returns ok when container_setup script is not running
func isFinishedSetup(conStr string) (ok bool, err error) {
    ok = false
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT NOT EXISTS (
        SELECT 1 from pg_stat_activity
        WHERE application_name = 'container_setup');`

    err = pg.QueryRow(query).Scan(&ok)
    if err != nil {
        return
    }

    return
}

// returns pg_is_in_backup()
func isInBackup(conStr string) (ok bool, err error) {
    ok = false
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    err = pg.QueryRow("SELECT pg_is_in_backup();").Scan(&ok)
    if err != nil {
        return
    }

    return
}

// assert a configurable parameter is set to value
func assertPostgresConf(
    conStr string,
    setting string,
    value string) (ok bool, foundval string, err error) {

    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    // SHOW command does not support $1 style variable replacement
    show := fmt.Sprintf("SHOW %s;", setting)

    err = pg.QueryRow(show).Scan(&foundval)
    if err != nil {
        return
    }

    ok = (foundval == value)
    return
}

// streams some vital columns from pg_stat_activity in tab form as they are retrieved
func pgStatActivity(conStr string) error {

    type activityrow struct {
        Pid int64 `json:"pid"`
        Database string `json:"database"`
        User string `json:"user"`
        ApplicationName string `json:"application_name"`
        State string `json:"state"`
        WaitEventType string `json:"wait_event_type"`
        WaitEvent string `json:"wait_event"`
        Query string `json:"query"`
        StartTime time.Time `json:"backend_start"`
    }

    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT pid, datname, usename, application_name
        , state, coalesce(wait_event_type, ''), coalesce(wait_event, ''), query, backend_start
        from pg_stat_activity WHERE pid != pg_backend_pid();`

    rows, err := pg.Query(query)
    if err != nil {
        return err
    }

    for rows.Next() {
        var r activityrow
        err = rows.Scan(&r.Pid, &r.Database, &r.User, &r.ApplicationName,
            &r.State, &r.WaitEventType, &r.WaitEvent, &r.Query, &r.StartTime)
        if err != nil {
            return err
        }
        fmt.Printf("%+v\n", r)
    }

    return nil
}

// returns pg_is_in_backup()
func isVacuuming(conStr string) (ok bool, err error) {
    ok = false
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT EXISTS (SELECT * from pg_stat_activity WHERE query like 'VACUUM%');`
    err = pg.QueryRow(query).Scan(&ok)
    if err != nil {
        return
    }

    return
}

func waitForVacuum(
    conStr string,
    timeoutSeconds int64) (err error) {

    fmt.Printf("Waiting maximum %d seconds for VACUUM", timeoutSeconds)

    escape := func() (bool, error) {
        return false, nil
    }
    condition1 := func() (bool, error) {
        v, err := isVacuuming(conStr)
        return ! v, err
    }
    var pollingMilliseconds int64 = 500
    if ok, err := timeoutOrReady(
        timeoutSeconds,
        escape,
        []func() (bool, error){condition1},
        pollingMilliseconds); err != nil {
        return err
    } else if ! ok {
        return fmt.Errorf("Timeout expired and VACUUM has not finished")
    }

    return
}

// print some basics on table vacuum & analyze
func pgTableStat(conStr string, tableName string) error {

    type tablestatrow struct {
        Relid int64 `json:"relid"`
        SchemaName string `json:"schemaname"`
        RelName string `json:"relname"`
        RowEstimate int64 `json:"row_estimate"`
        LastVacuum time.Time `json:"last_vacuum"`
        LastAnalyze time.Time `json:"last_analyze"`
        VacuumCount int64 `json:"vacuum_count"`
        AnalyzeCount int64 `json:"analyze_count"`
    }

    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT relid, schemaname, relname, n_live_tup
        , coalesce(last_vacuum, '2000-01-01'), coalesce(last_analyze, '2000-01-01')
        , vacuum_count, analyze_count
        FROM pg_stat_all_tables WHERE relname = $1;`

    rows, err := pg.Query(query, tableName)
    if err != nil {
        return err
    }

    for rows.Next() {
        var r tablestatrow
        err = rows.Scan(&r.Relid, &r.SchemaName, &r.RelName, &r.RowEstimate,
            &r.LastVacuum, &r.LastAnalyze, &r.VacuumCount, &r.AnalyzeCount)
        if err != nil {
            return err
        }
        fmt.Printf("%+v\n", r)
    }

    return nil
}

// CREATE, INSERT INTO, DROP TABLE
func relCreateInsertDrop(conStr string, temporary bool) (ok bool, err error) {
    ok = false

    pg, _ := sql.Open("postgres", conStr)

    defer pg.Close()

    var create string
    if temporary {
        create = "CREATE TEMPORARY TABLE"
    } else {
        create = "CREATE TABLE"
    }
    create += ` some_table(some_id integer NOT NULL PRIMARY KEY);`
    insert := `INSERT INTO some_table(some_id) VALUES(1), (2), (3);`
    drop := `DROP TABLE some_table;`

    _, err = pg.Exec(create)
    if err != nil {
        err = fmt.Errorf("ON CREATE TABLE\n%s", err.Error())
        return
    }

    if result, e := pg.Exec(insert); e != nil {
        err = fmt.Errorf("ON INSERT\n%s", e.Error())
        return
    } else {
        rc, e := result.RowsAffected();
        if e != nil || rc != 3 {
            err = fmt.Errorf(
                "Error or rowcount mismatch. Expected rc 3, got %d\n%s", rc, e.Error())
            return
        }
    }
    _, err = pg.Exec(drop)
    if err != nil {
        err = fmt.Errorf("ON DROP TABLE\n%s", err.Error())
        return
    }

    ok = true
    return
}

// does role exist on specified host?
func roleExists(conStr string, roleName string) (ok bool, err error) {
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT EXISTS (SELECT 1 from pg_roles WHERE rolname = $1);`

    err = pg.QueryRow(query, roleName).Scan(&ok)
    if err != nil {
        return
    }

    return
}

// does database exist on specified host?
func dbExists(conStr string, dbName string) (ok bool, err error) {
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT EXISTS (SELECT 1 from pg_database WHERE datname = $1);`

    err = pg.QueryRow(query, dbName).Scan(&ok)
    if err != nil {
        return
    }

    return
}

// wraps pg_isready via docker exec
func isPostgresReady(
    docker *client.Client,
    containerId string) (ok bool, err error) {

    ok = false

    pgroot, err := envValueFromContainer(docker, containerId, "PGROOT")
    if err != nil {
        return
    }
    cmd := []string{path.Join(pgroot, "bin/pg_isready")}

    execConf := types.ExecConfig{
        User: "postgres",
        Detach: true,
        Cmd: cmd,
    }
    execId, err := docker.ContainerExecCreate(
        context.Background(), containerId, execConf)
    if err != nil {
        return
    }

    err = docker.ContainerExecStart(
        context.Background(), execId.ID, types.ExecStartCheck{})
    if err != nil {
        return
    }

    // allow pg_isready to complete
    doneC := make(chan bool)
    timer := time.AfterFunc(time.Second * 2, func() {
        close(doneC)
    })

    ticker := time.NewTicker(time.Millisecond * 100)
    defer ticker.Stop()

    tick := func() (bool, error) {
        inspect, err := docker.ContainerExecInspect(
            context.Background(), execId.ID)

        if inspect.Running || err != nil {
            return false, err
        }

        close(doneC)
        return (inspect.ExitCode == 0), nil
    }

    for {
        select {
        case <- ticker.C:
            ok, err = tick()
            if err != nil {
                close(doneC)
            }
        case <- doneC:
            _ = timer.Stop()
            return
        }
    }
}

func waitForPostgresContainer(
    docker *client.Client,
    name string,
    timeoutSeconds int64) (containerId string, err error) {

    if c, err := ContainerFromName(docker, name); err != nil {
        return "", err
    } else {
        containerId = c.ID
    }

    conStr, err := buildConnectionString(docker, containerId, "postgres", "postgres")
    if err != nil {
        return "", fmt.Errorf("Connection String error\n%s\n", err.Error())
    }

    escape := func () (bool, error) {
        return isContainerDead(docker, containerId)
    }
    condition1 := func () (bool, error) {
        return isContainerRunning(docker, containerId)
    }
    condition2 := func () (bool, error) {
        if ok, err := isPostgresReady(docker, containerId); ! ok || err != nil {
            return false, err
        }
        return isAcceptingConnectionString(conStr)
    }
    condition3 := func () (bool, error) {
        return isFinishedSetup(conStr)
    }

    var ok bool
    var pollingMilliseconds int64 = 500
    if ok, err = timeoutOrReady(
        timeoutSeconds,
        escape,
        []func() (bool, error){condition1, condition2, condition3},
        pollingMilliseconds); err != nil {
        return
    } else if ! ok {
        return containerId, fmt.Errorf("Container stopped; or timeout expired, and container is not ready.")
    }

    // the container receives a stop at the end of setup. Make sure we haven't missed this, and let the db start again if we have.
    time.Sleep(1 * time.Second)

    if ok, err = timeoutOrReady(
        timeoutSeconds,
        escape,
        []func() (bool, error) {condition1, condition2, condition3},
        pollingMilliseconds); err != nil {
        return
    } else if ! ok {
        return containerId, fmt.Errorf("Container stopped; or timeout expired, and container is not ready.")
    }

    return
}

// Waits maximum of timeout seconds, or passes when all conditions are true, or will escape if escape function returns true. Returns false if timeout expired without meeting conditions.
func timeoutOrReady(
    timeoutSeconds int64,
    escape func() (bool, error),
    conditions []func() (bool, error),
    pollingIntervalMilliseconds int64) (ready bool, err error) {

    ready = false

    doneC := make(chan bool)

    timer := time.AfterFunc(time.Second * time.Duration(timeoutSeconds), func() {
        close(doneC)
    })

    ticker := time.NewTicker(time.Millisecond * time.Duration(pollingIntervalMilliseconds))
    defer ticker.Stop()

    tick := func() error {
        fmt.Printf(".")
        if esc, err := escape(); esc || err != nil {
            fmt.Println("Escape!")
            close(doneC)
            return err
        }

        for _, f := range conditions {
            if ok, err := f(); err != nil || ! ok {
                return err
            }
        }

        fmt.Printf("\n")
        ready = true
        close(doneC)
        return nil
    }

    for {
        select {
        case <- ticker.C:
            err = tick()
            if err != nil {
                close(doneC)
            }
        case <- doneC:
            _ = timer.Stop()
            return
        }
    }
}
<commit_msg>add timestamp, /tmp socket dir<commit_after>/*
 Copyright 2017 Crunchy Data Solutions, Inc.
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

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

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

// Crunchy Container Test
package cct

import (
    "context"
    "database/sql"
    "fmt"
    "path"
    "strings"
    "time"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/client"

    _ "github.com/lib/pq"
)

// returns the HostIP and Port reported by the service on 5432/tcp, which should always be postgresql
func pgHostFromContainer(
    docker *client.Client,
    containerId string) (host string, port string, err error) {

    inspect, err := docker.ContainerInspect(
        context.Background(), containerId)
    if err != nil {
        return
    }
    pb := inspect.HostConfig.PortBindings
    if pb == nil || len(pb) == 0 {
        err = fmt.Errorf(
            "Container network in an incomplete state; no PortBindings\n%#v\n",
            inspect.HostConfig)
        return
    }
    binding := inspect.HostConfig.PortBindings["5432/tcp"][0]

    host, port = binding.HostIP, binding.HostPort
    return
}

func isShutdownErr(err error) bool {
    return strings.HasPrefix(string(err.Error()),
        "pq: the database system is shutting down")
}

func isPasswordErr(err error) bool {
    return strings.HasPrefix(string(err.Error()),
        "pq: password authentication failed")
}

func isAcceptingConnectionString(conStr string) (ok bool, err error) {
    ok = false
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    err = pg.Ping()
    if err != nil {
        switch {
        case isShutdownErr(err):
            return false, nil
        case isPasswordErr(err):
            return false, nil
        default:
            err := fmt.Errorf("Database Ping error\n%s\n", err.Error())
            return false, err
        }
    }

    ok = true
    return
}

// returns ok when container_setup script is not running
func isFinishedSetup(conStr string) (ok bool, err error) {
    ok = false
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT NOT EXISTS (
        SELECT 1 from pg_stat_activity
        WHERE application_name = 'container_setup');`

    err = pg.QueryRow(query).Scan(&ok)
    if err != nil {
        return
    }

    return
}

// returns pg_is_in_backup()
func isInBackup(conStr string) (ok bool, err error) {
    ok = false
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    err = pg.QueryRow("SELECT pg_is_in_backup();").Scan(&ok)
    if err != nil {
        return
    }

    return
}

// assert a configurable parameter is set to value
func assertPostgresConf(
    conStr string,
    setting string,
    value string) (ok bool, foundval string, err error) {

    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    // SHOW command does not support $1 style variable replacement
    show := fmt.Sprintf("SHOW %s;", setting)

    err = pg.QueryRow(show).Scan(&foundval)
    if err != nil {
        return
    }

    ok = (foundval == value)
    return
}

// streams some vital columns from pg_stat_activity in tab form as they are retrieved
func pgStatActivity(conStr string) error {

    type activityrow struct {
        Pid int64 `json:"pid"`
        Database string `json:"database"`
        User string `json:"user"`
        ApplicationName string `json:"application_name"`
        State string `json:"state"`
        WaitEventType string `json:"wait_event_type"`
        WaitEvent string `json:"wait_event"`
        Query string `json:"query"`
        StartTime time.Time `json:"backend_start"`
    }

    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT pid, datname, usename, application_name
        , state, coalesce(wait_event_type, ''), coalesce(wait_event, ''), query, backend_start
        from pg_stat_activity WHERE pid != pg_backend_pid();`

    rows, err := pg.Query(query)
    if err != nil {
        return err
    }

    for rows.Next() {
        var r activityrow
        err = rows.Scan(&r.Pid, &r.Database, &r.User, &r.ApplicationName,
            &r.State, &r.WaitEventType, &r.WaitEvent, &r.Query, &r.StartTime)
        if err != nil {
            return err
        }
        fmt.Printf("%+v\n", r)
    }

    return nil
}

// returns pg_is_in_backup()
func isVacuuming(conStr string) (ok bool, err error) {
    ok = false
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT EXISTS (SELECT * from pg_stat_activity WHERE query like 'VACUUM%');`
    err = pg.QueryRow(query).Scan(&ok)
    if err != nil {
        return
    }

    return
}

func waitForVacuum(
    conStr string,
    timeoutSeconds int64) (err error) {

    fmt.Printf("Waiting maximum %d seconds for VACUUM", timeoutSeconds)

    escape := func() (bool, error) {
        return false, nil
    }
    condition1 := func() (bool, error) {
        v, err := isVacuuming(conStr)
        return ! v, err
    }
    var pollingMilliseconds int64 = 500
    if ok, err := timeoutOrReady(
        timeoutSeconds,
        escape,
        []func() (bool, error){condition1},
        pollingMilliseconds); err != nil {
        return err
    } else if ! ok {
        return fmt.Errorf("Timeout expired and VACUUM has not finished")
    }

    return
}

// print some basics on table vacuum & analyze
func pgTableStat(conStr string, tableName string) error {

    type tablestatrow struct {
        Relid int64 `json:"relid"`
        SchemaName string `json:"schemaname"`
        RelName string `json:"relname"`
        RowEstimate int64 `json:"row_estimate"`
        LastVacuum time.Time `json:"last_vacuum"`
        LastAnalyze time.Time `json:"last_analyze"`
        VacuumCount int64 `json:"vacuum_count"`
        AnalyzeCount int64 `json:"analyze_count"`
    }

    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT relid, schemaname, relname, n_live_tup
        , coalesce(last_vacuum, '2000-01-01'), coalesce(last_analyze, '2000-01-01')
        , vacuum_count, analyze_count
        FROM pg_stat_all_tables WHERE relname = $1;`

    rows, err := pg.Query(query, tableName)
    if err != nil {
        return err
    }

    for rows.Next() {
        var r tablestatrow
        err = rows.Scan(&r.Relid, &r.SchemaName, &r.RelName, &r.RowEstimate,
            &r.LastVacuum, &r.LastAnalyze, &r.VacuumCount, &r.AnalyzeCount)
        if err != nil {
            return err
        }
        fmt.Printf("%+v\n", r)
    }

    return nil
}

// CREATE, INSERT INTO, DROP TABLE
func relCreateInsertDrop(conStr string, temporary bool) (ok bool, err error) {
    ok = false

    pg, _ := sql.Open("postgres", conStr)

    defer pg.Close()

    var create string
    if temporary {
        create = "CREATE TEMPORARY TABLE"
    } else {
        create = "CREATE TABLE"
    }
    create += ` some_table(some_id integer NOT NULL PRIMARY KEY);`
    insert := `INSERT INTO some_table(some_id) VALUES(1), (2), (3);`
    drop := `DROP TABLE some_table;`

    _, err = pg.Exec(create)
    if err != nil {
        err = fmt.Errorf("ON CREATE TABLE\n%s", err.Error())
        return
    }

    if result, e := pg.Exec(insert); e != nil {
        err = fmt.Errorf("ON INSERT\n%s", e.Error())
        return
    } else {
        rc, e := result.RowsAffected();
        if e != nil || rc != 3 {
            err = fmt.Errorf(
                "Error or rowcount mismatch. Expected rc 3, got %d\n%s", rc, e.Error())
            return
        }
    }
    _, err = pg.Exec(drop)
    if err != nil {
        err = fmt.Errorf("ON DROP TABLE\n%s", err.Error())
        return
    }

    ok = true
    return
}

// does role exist on specified host?
func roleExists(conStr string, roleName string) (ok bool, err error) {
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT EXISTS (SELECT 1 from pg_roles WHERE rolname = $1);`

    err = pg.QueryRow(query, roleName).Scan(&ok)
    if err != nil {
        return
    }

    return
}

// does database exist on specified host?
func dbExists(conStr string, dbName string) (ok bool, err error) {
    pg, _ := sql.Open("postgres", conStr)
    defer pg.Close()

    query := `SELECT EXISTS (SELECT 1 from pg_database WHERE datname = $1);`

    err = pg.QueryRow(query, dbName).Scan(&ok)
    if err != nil {
        return
    }

    return
}

// wraps pg_isready via docker exec
func isPostgresReady(
    docker *client.Client,
    containerId string) (ok bool, err error) {

    ok = false

    pgroot, err := envValueFromContainer(docker, containerId, "PGROOT")
    if err != nil {
        return
    }
    cmd := []string{path.Join(pgroot, "bin/pg_isready"), "-h", "/tmp"}

    execConf := types.ExecConfig{
        User: "postgres",
        Detach: true,
        Cmd: cmd,
    }
    execId, err := docker.ContainerExecCreate(
        context.Background(), containerId, execConf)
    if err != nil {
        return
    }

    err = docker.ContainerExecStart(
        context.Background(), execId.ID, types.ExecStartCheck{})
    if err != nil {
        return
    }

    // allow pg_isready to complete
    doneC := make(chan bool)
    timer := time.AfterFunc(time.Second * 2, func() {
        close(doneC)
    })

    ticker := time.NewTicker(time.Millisecond * 100)
    defer ticker.Stop()

    tick := func() (bool, error) {
        inspect, err := docker.ContainerExecInspect(
            context.Background(), execId.ID)

        if inspect.Running || err != nil {
            return false, err
        }

        close(doneC)
        return (inspect.ExitCode == 0), nil
    }

    for {
        select {
        case <- ticker.C:
            ok, err = tick()
            if err != nil {
                close(doneC)
            }
        case <- doneC:
            _ = timer.Stop()
            return
        }
    }
}

func waitForPostgresContainer(
    docker *client.Client,
    name string,
    timeoutSeconds int64) (containerId string, err error) {

    if c, err := ContainerFromName(docker, name); err != nil {
        return "", err
    } else {
        containerId = c.ID
    }

    conStr, err := buildConnectionString(docker, containerId, "postgres", "postgres")
    if err != nil {
        return "", fmt.Errorf("Connection String error\n%s\n", err.Error())
    }

    escape := func () (bool, error) {
        return isContainerDead(docker, containerId)
    }
    condition1 := func () (bool, error) {
        return isContainerRunning(docker, containerId)
    }
    condition2 := func () (bool, error) {
        if ok, err := isPostgresReady(docker, containerId); ! ok || err != nil {
            return false, err
        }
        return isAcceptingConnectionString(conStr)
    }
    condition3 := func () (bool, error) {
        return isFinishedSetup(conStr)
    }

    var ok bool
    var pollingMilliseconds int64 = 500
    if ok, err = timeoutOrReady(
        timeoutSeconds,
        escape,
        []func() (bool, error){condition1, condition2, condition3},
        pollingMilliseconds); err != nil {
        return
    } else if ! ok {
        now := time.Now()
        return containerId,
            fmt.Errorf("%s: Container stopped; or timeout expired, and container is not ready.",
                now.Format("2006-01-02 15:04:05.000"))
    }

    // the container receives a stop at the end of setup. Make sure we haven't missed this, and let the db start again if we have.
    time.Sleep(1 * time.Second)

    if ok, err = timeoutOrReady(
        timeoutSeconds,
        escape,
        []func() (bool, error) {condition1, condition2, condition3},
        pollingMilliseconds); err != nil {
        return
    } else if ! ok {
        return containerId, fmt.Errorf("Container stopped; or timeout expired, and container is not ready.")
    }

    return
}

// Waits maximum of timeout seconds, or passes when all conditions are true, or will escape if escape function returns true. Returns false if timeout expired without meeting conditions.
func timeoutOrReady(
    timeoutSeconds int64,
    escape func() (bool, error),
    conditions []func() (bool, error),
    pollingIntervalMilliseconds int64) (ready bool, err error) {

    ready = false

    doneC := make(chan bool)

    timer := time.AfterFunc(time.Second * time.Duration(timeoutSeconds), func() {
        close(doneC)
    })

    ticker := time.NewTicker(time.Millisecond * time.Duration(pollingIntervalMilliseconds))
    defer ticker.Stop()

    tick := func() error {
        fmt.Printf(".")
        if esc, err := escape(); esc || err != nil {
            fmt.Println("Escape!")
            close(doneC)
            return err
        }

        for _, f := range conditions {
            if ok, err := f(); err != nil || ! ok {
                return err
            }
        }

        fmt.Printf("\n")
        ready = true
        close(doneC)
        return nil
    }

    for {
        select {
        case <- ticker.C:
            err = tick()
            if err != nil {
                close(doneC)
            }
        case <- doneC:
            _ = timer.Stop()
            return
        }
    }
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"

	"code.google.com/p/gopacket"
	"code.google.com/p/gopacket/layers"
	"code.google.com/p/gopacket/pcap"
	"github.com/Sirupsen/logrus"
)

func watch(iface *net.Interface) error {
	addr, err := getInterfaceIPAddress(iface)
	if err != nil {
		return err
	}

	if addr.IP.String() == "127.0.0.1" {
		return fmt.Errorf("Skipping %s (%s).", addr.String(), addr.IP)
	}

	Log.WithFields(logrus.Fields{
		"interface": iface.Name,
		"address":   addr.String(),
	}).Infof("Watching interface.")

	handle, err := pcap.OpenLive(iface.Name, 65536, true, pcap.BlockForever)
	if err != nil {
		return err
	}

	// This is a blocking call.
	processARP(handle, iface)

	Log.WithFields(logrus.Fields{
		"interface": iface.Name,
		"address":   addr.String(),
	}).Infof("Stopped watching interface.")

	return nil
}

func processARP(handle *pcap.Handle, iface *net.Interface) {
	src := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
	for {
		var packet gopacket.Packet
		select {
		case <-stop:
			return
		case packet = <-src.Packets():
			arpLayer := packet.Layer(layers.LayerTypeARP)
			if arpLayer == nil {
				continue
			}

			arp := arpLayer.(*layers.ARP)

			Log.WithFields(logrus.Fields{
				"IP address":  net.IP(arp.SourceProtAddress).String(),
				"MAC address": net.HardwareAddr(arp.SourceHwAddress).String(),
			}).Infof("Recieved ARP.")
		}
	}
}
<commit_msg>Separated out ARP handling functions with some comments about the data that forms an ARP packet.<commit_after>package main

import (
	"fmt"
	"net"

	"code.google.com/p/gopacket"
	"code.google.com/p/gopacket/layers"
	"code.google.com/p/gopacket/pcap"
	"github.com/Sirupsen/logrus"
)

func watch(iface *net.Interface) error {
	addr, err := getInterfaceIPAddress(iface)
	if err != nil {
		return err
	}

	if addr.IP.String() == "127.0.0.1" {
		return fmt.Errorf("Skipping %s (%s).", addr.String(), addr.IP)
	}

	Log.WithFields(logrus.Fields{
		"interface": iface.Name,
		"address":   addr.String(),
	}).Infof("Watching interface.")

	handle, err := pcap.OpenLive(iface.Name, 65536, true, pcap.BlockForever)
	if err != nil {
		return err
	}

	// This is a blocking call.
	process(handle, iface)

	Log.WithFields(logrus.Fields{
		"interface": iface.Name,
		"address":   addr.String(),
	}).Infof("Stopped watching interface.")

	return nil
}

func process(handle *pcap.Handle, iface *net.Interface) {
	src := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
	for {
		var packet gopacket.Packet
		select {
		case <-stop:
			return
		case packet = <-src.Packets():
			arpLayer := packet.Layer(layers.LayerTypeARP)
			if arpLayer == nil {
				continue
			}

			handleARP(arpLayer.(*layers.ARP))

		}
	}
}

func handleARP(arp *layers.ARP) {
	switch arp.Operation {
	case 1: // This operation value defines an arp request.
		// For a standard ARP request:
		// - SourceHwAddress: This is the MAC address of the requestor.
		// - SourceProtAddress: This is the IP address of the requestor.
		// - DstHwAddress: This field is ignored. Basically, this is what an ARP request is actually requesting.
		// - DstProtAddress: This is the IP address for which the requestor would like the MAC address for (i.e. a reply).
		Log.WithFields(logrus.Fields{
			"Requestor MAC Address":  net.HardwareAddr(arp.SourceHwAddress).String(),
			"Requestor IP Address":   net.IP(arp.SourceProtAddress).String(),
			"Ignored MAC Address":    net.HardwareAddr(arp.DstHwAddress).String(),
			"Destination IP Address": net.IP(arp.DstProtAddress).String(),
		}).Infof("Recieved ARP request.")
	case 2: // This operation value defines an arp reply.
		// For an ARP reply:
		// - SourceHwAddress: This is the MAC address of the replier.
		// - SourceProtAddress: This is the IP address of the replier.
		// - DstHwAddress: This field indicates the address of the requesting host.
		// - DstProtAddress: This is the IP address of the requesting host.
		Log.WithFields(logrus.Fields{
			"Replier MAC Address":   net.HardwareAddr(arp.SourceHwAddress).String(),
			"Replier IP Address":    net.IP(arp.SourceProtAddress).String(),
			"Requestor MAC Address": net.HardwareAddr(arp.DstHwAddress).String(),
			"Requestor IP Address":  net.IP(arp.DstProtAddress).String(),
		}).Infof("Recieved ARP reply.")
	default:
		Log.Warnf("Unknown sender operation for ARP packet: %#v", *arp)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/dsa"
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/x509"
	"flag"
	"fmt"
	tc "github.com/thijzert/go-termcolours"
	"github.com/thijzert/sslprobe"
	"os"
)

var (
	port = flag.Int("port", 443, "Connect to this port")

	full  = flag.Bool("full", false, "Perform cipher and curve preference tests on every supported version rather than just the highest")
	quick = flag.Bool("quick", false, "Just test for protocol version support and exit early")

	full_a  = flag.Bool("f", false, "Alias for --full")
	quick_a = flag.Bool("q", false, "Alias for --quick")
	port_a  = flag.Int("p", 443, "Alias for --port")

	just_ssl2  = flag.Bool("ssl2", false, "Just use SSLv2 (experimental)")
	just_ssl3  = flag.Bool("ssl3", false, "Just use SSLv3")
	just_tls10 = flag.Bool("tls10", false, "Just use TLSv1.0")
	just_tls11 = flag.Bool("tls11", false, "Just use TLSv1.1")
	just_tls12 = flag.Bool("tls12", false, "Just use TLSv1.2")
	just_tls13 = flag.Bool("tls13", false, "Just use TLSv1.3 (experimental)")
)

func init() {
	flag.Parse()
	if *quick_a {
		*quick = true
	}
	if *full_a {
		*full = true
	}
	if *full {
		*quick = false
	}

	if *just_tls13 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.TLS_1_3}
	} else if *just_tls12 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.TLS_1_2}
	} else if *just_tls11 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.TLS_1_1}
	} else if *just_tls10 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.TLS_1_0}
	} else if *just_ssl3 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.SSL_3_0}
	} else if *just_ssl2 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.SSL_2_0}
	}

	if *port == 443 && *port_a != 443 {
		*port = *port_a
	}
}

func main() {
	if flag.NArg() == 0 {
		fmt.Fprintf(os.Stderr, "Usage: %s {HOST} [{OPTIONS}]\n", os.Args[0])
		flag.PrintDefaults()
		os.Exit(1)
	}

	var col func(string) string = nil

	host := flag.Arg(0)
	fmt.Printf("Server:   %s\n", tc.Bblue(fmt.Sprintf("%s:%d", host, *port)))

	probe := sslprobe.New(host, *port)

	var max_version sslprobe.TLSVersion = 0
	fmt.Printf("Protocol support:")
	for _, sv := range probe.SupportedVersions {
		if sv.Supported {
			max_version = sv.Version
		}
		fmt.Printf("  %s", sv.Pretty())
	}
	fmt.Printf("\n")
	if max_version == 0 {
		return
	}

	// Print certificate chain(s)
	for i, _ := range probe.SupportedVersions {
		sv := &probe.SupportedVersions[len(probe.SupportedVersions)-i-1]
		if !*full && sv.Version != max_version {
			continue
		}
		if !sv.Supported || sv.CertificateChain == nil {
			continue
		}

		fmt.Printf("\nCertificate chain:\n")
		for i, b := range sv.CertificateChain {
			cert, err := x509.ParseCertificate(b)
			if err != nil {
				fmt.Printf("   %2d %s: %s\n", i, tc.Red("error"), err)
				continue
			}
			subj, iss := prettyCertificate(cert)
			fmt.Printf("   %2d %s\n      %s\n", i, subj, iss)
		}
	}

	if *quick {
		return
	}

	fmt.Printf("\nCipher suites, in server-preferred order:\n")
	var cipher_prefs []sslprobe.CipherInfo = []sslprobe.CipherInfo{}
	for i, _ := range probe.SupportedVersions {
		sv := &probe.SupportedVersions[len(probe.SupportedVersions)-i-1]
		if !*full && sv.Version != max_version {
			continue
		}
		if sv.Supported {
			probe.FillDetails(sv.Version)

			if len(cipher_prefs) == 0 {
				cipher_prefs = sv.SupportedCiphers
			}
			fmt.Printf("  %s\n", sv.Version)
			for _, c := range sv.SupportedCiphers {
				fmt.Printf("     %s\n", c.Pretty())
			}
		}
	}

	// Loop over the highest protocol version's ciphers again and figure out if
	// there's any useful information in the ServerKeyExchange
	for i, _ := range probe.SupportedVersions {
		sv := probe.SupportedVersions[len(probe.SupportedVersions)-i-1]
		if !sv.Supported {
			continue
		}

		if sv.FFDHSize > 0 || len(sv.SupportedCurves) > 0 {
			fmt.Printf("\nEphemeral Key Exchange strength\n")

			if sv.FFDHSize > 0 {
				col = cStrength(sv.FFDHSize)
				fmt.Printf("   DH Modulus size: %5s bits\n", col(fmt.Sprintf("%d", sv.FFDHSize)))
			}
			if len(sv.SupportedCurves) == 1 {
				curve := sv.SupportedCurves[0]
				dlen := curve.DHBits()
				col = cStrength(dlen)
				fmt.Printf("   Preferred Curve: %s (%d bits, eq %s bits DH)\n", col(curve.Name), curve.Bits, col(fmt.Sprintf("%d", dlen)))
			} else if len(sv.SupportedCurves) > 1 {
				fmt.Printf("   Supported elliptic curves:\n")
				for _, curve := range sv.SupportedCurves {
					dlen := curve.DHBits()
					col = cStrength(dlen)
					fmt.Printf("        %s (%d bits, eq %s bits DH)\n", col(curve.Name), curve.Bits, col(fmt.Sprintf("%d", dlen)))
				}
			}
		}

		break
	}
}

func cStrength(bits int) func(string) string {
	if bits > 2048 {
		return tc.Green
	} else if bits > 1536 {
		return tc.Yellow
	} else if bits > 1024 {
		return tc.Red
	} else {
		return tc.Bred
	}
}

func prettyCertificate(cert *x509.Certificate) (string, string) {
	key := "unknown"
	if cert.PublicKeyAlgorithm == x509.RSA {
		pk, ok := cert.PublicKey.(*rsa.PublicKey)
		if !ok {
			key = tc.Bred("RSA - error")
		} else {
			col := cStrength(pk.N.BitLen())
			key = col(fmt.Sprintf("RSA-%d", pk.N.BitLen()))
		}
	} else if cert.PublicKeyAlgorithm == x509.DSA {
		pk, ok := cert.PublicKey.(*dsa.PublicKey)
		if !ok {
			key = tc.Bred("DSA - error")
		} else {
			bl := pk.P.BitLen()
			col := tc.Red
			if bl < 1536 {
				col = tc.Bred
			}
			key = col(fmt.Sprintf("DSA-%d", bl))
		}
	} else if cert.PublicKeyAlgorithm == x509.ECDSA {
		pk, ok := cert.PublicKey.(*ecdsa.PublicKey)
		if !ok {
			key = tc.Bred("ECDSA - error")
		} else {
			bl := pk.Params().P.BitLen()
			col := tc.Green
			if bl < 224 {
				col = tc.Red
			} else if bl < 256 {
				col = tc.Yellow
			}
			key = col(fmt.Sprintf("ECDSA-%d", bl))
		}
	}

	sig := strSigAlg(cert.SignatureAlgorithm)
	if cert.SignatureAlgorithm == x509.UnknownSignatureAlgorithm ||
		cert.SignatureAlgorithm == x509.MD2WithRSA ||
		cert.SignatureAlgorithm == x509.MD5WithRSA {
		sig = tc.Bred(sig)
	} else if cert.SignatureAlgorithm == x509.SHA1WithRSA ||
		cert.SignatureAlgorithm == x509.DSAWithSHA1 ||
		cert.SignatureAlgorithm == x509.DSAWithSHA256 ||
		cert.SignatureAlgorithm == x509.ECDSAWithSHA1 {
		sig = tc.Red(sig)
	} else {
		sig = tc.Green(sig)
	}

	subject := cert.Subject.CommonName
	if len(subject) > 45 {
		subject = subject[0:45]
	}
	issuer := cert.Issuer.CommonName
	if len(issuer) > 45 {
		issuer = issuer[0:45]
	}

	subject = fmt.Sprintf("subject: %-45s  key type:  %s", subject, key)
	issuer = fmt.Sprintf("issuer:  %-45s  signature: %s", issuer, sig)

	return subject, issuer
}

func strSigAlg(s x509.SignatureAlgorithm) string {
	if s == x509.MD2WithRSA {
		return "MD2-RSA"
	} else if s == x509.MD5WithRSA {
		return "MD5-RSA"
	} else if s == x509.SHA1WithRSA {
		return "SHA1-RSA"
	} else if s == x509.SHA256WithRSA {
		return "SHA256-RSA"
	} else if s == x509.SHA384WithRSA {
		return "SHA384-RSA"
	} else if s == x509.SHA512WithRSA {
		return "SHA512-RSA"
	} else if s == x509.DSAWithSHA1 {
		return "SHA1-DSA"
	} else if s == x509.DSAWithSHA256 {
		return "SHA256-DSA"
	} else if s == x509.ECDSAWithSHA1 {
		return "SHA1-ECDSA"
	} else if s == x509.ECDSAWithSHA256 {
		return "SHA256-ECDSA"
	} else if s == x509.ECDSAWithSHA384 {
		return "SHA384-ECDSA"
	} else if s == x509.ECDSAWithSHA512 {
		return "SHA512-ECDSA"
	}
	return "UnknownSignatureAlgorithm"
}
<commit_msg>Add certificate fingerprints<commit_after>package main

import (
	"crypto/dsa"
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/x509"
	"flag"
	"fmt"
	tc "github.com/thijzert/go-termcolours"
	"github.com/thijzert/sslprobe"
	"os"
)

var (
	port = flag.Int("port", 443, "Connect to this port")

	full  = flag.Bool("full", false, "Perform cipher and curve preference tests on every supported version rather than just the highest")
	quick = flag.Bool("quick", false, "Just test for protocol version support and exit early")

	full_a  = flag.Bool("f", false, "Alias for --full")
	quick_a = flag.Bool("q", false, "Alias for --quick")
	port_a  = flag.Int("p", 443, "Alias for --port")

	just_ssl2  = flag.Bool("ssl2", false, "Just use SSLv2 (experimental)")
	just_ssl3  = flag.Bool("ssl3", false, "Just use SSLv3")
	just_tls10 = flag.Bool("tls10", false, "Just use TLSv1.0")
	just_tls11 = flag.Bool("tls11", false, "Just use TLSv1.1")
	just_tls12 = flag.Bool("tls12", false, "Just use TLSv1.2")
	just_tls13 = flag.Bool("tls13", false, "Just use TLSv1.3 (experimental)")
)

func init() {
	flag.Parse()
	if *quick_a {
		*quick = true
	}
	if *full_a {
		*full = true
	}
	if *full {
		*quick = false
	}

	if *just_tls13 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.TLS_1_3}
	} else if *just_tls12 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.TLS_1_2}
	} else if *just_tls11 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.TLS_1_1}
	} else if *just_tls10 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.TLS_1_0}
	} else if *just_ssl3 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.SSL_3_0}
	} else if *just_ssl2 {
		sslprobe.AllVersions = []sslprobe.TLSVersion{sslprobe.SSL_2_0}
	}

	if *port == 443 && *port_a != 443 {
		*port = *port_a
	}
}

func main() {
	if flag.NArg() == 0 {
		fmt.Fprintf(os.Stderr, "Usage: %s {HOST} [{OPTIONS}]\n", os.Args[0])
		flag.PrintDefaults()
		os.Exit(1)
	}

	var col func(string) string = nil

	host := flag.Arg(0)
	fmt.Printf("Server:   %s\n", tc.Bblue(fmt.Sprintf("%s:%d", host, *port)))

	probe := sslprobe.New(host, *port)

	var max_version sslprobe.TLSVersion = 0
	fmt.Printf("Protocol support:")
	for _, sv := range probe.SupportedVersions {
		if sv.Supported {
			max_version = sv.Version
		}
		fmt.Printf("  %s", sv.Pretty())
	}
	fmt.Printf("\n")
	if max_version == 0 {
		return
	}

	// Print certificate chain(s)
	for i, _ := range probe.SupportedVersions {
		sv := &probe.SupportedVersions[len(probe.SupportedVersions)-i-1]
		if !*full && sv.Version != max_version {
			continue
		}
		if !sv.Supported || sv.CertificateChain == nil {
			continue
		}

		fmt.Printf("\nCertificate chain:\n")
		for i, b := range sv.CertificateChain {
			cert, err := x509.ParseCertificate(b)
			if err != nil {
				fmt.Printf("   %2d %s: %s\n", i, tc.Red("error"), err)
				continue
			}
			subj, iss := prettyCertificate(cert)
			fmt.Printf("   %2d %s\n      %s\n", i, subj, iss)
		}
	}

	if *quick {
		return
	}

	fmt.Printf("\nCipher suites, in server-preferred order:\n")
	var cipher_prefs []sslprobe.CipherInfo = []sslprobe.CipherInfo{}
	for i, _ := range probe.SupportedVersions {
		sv := &probe.SupportedVersions[len(probe.SupportedVersions)-i-1]
		if !*full && sv.Version != max_version {
			continue
		}
		if sv.Supported {
			probe.FillDetails(sv.Version)

			if len(cipher_prefs) == 0 {
				cipher_prefs = sv.SupportedCiphers
			}
			fmt.Printf("  %s\n", sv.Version)
			for _, c := range sv.SupportedCiphers {
				fmt.Printf("     %s\n", c.Pretty())
			}
		}
	}

	// Loop over the highest protocol version's ciphers again and figure out if
	// there's any useful information in the ServerKeyExchange
	for i, _ := range probe.SupportedVersions {
		sv := probe.SupportedVersions[len(probe.SupportedVersions)-i-1]
		if !sv.Supported {
			continue
		}

		if sv.FFDHSize > 0 || len(sv.SupportedCurves) > 0 {
			fmt.Printf("\nEphemeral Key Exchange strength\n")

			if sv.FFDHSize > 0 {
				col = cStrength(sv.FFDHSize)
				fmt.Printf("   DH Modulus size: %5s bits\n", col(fmt.Sprintf("%d", sv.FFDHSize)))
			}
			if len(sv.SupportedCurves) == 1 {
				curve := sv.SupportedCurves[0]
				dlen := curve.DHBits()
				col = cStrength(dlen)
				fmt.Printf("   Preferred Curve: %s (%d bits, eq %s bits DH)\n", col(curve.Name), curve.Bits, col(fmt.Sprintf("%d", dlen)))
			} else if len(sv.SupportedCurves) > 1 {
				fmt.Printf("   Supported elliptic curves:\n")
				for _, curve := range sv.SupportedCurves {
					dlen := curve.DHBits()
					col = cStrength(dlen)
					fmt.Printf("        %s (%d bits, eq %s bits DH)\n", col(curve.Name), curve.Bits, col(fmt.Sprintf("%d", dlen)))
				}
			}
		}

		break
	}
}

func cStrength(bits int) func(string) string {
	if bits > 2048 {
		return tc.Green
	} else if bits > 1536 {
		return tc.Yellow
	} else if bits > 1024 {
		return tc.Red
	} else {
		return tc.Bred
	}
}

func prettyCertificate(cert *x509.Certificate) (string, string) {
	key := "unknown"
	if cert.PublicKeyAlgorithm == x509.RSA {
		pk, ok := cert.PublicKey.(*rsa.PublicKey)
		if !ok {
			key = tc.Bred("RSA - error")
		} else {
			col := cStrength(pk.N.BitLen())
			key = col(fmt.Sprintf("RSA-%d", pk.N.BitLen()))
		}
	} else if cert.PublicKeyAlgorithm == x509.DSA {
		pk, ok := cert.PublicKey.(*dsa.PublicKey)
		if !ok {
			key = tc.Bred("DSA - error")
		} else {
			bl := pk.P.BitLen()
			col := tc.Red
			if bl < 1536 {
				col = tc.Bred
			}
			key = col(fmt.Sprintf("DSA-%d", bl))
		}
	} else if cert.PublicKeyAlgorithm == x509.ECDSA {
		pk, ok := cert.PublicKey.(*ecdsa.PublicKey)
		if !ok {
			key = tc.Bred("ECDSA - error")
		} else {
			bl := pk.Params().P.BitLen()
			col := tc.Green
			if bl < 224 {
				col = tc.Red
			} else if bl < 256 {
				col = tc.Yellow
			}
			key = col(fmt.Sprintf("ECDSA-%d", bl))
		}
	}

	sig := strSigAlg(cert.SignatureAlgorithm)
	if cert.SignatureAlgorithm == x509.UnknownSignatureAlgorithm ||
		cert.SignatureAlgorithm == x509.MD2WithRSA ||
		cert.SignatureAlgorithm == x509.MD5WithRSA {
		sig = tc.Bred(sig)
	} else if cert.SignatureAlgorithm == x509.SHA1WithRSA ||
		cert.SignatureAlgorithm == x509.DSAWithSHA1 ||
		cert.SignatureAlgorithm == x509.DSAWithSHA256 ||
		cert.SignatureAlgorithm == x509.ECDSAWithSHA1 {
		sig = tc.Red(sig)
	} else {
		sig = tc.Green(sig)
	}

	subject := cert.Subject.CommonName
	if len(subject) > 45 {
		subject = subject[0:45]
	}
	issuer := cert.Issuer.CommonName
	if len(issuer) > 45 {
		issuer = issuer[0:45]
	}

	fpr := tc.Bblack(fmt.Sprintf("%x", sha1.Sum(cert.Raw)))

	subject = fmt.Sprintf("subject: %-45s  key type: %s / sig: %s", subject, key, sig)
	issuer = fmt.Sprintf("issuer:  %-45s  fingerprint: %s", issuer, fpr)

	return subject, issuer
}

func strSigAlg(s x509.SignatureAlgorithm) string {
	if s == x509.MD2WithRSA {
		return "MD2-RSA"
	} else if s == x509.MD5WithRSA {
		return "MD5-RSA"
	} else if s == x509.SHA1WithRSA {
		return "SHA1-RSA"
	} else if s == x509.SHA256WithRSA {
		return "SHA256-RSA"
	} else if s == x509.SHA384WithRSA {
		return "SHA384-RSA"
	} else if s == x509.SHA512WithRSA {
		return "SHA512-RSA"
	} else if s == x509.DSAWithSHA1 {
		return "SHA1-DSA"
	} else if s == x509.DSAWithSHA256 {
		return "SHA256-DSA"
	} else if s == x509.ECDSAWithSHA1 {
		return "SHA1-ECDSA"
	} else if s == x509.ECDSAWithSHA256 {
		return "SHA256-ECDSA"
	} else if s == x509.ECDSAWithSHA384 {
		return "SHA384-ECDSA"
	} else if s == x509.ECDSAWithSHA512 {
		return "SHA512-ECDSA"
	}
	return "UnknownSignatureAlgorithm"
}
<|endoftext|>


<commit_before>// All of the methods used to communicate with the digital_ocean API
// are here. Their API is on a path to V2, so just plain JSON is used
// in place of a proper client library for now.

package digitalocean

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/mitchellh/mapstructure"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const DIGITALOCEAN_API_URL = "https://api.digitalocean.com"

type Image struct {
	Id           uint
	Name         string
	Distribution string
}

type ImagesResp struct {
	Images []Image
}

type DigitalOceanClient struct {
	// The http client for communicating
	client *http.Client

	// The base URL of the API
	BaseURL string

	// Credentials
	ClientID string
	APIKey   string
}

// Creates a new client for communicating with DO
func (d DigitalOceanClient) New(client string, key string) *DigitalOceanClient {
	c := &DigitalOceanClient{
		client: &http.Client{
			Transport: &http.Transport{
				Proxy: http.ProxyFromEnvironment,
			},
		},
		BaseURL:  DIGITALOCEAN_API_URL,
		ClientID: client,
		APIKey:   key,
	}
	return c
}

// Creates an SSH Key and returns it's id
func (d DigitalOceanClient) CreateKey(name string, pub string) (uint, error) {
	params := url.Values{}
	params.Set("name", name)
	params.Set("ssh_pub_key", pub)

	body, err := NewRequest(d, "ssh_keys/new", params)
	if err != nil {
		return 0, err
	}

	// Read the SSH key's ID we just created
	key := body["ssh_key"].(map[string]interface{})
	keyId := key["id"].(float64)
	return uint(keyId), nil
}

// Destroys an SSH key
func (d DigitalOceanClient) DestroyKey(id uint) error {
	path := fmt.Sprintf("ssh_keys/%v/destroy", id)
	_, err := NewRequest(d, path, url.Values{})
	return err
}

// Creates a droplet and returns it's id
func (d DigitalOceanClient) CreateDroplet(name string, size uint, image uint, region uint, keyId uint) (uint, error) {
	params := url.Values{}
	params.Set("name", name)
	params.Set("size_id", fmt.Sprintf("%v", size))
	params.Set("image_id", fmt.Sprintf("%v", image))
	params.Set("region_id", fmt.Sprintf("%v", region))
	params.Set("ssh_key_ids", fmt.Sprintf("%v", keyId))

	body, err := NewRequest(d, "droplets/new", params)
	if err != nil {
		return 0, err
	}

	// Read the Droplets ID
	droplet := body["droplet"].(map[string]interface{})
	dropletId := droplet["id"].(float64)
	return uint(dropletId), err
}

// Destroys a droplet
func (d DigitalOceanClient) DestroyDroplet(id uint) error {
	path := fmt.Sprintf("droplets/%v/destroy", id)
	_, err := NewRequest(d, path, url.Values{})
	return err
}

// Powers off a droplet
func (d DigitalOceanClient) PowerOffDroplet(id uint) error {
	path := fmt.Sprintf("droplets/%v/power_off", id)

	_, err := NewRequest(d, path, url.Values{})

	return err
}

// Shutsdown a droplet. This is a "soft" shutdown.
func (d DigitalOceanClient) ShutdownDroplet(id uint) error {
	path := fmt.Sprintf("droplets/%v/shutdown", id)

	_, err := NewRequest(d, path, url.Values{})

	return err
}

// Creates a snaphot of a droplet by it's ID
func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error {
	path := fmt.Sprintf("droplets/%v/snapshot", id)

	params := url.Values{}
	params.Set("name", name)

	_, err := NewRequest(d, path, params)

	return err
}

// Returns all available images.
func (d DigitalOceanClient) Images() ([]Image, error) {
	resp, err := NewRequest(d, "images", url.Values{})
	if err != nil {
		return nil, err
	}

	var result ImagesResp
	if err := mapstructure.Decode(resp, &result); err != nil {
		return nil, err
	}

	return result.Images, nil
}

// Destroys an image by its ID.
func (d DigitalOceanClient) DestroyImage(id uint) error {
	path := fmt.Sprintf("images/%d/destroy", id)
	_, err := NewRequest(d, path, url.Values{})
	return err
}

// Returns DO's string representation of status "off" "new" "active" etc.
func (d DigitalOceanClient) DropletStatus(id uint) (string, string, error) {
	path := fmt.Sprintf("droplets/%v", id)

	body, err := NewRequest(d, path, url.Values{})
	if err != nil {
		return "", "", err
	}

	var ip string

	// Read the droplet's "status"
	droplet := body["droplet"].(map[string]interface{})
	status := droplet["status"].(string)

	if droplet["ip_address"] != nil {
		ip = droplet["ip_address"].(string)
	}

	return ip, status, err
}

// Sends an api request and returns a generic map[string]interface of
// the response.
func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[string]interface{}, error) {
	client := d.client

	// Add the authentication parameters
	params.Set("client_id", d.ClientID)
	params.Set("api_key", d.APIKey)

	url := fmt.Sprintf("%s/%s?%s", DIGITALOCEAN_API_URL, path, params.Encode())

	// Do some basic scrubbing so sensitive information doesn't appear in logs
	scrubbedUrl := strings.Replace(url, d.ClientID, "CLIENT_ID", -1)
	scrubbedUrl = strings.Replace(scrubbedUrl, d.APIKey, "API_KEY", -1)
	log.Printf("sending new request to digitalocean: %s", scrubbedUrl)

	var lastErr error
	for attempts := 1; attempts < 10; attempts++ {
		resp, err := client.Get(url)
		if err != nil {
			return nil, err
		}

		body, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			return nil, err
		}

		log.Printf("response from digitalocean: %s", body)

		var decodedResponse map[string]interface{}
		err = json.Unmarshal(body, &decodedResponse)
		if err != nil {
			err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s",
				resp.StatusCode, body))
			return decodedResponse, err
		}

		// Check for errors sent by digitalocean
		status := decodedResponse["status"].(string)
		if status == "OK" {
			return decodedResponse, nil
		}

		if status == "ERROR" {
			status = decodedResponse["error_message"].(string)
		}

		lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s",
			resp.StatusCode, status))
		log.Println(lastErr)
		if strings.Contains(status, "a pending event") {
			// Retry, DigitalOcean sends these dumb "pending event"
			// errors all the time.
			time.Sleep(5 * time.Second)
			continue
		}

		// Some other kind of error. Just return.
		return decodedResponse, lastErr
	}

	return nil, lastErr
}
<commit_msg>builder/digitalocean: error message key is "message" not "error_message"<commit_after>// All of the methods used to communicate with the digital_ocean API
// are here. Their API is on a path to V2, so just plain JSON is used
// in place of a proper client library for now.

package digitalocean

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/mitchellh/mapstructure"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const DIGITALOCEAN_API_URL = "https://api.digitalocean.com"

type Image struct {
	Id           uint
	Name         string
	Distribution string
}

type ImagesResp struct {
	Images []Image
}

type DigitalOceanClient struct {
	// The http client for communicating
	client *http.Client

	// The base URL of the API
	BaseURL string

	// Credentials
	ClientID string
	APIKey   string
}

// Creates a new client for communicating with DO
func (d DigitalOceanClient) New(client string, key string) *DigitalOceanClient {
	c := &DigitalOceanClient{
		client: &http.Client{
			Transport: &http.Transport{
				Proxy: http.ProxyFromEnvironment,
			},
		},
		BaseURL:  DIGITALOCEAN_API_URL,
		ClientID: client,
		APIKey:   key,
	}
	return c
}

// Creates an SSH Key and returns it's id
func (d DigitalOceanClient) CreateKey(name string, pub string) (uint, error) {
	params := url.Values{}
	params.Set("name", name)
	params.Set("ssh_pub_key", pub)

	body, err := NewRequest(d, "ssh_keys/new", params)
	if err != nil {
		return 0, err
	}

	// Read the SSH key's ID we just created
	key := body["ssh_key"].(map[string]interface{})
	keyId := key["id"].(float64)
	return uint(keyId), nil
}

// Destroys an SSH key
func (d DigitalOceanClient) DestroyKey(id uint) error {
	path := fmt.Sprintf("ssh_keys/%v/destroy", id)
	_, err := NewRequest(d, path, url.Values{})
	return err
}

// Creates a droplet and returns it's id
func (d DigitalOceanClient) CreateDroplet(name string, size uint, image uint, region uint, keyId uint) (uint, error) {
	params := url.Values{}
	params.Set("name", name)
	params.Set("size_id", fmt.Sprintf("%v", size))
	params.Set("image_id", fmt.Sprintf("%v", image))
	params.Set("region_id", fmt.Sprintf("%v", region))
	params.Set("ssh_key_ids", fmt.Sprintf("%v", keyId))

	body, err := NewRequest(d, "droplets/new", params)
	if err != nil {
		return 0, err
	}

	// Read the Droplets ID
	droplet := body["droplet"].(map[string]interface{})
	dropletId := droplet["id"].(float64)
	return uint(dropletId), err
}

// Destroys a droplet
func (d DigitalOceanClient) DestroyDroplet(id uint) error {
	path := fmt.Sprintf("droplets/%v/destroy", id)
	_, err := NewRequest(d, path, url.Values{})
	return err
}

// Powers off a droplet
func (d DigitalOceanClient) PowerOffDroplet(id uint) error {
	path := fmt.Sprintf("droplets/%v/power_off", id)

	_, err := NewRequest(d, path, url.Values{})

	return err
}

// Shutsdown a droplet. This is a "soft" shutdown.
func (d DigitalOceanClient) ShutdownDroplet(id uint) error {
	path := fmt.Sprintf("droplets/%v/shutdown", id)

	_, err := NewRequest(d, path, url.Values{})

	return err
}

// Creates a snaphot of a droplet by it's ID
func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error {
	path := fmt.Sprintf("droplets/%v/snapshot", id)

	params := url.Values{}
	params.Set("name", name)

	_, err := NewRequest(d, path, params)

	return err
}

// Returns all available images.
func (d DigitalOceanClient) Images() ([]Image, error) {
	resp, err := NewRequest(d, "images", url.Values{})
	if err != nil {
		return nil, err
	}

	var result ImagesResp
	if err := mapstructure.Decode(resp, &result); err != nil {
		return nil, err
	}

	return result.Images, nil
}

// Destroys an image by its ID.
func (d DigitalOceanClient) DestroyImage(id uint) error {
	path := fmt.Sprintf("images/%d/destroy", id)
	_, err := NewRequest(d, path, url.Values{})
	return err
}

// Returns DO's string representation of status "off" "new" "active" etc.
func (d DigitalOceanClient) DropletStatus(id uint) (string, string, error) {
	path := fmt.Sprintf("droplets/%v", id)

	body, err := NewRequest(d, path, url.Values{})
	if err != nil {
		return "", "", err
	}

	var ip string

	// Read the droplet's "status"
	droplet := body["droplet"].(map[string]interface{})
	status := droplet["status"].(string)

	if droplet["ip_address"] != nil {
		ip = droplet["ip_address"].(string)
	}

	return ip, status, err
}

// Sends an api request and returns a generic map[string]interface of
// the response.
func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[string]interface{}, error) {
	client := d.client

	// Add the authentication parameters
	params.Set("client_id", d.ClientID)
	params.Set("api_key", d.APIKey)

	url := fmt.Sprintf("%s/%s?%s", DIGITALOCEAN_API_URL, path, params.Encode())

	// Do some basic scrubbing so sensitive information doesn't appear in logs
	scrubbedUrl := strings.Replace(url, d.ClientID, "CLIENT_ID", -1)
	scrubbedUrl = strings.Replace(scrubbedUrl, d.APIKey, "API_KEY", -1)
	log.Printf("sending new request to digitalocean: %s", scrubbedUrl)

	var lastErr error
	for attempts := 1; attempts < 10; attempts++ {
		resp, err := client.Get(url)
		if err != nil {
			return nil, err
		}

		body, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			return nil, err
		}

		log.Printf("response from digitalocean: %s", body)

		var decodedResponse map[string]interface{}
		err = json.Unmarshal(body, &decodedResponse)
		if err != nil {
			err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s",
				resp.StatusCode, body))
			return decodedResponse, err
		}

		// Check for errors sent by digitalocean
		status := decodedResponse["status"].(string)
		if status == "OK" {
			return decodedResponse, nil
		}

		if status == "ERROR" {
			status = decodedResponse["message"].(string)
		}

		lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s",
			resp.StatusCode, status))
		log.Println(lastErr)
		if strings.Contains(status, "a pending event") {
			// Retry, DigitalOcean sends these dumb "pending event"
			// errors all the time.
			time.Sleep(5 * time.Second)
			continue
		}

		// Some other kind of error. Just return.
		return decodedResponse, lastErr
	}

	return nil, lastErr
}
<|endoftext|>


<commit_before>package auth

import (
	"fmt"
	"os"

	"github.com/micro/cli/v2"
	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/auth"
	srvAuth "github.com/micro/go-micro/v2/auth/service"
	pb "github.com/micro/go-micro/v2/auth/service/proto"
	"github.com/micro/go-micro/v2/config/cmd"
	log "github.com/micro/go-micro/v2/logger"
	"github.com/micro/go-micro/v2/util/config"
	"github.com/micro/micro/v2/auth/api"
	"github.com/micro/micro/v2/auth/handler"
	"github.com/micro/micro/v2/auth/web"
)

var (
	// Name of the service
	Name = "go.micro.auth"
	// Address of the service
	Address = ":8010"
)

// run the auth service
func run(ctx *cli.Context, srvOpts ...micro.Option) {
	log.Init(log.WithFields(map[string]interface{}{"service": "auth"}))

	// Init plugins
	for _, p := range Plugins() {
		p.Init(ctx)
	}

	if len(ctx.String("address")) > 0 {
		Address = ctx.String("address")
	}
	if len(Address) > 0 {
		srvOpts = append(srvOpts, micro.Address(Address))
	}

	// Init plugins
	for _, p := range Plugins() {
		p.Init(ctx)
	}

	// setup service
	srvOpts = append(srvOpts, micro.Name(Name))
	service := micro.NewService(srvOpts...)

	// run service
	pb.RegisterAuthHandler(service.Server(), handler.New())
	if err := service.Run(); err != nil {
		log.Fatal(err)
	}
}

func authFromContext(ctx *cli.Context) auth.Auth {
	if ctx.Bool("platform") {
		os.Setenv("MICRO_PROXY", "service")
		os.Setenv("MICRO_PROXY_ADDRESS", "proxy.micro.mu:443")
		return srvAuth.NewAuth()
	}

	return *cmd.DefaultCmd.Options().Auth
}

// login using a token
func login(ctx *cli.Context) {
	if ctx.Args().Len() != 1 {
		fmt.Println("Usage: `micro login [token]`")
		os.Exit(1)
	}
	token := ctx.Args().First()

	// Execute the request
	acc, err := authFromContext(ctx).Verify(token)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	} else if acc == nil {
		fmt.Printf("[%v] did not generate an account\n", authFromContext(ctx).String())
		os.Exit(1)
	}

	// Store the token in micro config
	if err := config.Set("token", token); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Inform the user
	fmt.Println("You have been logged in")
}

func Commands(srvOpts ...micro.Option) []*cli.Command {
	commands := []*cli.Command{
		&cli.Command{
			Name:  "auth",
			Usage: "Run the auth service",
			Action: func(ctx *cli.Context) error {
				run(ctx)
				return nil
			},
			Subcommands: append([]*cli.Command{
				{
					Name:        "api",
					Usage:       "Run the auth api",
					Description: "Run the auth api",
					Action: func(ctx *cli.Context) error {
						api.Run(ctx, srvOpts...)
						return nil
					},
				},
				{
					Name:        "web",
					Usage:       "Run the auth web",
					Description: "Run the auth web",
					Action: func(ctx *cli.Context) error {
						web.Run(ctx, srvOpts...)
						return nil
					},
				},
			}),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:    "address",
					Usage:   "Set the auth http address e.g 0.0.0.0:8010",
					EnvVars: []string{"MICRO_SERVER_ADDRESS"},
				},
				&cli.StringFlag{
					Name:     "auth_provider",
					EnvVars:  []string{"MICRO_AUTH_PROVIDER"},
					Usage:    "Auth provider enables account generation",
					Required: true,
				},
			},
		},
		&cli.Command{
			Name:  "login",
			Usage: "Login using a token",
			Action: func(ctx *cli.Context) error {
				login(ctx)
				return nil
			},
			Flags: []cli.Flag{
				&cli.BoolFlag{
					Name:  "platform",
					Usage: "Connect to the platform",
					Value: false,
				},
			},
		},
	}

	for _, c := range commands {
		for _, p := range Plugins() {
			if cmds := p.Commands(); len(cmds) > 0 {
				c.Subcommands = append(c.Subcommands, cmds...)
			}

			if flags := p.Flags(); len(flags) > 0 {
				c.Flags = append(c.Flags, flags...)
			}
		}
	}

	return commands
}
<commit_msg>Remove Required Auth Flag (#600)<commit_after>package auth

import (
	"fmt"
	"os"

	"github.com/micro/cli/v2"
	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/auth"
	srvAuth "github.com/micro/go-micro/v2/auth/service"
	pb "github.com/micro/go-micro/v2/auth/service/proto"
	"github.com/micro/go-micro/v2/config/cmd"
	log "github.com/micro/go-micro/v2/logger"
	"github.com/micro/go-micro/v2/util/config"
	"github.com/micro/micro/v2/auth/api"
	"github.com/micro/micro/v2/auth/handler"
	"github.com/micro/micro/v2/auth/web"
)

var (
	// Name of the service
	Name = "go.micro.auth"
	// Address of the service
	Address = ":8010"
)

// run the auth service
func run(ctx *cli.Context, srvOpts ...micro.Option) {
	log.Init(log.WithFields(map[string]interface{}{"service": "auth"}))

	// Init plugins
	for _, p := range Plugins() {
		p.Init(ctx)
	}

	if len(ctx.String("address")) > 0 {
		Address = ctx.String("address")
	}
	if len(Address) > 0 {
		srvOpts = append(srvOpts, micro.Address(Address))
	}

	// Init plugins
	for _, p := range Plugins() {
		p.Init(ctx)
	}

	// setup service
	srvOpts = append(srvOpts, micro.Name(Name))
	service := micro.NewService(srvOpts...)

	// run service
	pb.RegisterAuthHandler(service.Server(), handler.New())
	if err := service.Run(); err != nil {
		log.Fatal(err)
	}
}

func authFromContext(ctx *cli.Context) auth.Auth {
	if ctx.Bool("platform") {
		os.Setenv("MICRO_PROXY", "service")
		os.Setenv("MICRO_PROXY_ADDRESS", "proxy.micro.mu:443")
		return srvAuth.NewAuth()
	}

	return *cmd.DefaultCmd.Options().Auth
}

// login using a token
func login(ctx *cli.Context) {
	if ctx.Args().Len() != 1 {
		fmt.Println("Usage: `micro login [token]`")
		os.Exit(1)
	}
	token := ctx.Args().First()

	// Execute the request
	acc, err := authFromContext(ctx).Verify(token)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	} else if acc == nil {
		fmt.Printf("[%v] did not generate an account\n", authFromContext(ctx).String())
		os.Exit(1)
	}

	// Store the token in micro config
	if err := config.Set("token", token); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Inform the user
	fmt.Println("You have been logged in")
}

func Commands(srvOpts ...micro.Option) []*cli.Command {
	commands := []*cli.Command{
		&cli.Command{
			Name:  "auth",
			Usage: "Run the auth service",
			Action: func(ctx *cli.Context) error {
				run(ctx)
				return nil
			},
			Subcommands: append([]*cli.Command{
				{
					Name:        "api",
					Usage:       "Run the auth api",
					Description: "Run the auth api",
					Action: func(ctx *cli.Context) error {
						api.Run(ctx, srvOpts...)
						return nil
					},
				},
				{
					Name:        "web",
					Usage:       "Run the auth web",
					Description: "Run the auth web",
					Action: func(ctx *cli.Context) error {
						web.Run(ctx, srvOpts...)
						return nil
					},
				},
			}),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:    "address",
					Usage:   "Set the auth http address e.g 0.0.0.0:8010",
					EnvVars: []string{"MICRO_SERVER_ADDRESS"},
				},
				&cli.StringFlag{
					Name:    "auth_provider",
					EnvVars: []string{"MICRO_AUTH_PROVIDER"},
					Usage:   "Auth provider enables account generation",
				},
			},
		},
		&cli.Command{
			Name:  "login",
			Usage: "Login using a token",
			Action: func(ctx *cli.Context) error {
				login(ctx)
				return nil
			},
			Flags: []cli.Flag{
				&cli.BoolFlag{
					Name:  "platform",
					Usage: "Connect to the platform",
					Value: false,
				},
			},
		},
	}

	for _, c := range commands {
		for _, p := range Plugins() {
			if cmds := p.Commands(); len(cmds) > 0 {
				c.Subcommands = append(c.Subcommands, cmds...)
			}

			if flags := p.Flags(); len(flags) > 0 {
				c.Flags = append(c.Flags, flags...)
			}
		}
	}

	return commands
}
<|endoftext|>


<commit_before>package auth2

import (
	"errors"
	"time"

	"github.com/dfreire/fservices/mailer"
	"github.com/dfreire/fservices/util"
	"github.com/dgrijalva/jwt-go"
	"github.com/satori/go.uuid"
	"golang.org/x/crypto/bcrypt"
)

type Auth interface {
	Signup(appId, email, password, lang string) (confirmationToken string, err error)
	ResendConfirmationMail(appId, email, lang string) (confirmationToken string, err error)
	ConfirmSignup(confirmationToken string) error
	Signin(appId, email, password string) (sessionToken string, err error)
	Signout(sessionToken string) error
	ForgotPasword(appId, email, lang string) (resetToken string, err error)
	// ResetPassword(resetToken, newPassword string) error
	// ChangePassword(userId, oldPassword, newPassword string) error
	// ChangeEmail(userId, password, newEmail string) error
	// GetAllUsers() ([]UserView, error)
	// GetUsersByAppId(appId string) ([]UserView, error)
	// CreateUser(appId, email, password string) error
	// ChangeUserPassword(userId, newPassword string) error
	// ChangeUserEmail(userId, newEmail string) error
	// RemoveUserById(userId string) error

	// RemoveExpiredSessions(appId strings, duration time.Duration) error
}

type UserView struct {
	Id    string
	AppId string
	Email string
}

type AuthConfig struct {
	JwtKey             string
	FromEmail          string
	ConfirmationEmail  AuthMailConfig
	ResetPasswordEmail AuthMailConfig
}
type AuthMailConfig map[string]struct {
	Subject string
	Body    string
}

type authImpl struct {
	cfg    AuthConfig
	store  store
	mailer mailer.Mailer
}

func NewAuth(cfg AuthConfig, store store, mailer mailer.Mailer) authImpl {
	return authImpl{cfg, store, mailer}
}

func (self authImpl) Signup(appId, email, password, lang string) (confirmationToken string, err error) {
	confirmationKey, err := self.createUser(appId, email, password, lang, false)
	if err != nil {
		return
	}

	return self.sendConfirmationEmail(appId, email, lang, confirmationKey)
}

func (self authImpl) ResendConfirmationMail(appId, email, lang string) (confirmationToken string, err error) {
	confirmationKey, _, err := self.store.getUserConfirmation(appId, email)
	if err != nil {
		return
	}

	return self.sendConfirmationEmail(appId, email, lang, confirmationKey)
}

func (self authImpl) ConfirmSignup(confirmationToken string) error {
	appId, email, _, tokenConfirmationKey, err := self.parseConfirmationToken(confirmationToken)
	if err != nil {
		return err
	}

	confirmationKey, _, err := self.store.getUserConfirmation(appId, email)
	if err != nil {
		return err
	}

	if tokenConfirmationKey != confirmationKey {
		return errors.New("The token confirmation key does not match the expected confirmation key.")
	}

	return self.store.setUserConfirmedAt(appId, email, time.Now())
}

func (self authImpl) Signin(appId, email, password string) (sessionToken string, err error) {
	userId, hashedPass, confirmedAt, err := self.store.getUserPassword(appId, email)
	if err != nil {
		return
	}

	if confirmedAt.Equal(time.Time{}) {
		err = errors.New("The account is not yet confirmed.")
		return
	}

	if err = bcrypt.CompareHashAndPassword([]byte(hashedPass), []byte(password)); err != nil {
		return
	}

	sessionId := uuid.NewV4().String()
	sessionCreatedAt := time.Unix(time.Now().Unix(), 0)

	if err = self.store.createSession(sessionId, userId, sessionCreatedAt); err != nil {
		return
	}

	sessionToken, err = self.createSessionToken(sessionId, userId, sessionCreatedAt)
	return
}

func (self authImpl) Signout(sessionToken string) error {
	sessionId, _, _, err := self.parseSessionToken(sessionToken)
	if err != nil {
		return err
	}

	return self.store.removeSession(sessionId)
}

func (self authImpl) ForgotPasword(appId, email, lang string) (resetToken string, err error) {
	resetKey := uuid.NewV4().String()

	err = self.store.setResetKey(appId, email, resetKey, time.Now())
	if err != nil {
		return
	}

	return self.sendResetPaswordEmail(appId, email, lang, resetKey)
}

func (self authImpl) createUser(appId, email, password, lang string, isConfirmed bool) (confirmationKey string, err error) {
	hashedPass, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}

	now := time.Now()
	var confirmedAt time.Time

	if isConfirmed {
		confirmedAt = now
	} else {
		confirmationKey = uuid.NewV4().String()
	}

	err = self.store.createUser(uuid.NewV4().String(), appId, email, string(hashedPass), lang, confirmationKey, now, confirmedAt)
	return confirmationKey, err
}

func (self authImpl) sendConfirmationEmail(appId, email, lang, confirmationKey string) (confirmationToken string, err error) {
	confirmationToken, err = self.createConfirmationToken(appId, email, lang, confirmationKey)
	if err != nil {
		return
	}

	templateValues := struct{ ConfirmationToken string }{confirmationToken}
	body, err := util.RenderTemplate(self.cfg.ConfirmationEmail[lang].Body, templateValues)
	if err != nil {
		return
	}

	mail := mailer.Mail{
		From:    self.cfg.FromEmail,
		To:      []string{email},
		Subject: self.cfg.ConfirmationEmail[lang].Subject,
		Body:    body,
	}

	return confirmationToken, self.mailer.Send(mail)
}

func (self authImpl) sendResetPaswordEmail(appId, email, lang, resetKey string) (resetToken string, err error) {
	resetToken, err = self.createResetToken(appId, email, lang, resetKey)
	if err != nil {
		return
	}

	templateValues := struct{ ResetToken string }{resetToken}
	body, err := util.RenderTemplate(self.cfg.ResetPasswordEmail[lang].Body, templateValues)
	if err != nil {
		return
	}

	mail := mailer.Mail{
		From:    self.cfg.FromEmail,
		To:      []string{email},
		Subject: self.cfg.ResetPasswordEmail[lang].Subject,
		Body:    body,
	}

	return resetToken, self.mailer.Send(mail)
}

func (self authImpl) createConfirmationToken(appId, email, lang, confirmationKey string) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims["appId"] = appId
	token.Claims["email"] = email
	token.Claims["lang"] = lang
	token.Claims["confirmationKey"] = confirmationKey
	return token.SignedString([]byte(self.cfg.JwtKey))
}

func (self authImpl) parseConfirmationToken(confirmationToken string) (appId, email, lang, confirmationKey string, err error) {
	token, err := jwt.Parse(confirmationToken, func(token *jwt.Token) (interface{}, error) {
		return []byte(self.cfg.JwtKey), nil
	})
	if err != nil {
		return
	}
	if !token.Valid {
		return
	}

	appId = token.Claims["appId"].(string)
	email = token.Claims["email"].(string)
	lang = token.Claims["lang"].(string)
	confirmationKey = token.Claims["confirmationKey"].(string)
	return
}

func (self authImpl) createSessionToken(id, userId string, createdAt time.Time) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims["id"] = id
	token.Claims["userId"] = userId
	token.Claims["createdAt"] = createdAt.Unix()
	return token.SignedString([]byte(self.cfg.JwtKey))
}

func (self authImpl) parseSessionToken(sessionToken string) (id, userId string, createdAt time.Time, err error) {
	token, err := jwt.Parse(sessionToken, func(token *jwt.Token) (interface{}, error) {
		return []byte(self.cfg.JwtKey), nil
	})
	if err != nil {
		return
	}
	if !token.Valid {
		return
	}

	id = token.Claims["id"].(string)
	userId = token.Claims["userId"].(string)
	createdAt = time.Unix(int64(token.Claims["createdAt"].(float64)), 0)
	return
}

func (self authImpl) createResetToken(appId, email, lang, resetKey string) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims["appId"] = appId
	token.Claims["email"] = email
	token.Claims["lang"] = lang
	token.Claims["resetKey"] = resetKey
	return token.SignedString([]byte(self.cfg.JwtKey))
}

func (self authImpl) parseResetToken(resetToken string) (appId, email, lang, resetKey string, err error) {
	token, err := jwt.Parse(resetToken, func(token *jwt.Token) (interface{}, error) {
		return []byte(self.cfg.JwtKey), nil
	})
	if err != nil {
		return
	}
	if !token.Valid {
		return
	}

	appId = token.Claims["appId"].(string)
	email = token.Claims["email"].(string)
	lang = token.Claims["lang"].(string)
	resetKey = token.Claims["resetKey"].(string)
	return
}
<commit_msg>update<commit_after>package auth2

import (
	"errors"
	"time"

	"github.com/dfreire/fservices/mailer"
	"github.com/dfreire/fservices/util"
	"github.com/dgrijalva/jwt-go"
	"github.com/satori/go.uuid"
	"golang.org/x/crypto/bcrypt"
)

type Auth interface {
	Signup(appId, email, password, lang string) (confirmationToken string, err error)
	ResendConfirmationMail(appId, email, lang string) (confirmationToken string, err error)
	ConfirmSignup(confirmationToken string) error
	Signin(appId, email, password string) (sessionToken string, err error)
	Signout(sessionToken string) error
	ForgotPasword(appId, email, lang string) (resetToken string, err error)
	// ResetPassword(resetToken, newPassword string) error
	// ChangePassword(userId, oldPassword, newPassword string) error
	// ChangeEmail(userId, password, newEmail string) error
	// GetAllUsers() ([]UserView, error)
	// GetUsersByAppId(appId string) ([]UserView, error)
	// CreateUser(appId, email, password string) error
	// ChangeUserPassword(userId, newPassword string) error
	// ChangeUserEmail(userId, newEmail string) error
	// RemoveUserById(userId string) error

	// RemoveExpiredSessions(appId strings, duration time.Duration) error
}

type UserView struct {
	Id    string
	AppId string
	Email string
}

type AuthConfig struct {
	JwtKey             string
	FromEmail          string
	ConfirmationEmail  AuthMailConfig
	ResetPasswordEmail AuthMailConfig
}
type AuthMailConfig map[string]struct {
	Subject string
	Body    string
}

type authImpl struct {
	cfg    AuthConfig
	store  store
	mailer mailer.Mailer
}

func NewAuth(cfg AuthConfig, store store, mailer mailer.Mailer) authImpl {
	return authImpl{cfg, store, mailer}
}

func (self authImpl) Signup(appId, email, password, lang string) (confirmationToken string, err error) {
	confirmationKey, err := self.createUser(appId, email, password, lang, false)
	if err != nil {
		return
	}

	return self.sendConfirmationEmail(appId, email, lang, confirmationKey)
}

func (self authImpl) ResendConfirmationMail(appId, email, lang string) (confirmationToken string, err error) {
	confirmationKey, _, err := self.store.getUserConfirmation(appId, email)
	if err != nil {
		return
	}

	return self.sendConfirmationEmail(appId, email, lang, confirmationKey)
}

func (self authImpl) ConfirmSignup(confirmationToken string) error {
	appId, email, _, tokenConfirmationKey, err := self.parseConfirmationToken(confirmationToken)
	if err != nil {
		return err
	}

	confirmationKey, _, err := self.store.getUserConfirmation(appId, email)
	if err != nil {
		return err
	}

	if tokenConfirmationKey != confirmationKey {
		return errors.New("The token confirmation key does not match the expected confirmation key.")
	}

	return self.store.setUserConfirmedAt(appId, email, time.Now())
}

func (self authImpl) Signin(appId, email, password string) (sessionToken string, err error) {
	userId, hashedPass, confirmedAt, err := self.store.getUserPassword(appId, email)
	if err != nil {
		return
	}

	if confirmedAt.Equal(time.Time{}) {
		err = errors.New("The account is not yet confirmed.")
		return
	}

	if err = bcrypt.CompareHashAndPassword([]byte(hashedPass), []byte(password)); err != nil {
		return
	}

	sessionId := uuid.NewV4().String()
	sessionCreatedAt := time.Unix(time.Now().Unix(), 0)

	if err = self.store.createSession(sessionId, userId, sessionCreatedAt); err != nil {
		return
	}

	sessionToken, err = self.createSessionToken(sessionId, userId, sessionCreatedAt)
	return
}

func (self authImpl) Signout(sessionToken string) error {
	sessionId, _, _, err := self.parseSessionToken(sessionToken)
	if err != nil {
		return err
	}

	return self.store.removeSession(sessionId)
}

func (self authImpl) ForgotPasword(appId, email, lang string) (resetToken string, err error) {
	_, confirmedAt, err := self.store.getUserConfirmation(appId, email)
	if err != nil {
		return
	}

	if confirmedAt.Equal(time.Time{}) {
		err = errors.New("The account is not yet confirmed.")
		return
	}

	resetKey := uuid.NewV4().String()

	err = self.store.setResetKey(appId, email, resetKey, time.Now())
	if err != nil {
		return
	}

	return self.sendResetPaswordEmail(appId, email, lang, resetKey)
}

func (self authImpl) createUser(appId, email, password, lang string, isConfirmed bool) (confirmationKey string, err error) {
	hashedPass, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}

	now := time.Now()
	var confirmedAt time.Time

	if isConfirmed {
		confirmedAt = now
	} else {
		confirmationKey = uuid.NewV4().String()
	}

	err = self.store.createUser(uuid.NewV4().String(), appId, email, string(hashedPass), lang, confirmationKey, now, confirmedAt)
	return confirmationKey, err
}

func (self authImpl) sendConfirmationEmail(appId, email, lang, confirmationKey string) (confirmationToken string, err error) {
	confirmationToken, err = self.createConfirmationToken(appId, email, lang, confirmationKey)
	if err != nil {
		return
	}

	templateValues := struct{ ConfirmationToken string }{confirmationToken}
	body, err := util.RenderTemplate(self.cfg.ConfirmationEmail[lang].Body, templateValues)
	if err != nil {
		return
	}

	mail := mailer.Mail{
		From:    self.cfg.FromEmail,
		To:      []string{email},
		Subject: self.cfg.ConfirmationEmail[lang].Subject,
		Body:    body,
	}

	return confirmationToken, self.mailer.Send(mail)
}

func (self authImpl) sendResetPaswordEmail(appId, email, lang, resetKey string) (resetToken string, err error) {
	resetToken, err = self.createResetToken(appId, email, lang, resetKey)
	if err != nil {
		return
	}

	templateValues := struct{ ResetToken string }{resetToken}
	body, err := util.RenderTemplate(self.cfg.ResetPasswordEmail[lang].Body, templateValues)
	if err != nil {
		return
	}

	mail := mailer.Mail{
		From:    self.cfg.FromEmail,
		To:      []string{email},
		Subject: self.cfg.ResetPasswordEmail[lang].Subject,
		Body:    body,
	}

	return resetToken, self.mailer.Send(mail)
}

func (self authImpl) createConfirmationToken(appId, email, lang, confirmationKey string) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims["appId"] = appId
	token.Claims["email"] = email
	token.Claims["lang"] = lang
	token.Claims["confirmationKey"] = confirmationKey
	return token.SignedString([]byte(self.cfg.JwtKey))
}

func (self authImpl) parseConfirmationToken(confirmationToken string) (appId, email, lang, confirmationKey string, err error) {
	token, err := jwt.Parse(confirmationToken, func(token *jwt.Token) (interface{}, error) {
		return []byte(self.cfg.JwtKey), nil
	})
	if err != nil {
		return
	}
	if !token.Valid {
		return
	}

	appId = token.Claims["appId"].(string)
	email = token.Claims["email"].(string)
	lang = token.Claims["lang"].(string)
	confirmationKey = token.Claims["confirmationKey"].(string)
	return
}

func (self authImpl) createSessionToken(id, userId string, createdAt time.Time) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims["id"] = id
	token.Claims["userId"] = userId
	token.Claims["createdAt"] = createdAt.Unix()
	return token.SignedString([]byte(self.cfg.JwtKey))
}

func (self authImpl) parseSessionToken(sessionToken string) (id, userId string, createdAt time.Time, err error) {
	token, err := jwt.Parse(sessionToken, func(token *jwt.Token) (interface{}, error) {
		return []byte(self.cfg.JwtKey), nil
	})
	if err != nil {
		return
	}
	if !token.Valid {
		return
	}

	id = token.Claims["id"].(string)
	userId = token.Claims["userId"].(string)
	createdAt = time.Unix(int64(token.Claims["createdAt"].(float64)), 0)
	return
}

func (self authImpl) createResetToken(appId, email, lang, resetKey string) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims["appId"] = appId
	token.Claims["email"] = email
	token.Claims["lang"] = lang
	token.Claims["resetKey"] = resetKey
	return token.SignedString([]byte(self.cfg.JwtKey))
}

func (self authImpl) parseResetToken(resetToken string) (appId, email, lang, resetKey string, err error) {
	token, err := jwt.Parse(resetToken, func(token *jwt.Token) (interface{}, error) {
		return []byte(self.cfg.JwtKey), nil
	})
	if err != nil {
		return
	}
	if !token.Valid {
		return
	}

	appId = token.Claims["appId"].(string)
	email = token.Claims["email"].(string)
	lang = token.Claims["lang"].(string)
	resetKey = token.Claims["resetKey"].(string)
	return
}
<|endoftext|>


<commit_before>// Copyright 2015 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package auth

import (
	"errors"
	"fmt"
	"regexp"
	"strings"

	"github.com/tsuru/tsuru/db"
	"github.com/tsuru/tsuru/log"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

var (
	ErrInvalidTeamName   = errors.New("invalid team name")
	ErrTeamAlreadyExists = errors.New("team already exists")
	ErrTeamNotFound      = errors.New("team not found")

	teamNameRegexp = regexp.MustCompile(`^[a-zA-Z][-@_.+\w]+$`)
)

type Team struct {
	Name  string   `bson:"_id" json:"name"`
	Users []string `json:"users"`
}

func (t *Team) ContainsUser(u *User) bool {
	for _, user := range t.Users {
		if u.Email == user {
			return true
		}
	}
	return false
}

func (t *Team) AddUser(u *User) error {
	if t.ContainsUser(u) {
		return fmt.Errorf("User %s is already in the team %s.", u.Email, t.Name)
	}
	t.Users = append(t.Users, u.Email)
	return nil
}

func (t *Team) RemoveUser(u *User) error {
	index := -1
	for i, user := range t.Users {
		if u.Email == user {
			index = i
			break
		}
	}
	if index < 0 {
		return fmt.Errorf("User %s is not in the team %s.", u.Email, t.Name)
	}
	last := len(t.Users) - 1
	if index < last {
		t.Users[index] = t.Users[last]
	}
	t.Users = t.Users[:last]
	return nil
}

func (t *Team) AllowedApps() ([]string, error) {
	conn, err := db.Conn()
	if err != nil {
		return nil, err
	}
	defer conn.Close()
	allowedApps := []map[string]string{}
	query := conn.Apps().Find(bson.M{"teams": t.Name})
	if err := query.Select(bson.M{"name": 1}).All(&allowedApps); err != nil {
		return nil, err
	}
	appNames := make([]string, len(allowedApps))
	for i, app := range allowedApps {
		appNames[i] = app["name"]
	}
	return appNames, nil
}

func CreateTeam(name string, user ...*User) error {
	name = strings.TrimSpace(name)
	if !isTeamNameValid(name) {
		return ErrInvalidTeamName
	}
	team := Team{
		Name:  name,
		Users: make([]string, len(user)),
	}
	for i, u := range user {
		team.Users[i] = u.Email
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	err = conn.Teams().Insert(team)
	if mgo.IsDup(err) {
		return ErrTeamAlreadyExists
	}
	return err
}

func isTeamNameValid(name string) bool {
	return teamNameRegexp.MatchString(name)
}

func GetTeam(name string) (*Team, error) {
	var t Team
	conn, err := db.Conn()
	if err != nil {
		return nil, err
	}
	err = conn.Teams().FindId(name).One(&t)
	if err != nil {
		if err == mgo.ErrNotFound {
			err = ErrTeamNotFound
		}
		return nil, err
	}
	return &t, nil
}

func GetTeamsNames(teams []Team) []string {
	tn := make([]string, len(teams))
	for i, t := range teams {
		tn[i] = t.Name
	}
	return tn
}

func CheckUserAccess(teamNames []string, u *User) bool {
	q := bson.M{"_id": bson.M{"$in": teamNames}}
	var teams []Team
	conn, err := db.Conn()
	if err != nil {
		log.Errorf("Failed to connect to the database: %s", err)
		return false
	}
	defer conn.Close()
	conn.Teams().Find(q).All(&teams)
	for _, team := range teams {
		if team.ContainsUser(u) {
			return true
		}
	}
	return false
}
<commit_msg>auth/team: add comments.<commit_after>// Copyright 2015 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package auth

import (
	"errors"
	"fmt"
	"regexp"
	"strings"

	"github.com/tsuru/tsuru/db"
	"github.com/tsuru/tsuru/log"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

var (
	ErrInvalidTeamName   = errors.New("invalid team name")
	ErrTeamAlreadyExists = errors.New("team already exists")
	ErrTeamNotFound      = errors.New("team not found")

	teamNameRegexp = regexp.MustCompile(`^[a-zA-Z][-@_.+\w]+$`)
)

// Team represents a real world team, a team has team members (users) and
// a name.
type Team struct {
	Name  string   `bson:"_id" json:"name"`
	Users []string `json:"users"`
}

// ContainsUser checks if the team contains the user.
func (t *Team) ContainsUser(u *User) bool {
	for _, user := range t.Users {
		if u.Email == user {
			return true
		}
	}
	return false
}

// AddUser adds a user to the team.
func (t *Team) AddUser(u *User) error {
	if t.ContainsUser(u) {
		return fmt.Errorf("User %s is already in the team %s.", u.Email, t.Name)
	}
	t.Users = append(t.Users, u.Email)
	return nil
}

// RemoveUser removes a user from the team.
func (t *Team) RemoveUser(u *User) error {
	index := -1
	for i, user := range t.Users {
		if u.Email == user {
			index = i
			break
		}
	}
	if index < 0 {
		return fmt.Errorf("User %s is not in the team %s.", u.Email, t.Name)
	}
	last := len(t.Users) - 1
	if index < last {
		t.Users[index] = t.Users[last]
	}
	t.Users = t.Users[:last]
	return nil
}

// AllowedApps returns the apps that the team has access.
func (t *Team) AllowedApps() ([]string, error) {
	conn, err := db.Conn()
	if err != nil {
		return nil, err
	}
	defer conn.Close()
	allowedApps := []map[string]string{}
	query := conn.Apps().Find(bson.M{"teams": t.Name})
	if err := query.Select(bson.M{"name": 1}).All(&allowedApps); err != nil {
		return nil, err
	}
	appNames := make([]string, len(allowedApps))
	for i, app := range allowedApps {
		appNames[i] = app["name"]
	}
	return appNames, nil
}

// CreateTeam creates a team and add users to this team.
func CreateTeam(name string, user ...*User) error {
	name = strings.TrimSpace(name)
	if !isTeamNameValid(name) {
		return ErrInvalidTeamName
	}
	team := Team{
		Name:  name,
		Users: make([]string, len(user)),
	}
	for i, u := range user {
		team.Users[i] = u.Email
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	err = conn.Teams().Insert(team)
	if mgo.IsDup(err) {
		return ErrTeamAlreadyExists
	}
	return err
}

func isTeamNameValid(name string) bool {
	return teamNameRegexp.MatchString(name)
}

// GetTeam find a team by name.
func GetTeam(name string) (*Team, error) {
	var t Team
	conn, err := db.Conn()
	if err != nil {
		return nil, err
	}
	err = conn.Teams().FindId(name).One(&t)
	if err != nil {
		if err == mgo.ErrNotFound {
			err = ErrTeamNotFound
		}
		return nil, err
	}
	return &t, nil
}

// GetTeamsNames find teams by a list of team names.
func GetTeamsNames(teams []Team) []string {
	tn := make([]string, len(teams))
	for i, t := range teams {
		tn[i] = t.Name
	}
	return tn
}

// CheckUserAccess verifies if the user has access to a list
// of teams.
func CheckUserAccess(teamNames []string, u *User) bool {
	q := bson.M{"_id": bson.M{"$in": teamNames}}
	var teams []Team
	conn, err := db.Conn()
	if err != nil {
		log.Errorf("Failed to connect to the database: %s", err)
		return false
	}
	defer conn.Close()
	conn.Teams().Find(q).All(&teams)
	for _, team := range teams {
		if team.ContainsUser(u) {
			return true
		}
	}
	return false
}
<|endoftext|>