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

// Binary extract extracts a pre-configured OpenJDK11 source tree to produce Kythe compilation units.
package main

import (
	"bufio"
	"context"
	"errors"
	"flag"
	"io"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"regexp"
	"strings"

	"kythe.io/kythe/go/util/flagutil"

	"github.com/bazelbuild/rules_go/go/tools/bazel"
)

const (
	javaCommandVar      = "KYTHE_JAVA_COMMAND"
	wrapperExtractorVar = "KYTHE_JAVA_EXTRACTOR_JAR"

	kytheRootVar   = "KYTHE_ROOT_DIRECTORY"
	kytheOutputVar = "KYTHE_OUTPUT_DIRECTORY"
	kytheCorpusVar = "KYTHE_CORPUS"
	kytheVNameVar  = "KYTHE_VNAMES"

	javaMakeVar           = "JAVA_CMD"
	runfilesWrapperPath   = "io_kythe/kythe/extractors/openjdk11/java_wrapper"
	runfilesVNamesPath    = "io_kythe/kythe/extractors/openjdk11/vnames.json"
	runfilesExtractorPath = "io_kythe/kythe/java/com/google/devtools/kythe/extractors/java/standalone/javac9_extractor_deploy.jar"
)

var (
	makeDir       string
	outputDir     string
	vNameRules    string
	wrapperPath   string
	extractorPath string
	errorPattern  = regexp.MustCompile("ERROR: extractor failure for module ([^:]*):")
)

func setupRunfiles() error {
	if os.Getenv("RUNFILES_DIR") != "" || os.Getenv("RUNFILES_MANIFEST_FILE") != "" {
		return nil
	}
	for _, base := range []string{os.Args[0] + ".runfiles", "."} {
		root, err := filepath.Abs(base)
		if err != nil {
			continue
		} else if _, err := os.Stat(root); err != nil {
			continue
		}
		os.Setenv("RUNFILES_DIR", root)
		manifest := filepath.Join(root, "MANIFEST")
		if _, err := os.Stat(manifest); err == nil {
			os.Setenv("RUNFILES_MANIFEST_FILE", manifest)
		}
		return nil
	}
	return errors.New("unable to setup runfiles")
}

func defaultWrapperPath() string {
	val, _ := bazel.Runfile(runfilesWrapperPath)
	return val
}

func defaultVNamesPath() string {
	val, _ := bazel.Runfile(runfilesVNamesPath)
	return val
}

func defaultExtractorPath() string {
	val, _ := bazel.Runfile(runfilesExtractorPath)
	return val
}

func defaultOutputDir() string {
	val := os.Getenv(kytheOutputVar)
	if val == "" {
		usr, err := user.Current()
		if err != nil {
			log.Fatalf("ERROR: unable to determine current user: %v", err)
		}
		val = filepath.Join(usr.HomeDir, "kythe-openjdk11-output")
	}
	return val
}

func findJavaCommand() (string, error) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	cmd := exec.CommandContext(ctx, "make", "-n", "-p")
	cmd.Dir = makeDir
	cmd.Stderr = os.Stderr
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return "", err
	}
	if err := cmd.Start(); err != nil {
		return "", err
	}
	const prefix = javaMakeVar + " := "
	scanner := bufio.NewScanner(stdout)
	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), prefix) {
			cancel() // Safe to call repeatedly.
			cmd.Wait()
			return strings.TrimPrefix(scanner.Text(), prefix), nil
		}
	}
	return "", cmd.Wait()
}

func mustFindJavaCommand() string {
	java, err := findJavaCommand()
	if err != nil {
		log.Fatalf("unable to determine %s: %v", javaCommandVar, err)
	}
	return java
}

func setEnvDefaultFunc(env []string, key string, value func() string) []string {
	if val := os.Getenv(key); val == "" {
		env = append(env, key+"="+value())
	}
	return env
}

func setEnvDefault(env []string, key, value string) []string {
	return setEnvDefaultFunc(env, key, func() string { return value })
}

func makeEnv() []string {
	env := os.Environ()
	env = setEnvDefaultFunc(env, javaCommandVar, mustFindJavaCommand)
	env = setEnvDefault(env, kytheCorpusVar, "openjdk11")
	if vNameRules != "" {
		env = setEnvDefault(env, kytheVNameVar, vNameRules)
	}
	env = append(env,
		kytheRootVar+"="+makeDir,
		kytheOutputVar+"="+outputDir,
		wrapperExtractorVar+"="+extractorPath)
	return env
}

func init() {
	setupRunfiles()
	flag.StringVar(&makeDir, "jdk", "", "path to the OpenJDK11 source tree (required)")
	flag.StringVar(&outputDir, "output", defaultOutputDir(), "path to which the compilations and errors should be written (optional)")
	flag.StringVar(&vNameRules, "rules", defaultVNamesPath(), "path of vnames.json file (optional)")
	flag.StringVar(&wrapperPath, "java_wrapper", defaultWrapperPath(), "path to the java_wrapper executable (optional)")
	flag.StringVar(&extractorPath, "extractor_jar", defaultExtractorPath(), "path to the javac_extractor_deployt.jar (optional)")
	flag.Usage = flagutil.SimpleUsage("Extract a configured openjdk11 source directory", "[--java_wrapper=] [path]")
}

func main() {
	flag.Parse()
	if makeDir == "" {
		flagutil.UsageError("missing -jdk")
	}
	if wrapperPath == "" {
		flagutil.UsageError("missing -java_wrapper")
	}
	if _, err := os.Stat(wrapperPath); err != nil {
		flagutil.UsageErrorf("java_wrapper not found: %v", err)
	}

	cmd := exec.Command("make", javaMakeVar+"="+wrapperPath, "ENABLE_JAVAC_SERVER=no", "clean", "jdk")
	cmd.Dir = makeDir
	cmd.Env = makeEnv()
	cmd.Stdout = nil // Quiet, you
	stderr, err := cmd.StderrPipe()

	if err != nil {
		log.Fatal(err)
	}

	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	logCollectedErrors(io.TeeReader(stderr, os.Stderr))

	if err := cmd.Wait(); err != nil {
		log.Fatal(err)
	}
}

func collectExtractionErrors(stderr io.Reader) ([]string, error) {
	var result []string
	scanner := bufio.NewScanner(stderr)
	for scanner.Scan() {
		if subs := errorPattern.FindSubmatch(scanner.Bytes()); subs != nil {
			result = append(result, string(subs[1]))
		}
	}
	return result, scanner.Err()
}

func logCollectedErrors(stderr io.Reader) {
	errors, err := collectExtractionErrors(stderr)
	if err != nil {
		log.Println(err)
	}
	log.Println("Error extracting modules:\n\t", strings.Join(errors, "\n\t"))
}
<commit_msg>feat(jdk_indexing): allow extracting only specific make targets (#3935)<commit_after>/*
 * Copyright 2019 The Kythe Authors. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Binary extract extracts a pre-configured OpenJDK11 source tree to produce Kythe compilation units.
package main

import (
	"bufio"
	"context"
	"errors"
	"flag"
	"io"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"regexp"
	"strings"

	"kythe.io/kythe/go/util/flagutil"

	"github.com/bazelbuild/rules_go/go/tools/bazel"
)

const (
	javaCommandVar      = "KYTHE_JAVA_COMMAND"
	wrapperExtractorVar = "KYTHE_JAVA_EXTRACTOR_JAR"

	kytheRootVar   = "KYTHE_ROOT_DIRECTORY"
	kytheOutputVar = "KYTHE_OUTPUT_DIRECTORY"
	kytheCorpusVar = "KYTHE_CORPUS"
	kytheVNameVar  = "KYTHE_VNAMES"

	javaMakeVar           = "JAVA_CMD"
	runfilesWrapperPath   = "io_kythe/kythe/extractors/openjdk11/java_wrapper"
	runfilesVNamesPath    = "io_kythe/kythe/extractors/openjdk11/vnames.json"
	runfilesExtractorPath = "io_kythe/kythe/java/com/google/devtools/kythe/extractors/java/standalone/javac9_extractor_deploy.jar"
)

var (
	makeDir       string
	makeTargets   = targetList{"clean", "jdk"}
	outputDir     string
	vNameRules    string
	wrapperPath   string
	extractorPath string
	errorPattern  = regexp.MustCompile("ERROR: extractor failure for module ([^:]*):")
)

// targetList is a simple comma-separated list of strings used
// for the -targets flag.
// flagutil.StringList always accumulates values, which we don't want.
type targetList []string

// Set implements part of the flag.Getter interface for targetList and will
// set the new value from s.
func (tl *targetList) Set(s string) error {
	*tl = strings.Split(s, ",")
	return nil
}

// String implements part of the flag.Getter interface for targetList and will
// return the value as a comma-separate string.
func (tl *targetList) String() string {
	if tl == nil {
		return ""
	}
	return strings.Join(*tl, ",")
}

func setupRunfiles() error {
	if os.Getenv("RUNFILES_DIR") != "" || os.Getenv("RUNFILES_MANIFEST_FILE") != "" {
		return nil
	}
	for _, base := range []string{os.Args[0] + ".runfiles", "."} {
		root, err := filepath.Abs(base)
		if err != nil {
			continue
		} else if _, err := os.Stat(root); err != nil {
			continue
		}
		os.Setenv("RUNFILES_DIR", root)
		manifest := filepath.Join(root, "MANIFEST")
		if _, err := os.Stat(manifest); err == nil {
			os.Setenv("RUNFILES_MANIFEST_FILE", manifest)
		}
		return nil
	}
	return errors.New("unable to setup runfiles")
}

func defaultWrapperPath() string {
	val, _ := bazel.Runfile(runfilesWrapperPath)
	return val
}

func defaultVNamesPath() string {
	val, _ := bazel.Runfile(runfilesVNamesPath)
	return val
}

func defaultExtractorPath() string {
	val, _ := bazel.Runfile(runfilesExtractorPath)
	return val
}

func defaultOutputDir() string {
	val := os.Getenv(kytheOutputVar)
	if val == "" {
		usr, err := user.Current()
		if err != nil {
			log.Fatalf("ERROR: unable to determine current user: %v", err)
		}
		val = filepath.Join(usr.HomeDir, "kythe-openjdk11-output")
	}
	return val
}

func findJavaCommand() (string, error) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	cmd := exec.CommandContext(ctx, "make", "-n", "-p")
	cmd.Dir = makeDir
	cmd.Stderr = os.Stderr
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return "", err
	}
	if err := cmd.Start(); err != nil {
		return "", err
	}
	const prefix = javaMakeVar + " := "
	scanner := bufio.NewScanner(stdout)
	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), prefix) {
			cancel() // Safe to call repeatedly.
			cmd.Wait()
			return strings.TrimPrefix(scanner.Text(), prefix), nil
		}
	}
	return "", cmd.Wait()
}

func mustFindJavaCommand() string {
	java, err := findJavaCommand()
	if err != nil {
		log.Fatalf("unable to determine %s: %v", javaCommandVar, err)
	}
	return java
}

func setEnvDefaultFunc(env []string, key string, value func() string) []string {
	if val := os.Getenv(key); val == "" {
		env = append(env, key+"="+value())
	}
	return env
}

func setEnvDefault(env []string, key, value string) []string {
	return setEnvDefaultFunc(env, key, func() string { return value })
}

func makeEnv() []string {
	env := os.Environ()
	env = setEnvDefaultFunc(env, javaCommandVar, mustFindJavaCommand)
	env = setEnvDefault(env, kytheCorpusVar, "openjdk11")
	if vNameRules != "" {
		env = setEnvDefault(env, kytheVNameVar, vNameRules)
	}
	env = append(env,
		kytheRootVar+"="+makeDir,
		kytheOutputVar+"="+outputDir,
		wrapperExtractorVar+"="+extractorPath)
	return env
}

func init() {
	setupRunfiles()
	flag.StringVar(&makeDir, "jdk", "", "path to the OpenJDK11 source tree (required)")
	flag.StringVar(&outputDir, "output", defaultOutputDir(), "path to which the compilations and errors should be written (optional)")
	flag.StringVar(&vNameRules, "rules", defaultVNamesPath(), "path of vnames.json file (optional)")
	flag.StringVar(&wrapperPath, "java_wrapper", defaultWrapperPath(), "path to the java_wrapper executable (optional)")
	flag.StringVar(&extractorPath, "extractor_jar", defaultExtractorPath(), "path to the javac_extractor_deployt.jar (optional)")
	flag.Var(&makeTargets, "targets", "comma-separated list of make targets to build")
	flag.Usage = flagutil.SimpleUsage("Extract a configured openjdk11 source directory", "[--java_wrapper=] [path]")
}

func main() {
	flag.Parse()
	if makeDir == "" {
		flagutil.UsageError("missing -jdk")
	}
	if wrapperPath == "" {
		flagutil.UsageError("missing -java_wrapper")
	}
	if _, err := os.Stat(wrapperPath); err != nil {
		flagutil.UsageErrorf("java_wrapper not found: %v", err)
	}

	cmd := exec.Command("make", append([]string{javaMakeVar + "=" + wrapperPath, "ENABLE_JAVAC_SERVER=no"}, makeTargets...)...)
	cmd.Dir = makeDir
	cmd.Env = makeEnv()
	cmd.Stdout = nil // Quiet, you
	stderr, err := cmd.StderrPipe()

	if err != nil {
		log.Fatal(err)
	}

	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	logCollectedErrors(io.TeeReader(stderr, os.Stderr))

	if err := cmd.Wait(); err != nil {
		log.Fatal(err)
	}
}

func collectExtractionErrors(stderr io.Reader) ([]string, error) {
	var result []string
	scanner := bufio.NewScanner(stderr)
	for scanner.Scan() {
		if subs := errorPattern.FindSubmatch(scanner.Bytes()); subs != nil {
			result = append(result, string(subs[1]))
		}
	}
	return result, scanner.Err()
}

func logCollectedErrors(stderr io.Reader) {
	errors, err := collectExtractionErrors(stderr)
	if err != nil {
		log.Println(err)
	}
	log.Println("Error extracting modules:\n\t", strings.Join(errors, "\n\t"))
}
<|endoftext|>


<commit_before>package god

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/Felamande/jsvm"
	"github.com/Felamande/otto"
	"github.com/demon-xxi/wildmatch"
	"gopkg.in/fsnotify.v1"
)

type taskRunner struct {
	wildcard string
	eventCb  otto.Value
	// errCb    otto.Value
	lastTime time.Time
	lastPath string
	unique   bool
}

var tasks []*taskRunner
var ignored []string
var wd string
var initCall []otto.Value
var once = new(sync.Once)

func callInit() {
	once.Do(func() {
		lenArg := len(initCall)
		if initCall != nil && lenArg > 0 {
			iarg := make([]interface{}, len(initCall)-1)

			for idx, arg := range initCall {
				if idx == 0 {
					continue
				}
				iarg[idx-1] = arg
			}
			jsvm.Callback(initCall[0], iarg...)
		}
	})

}

func init() {
	wd, _ = os.Getwd()
	wd = format(wd)
	if p := jsvm.Module("god"); p != nil {
		p.Extend("watch", watch)
		p.Extend("ignore", ignore)
		p.Extend("init", initfn)
	}
}

// function init(initfn, fnArgs...)
func initfn(call otto.FunctionCall) otto.Value {
	initCall = call.ArgumentList
	return otto.UndefinedValue()
}

func ignore(call otto.FunctionCall) otto.Value {
	for _, v := range call.ArgumentList {
		iarg, _ := v.Export()
		switch arg := iarg.(type) {
		case string:
			ignored = append(ignored, arg)
		case []string:
			ignored = append(ignored, arg...)
		}
	}
	return otto.UndefinedValue()
}

func watch(call otto.FunctionCall) otto.Value {
	wildcards, _ := call.Argument(0).Export()
	unique, _ := call.Argument(1).ToBoolean()
	eventCb := call.Argument(2)
	switch w := wildcards.(type) {
	case string:
		tasks = append(tasks, &taskRunner{w, eventCb, time.Now(), "", unique})
	case []string:
		for _, wildcard := range w {
			tasks = append(tasks, &taskRunner{wildcard, eventCb, time.Now(), "", unique})
		}
	}

	return otto.UndefinedValue()
}

func Run() {
	callInit()

	w, _ := fsnotify.NewWatcher()
	filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
		for _, ig := range ignored {
			if wildmatch.IsSubsetOf(format(path), ig) {

				return filepath.SkipDir
			}
		}
		if !info.IsDir() {
			return nil
		}
		w.Add(path)

		return nil
	})
	for {
		select {
		case e := <-w.Events:
			rel := format(e.Name)
			abs := filepath.Join(wd, rel)
			if isDir(abs) {
				w.Add(rel)
			}
			var uniqueTask *taskRunner
			var normalTasks []*taskRunner
			for _, t := range tasks {

				if wildmatch.IsSubsetOf(path.Clean(rel), t.wildcard) {
					if t.lastPath == rel && time.Now().Sub(t.lastTime).Seconds() < 1 {
						t.lastPath = rel
						t.lastTime = time.Now()
						continue
					}
					if t.unique {
						uniqueTask = t
					} else {
						normalTasks = append(normalTasks, t)
					}
					// jsvm.Callback(t.eventCb, abs, rel)
				}

			}
			if uniqueTask != nil {
				fmt.Println("I am unique")
				jsvm.Callback(uniqueTask.eventCb, abs, rel)
				uniqueTask.lastPath = rel
				uniqueTask.lastTime = time.Now()
				continue
			}
			for _, t := range normalTasks {
				fmt.Println("I am not unique")
				jsvm.Callback(t.eventCb, abs, rel)
				t.lastPath = rel
				t.lastTime = time.Now()
			}
		}
	}
	// return otto.UndefinedValue()

}
func format(path string) string {
	return strings.Replace(path, "\\", "/", -1)
}
func isDir(p string) bool {
	fi, err := os.Stat(p)
	return err == nil && fi.IsDir()
}
<commit_msg>unique task.<commit_after>package god

import (
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/Felamande/jsvm"
	"github.com/Felamande/otto"
	"github.com/demon-xxi/wildmatch"
	"gopkg.in/fsnotify.v1"
)

type taskRunner struct {
	wildcard string
	eventCb  otto.Value
	// errCb    otto.Value
	lastTime time.Time
	lastPath string
	unique   bool
}

var tasks []*taskRunner
var ignored []string
var wd string
var initCall []otto.Value
var once = new(sync.Once)

func callInit() {
	once.Do(func() {
		lenArg := len(initCall)
		if initCall != nil && lenArg > 0 {
			iarg := make([]interface{}, len(initCall)-1)

			for idx, arg := range initCall {
				if idx == 0 {
					continue
				}
				iarg[idx-1] = arg
			}
			jsvm.Callback(initCall[0], iarg...)
		}
	})

}

func init() {
	wd, _ = os.Getwd()
	wd = format(wd)
	if p := jsvm.Module("god"); p != nil {
		p.Extend("watch", watch)
		p.Extend("ignore", ignore)
		p.Extend("init", initfn)
	}
}

// function init(initfn, fnArgs...)
func initfn(call otto.FunctionCall) otto.Value {
	initCall = call.ArgumentList
	return otto.UndefinedValue()
}

func ignore(call otto.FunctionCall) otto.Value {
	for _, v := range call.ArgumentList {
		iarg, _ := v.Export()
		switch arg := iarg.(type) {
		case string:
			ignored = append(ignored, arg)
		case []string:
			ignored = append(ignored, arg...)
		}
	}
	return otto.UndefinedValue()
}

func watch(call otto.FunctionCall) otto.Value {
	wildcards, _ := call.Argument(0).Export()
	unique, _ := call.Argument(1).ToBoolean()
	eventCb := call.Argument(2)
	switch w := wildcards.(type) {
	case string:
		tasks = append(tasks, &taskRunner{w, eventCb, time.Now(), "", unique})
	case []string:
		for _, wildcard := range w {
			tasks = append(tasks, &taskRunner{wildcard, eventCb, time.Now(), "", unique})
		}
	}

	return otto.UndefinedValue()
}

func Run() {
	callInit()

	w, _ := fsnotify.NewWatcher()
	filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
		for _, ig := range ignored {
			if wildmatch.IsSubsetOf(format(path), ig) {

				return filepath.SkipDir
			}
		}
		if !info.IsDir() {
			return nil
		}
		w.Add(path)

		return nil
	})
	for {
		select {
		case e := <-w.Events:
			if e.Op == fsnotify.Rename || e.Op == fsnotify.Remove || e.Op == fsnotify.Chmod {
				continue
			}
			rel := format(e.Name)
			abs := filepath.Join(wd, rel)
			if isDir(abs) {
				w.Add(rel)
			}
			var uniqueTask *taskRunner
			var normalTasks []*taskRunner
			for _, t := range tasks {

				if t.match(rel) {
					if t.intervalTooShort(rel) {
						t.delay(rel)
						continue
					}
					if t.unique {
						uniqueTask = t
					} else {
						normalTasks = append(normalTasks, t)
					}
					// jsvm.Callback(t.eventCb, abs, rel)
				}

			}
			if uniqueTask != nil {
				// fmt.Println("I am unique")
				uniqueTask.raise(abs, rel)
				for _, nt := range normalTasks {
					nt.delay(rel)
				}
				continue
			}
			for _, t := range normalTasks {
				// fmt.Println("I am not unique")
				t.raise(abs, rel)
			}
		}
	}
	// return otto.UndefinedValue()

}

func (t *taskRunner) raise(abs, rel string) {
	jsvm.Callback(t.eventCb, abs, rel)
	t.delay(rel)
}

func (t *taskRunner) delay(rel string) {
	t.lastPath = rel
	t.lastTime = time.Now()
}

func (t *taskRunner) match(rel string) bool {
	return wildmatch.IsSubsetOf(path.Clean(rel), t.wildcard)
}

func (t *taskRunner) intervalTooShort(rel string) bool {
	return t.lastPath == rel && time.Now().Sub(t.lastTime).Seconds() < 1
}

func format(path string) string {
	return strings.Replace(path, "\\", "/", -1)
}
func isDir(p string) bool {
	fi, err := os.Stat(p)
	return err == nil && fi.IsDir()
}
<|endoftext|>


<commit_before>package oauth2

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/hooklift/oauth2/providers/test"
	"github.com/hooklift/oauth2/types"
)

// getTestAuthzCode returns authorization tokens for access tokens issuing tests
func getTestAuthzCode(t *testing.T) (Provider, string) {
	provider := test.NewProvider(true)

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	equals(t, http.StatusOK, w.Code)

	body := w.Body.String()
	stringz := []string{
		"client_id",
		"redirect_uri",
		"response_type",
		"state",
		"scope",
		"code",
		"read write identity",
		"state-test",
	}

	for _, s := range stringz {
		assert(t, strings.Contains(body, s), "Does not look like we got an authorization form: '%s' was not found in %v", s, body)
	}

	// Sending post to acquire authorization token
	buffer := bytes.NewBufferString(queryStr)
	req, err = http.NewRequest("POST", "https://example.com/oauth2/authzs", buffer)
	ok(t, err)

	req.Header.Set("Content-type", "application/x-www-form-urlencoded")

	w = httptest.NewRecorder()
	CreateGrant(w, req, provider)

	// Tests http://tools.ietf.org/html/rfc6749#section-4.1.2
	equals(t, http.StatusFound, w.Code)

	redirectTo := w.Header().Get("Location")
	u, err := url.Parse(redirectTo)
	ok(t, err)

	authzCode := u.Query().Get("code")
	assert(t, authzCode != "", "It looks like the authorization code came back empty: %s", authzCode)

	// makes sure the same state parameter value received to acquire
	// the authorization grant is send back when delivering the access token.
	equals(t, state, u.Query().Get("state"))

	return provider, authzCode
}

// TestAuthorizationGrant tests a happy web authorization flow in accordance with
// http://tools.ietf.org/html/rfc6749#section-4.1
func TestAuthorizationGrant(t *testing.T) {
	getTestAuthzCode(t)
}

// TestLoginRedirect tests that logging in is required for a resource owner to
// grant any authorization codes to clients.
func TestLoginRedirect(t *testing.T) {
	provider := test.NewProvider(false)

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"
	clientID := provider.Client.ID
	redirectURL := provider.Client.RedirectURL.String()

	values := url.Values{
		"client_id":     {clientID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {redirectURL},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	authzURL := "https://example.com/oauth2/authzs?" + queryStr
	req, err := http.NewRequest("GET", authzURL, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	equals(t, http.StatusFound, w.Code)
	equals(t, provider.LoginURL(authzURL), w.Header().Get("Location"))
}

// TestImplicitGrant tests a happy implicit flow
func TestImplicitGrant(t *testing.T) {
	provider := test.NewProvider(true)

	state := "state-test"
	scopes := "read write identity"
	grantType := "token"
	clientID := provider.Client.ID
	redirectURL := provider.Client.RedirectURL.String()

	values := url.Values{
		"client_id":     {clientID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {redirectURL},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.2.1
	queryStr := values.Encode()
	authzURL := "https://example.com/oauth2/authzs?" + queryStr
	req, err := http.NewRequest("GET", authzURL, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	body := w.Body.String()
	stringz := []string{
		"client_id",
		"redirect_uri",
		"response_type",
		"state",
		"scope",
		"token",
		"read write identity",
		"state-test",
	}

	for _, s := range stringz {
		assert(t, strings.Contains(body, s), "Does not look like we got an authorization form: '%s' was not found in %v", s, body)
	}

	// Sending post to acquire authorization token
	buffer := bytes.NewBufferString(queryStr)
	req, err = http.NewRequest("POST", "https://example.com/oauth2/authzs", buffer)
	ok(t, err)

	req.Header.Set("Content-type", "application/x-www-form-urlencoded")

	w = httptest.NewRecorder()
	CreateGrant(w, req, provider)

	// Tests http://tools.ietf.org/html/rfc6749#section-4.2.2
	equals(t, http.StatusFound, w.Code)

	redirectTo := w.Header().Get("Location")
	u, err := url.Parse(redirectTo)
	ok(t, err)

	fragment, err := url.ParseQuery(strings.TrimPrefix(u.Fragment, "#"))
	ok(t, err)
	accessToken := fragment.Get("access_token")
	assert(t, accessToken != "", "It looks like the authorization code came back empty: ->%s<-", accessToken)
	equals(t, state, fragment.Get("state"))
	equals(t, "600", fragment.Get("expires_in"))
	equals(t, scopes, fragment.Get("scope"))
	equals(t, "bearer", fragment.Get("token_type"))

	// Implict flow should not emit refresh tokens
	refreshToken := fragment.Get("refresh_token")
	equals(t, "", refreshToken)
}

// TestReplayAttackProtection tests that the authorization grant can be used
// only once.
func TestReplayAttackProtection(t *testing.T) {
	provider, authzCode := getTestAuthzCode(t)

	req := AuthzGrantTokenRequestTest(t, "authorization_code", authzCode)
	req.SetBasicAuth("test_client_id", "test_client_id")

	w := httptest.NewRecorder()
	IssueAccessToken(w, req, provider)
	token := types.Token{}
	err := json.Unmarshal(w.Body.Bytes(), &token)
	ok(t, err)
	equals(t, "bearer", token.Type)
	equals(t, "600", token.ExpiresIn)

	w2 := httptest.NewRecorder()
	IssueAccessToken(w2, req, provider)

	// http://tools.ietf.org/html/rfc6749#section-4.1.4
	authzErr := AuthzError{}
	//log.Printf("%s", w2.Body.String())
	err = json.Unmarshal(w2.Body.Bytes(), &authzErr)
	ok(t, err)
	equals(t, "invalid_grant", authzErr.Code)
	equals(t, "Grant code was revoked, expired or already used.", authzErr.Desc)

}

// TestRedirectURLMatch makes sure redirect_uri for requesting an authorization
// grant is the same as the redirect_uri provided to get the correspondent access token.
// This is intended to mitigate the risk of account hijacking by leaking
// authorization codes.
func TestRedirectURLMatch(t *testing.T) {

}

// TestAccessTokenIssuer makes sure a token belongs to the client_id making
// the request with it. This mitigates account hijacking as well.
func TestAccessTokenOwnership(t *testing.T) {

}

// TestAccessTokenExpiration makes sure that access tokens are actually expired.
func TestAccessTokenExpiration(t *testing.T) {

}

// TestScopeIsRequired makes sure it requires clients to provide access scopes when
// getting authorization codes.
func TestScopeIsRequired(t *testing.T) {
	provider := test.NewProvider(true)
	state := "my-state"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	equals(t, http.StatusFound, w.Code)
	u, err := url.Parse(w.Header().Get("Location"))
	ok(t, err)
	equals(t, "invalid_request", u.Query().Get("error"))
	equals(t, "scope parameter is required by this authorization server.", u.Query().Get("error_description"))
}

// TestStateIsRequired makes sure it requires clients to provide a state when
// getting authorization codes.
func TestStateIsRequired(t *testing.T) {
	provider := test.NewProvider(true)

	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	equals(t, http.StatusFound, w.Code)
	u, err := url.Parse(w.Header().Get("Location"))
	ok(t, err)
	equals(t, "invalid_request", u.Query().Get("error"))
	equals(t, "state parameter is required by this authorization server.", u.Query().Get("error_description"))
}

// TestSecurityHeaders makes sure security headers are sent along the authorization form.
func TestSecurityHeaders(t *testing.T) {
	provider := test.NewProvider(true)

	state := "mystate"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"state":         {state},
		"response_type": {grantType},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	//log.Printf("%+v", w.HeaderMap)

	equals(t, "max-age=0", w.Header().Get("Strict-Transport-Security"))
	equals(t, "1; mode=block", w.Header().Get("X-XSS-Protection"))
	equals(t, "nosniff", w.Header().Get("X-Content-Type-Options"))
	equals(t, "SAMEORIGIN", w.Header().Get("X-Frame-Options"))
}

// TestRedirectURIScheme makes sure clients provide redirect URLs that use TLS
func TestRedirectURIScheme(t *testing.T) {
	provider := test.NewProvider(true)

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {"http://attacker.com/callback"},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	body := w.Body.String()
	assert(t, strings.Contains(body, "access_denied") == true, "access-denied was not found in response body")
	assert(t, strings.Contains(body, "3rd-party client app provided an invalid redirect_uri. It does not comply with http://tools.ietf.org/html/rfc3986#section-4.3 or does not use HTTPS") == true, "error description does not match.")
}
<commit_msg>Tests mismatching redirect URIs<commit_after>package oauth2

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/hooklift/oauth2/providers/test"
	"github.com/hooklift/oauth2/types"
)

// getTestAuthzCode returns authorization tokens for access tokens issuing tests
func getTestAuthzCode(t *testing.T) (Provider, string) {
	provider := test.NewProvider(true)

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	equals(t, http.StatusOK, w.Code)

	body := w.Body.String()
	stringz := []string{
		"client_id",
		"redirect_uri",
		"response_type",
		"state",
		"scope",
		"code",
		"read write identity",
		"state-test",
	}

	for _, s := range stringz {
		assert(t, strings.Contains(body, s), "Does not look like we got an authorization form: '%s' was not found in %v", s, body)
	}

	// Sending post to acquire authorization token
	buffer := bytes.NewBufferString(queryStr)
	req, err = http.NewRequest("POST", "https://example.com/oauth2/authzs", buffer)
	ok(t, err)

	req.Header.Set("Content-type", "application/x-www-form-urlencoded")

	w = httptest.NewRecorder()
	CreateGrant(w, req, provider)

	// Tests http://tools.ietf.org/html/rfc6749#section-4.1.2
	equals(t, http.StatusFound, w.Code)

	redirectTo := w.Header().Get("Location")
	u, err := url.Parse(redirectTo)
	ok(t, err)

	authzCode := u.Query().Get("code")
	assert(t, authzCode != "", "It looks like the authorization code came back empty: %s", authzCode)

	// makes sure the same state parameter value received to acquire
	// the authorization grant is send back when delivering the access token.
	equals(t, state, u.Query().Get("state"))

	return provider, authzCode
}

// TestAuthorizationGrant tests a happy web authorization flow in accordance with
// http://tools.ietf.org/html/rfc6749#section-4.1
func TestAuthorizationGrant(t *testing.T) {
	getTestAuthzCode(t)
}

// TestLoginRedirect tests that logging in is required for a resource owner to
// grant any authorization codes to clients.
func TestLoginRedirect(t *testing.T) {
	provider := test.NewProvider(false)

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"
	clientID := provider.Client.ID
	redirectURL := provider.Client.RedirectURL.String()

	values := url.Values{
		"client_id":     {clientID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {redirectURL},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	authzURL := "https://example.com/oauth2/authzs?" + queryStr
	req, err := http.NewRequest("GET", authzURL, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	equals(t, http.StatusFound, w.Code)
	equals(t, provider.LoginURL(authzURL), w.Header().Get("Location"))
}

// TestImplicitGrant tests a happy implicit flow
func TestImplicitGrant(t *testing.T) {
	provider := test.NewProvider(true)

	state := "state-test"
	scopes := "read write identity"
	grantType := "token"
	clientID := provider.Client.ID
	redirectURL := provider.Client.RedirectURL.String()

	values := url.Values{
		"client_id":     {clientID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {redirectURL},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.2.1
	queryStr := values.Encode()
	authzURL := "https://example.com/oauth2/authzs?" + queryStr
	req, err := http.NewRequest("GET", authzURL, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	body := w.Body.String()
	stringz := []string{
		"client_id",
		"redirect_uri",
		"response_type",
		"state",
		"scope",
		"token",
		"read write identity",
		"state-test",
	}

	for _, s := range stringz {
		assert(t, strings.Contains(body, s), "Does not look like we got an authorization form: '%s' was not found in %v", s, body)
	}

	// Sending post to acquire authorization token
	buffer := bytes.NewBufferString(queryStr)
	req, err = http.NewRequest("POST", "https://example.com/oauth2/authzs", buffer)
	ok(t, err)

	req.Header.Set("Content-type", "application/x-www-form-urlencoded")

	w = httptest.NewRecorder()
	CreateGrant(w, req, provider)

	// Tests http://tools.ietf.org/html/rfc6749#section-4.2.2
	equals(t, http.StatusFound, w.Code)

	redirectTo := w.Header().Get("Location")
	u, err := url.Parse(redirectTo)
	ok(t, err)

	fragment, err := url.ParseQuery(strings.TrimPrefix(u.Fragment, "#"))
	ok(t, err)
	accessToken := fragment.Get("access_token")
	assert(t, accessToken != "", "It looks like the authorization code came back empty: ->%s<-", accessToken)
	equals(t, state, fragment.Get("state"))
	equals(t, "600", fragment.Get("expires_in"))
	equals(t, scopes, fragment.Get("scope"))
	equals(t, "bearer", fragment.Get("token_type"))

	// Implict flow should not emit refresh tokens
	refreshToken := fragment.Get("refresh_token")
	equals(t, "", refreshToken)
}

// TestReplayAttackProtection tests that the authorization grant can be used
// only once.
func TestReplayAttackProtection(t *testing.T) {
	provider, authzCode := getTestAuthzCode(t)

	req := AuthzGrantTokenRequestTest(t, "authorization_code", authzCode)
	req.SetBasicAuth("test_client_id", "test_client_id")

	w := httptest.NewRecorder()
	IssueAccessToken(w, req, provider)
	token := types.Token{}
	err := json.Unmarshal(w.Body.Bytes(), &token)
	ok(t, err)
	equals(t, "bearer", token.Type)
	equals(t, "600", token.ExpiresIn)

	w2 := httptest.NewRecorder()
	IssueAccessToken(w2, req, provider)

	// http://tools.ietf.org/html/rfc6749#section-4.1.4
	authzErr := AuthzError{}
	//log.Printf("%s", w2.Body.String())
	err = json.Unmarshal(w2.Body.Bytes(), &authzErr)
	ok(t, err)
	equals(t, "invalid_grant", authzErr.Code)
	equals(t, "Grant code was revoked, expired or already used.", authzErr.Desc)

}

// TestRedirectURLMatch makes sure redirect_uri for requesting an authorization
// grant is the same as the redirect_uri provided to get the correspondent access token.
// This is intended to mitigate the risk of account hijacking by leaking
// authorization codes.
func TestRedirectURLMatch(t *testing.T) {
	provider := test.NewProvider(true)

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	equals(t, http.StatusOK, w.Code)

	// Sending post to acquire authorization token
	values.Set("redirect_uri", "https://attacker.com/callback")
	queryStr2 := values.Encode()
	buffer := bytes.NewBufferString(queryStr2)
	req, err = http.NewRequest("POST", "https://example.com/oauth2/authzs", buffer)
	ok(t, err)

	req.Header.Set("Content-type", "application/x-www-form-urlencoded")

	w2 := httptest.NewRecorder()
	CreateGrant(w2, req, provider)
	body := w2.Body.String()
	assert(t, strings.Contains(body, "access_denied"), "access_denied was expected as response")
	assert(t, strings.Contains(body, "3rd-party client app provided a redirect_uri that does not match the URI registered for this client in our database."), "unexpected error description.")
}

// TestAccessTokenOwnership makes sure a token belongs to the client_id making
// the request with it. This mitigates account hijacking as well.
func TestAccessTokenOwnership(t *testing.T) {

}

// TestAccessTokenExpiration makes sure that access tokens are actually expired.
func TestAccessTokenExpiration(t *testing.T) {

}

// TestScopeIsRequired makes sure it requires clients to provide access scopes when
// getting authorization codes.
func TestScopeIsRequired(t *testing.T) {
	provider := test.NewProvider(true)
	state := "my-state"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	equals(t, http.StatusFound, w.Code)
	u, err := url.Parse(w.Header().Get("Location"))
	ok(t, err)
	equals(t, "invalid_request", u.Query().Get("error"))
	equals(t, "scope parameter is required by this authorization server.", u.Query().Get("error_description"))
}

// TestStateIsRequired makes sure it requires clients to provide a state when
// getting authorization codes.
func TestStateIsRequired(t *testing.T) {
	provider := test.NewProvider(true)

	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	equals(t, http.StatusFound, w.Code)
	u, err := url.Parse(w.Header().Get("Location"))
	ok(t, err)
	equals(t, "invalid_request", u.Query().Get("error"))
	equals(t, "state parameter is required by this authorization server.", u.Query().Get("error_description"))
}

// TestSecurityHeaders makes sure security headers are sent along the authorization form.
func TestSecurityHeaders(t *testing.T) {
	provider := test.NewProvider(true)

	state := "mystate"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"state":         {state},
		"response_type": {grantType},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	//log.Printf("%+v", w.HeaderMap)

	equals(t, "max-age=0", w.Header().Get("Strict-Transport-Security"))
	equals(t, "1; mode=block", w.Header().Get("X-XSS-Protection"))
	equals(t, "nosniff", w.Header().Get("X-Content-Type-Options"))
	equals(t, "SAMEORIGIN", w.Header().Get("X-Frame-Options"))
}

// TestRedirectURIScheme makes sure clients provide redirect URLs that use TLS
func TestRedirectURIScheme(t *testing.T) {
	provider := test.NewProvider(true)

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {"http://attacker.com/callback"},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, provider)
	body := w.Body.String()
	assert(t, strings.Contains(body, "access_denied") == true, "access-denied was not found in response body")
	assert(t, strings.Contains(body, "3rd-party client app provided an invalid redirect_uri. It does not comply with http://tools.ietf.org/html/rfc3986#section-4.3 or does not use HTTPS") == true, "error description does not match.")
}
<|endoftext|>


<commit_before>package facebook

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

	"github.com/tchap/steemwatch/server/auth"

	"github.com/labstack/echo"
	"github.com/pkg/errors"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/facebook"
)

type FacebookProfile struct {
	Id    string `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

type Authenticator struct {
	config *oauth2.Config
}

func NewAuthenticator(clientId, clientSecret, redirectURL string) *Authenticator {
	return &Authenticator{
		config: &oauth2.Config{
			ClientID:     clientId,
			ClientSecret: clientSecret,
			RedirectURL:  redirectURL,
			Scopes: []string{
				"public_profile",
				"email",
			},
			Endpoint: facebook.Endpoint,
		},
	}
}

func (authenticator *Authenticator) Authenticate(ctx echo.Context) error {
	// Redirect to the consent page.
	consentPageURL := authenticator.config.AuthCodeURL("state")
	return ctx.Redirect(http.StatusTemporaryRedirect, consentPageURL)
}

func (authenticator *Authenticator) Callback(ctx echo.Context) (*auth.UserProfile, error) {
	// Handle the exchange code to initiate a transport.
	token, err := authenticator.config.Exchange(oauth2.NoContext, ctx.QueryParam("code"))
	if err != nil {
		return nil, err
	}

	// Get an authenticated HTTP client.
	httpClient := authenticator.config.Client(oauth2.NoContext, token)

	// Call Facebook API.
	resp, err := httpClient.Get("https://graph.facebook.com/v2.6/me?fields=name,email")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// Decode the response.
	var me FacebookProfile
	if err := json.NewDecoder(resp.Body).Decode(&me); err != nil {
		return nil, err
	}

	// Make sure the email address is set.
	if me.Email == "" {
		return nil, errors.New("Facebook did not return any email address")
	}

	// Assemble the profile that we use internally.
	return &auth.UserProfile{
		Email: me.Email,
	}, nil
}
<commit_msg>server/auth/facebook: Improve error message<commit_after>package facebook

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

	"github.com/tchap/steemwatch/server/auth"

	"github.com/labstack/echo"
	"github.com/pkg/errors"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/facebook"
)

type FacebookProfile struct {
	Id    string `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

type Authenticator struct {
	config *oauth2.Config
}

func NewAuthenticator(clientId, clientSecret, redirectURL string) *Authenticator {
	return &Authenticator{
		config: &oauth2.Config{
			ClientID:     clientId,
			ClientSecret: clientSecret,
			RedirectURL:  redirectURL,
			Scopes: []string{
				"public_profile",
				"email",
			},
			Endpoint: facebook.Endpoint,
		},
	}
}

func (authenticator *Authenticator) Authenticate(ctx echo.Context) error {
	// Redirect to the consent page.
	consentPageURL := authenticator.config.AuthCodeURL("state")
	return ctx.Redirect(http.StatusTemporaryRedirect, consentPageURL)
}

func (authenticator *Authenticator) Callback(ctx echo.Context) (*auth.UserProfile, error) {
	// Handle the exchange code to initiate a transport.
	token, err := authenticator.config.Exchange(oauth2.NoContext, ctx.QueryParam("code"))
	if err != nil {
		return nil, err
	}

	// Get an authenticated HTTP client.
	httpClient := authenticator.config.Client(oauth2.NoContext, token)

	// Call Facebook API.
	resp, err := httpClient.Get("https://graph.facebook.com/v2.6/me?fields=name,email")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// Decode the response.
	var me FacebookProfile
	if err := json.NewDecoder(resp.Body).Decode(&me); err != nil {
		return nil, err
	}

	// Make sure the email address is set.
	if me.Email == "" {
		return nil, errors.Errorf("Facebook did not return any email address: %+v", me)
	}

	// Assemble the profile that we use internally.
	return &auth.UserProfile{
		Email: me.Email,
	}, nil
}
<|endoftext|>


<commit_before>package session

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/client"
	"github.com/aws/aws-sdk-go/aws/corehandlers"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/defaults"
	"github.com/aws/aws-sdk-go/aws/endpoints"
	"github.com/aws/aws-sdk-go/aws/request"
)

// A Session provides a central location to create service clients from and
// store configurations and request handlers for those services.
//
// Sessions are safe to create service clients concurrently, but it is not safe
// to mutate the Session concurrently.
//
// The Session satisfies the service client's client.ClientConfigProvider.
type Session struct {
	Config   *aws.Config
	Handlers request.Handlers
}

// New creates a new instance of the handlers merging in the provided configs
// on top of the SDK's default configurations. Once the Session is created it
// can be mutated to modify the Config or Handlers. The Session is safe to be
// read concurrently, but it should not be written to concurrently.
//
// If the AWS_SDK_LOAD_CONFIG environment is set to a truthy value, the New
// method could now encounter an error when loading the configuration. When
// The environment variable is set, and an error occurs, New will return a
// session that will fail all requests reporting the error that occurred while
// loading the session. Use NewSession to get the error when creating the
// session.
//
// If the AWS_SDK_LOAD_CONFIG environment variable is set to a truthy value
// the shared config file (~/.aws/config) will also be loaded, in addition to
// the shared credentials file (~/.aws/config). Values set in both the
// shared config, and shared credentials will be taken from the shared
// credentials file.
//
// Deprecated: Use NewSession functions to create sessions instead. NewSession
// has the same functionality as New except an error can be returned when the
// func is called instead of waiting to receive an error until a request is made.
func New(cfgs ...*aws.Config) *Session {
	// load initial config from environment
	envCfg := loadEnvConfig()

	if envCfg.EnableSharedConfig {
		s, err := newSession(envCfg, cfgs...)
		if err != nil {
			// Old session.New expected all errors to be discovered when
			// a request is made, and would report the errors then. This
			// needs to be replicated if an error occurs while creating
			// the session.
			msg := "failed to create session with AWS_SDK_LOAD_CONFIG enabled. " +
				"Use session.NewSession to handle errors occurring during session creation."

			// Session creation failed, need to report the error and prevent
			// any requests from succeeding.
			s = &Session{Config: defaults.Config()}
			s.Config.MergeIn(cfgs...)
			s.Config.Logger.Log("ERROR:", msg, "Error:", err)
			s.Handlers.Validate.PushBack(func(r *request.Request) {
				r.Error = err
			})
		}
		return s
	}

	return oldNewSession(cfgs...)
}

// NewSession returns a new Session created from SDK defaults, config files,
// environment, and user provided config files. Once the Session is created
// it can be mutated to modify the Config or Handlers. The Session is safe to
// be read concurrently, but it should not be written to concurrently.
//
// If the AWS_SDK_LOAD_CONFIG environment variable is set to a truthy value
// the shared config file (~/.aws/config) will also be loaded in addition to
// the shared credentials file (~/.aws/config). Values set in both the
// shared config, and shared credentials will be taken from the shared
// credentials file. Enabling the Shared Config will also allow the Session
// to be built with retrieving credentials with AssumeRole set in the config.
//
// See the NewSessionWithOptions func for information on how to override or
// control through code how the Session will be created. Such as specifying the
// config profile, and controlling if shared config is enabled or not.
func NewSession(cfgs ...*aws.Config) (*Session, error) {
	envCfg := loadEnvConfig()

	return newSession(envCfg, cfgs...)
}

// SharedConfigState provides the ability to optionally override the state
// of the session's creation based on the shared config being enabled or
// disabled.
type SharedConfigState int

const (
	// SharedConfigStateFromEnv does not override any state of the
	// AWS_SDK_LOAD_CONFIG env var. It is the default value of the
	// SharedConfigState type.
	SharedConfigStateFromEnv SharedConfigState = iota

	// SharedConfigDisable overrides the AWS_SDK_LOAD_CONFIG env var value
	// and disables the shared config functionality.
	SharedConfigDisable

	// SharedConfigEnable overrides the AWS_SDK_LOAD_CONFIG env var value
	// and enables the shared config functionality.
	SharedConfigEnable
)

// Options provides the means to control how a Session is created and what
// configuration values will be loaded.
//
type Options struct {
	// Provides config values for the SDK to use when creating service clients
	// and making API requests to services. Any value set in with this field
	// will override the associated value provided by the SDK defaults,
	// environment or config files where relevant.
	//
	// If not set, configuration values from from SDK defaults, environment,
	// config will be used.
	Config aws.Config

	// Overrides the config profile the Session should be created from. If not
	// set the value of the environment variable will be loaded (AWS_PROFILE,
	// or AWS_DEFAULT_PROFILE if the Shared Config is enabled).
	//
	// If not set and environment variables are not set the "default"
	// (DefaultSharedConfigProfile) will be used as the profile to load the
	// session config from.
	Profile string

	// Instructs how the Session will be created based on the AWS_SDK_LOAD_CONFIG
	// environment variable. By default a Session will be created using the
	// value provided by the AWS_SDK_LOAD_CONFIG environment variable.
	//
	// Setting this value to SharedConfigEnable or SharedConfigDisable
	// will allow you to override the AWS_SDK_LOAD_CONFIG environment variable
	// and enable or disable the shared config functionality.
	SharedConfigState SharedConfigState
}

// NewSessionWithOptions returns a new Session created from SDK defaults, config files,
// environment, and user provided config files. This func uses the Options
// values to configure how the Session is created.
//
// If the AWS_SDK_LOAD_CONFIG environment variable is set to a truthy value
// the shared config file (~/.aws/config) will also be loaded in addition to
// the shared credentials file (~/.aws/config). Values set in both the
// shared config, and shared credentials will be taken from the shared
// credentials file. Enabling the Shared Config will also allow the Session
// to be built with retrieving credentials with AssumeRole set in the config.
//
//     // Equivalent to session.New
//     sess, err := session.NewSessionWithOptions(session.Options{})
//
//     // Specify profile to load for the session's config
//     sess, err := session.NewSessionWithOptions(session.Options{
//          Profile: "profile_name",
//     })
//
//     // Specify profile for config and region for requests
//     sess, err := session.NewSessionWithOptions(session.Options{
//          Config: aws.Config{Region: aws.String("us-east-1")},
//          Profile: "profile_name",
//     })
//
//     // Force enable Shared Config support
//     sess, err := session.NewSessionWithOptions(session.Options{
//         SharedConfigState: SharedConfigEnable,
//     })
func NewSessionWithOptions(opts Options) (*Session, error) {
	var envCfg envConfig
	if opts.SharedConfigState == SharedConfigEnable {
		envCfg = loadSharedEnvConfig()
	} else {
		envCfg = loadEnvConfig()
	}

	if len(opts.Profile) > 0 {
		envCfg.Profile = opts.Profile
	}

	switch opts.SharedConfigState {
	case SharedConfigDisable:
		envCfg.EnableSharedConfig = false
	case SharedConfigEnable:
		envCfg.EnableSharedConfig = true
	}

	return newSession(envCfg, &opts.Config)
}

// Must is a helper function to ensure the Session is valid and there was no
// error when calling a NewSession function.
//
// This helper is intended to be used in variable initialization to load the
// Session and configuration at startup. Such as:
//
//     var sess = session.Must(session.NewSession())
func Must(sess *Session, err error) *Session {
	if err != nil {
		panic(err)
	}

	return sess
}

func oldNewSession(cfgs ...*aws.Config) *Session {
	cfg := defaults.Config()
	handlers := defaults.Handlers()

	// Apply the passed in configs so the configuration can be applied to the
	// default credential chain
	cfg.MergeIn(cfgs...)
	if cfg.EndpointResolver == nil {
		// An endpoint resolver is required for a session to be able to provide
		// endpoints for service client configurations.
		cfg.EndpointResolver = endpoints.DefaultResolver()
	}
	cfg.Credentials = defaults.CredChain(cfg, handlers)

	// Reapply any passed in configs to override credentials if set
	cfg.MergeIn(cfgs...)

	s := &Session{
		Config:   cfg,
		Handlers: handlers,
	}

	initHandlers(s)

	return s
}

func newSession(envCfg envConfig, cfgs ...*aws.Config) (*Session, error) {
	cfg := defaults.Config()
	handlers := defaults.Handlers()

	// Get a merged version of the user provided config to determine if
	// credentials were.
	userCfg := &aws.Config{}
	userCfg.MergeIn(cfgs...)

	// Order config files will be loaded in with later files overwriting
	// previous config file values.
	cfgFiles := []string{envCfg.SharedConfigFile, envCfg.SharedCredentialsFile}
	if !envCfg.EnableSharedConfig {
		// The shared config file (~/.aws/config) is only loaded if instructed
		// to load via the envConfig.EnableSharedConfig (AWS_SDK_LOAD_CONFIG).
		cfgFiles = cfgFiles[1:]
	}

	// Load additional config from file(s)
	sharedCfg, err := loadSharedConfig(envCfg.Profile, cfgFiles)
	if err != nil {
		return nil, err
	}

	mergeConfigSrcs(cfg, userCfg, envCfg, sharedCfg, handlers)

	s := &Session{
		Config:   cfg,
		Handlers: handlers,
	}

	initHandlers(s)

	return s, nil
}

func mergeConfigSrcs(cfg, userCfg *aws.Config, envCfg envConfig, sharedCfg sharedConfig, handlers request.Handlers) {
	// Merge in user provided configuration
	cfg.MergeIn(userCfg)

	// Region if not already set by user
	if len(aws.StringValue(cfg.Region)) == 0 {
		if len(envCfg.Region) > 0 {
			cfg.WithRegion(envCfg.Region)
		} else if envCfg.EnableSharedConfig && len(sharedCfg.Region) > 0 {
			cfg.WithRegion(sharedCfg.Region)
		}
	}

	// Configure credentials if not already set
	if cfg.Credentials == credentials.AnonymousCredentials && userCfg.Credentials == nil {
		if len(envCfg.Creds.AccessKeyID) > 0 {
			cfg.Credentials = credentials.NewStaticCredentialsFromCreds(
				envCfg.Creds,
			)
		} else if envCfg.EnableSharedConfig && len(sharedCfg.AssumeRole.RoleARN) > 0 && sharedCfg.AssumeRoleSource != nil {
			cfgCp := *cfg
			cfgCp.Credentials = credentials.NewStaticCredentialsFromCreds(
				sharedCfg.AssumeRoleSource.Creds,
			)
			cfg.Credentials = stscreds.NewCredentials(
				&Session{
					Config:   &cfgCp,
					Handlers: handlers.Copy(),
				},
				sharedCfg.AssumeRole.RoleARN,
				func(opt *stscreds.AssumeRoleProvider) {
					opt.RoleSessionName = sharedCfg.AssumeRole.RoleSessionName

					if len(sharedCfg.AssumeRole.ExternalID) > 0 {
						opt.ExternalID = aws.String(sharedCfg.AssumeRole.ExternalID)
					}

					// MFA not supported
				},
			)
		} else if len(sharedCfg.Creds.AccessKeyID) > 0 {
			cfg.Credentials = credentials.NewStaticCredentialsFromCreds(
				sharedCfg.Creds,
			)
		} else {
			// Fallback to default credentials provider, include mock errors
			// for the credential chain so user can identify why credentials
			// failed to be retrieved.
			cfg.Credentials = credentials.NewCredentials(&credentials.ChainProvider{
				VerboseErrors: aws.BoolValue(cfg.CredentialsChainVerboseErrors),
				Providers: []credentials.Provider{
					&credProviderError{Err: awserr.New("EnvAccessKeyNotFound", "failed to find credentials in the environment.", nil)},
					&credProviderError{Err: awserr.New("SharedCredsLoad", fmt.Sprintf("failed to load profile, %s.", envCfg.Profile), nil)},
					defaults.RemoteCredProvider(*cfg, handlers),
				},
			})
		}
	}
}

type credProviderError struct {
	Err error
}

var emptyCreds = credentials.Value{}

func (c credProviderError) Retrieve() (credentials.Value, error) {
	return credentials.Value{}, c.Err
}
func (c credProviderError) IsExpired() bool {
	return true
}

func initHandlers(s *Session) {
	// Add the Validate parameter handler if it is not disabled.
	s.Handlers.Validate.Remove(corehandlers.ValidateParametersHandler)
	if !aws.BoolValue(s.Config.DisableParamValidation) {
		s.Handlers.Validate.PushBackNamed(corehandlers.ValidateParametersHandler)
	}
}

// Copy creates and returns a copy of the current Session, coping the config
// and handlers. If any additional configs are provided they will be merged
// on top of the Session's copied config.
//
//     // Create a copy of the current Session, configured for the us-west-2 region.
//     sess.Copy(&aws.Config{Region: aws.String("us-west-2")})
func (s *Session) Copy(cfgs ...*aws.Config) *Session {
	newSession := &Session{
		Config:   s.Config.Copy(cfgs...),
		Handlers: s.Handlers.Copy(),
	}

	initHandlers(newSession)

	return newSession
}

// ClientConfig satisfies the client.ConfigProvider interface and is used to
// configure the service client instances. Passing the Session to the service
// client's constructor (New) will use this method to configure the client.
func (s *Session) ClientConfig(serviceName string, cfgs ...*aws.Config) client.Config {
	// Backwards compatibility, the error will be eaten if user calls ClientConfig
	// directly. All SDK services will use ClientconfigWithError.
	cfg, _ := s.clientConfigWithErr(serviceName, cfgs...)

	return cfg
}

func (s *Session) clientConfigWithErr(serviceName string, cfgs ...*aws.Config) (client.Config, error) {
	s = s.Copy(cfgs...)

	var resolved endpoints.ResolvedEndpoint
	var err error

	region := aws.StringValue(s.Config.Region)

	if endpoint := aws.StringValue(s.Config.Endpoint); len(endpoint) != 0 {
		resolved.URL = endpoints.AddScheme(endpoint, aws.BoolValue(s.Config.DisableSSL))
		resolved.SigningRegion = region
	} else {
		resolved, err = s.Config.EndpointResolver.EndpointFor(
			serviceName, region,
			func(opt *endpoints.Options) {
				opt.DisableSSL = aws.BoolValue(s.Config.DisableSSL)
				opt.UseDualStack = aws.BoolValue(s.Config.UseDualStack)
			},
		)
	}

	return client.Config{
		Config:        s.Config,
		Handlers:      s.Handlers,
		Endpoint:      resolved.URL,
		SigningRegion: resolved.SigningRegion,
		SigningName:   resolved.SigningName,
	}, err
}
<commit_msg>Fixing typos (#1047)<commit_after>package session

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/client"
	"github.com/aws/aws-sdk-go/aws/corehandlers"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/defaults"
	"github.com/aws/aws-sdk-go/aws/endpoints"
	"github.com/aws/aws-sdk-go/aws/request"
)

// A Session provides a central location to create service clients from and
// store configurations and request handlers for those services.
//
// Sessions are safe to create service clients concurrently, but it is not safe
// to mutate the Session concurrently.
//
// The Session satisfies the service client's client.ClientConfigProvider.
type Session struct {
	Config   *aws.Config
	Handlers request.Handlers
}

// New creates a new instance of the handlers merging in the provided configs
// on top of the SDK's default configurations. Once the Session is created it
// can be mutated to modify the Config or Handlers. The Session is safe to be
// read concurrently, but it should not be written to concurrently.
//
// If the AWS_SDK_LOAD_CONFIG environment is set to a truthy value, the New
// method could now encounter an error when loading the configuration. When
// The environment variable is set, and an error occurs, New will return a
// session that will fail all requests reporting the error that occurred while
// loading the session. Use NewSession to get the error when creating the
// session.
//
// If the AWS_SDK_LOAD_CONFIG environment variable is set to a truthy value
// the shared config file (~/.aws/config) will also be loaded, in addition to
// the shared credentials file (~/.aws/credentials). Values set in both the
// shared config, and shared credentials will be taken from the shared
// credentials file.
//
// Deprecated: Use NewSession functions to create sessions instead. NewSession
// has the same functionality as New except an error can be returned when the
// func is called instead of waiting to receive an error until a request is made.
func New(cfgs ...*aws.Config) *Session {
	// load initial config from environment
	envCfg := loadEnvConfig()

	if envCfg.EnableSharedConfig {
		s, err := newSession(envCfg, cfgs...)
		if err != nil {
			// Old session.New expected all errors to be discovered when
			// a request is made, and would report the errors then. This
			// needs to be replicated if an error occurs while creating
			// the session.
			msg := "failed to create session with AWS_SDK_LOAD_CONFIG enabled. " +
				"Use session.NewSession to handle errors occurring during session creation."

			// Session creation failed, need to report the error and prevent
			// any requests from succeeding.
			s = &Session{Config: defaults.Config()}
			s.Config.MergeIn(cfgs...)
			s.Config.Logger.Log("ERROR:", msg, "Error:", err)
			s.Handlers.Validate.PushBack(func(r *request.Request) {
				r.Error = err
			})
		}
		return s
	}

	return oldNewSession(cfgs...)
}

// NewSession returns a new Session created from SDK defaults, config files,
// environment, and user provided config files. Once the Session is created
// it can be mutated to modify the Config or Handlers. The Session is safe to
// be read concurrently, but it should not be written to concurrently.
//
// If the AWS_SDK_LOAD_CONFIG environment variable is set to a truthy value
// the shared config file (~/.aws/config) will also be loaded in addition to
// the shared credentials file (~/.aws/credentials). Values set in both the
// shared config, and shared credentials will be taken from the shared
// credentials file. Enabling the Shared Config will also allow the Session
// to be built with retrieving credentials with AssumeRole set in the config.
//
// See the NewSessionWithOptions func for information on how to override or
// control through code how the Session will be created. Such as specifying the
// config profile, and controlling if shared config is enabled or not.
func NewSession(cfgs ...*aws.Config) (*Session, error) {
	envCfg := loadEnvConfig()

	return newSession(envCfg, cfgs...)
}

// SharedConfigState provides the ability to optionally override the state
// of the session's creation based on the shared config being enabled or
// disabled.
type SharedConfigState int

const (
	// SharedConfigStateFromEnv does not override any state of the
	// AWS_SDK_LOAD_CONFIG env var. It is the default value of the
	// SharedConfigState type.
	SharedConfigStateFromEnv SharedConfigState = iota

	// SharedConfigDisable overrides the AWS_SDK_LOAD_CONFIG env var value
	// and disables the shared config functionality.
	SharedConfigDisable

	// SharedConfigEnable overrides the AWS_SDK_LOAD_CONFIG env var value
	// and enables the shared config functionality.
	SharedConfigEnable
)

// Options provides the means to control how a Session is created and what
// configuration values will be loaded.
//
type Options struct {
	// Provides config values for the SDK to use when creating service clients
	// and making API requests to services. Any value set in with this field
	// will override the associated value provided by the SDK defaults,
	// environment or config files where relevant.
	//
	// If not set, configuration values from from SDK defaults, environment,
	// config will be used.
	Config aws.Config

	// Overrides the config profile the Session should be created from. If not
	// set the value of the environment variable will be loaded (AWS_PROFILE,
	// or AWS_DEFAULT_PROFILE if the Shared Config is enabled).
	//
	// If not set and environment variables are not set the "default"
	// (DefaultSharedConfigProfile) will be used as the profile to load the
	// session config from.
	Profile string

	// Instructs how the Session will be created based on the AWS_SDK_LOAD_CONFIG
	// environment variable. By default a Session will be created using the
	// value provided by the AWS_SDK_LOAD_CONFIG environment variable.
	//
	// Setting this value to SharedConfigEnable or SharedConfigDisable
	// will allow you to override the AWS_SDK_LOAD_CONFIG environment variable
	// and enable or disable the shared config functionality.
	SharedConfigState SharedConfigState
}

// NewSessionWithOptions returns a new Session created from SDK defaults, config files,
// environment, and user provided config files. This func uses the Options
// values to configure how the Session is created.
//
// If the AWS_SDK_LOAD_CONFIG environment variable is set to a truthy value
// the shared config file (~/.aws/config) will also be loaded in addition to
// the shared credentials file (~/.aws/credentials). Values set in both the
// shared config, and shared credentials will be taken from the shared
// credentials file. Enabling the Shared Config will also allow the Session
// to be built with retrieving credentials with AssumeRole set in the config.
//
//     // Equivalent to session.New
//     sess, err := session.NewSessionWithOptions(session.Options{})
//
//     // Specify profile to load for the session's config
//     sess, err := session.NewSessionWithOptions(session.Options{
//          Profile: "profile_name",
//     })
//
//     // Specify profile for config and region for requests
//     sess, err := session.NewSessionWithOptions(session.Options{
//          Config: aws.Config{Region: aws.String("us-east-1")},
//          Profile: "profile_name",
//     })
//
//     // Force enable Shared Config support
//     sess, err := session.NewSessionWithOptions(session.Options{
//         SharedConfigState: SharedConfigEnable,
//     })
func NewSessionWithOptions(opts Options) (*Session, error) {
	var envCfg envConfig
	if opts.SharedConfigState == SharedConfigEnable {
		envCfg = loadSharedEnvConfig()
	} else {
		envCfg = loadEnvConfig()
	}

	if len(opts.Profile) > 0 {
		envCfg.Profile = opts.Profile
	}

	switch opts.SharedConfigState {
	case SharedConfigDisable:
		envCfg.EnableSharedConfig = false
	case SharedConfigEnable:
		envCfg.EnableSharedConfig = true
	}

	return newSession(envCfg, &opts.Config)
}

// Must is a helper function to ensure the Session is valid and there was no
// error when calling a NewSession function.
//
// This helper is intended to be used in variable initialization to load the
// Session and configuration at startup. Such as:
//
//     var sess = session.Must(session.NewSession())
func Must(sess *Session, err error) *Session {
	if err != nil {
		panic(err)
	}

	return sess
}

func oldNewSession(cfgs ...*aws.Config) *Session {
	cfg := defaults.Config()
	handlers := defaults.Handlers()

	// Apply the passed in configs so the configuration can be applied to the
	// default credential chain
	cfg.MergeIn(cfgs...)
	if cfg.EndpointResolver == nil {
		// An endpoint resolver is required for a session to be able to provide
		// endpoints for service client configurations.
		cfg.EndpointResolver = endpoints.DefaultResolver()
	}
	cfg.Credentials = defaults.CredChain(cfg, handlers)

	// Reapply any passed in configs to override credentials if set
	cfg.MergeIn(cfgs...)

	s := &Session{
		Config:   cfg,
		Handlers: handlers,
	}

	initHandlers(s)

	return s
}

func newSession(envCfg envConfig, cfgs ...*aws.Config) (*Session, error) {
	cfg := defaults.Config()
	handlers := defaults.Handlers()

	// Get a merged version of the user provided config to determine if
	// credentials were.
	userCfg := &aws.Config{}
	userCfg.MergeIn(cfgs...)

	// Order config files will be loaded in with later files overwriting
	// previous config file values.
	cfgFiles := []string{envCfg.SharedConfigFile, envCfg.SharedCredentialsFile}
	if !envCfg.EnableSharedConfig {
		// The shared config file (~/.aws/config) is only loaded if instructed
		// to load via the envConfig.EnableSharedConfig (AWS_SDK_LOAD_CONFIG).
		cfgFiles = cfgFiles[1:]
	}

	// Load additional config from file(s)
	sharedCfg, err := loadSharedConfig(envCfg.Profile, cfgFiles)
	if err != nil {
		return nil, err
	}

	mergeConfigSrcs(cfg, userCfg, envCfg, sharedCfg, handlers)

	s := &Session{
		Config:   cfg,
		Handlers: handlers,
	}

	initHandlers(s)

	return s, nil
}

func mergeConfigSrcs(cfg, userCfg *aws.Config, envCfg envConfig, sharedCfg sharedConfig, handlers request.Handlers) {
	// Merge in user provided configuration
	cfg.MergeIn(userCfg)

	// Region if not already set by user
	if len(aws.StringValue(cfg.Region)) == 0 {
		if len(envCfg.Region) > 0 {
			cfg.WithRegion(envCfg.Region)
		} else if envCfg.EnableSharedConfig && len(sharedCfg.Region) > 0 {
			cfg.WithRegion(sharedCfg.Region)
		}
	}

	// Configure credentials if not already set
	if cfg.Credentials == credentials.AnonymousCredentials && userCfg.Credentials == nil {
		if len(envCfg.Creds.AccessKeyID) > 0 {
			cfg.Credentials = credentials.NewStaticCredentialsFromCreds(
				envCfg.Creds,
			)
		} else if envCfg.EnableSharedConfig && len(sharedCfg.AssumeRole.RoleARN) > 0 && sharedCfg.AssumeRoleSource != nil {
			cfgCp := *cfg
			cfgCp.Credentials = credentials.NewStaticCredentialsFromCreds(
				sharedCfg.AssumeRoleSource.Creds,
			)
			cfg.Credentials = stscreds.NewCredentials(
				&Session{
					Config:   &cfgCp,
					Handlers: handlers.Copy(),
				},
				sharedCfg.AssumeRole.RoleARN,
				func(opt *stscreds.AssumeRoleProvider) {
					opt.RoleSessionName = sharedCfg.AssumeRole.RoleSessionName

					if len(sharedCfg.AssumeRole.ExternalID) > 0 {
						opt.ExternalID = aws.String(sharedCfg.AssumeRole.ExternalID)
					}

					// MFA not supported
				},
			)
		} else if len(sharedCfg.Creds.AccessKeyID) > 0 {
			cfg.Credentials = credentials.NewStaticCredentialsFromCreds(
				sharedCfg.Creds,
			)
		} else {
			// Fallback to default credentials provider, include mock errors
			// for the credential chain so user can identify why credentials
			// failed to be retrieved.
			cfg.Credentials = credentials.NewCredentials(&credentials.ChainProvider{
				VerboseErrors: aws.BoolValue(cfg.CredentialsChainVerboseErrors),
				Providers: []credentials.Provider{
					&credProviderError{Err: awserr.New("EnvAccessKeyNotFound", "failed to find credentials in the environment.", nil)},
					&credProviderError{Err: awserr.New("SharedCredsLoad", fmt.Sprintf("failed to load profile, %s.", envCfg.Profile), nil)},
					defaults.RemoteCredProvider(*cfg, handlers),
				},
			})
		}
	}
}

type credProviderError struct {
	Err error
}

var emptyCreds = credentials.Value{}

func (c credProviderError) Retrieve() (credentials.Value, error) {
	return credentials.Value{}, c.Err
}
func (c credProviderError) IsExpired() bool {
	return true
}

func initHandlers(s *Session) {
	// Add the Validate parameter handler if it is not disabled.
	s.Handlers.Validate.Remove(corehandlers.ValidateParametersHandler)
	if !aws.BoolValue(s.Config.DisableParamValidation) {
		s.Handlers.Validate.PushBackNamed(corehandlers.ValidateParametersHandler)
	}
}

// Copy creates and returns a copy of the current Session, coping the config
// and handlers. If any additional configs are provided they will be merged
// on top of the Session's copied config.
//
//     // Create a copy of the current Session, configured for the us-west-2 region.
//     sess.Copy(&aws.Config{Region: aws.String("us-west-2")})
func (s *Session) Copy(cfgs ...*aws.Config) *Session {
	newSession := &Session{
		Config:   s.Config.Copy(cfgs...),
		Handlers: s.Handlers.Copy(),
	}

	initHandlers(newSession)

	return newSession
}

// ClientConfig satisfies the client.ConfigProvider interface and is used to
// configure the service client instances. Passing the Session to the service
// client's constructor (New) will use this method to configure the client.
func (s *Session) ClientConfig(serviceName string, cfgs ...*aws.Config) client.Config {
	// Backwards compatibility, the error will be eaten if user calls ClientConfig
	// directly. All SDK services will use ClientconfigWithError.
	cfg, _ := s.clientConfigWithErr(serviceName, cfgs...)

	return cfg
}

func (s *Session) clientConfigWithErr(serviceName string, cfgs ...*aws.Config) (client.Config, error) {
	s = s.Copy(cfgs...)

	var resolved endpoints.ResolvedEndpoint
	var err error

	region := aws.StringValue(s.Config.Region)

	if endpoint := aws.StringValue(s.Config.Endpoint); len(endpoint) != 0 {
		resolved.URL = endpoints.AddScheme(endpoint, aws.BoolValue(s.Config.DisableSSL))
		resolved.SigningRegion = region
	} else {
		resolved, err = s.Config.EndpointResolver.EndpointFor(
			serviceName, region,
			func(opt *endpoints.Options) {
				opt.DisableSSL = aws.BoolValue(s.Config.DisableSSL)
				opt.UseDualStack = aws.BoolValue(s.Config.UseDualStack)
			},
		)
	}

	return client.Config{
		Config:        s.Config,
		Handlers:      s.Handlers,
		Endpoint:      resolved.URL,
		SigningRegion: resolved.SigningRegion,
		SigningName:   resolved.SigningName,
	}, err
}
<|endoftext|>


<commit_before>package battle

import (
	"time"

	"github.com/finalist736/seabotserver"
	"github.com/finalist736/seabotserver/logs"
)

func (s *Battle) Handle(bot *seabotserver.TcpBot, turn *seabotserver.FBTurn) {
	s.BattleChannel <- &BattleChannelData{bot, turn, false}
}

func (s *Battle) Exit(bot *seabotserver.TcpBot) {
	s.BattleChannel <- &BattleChannelData{bot, nil, true}
}

func (s *Battle) Listener() {
	var data *BattleChannelData
	timer := time.Tick(time.Second * 10)
	tb := seabotserver.ToBot{}
	tb.Turn = &seabotserver.TBTurn{}

	tbNextTurn := seabotserver.ToBot{}
	tbNextTurn.Turn = &seabotserver.TBTurn{}

	tbOppTurn := seabotserver.ToBot{}
	tbOppTurn.Turn = &seabotserver.TBTurn{}
	tbOppTurn.Turn.Opponent = &seabotserver.TBOpponentTurn{}

	var opponent *seabotserver.TcpBot
	for {
		select {
		case data = <-s.BattleChannel:

			switch data.Exit {
			case true:
				// need to close this battle!
				errMsg := &seabotserver.ToBot{
					Error: &seabotserver.TBError{Error: "player disconnected"}}
				if s.Bot1.ID == data.Bot.ID {
					s.Bot2.Send(errMsg)
					s.Bot2.Done <- true
				} else {
					s.Bot1.Send(errMsg)
					s.Bot1.Done <- true
				}
				return

			case false:
				//fmt.Printf("some data from bot: %d\n\t%+v\n", data.Bot.ID, data.Turn)
				if data.Turn == nil || data.Bot == nil {
					continue
				}

				if s.CurrentTurnID != data.Bot.ID {
					data.Bot.Send(&seabotserver.ToBot{Error: &seabotserver.TBError{Error: "incorrect turn"}})
					continue
				}

				if data.Turn.Shot[0] < 0 || data.Turn.Shot[0] > 9 ||
					data.Turn.Shot[1] < 0 || data.Turn.Shot[1] > 9 {
					data.Bot.Send(&seabotserver.ToBot{Error: &seabotserver.TBError{Error: "incorrect point"}})
					continue
				}
				var field *int
				var opppole *[10][10]int
				var oppShips *[10]*Ship
				if data.Bot.ID == s.Bot1.ID {
					field = &s.Pole2[data.Turn.Shot[0]][data.Turn.Shot[1]]
					opppole = s.Pole2
					opponent = s.Bot2
					oppShips = s.Ships2
				} else {
					field = &s.Pole1[data.Turn.Shot[0]][data.Turn.Shot[1]]
					opppole = s.Pole1
					opponent = s.Bot1
					oppShips = s.Ships1
				}
				if *field == 0 {
					tb.Turn.Result = -1
					*field = -10
				} else if *field == -10 {
					// calc misscount
					// and ban bot when count == 5
				} else if *field > -5 && *field < 0 {
					// shot in dead ship
					// and ban bot when count == 5
				} else {
					// check for ship dead and send 2
					tb.Turn.Result = checkShipDestroy(oppShips, data.Turn.Shot, *field)
					*field *= -1

					// check for battle end
					// -------------------------
					if checkFleet(opppole) {
						// end battle
						tbEnd := seabotserver.ToBot{}
						tbEnd.End = &seabotserver.TBEnd{}
						tbEnd.End.Winner = data.Bot.ID
						tbEnd.End.Ships = FormatShips(opppole)
						data.Bot.Send(tbEnd)
						if opppole == s.Pole1 {
							tbEnd.End.Ships = FormatShips(s.Pole2)
						} else {
							tbEnd.End.Ships = FormatShips(s.Pole1)
						}
						opponent.Send(tbEnd)

						//fmt.Println("POLE 1")
						//PrintPole(s.Pole1, s.Bot1.ID)

						//fmt.Println("POLE 2")
						//PrintPole(s.Pole2, s.Bot2.ID)

						s.Bot1.Battle = nil
						s.Bot2.Battle = nil

						s.Bot1.Done <- true
						s.Bot2.Done <- true

						// save battle result to log
						s.Log.Winner = tbEnd.End.Winner
						logs.SaveToFile(s.Log)
						// statistics save to DB
						return
					}

				}
				// save to log
				s.Log.Turns = append(s.Log.Turns,
					&seabotserver.LogTurn{
						data.Bot.ID,
						data.Turn.Shot,
						tb.Turn.Result})

				// send shot result
				data.Bot.Send(tb)
				// send opponent shot result
				// -------------------------
				tbOppTurn.Turn.Opponent.Shot = data.Turn.Shot
				tbOppTurn.Turn.Opponent.Result = tb.Turn.Result
				opponent.Send(tbOppTurn)

				// send next turn
				if tb.Turn.Result == -1 {
					tbNextTurn.Turn.ID = opponent.ID
					s.CurrentTurnID = opponent.ID
				} else {
					tbNextTurn.Turn.ID = data.Bot.ID
					s.CurrentTurnID = data.Bot.ID
				}
				s.Bot1.Send(tbNextTurn)
				s.Bot2.Send(tbNextTurn)
			}
		case <-timer:
			//fmt.Println("tick")
		}
	}
}
<commit_msg>verbose error 'incorrect turn'<commit_after>package battle

import (
	"fmt"
	"time"

	"github.com/finalist736/seabotserver"
	"github.com/finalist736/seabotserver/logs"
)

func (s *Battle) Handle(bot *seabotserver.TcpBot, turn *seabotserver.FBTurn) {
	s.BattleChannel <- &BattleChannelData{bot, turn, false}
}

func (s *Battle) Exit(bot *seabotserver.TcpBot) {
	s.BattleChannel <- &BattleChannelData{bot, nil, true}
}

func (s *Battle) Listener() {
	var data *BattleChannelData
	timer := time.Tick(time.Second * 10)
	tb := seabotserver.ToBot{}
	tb.Turn = &seabotserver.TBTurn{}

	tbNextTurn := seabotserver.ToBot{}
	tbNextTurn.Turn = &seabotserver.TBTurn{}

	tbOppTurn := seabotserver.ToBot{}
	tbOppTurn.Turn = &seabotserver.TBTurn{}
	tbOppTurn.Turn.Opponent = &seabotserver.TBOpponentTurn{}

	var opponent *seabotserver.TcpBot
	for {
		select {
		case data = <-s.BattleChannel:

			switch data.Exit {
			case true:
				// need to close this battle!
				errMsg := &seabotserver.ToBot{
					Error: &seabotserver.TBError{Error: "player disconnected"}}
				if s.Bot1.ID == data.Bot.ID {
					s.Bot2.Send(errMsg)
					s.Bot2.Done <- true
				} else {
					s.Bot1.Send(errMsg)
					s.Bot1.Done <- true
				}
				return

			case false:
				//fmt.Printf("some data from bot: %d\n\t%+v\n", data.Bot.ID, data.Turn)
				if data.Turn == nil || data.Bot == nil {
					continue
				}

				if s.CurrentTurnID != data.Bot.ID {
					data.Bot.Send(
						&seabotserver.ToBot{
							Error: &seabotserver.TBError{
								Error: fmt.Sprintf("not your turn! now turn bot id: %d. but your bot id: %d",
									s.CurrentTurnID, data.Bot.ID)}})
					continue
				}

				if data.Turn.Shot[0] < 0 || data.Turn.Shot[0] > 9 ||
					data.Turn.Shot[1] < 0 || data.Turn.Shot[1] > 9 {
					data.Bot.Send(
						&seabotserver.ToBot{
							Error: &seabotserver.TBError{
								Error: "incorrect point"}})
					continue
				}
				var field *int
				var opppole *[10][10]int
				var oppShips *[10]*Ship
				if data.Bot.ID == s.Bot1.ID {
					field = &s.Pole2[data.Turn.Shot[0]][data.Turn.Shot[1]]
					opppole = s.Pole2
					opponent = s.Bot2
					oppShips = s.Ships2
				} else {
					field = &s.Pole1[data.Turn.Shot[0]][data.Turn.Shot[1]]
					opppole = s.Pole1
					opponent = s.Bot1
					oppShips = s.Ships1
				}
				if *field == 0 {
					tb.Turn.Result = -1
					*field = -10
				} else if *field == -10 {
					// calc misscount
					// and ban bot when count == 5
				} else if *field > -5 && *field < 0 {
					// shot in dead ship
					// and ban bot when count == 5
				} else {
					// check for ship dead and send 2
					tb.Turn.Result = checkShipDestroy(oppShips, data.Turn.Shot, *field)
					*field *= -1

					// check for battle end
					// -------------------------
					if checkFleet(opppole) {
						// end battle
						tbEnd := seabotserver.ToBot{}
						tbEnd.End = &seabotserver.TBEnd{}
						tbEnd.End.Winner = data.Bot.ID
						tbEnd.End.Ships = FormatShips(opppole)
						data.Bot.Send(tbEnd)
						if opppole == s.Pole1 {
							tbEnd.End.Ships = FormatShips(s.Pole2)
						} else {
							tbEnd.End.Ships = FormatShips(s.Pole1)
						}
						opponent.Send(tbEnd)

						//fmt.Println("POLE 1")
						//PrintPole(s.Pole1, s.Bot1.ID)

						//fmt.Println("POLE 2")
						//PrintPole(s.Pole2, s.Bot2.ID)

						s.Bot1.Battle = nil
						s.Bot2.Battle = nil

						s.Bot1.Done <- true
						s.Bot2.Done <- true

						// save battle result to log
						s.Log.Winner = tbEnd.End.Winner
						logs.SaveToFile(s.Log)
						// statistics save to DB
						return
					}

				}
				// save to log
				s.Log.Turns = append(s.Log.Turns,
					&seabotserver.LogTurn{
						data.Bot.ID,
						data.Turn.Shot,
						tb.Turn.Result})

				// send shot result
				data.Bot.Send(tb)
				// send opponent shot result
				// -------------------------
				tbOppTurn.Turn.Opponent.Shot = data.Turn.Shot
				tbOppTurn.Turn.Opponent.Result = tb.Turn.Result
				opponent.Send(tbOppTurn)

				// send next turn
				if tb.Turn.Result == -1 {
					tbNextTurn.Turn.ID = opponent.ID
					s.CurrentTurnID = opponent.ID
				} else {
					tbNextTurn.Turn.ID = data.Bot.ID
					s.CurrentTurnID = data.Bot.ID
				}
				s.Bot1.Send(tbNextTurn)
				s.Bot2.Send(tbNextTurn)
			}
		case <-timer:
			//fmt.Println("tick")
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// +build sqlite_userauth

package sqlite3

/*
#cgo CFLAGS: -DSQLITE_USER_AUTHENTICATION
#cgo LDFLAGS: -lm
#ifndef USE_LIBSQLITE3
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
#include <stdlib.h>

static int
_sqlite3_user_authenticate(sqlite3* db, const char* zUsername, const char* aPW, int nPW)
{
  return sqlite3_user_authenticate(db, zUsername, aPW, nPW);
}

static int
_sqlite3_user_add(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
{
  return sqlite3_user_add(db, zUsername, aPW, nPW, isAdmin);
}

static int
_sqlite3_user_change(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
{
  return sqlite3_user_change(db, zUsername, aPW, nPW, isAdmin);
}

static int
_sqlite3_user_delete(sqlite3* db, const char* zUsername)
{
  return sqlite3_user_delete(db, zUsername);
}
*/
import "C"

const (
	SQLITE_AUTH = C.SQLITE_AUTH
)

// Authenticate will perform an authentication of the provided username
// and password against the database.
//
// If a database contains the SQLITE_USER table, then the
// call to Authenticate must be invoked with an
// appropriate username and password prior to enable read and write
//access to the database.
//
// Return SQLITE_OK on success or SQLITE_ERROR if the username/password
// combination is incorrect or unknown.
//
// If the SQLITE_USER table is not present in the database file, then
// this interface is a harmless no-op returnning SQLITE_OK.
func (c *SQLiteConn) Authenticate(username, password string) error {
	rv := C._sqlite3_user_authenticate(c.db, C.CString(username), C.CString(password), C.int(len(password)))
	if rv != C.SQLITE_OK {
		return c.lastError()
	}

	return nil
}

// AuthUserAdd can be used (by an admin user only)
// to create a new user. When called on a no-authentication-required
// database, this routine converts the database into an authentication-
// required database, automatically makes the added user an
// administrator, and logs in the current connection as that user.
// The AuthUserAdd only works for the "main" database, not
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
// non-admin user results in an error.
func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error {
	isAdmin := 0
	if admin {
		isAdmin = 1
	}

	rv := C._sqlite3_user_add(c.db, C.CString(username), C.CString(password), C.int(len(password)), C.int(isAdmin))
	if rv != C.SQLITE_OK {
		return c.lastError()
	}

	return nil
}

// AuthUserChange can be used to change a users
// login credentials or admin privilege.  Any user can change their own
// login credentials. Only an admin user can change another users login
// credentials or admin privilege setting. No user may change their own
// admin privilege setting.
func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error {
	isAdmin := 0
	if admin {
		isAdmin = 1
	}

	rv := C._sqlite3_user_change(c.db, C.CString(username), C.CString(password), C.int(len(password)), C.int(isAdmin))
	if rv != C.SQLITE_OK {
		return c.lastError()
	}

	return nil
}

// AuthUserDelete can be used (by an admin user only)
// to delete a user. The currently logged-in user cannot be deleted,
// which guarantees that there is always an admin user and hence that
// the database cannot be converted into a no-authentication-required
// database.
func (c *SQLiteConn) AuthUserDelete(username string) error {
	rv := C._sqlite3_user_delete(c.db, C.CString(username))
	if rv != C.SQLITE_OK {
		return c.lastError()
	}

	return nil
}

// EOF
<commit_msg>Fix: Free memory<commit_after>// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// +build sqlite_userauth

package sqlite3

/*
#cgo CFLAGS: -DSQLITE_USER_AUTHENTICATION
#cgo LDFLAGS: -lm
#ifndef USE_LIBSQLITE3
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
#include <stdlib.h>

static int
_sqlite3_user_authenticate(sqlite3* db, const char* zUsername, const char* aPW, int nPW)
{
  return sqlite3_user_authenticate(db, zUsername, aPW, nPW);
}

static int
_sqlite3_user_add(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
{
  return sqlite3_user_add(db, zUsername, aPW, nPW, isAdmin);
}

static int
_sqlite3_user_change(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
{
  return sqlite3_user_change(db, zUsername, aPW, nPW, isAdmin);
}

static int
_sqlite3_user_delete(sqlite3* db, const char* zUsername)
{
  return sqlite3_user_delete(db, zUsername);
}
*/
import "C"
import (
	"unsafe"
)

const (
	SQLITE_AUTH = C.SQLITE_AUTH
)

// Authenticate will perform an authentication of the provided username
// and password against the database.
//
// If a database contains the SQLITE_USER table, then the
// call to Authenticate must be invoked with an
// appropriate username and password prior to enable read and write
//access to the database.
//
// Return SQLITE_OK on success or SQLITE_ERROR if the username/password
// combination is incorrect or unknown.
//
// If the SQLITE_USER table is not present in the database file, then
// this interface is a harmless no-op returnning SQLITE_OK.
func (c *SQLiteConn) Authenticate(username, password string) error {
	// Allocate C Variables
	cuser := C.CString(username)
	cpass := C.CString(password)

	// Free C Variables
	defer func() {
		C.free(unsafe.Pointer(cuser))
		C.free(unsafe.Pointer(cpass))
	}()

	rv := C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password)))
	if rv != C.SQLITE_OK {
		return c.lastError()
	}

	return nil
}

// AuthUserAdd can be used (by an admin user only)
// to create a new user. When called on a no-authentication-required
// database, this routine converts the database into an authentication-
// required database, automatically makes the added user an
// administrator, and logs in the current connection as that user.
// The AuthUserAdd only works for the "main" database, not
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
// non-admin user results in an error.
func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error {
	isAdmin := 0
	if admin {
		isAdmin = 1
	}

	// Allocate C Variables
	cuser := C.CString(username)
	cpass := C.CString(password)

	// Free C Variables
	defer func() {
		C.free(unsafe.Pointer(cuser))
		C.free(unsafe.Pointer(cpass))
	}()

	rv := C._sqlite3_user_add(c.db, cuser, cpass, C.int(len(password)), C.int(isAdmin))
	if rv != C.SQLITE_OK {
		return c.lastError()
	}

	return nil
}

// AuthUserChange can be used to change a users
// login credentials or admin privilege.  Any user can change their own
// login credentials. Only an admin user can change another users login
// credentials or admin privilege setting. No user may change their own
// admin privilege setting.
func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error {
	isAdmin := 0
	if admin {
		isAdmin = 1
	}

	// Allocate C Variables
	cuser := C.CString(username)
	cpass := C.CString(password)

	// Free C Variables
	defer func() {
		C.free(unsafe.Pointer(cuser))
		C.free(unsafe.Pointer(cpass))
	}()

	rv := C._sqlite3_user_change(c.db, cuser, cpass, C.int(len(password)), C.int(isAdmin))
	if rv != C.SQLITE_OK {
		return c.lastError()
	}

	return nil
}

// AuthUserDelete can be used (by an admin user only)
// to delete a user. The currently logged-in user cannot be deleted,
// which guarantees that there is always an admin user and hence that
// the database cannot be converted into a no-authentication-required
// database.
func (c *SQLiteConn) AuthUserDelete(username string) error {
	// Allocate C Variables
	cuser := C.CString(username)

	// Free C Variables
	defer func() {
		C.free(unsafe.Pointer(cuser))
	}()

	rv := C._sqlite3_user_delete(c.db, cuser)
	if rv != C.SQLITE_OK {
		return c.lastError()
	}

	return nil
}

// EOF
<|endoftext|>