<commit_before>package send

import (
	"context"
	"crypto/tls"
	"net/http"
	"os"
	"time"

	hec "github.com/fuyufjh/splunk-hec-go"
	"github.com/mongodb/grip/level"
	"github.com/mongodb/grip/message"
	"github.com/pkg/errors"
)

const (
	splunkServerURL   = "GRIP_SPLUNK_SERVER_URL"
	splunkClientToken = "GRIP_SPLUNK_CLIENT_TOKEN"
	splunkChannel     = "GRIP_SPLUNK_CHANNEL"
)

type splunkLogger struct {
	info     SplunkConnectionInfo
	client   splunkClient
	hostname string
	*Base
}

// SplunkConnectionInfo stores all information needed to connect
// to a splunk server to send log messsages.
type SplunkConnectionInfo struct {
	ServerURL string `bson:"url" json:"url" yaml:"url"`
	Token     string `bson:"token" json:"token" yaml:"token"`
	Channel   string `bson:"channel" json:"channel" yaml:"channel"`
}

// GetSplunkConnectionInfo builds a SplunkConnectionInfo structure
// reading default values from the following environment variables:
//
//		GRIP_SPLUNK_SERVER_URL
//		GRIP_SPLUNK_CLIENT_TOKEN
//		GRIP_SPLUNK_CHANNEL
func GetSplunkConnectionInfo() SplunkConnectionInfo {
	return SplunkConnectionInfo{
		ServerURL: os.Getenv(splunkServerURL),
		Token:     os.Getenv(splunkClientToken),
		Channel:   os.Getenv(splunkChannel),
	}
}

// Populated validates a SplunkConnectionInfo, and returns false if
// there is missing data.
func (info SplunkConnectionInfo) Populated() bool {
	return info.ServerURL != "" && info.Token != ""
}

func (info SplunkConnectionInfo) validateFromEnv() error {
	if info.ServerURL == "" {
		return errors.Errorf("environment variable %s not defined, cannot create splunk client", splunkServerURL)
	}
	if info.Token == "" {
		return errors.Errorf("environment variable %s not defined, cannot create splunk client", splunkClientToken)
	}
	return nil
}

func (s *splunkLogger) Send(m message.Composer) {
	lvl := s.Level()

	if lvl.ShouldLog(m) {
		g, ok := m.(*message.GroupComposer)
		if ok {
			batch := []*hec.Event{}
			for _, c := range g.Messages() {
				if lvl.ShouldLog(c) {
					e := hec.NewEvent(c.Raw())
					e.SetHost(s.hostname)
					batch = append(batch, e)
				}
			}
			if err := s.client.WriteBatch(batch); err != nil {
				s.ErrorHandler()(err, m)
			}
			return
		}

		e := hec.NewEvent(m.Raw())
		e.SetHost(s.hostname)
		if err := s.client.WriteEvent(e); err != nil {
			s.ErrorHandler()(err, m)
		}
	}
}

func (s *splunkLogger) Flush(_ context.Context) error { return nil }

// NewSplunkLogger constructs a new Sender implementation that sends
// messages to a Splunk event collector using the credentials specified
// in the SplunkConnectionInfo struct.
func NewSplunkLogger(name string, info SplunkConnectionInfo, l LevelInfo) (Sender, error) {
	client := (&http.Client{
		Transport: &http.Transport{
			Proxy:               http.ProxyFromEnvironment,
			DisableKeepAlives:   true,
			TLSHandshakeTimeout: 5 * time.Second,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
		Timeout: 5 * time.Second,
	})

	s, err := buildSplunkLogger(name, client, info, l)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if err := s.client.Create(client, info); err != nil {
		return nil, errors.WithStack(err)
	}

	return s, nil
}

// NewSplunkLoggerWithClient makes it possible to pass an existing
// http.Client to the splunk instance, but is otherwise identical to
// NewSplunkLogger.
func NewSplunkLoggerWithClient(name string, info SplunkConnectionInfo, l LevelInfo, client *http.Client) (Sender, error) {
	s, err := buildSplunkLogger(name, client, info, l)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if err := s.client.Create(client, info); err != nil {
		return nil, errors.WithStack(err)
	}

	return s, nil
}

func buildSplunkLogger(name string, client *http.Client, info SplunkConnectionInfo, l LevelInfo) (*splunkLogger, error) {
	s := &splunkLogger{
		info:   info,
		client: &splunkClientImpl{},
		Base:   NewBase(name),
	}

	hostname, err := os.Hostname()
	if err != nil {
		return nil, errors.WithStack(err)
	}
	s.hostname = hostname

	if err := s.SetLevel(l); err != nil {
		return nil, errors.WithStack(err)
	}
	return s, nil
}

// MakeSplunkLogger constructs a new Sender implementation that reads
// the hostname, username, and password from environment variables:
//
//		GRIP_SPLUNK_SERVER_URL
//		GRIP_SPLUNK_CLIENT_TOKEN
//		GRIP_SPLUNK_CLIENT_CHANNEL
func MakeSplunkLogger(name string) (Sender, error) {
	info := GetSplunkConnectionInfo()
	if err := info.validateFromEnv(); err != nil {
		return nil, errors.WithStack(err)
	}

	return NewSplunkLogger(name, info, LevelInfo{level.Trace, level.Trace})
}

// MakeSplunkLoggerWithClient is identical to MakeSplunkLogger but
// allows you to pass in a http.Client.
func MakeSplunkLoggerWithClient(name string, client *http.Client) (Sender, error) {
	info := GetSplunkConnectionInfo()
	if err := info.validateFromEnv(); err != nil {
		return nil, errors.WithStack(err)
	}

	return NewSplunkLoggerWithClient(name, info, LevelInfo{level.Trace, level.Trace}, client)
}

////////////////////////////////////////////////////////////////////////
//
// interface wrapper for the splunk client so that we can mock things out
//
////////////////////////////////////////////////////////////////////////

type splunkClient interface {
	Create(*http.Client, SplunkConnectionInfo) error
	WriteEvent(*hec.Event) error
	WriteBatch([]*hec.Event) error
}

type splunkClientImpl struct {
	hec.HEC
}

func (c *splunkClientImpl) Create(client *http.Client, info SplunkConnectionInfo) error {
	c.HEC = hec.NewClient(info.ServerURL, info.Token)
	if info.Channel != "" {
		c.HEC.SetChannel(info.Channel)
	}

	c.HEC.SetKeepAlive(false)
	c.HEC.SetMaxRetry(0)
	c.HEC.SetHTTPClient(client)

	return nil
}
<commit_msg>MAKE-1258: add two retries<commit_after>package send

import (
	"context"
	"crypto/tls"
	"net/http"
	"os"
	"time"

	hec "github.com/fuyufjh/splunk-hec-go"
	"github.com/mongodb/grip/level"
	"github.com/mongodb/grip/message"
	"github.com/pkg/errors"
)

const (
	splunkServerURL   = "GRIP_SPLUNK_SERVER_URL"
	splunkClientToken = "GRIP_SPLUNK_CLIENT_TOKEN"
	splunkChannel     = "GRIP_SPLUNK_CHANNEL"
)

type splunkLogger struct {
	info     SplunkConnectionInfo
	client   splunkClient
	hostname string
	*Base
}

// SplunkConnectionInfo stores all information needed to connect
// to a splunk server to send log messsages.
type SplunkConnectionInfo struct {
	ServerURL string `bson:"url" json:"url" yaml:"url"`
	Token     string `bson:"token" json:"token" yaml:"token"`
	Channel   string `bson:"channel" json:"channel" yaml:"channel"`
}

// GetSplunkConnectionInfo builds a SplunkConnectionInfo structure
// reading default values from the following environment variables:
//
//		GRIP_SPLUNK_SERVER_URL
//		GRIP_SPLUNK_CLIENT_TOKEN
//		GRIP_SPLUNK_CHANNEL
func GetSplunkConnectionInfo() SplunkConnectionInfo {
	return SplunkConnectionInfo{
		ServerURL: os.Getenv(splunkServerURL),
		Token:     os.Getenv(splunkClientToken),
		Channel:   os.Getenv(splunkChannel),
	}
}

// Populated validates a SplunkConnectionInfo, and returns false if
// there is missing data.
func (info SplunkConnectionInfo) Populated() bool {
	return info.ServerURL != "" && info.Token != ""
}

func (info SplunkConnectionInfo) validateFromEnv() error {
	if info.ServerURL == "" {
		return errors.Errorf("environment variable %s not defined, cannot create splunk client", splunkServerURL)
	}
	if info.Token == "" {
		return errors.Errorf("environment variable %s not defined, cannot create splunk client", splunkClientToken)
	}
	return nil
}

func (s *splunkLogger) Send(m message.Composer) {
	lvl := s.Level()

	if lvl.ShouldLog(m) {
		g, ok := m.(*message.GroupComposer)
		if ok {
			batch := []*hec.Event{}
			for _, c := range g.Messages() {
				if lvl.ShouldLog(c) {
					e := hec.NewEvent(c.Raw())
					e.SetHost(s.hostname)
					batch = append(batch, e)
				}
			}
			if err := s.client.WriteBatch(batch); err != nil {
				s.ErrorHandler()(err, m)
			}
			return
		}

		e := hec.NewEvent(m.Raw())
		e.SetHost(s.hostname)
		if err := s.client.WriteEvent(e); err != nil {
			s.ErrorHandler()(err, m)
		}
	}
}

func (s *splunkLogger) Flush(_ context.Context) error { return nil }

// NewSplunkLogger constructs a new Sender implementation that sends
// messages to a Splunk event collector using the credentials specified
// in the SplunkConnectionInfo struct.
func NewSplunkLogger(name string, info SplunkConnectionInfo, l LevelInfo) (Sender, error) {
	client := (&http.Client{
		Transport: &http.Transport{
			Proxy:               http.ProxyFromEnvironment,
			DisableKeepAlives:   true,
			TLSHandshakeTimeout: 5 * time.Second,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
		Timeout: 5 * time.Second,
	})

	s, err := buildSplunkLogger(name, client, info, l)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if err := s.client.Create(client, info); err != nil {
		return nil, errors.WithStack(err)
	}

	return s, nil
}

// NewSplunkLoggerWithClient makes it possible to pass an existing
// http.Client to the splunk instance, but is otherwise identical to
// NewSplunkLogger.
func NewSplunkLoggerWithClient(name string, info SplunkConnectionInfo, l LevelInfo, client *http.Client) (Sender, error) {
	s, err := buildSplunkLogger(name, client, info, l)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if err := s.client.Create(client, info); err != nil {
		return nil, errors.WithStack(err)
	}

	return s, nil
}

func buildSplunkLogger(name string, client *http.Client, info SplunkConnectionInfo, l LevelInfo) (*splunkLogger, error) {
	s := &splunkLogger{
		info:   info,
		client: &splunkClientImpl{},
		Base:   NewBase(name),
	}

	hostname, err := os.Hostname()
	if err != nil {
		return nil, errors.WithStack(err)
	}
	s.hostname = hostname

	if err := s.SetLevel(l); err != nil {
		return nil, errors.WithStack(err)
	}
	return s, nil
}

// MakeSplunkLogger constructs a new Sender implementation that reads
// the hostname, username, and password from environment variables:
//
//		GRIP_SPLUNK_SERVER_URL
//		GRIP_SPLUNK_CLIENT_TOKEN
//		GRIP_SPLUNK_CLIENT_CHANNEL
func MakeSplunkLogger(name string) (Sender, error) {
	info := GetSplunkConnectionInfo()
	if err := info.validateFromEnv(); err != nil {
		return nil, errors.WithStack(err)
	}

	return NewSplunkLogger(name, info, LevelInfo{level.Trace, level.Trace})
}

// MakeSplunkLoggerWithClient is identical to MakeSplunkLogger but
// allows you to pass in a http.Client.
func MakeSplunkLoggerWithClient(name string, client *http.Client) (Sender, error) {
	info := GetSplunkConnectionInfo()
	if err := info.validateFromEnv(); err != nil {
		return nil, errors.WithStack(err)
	}

	return NewSplunkLoggerWithClient(name, info, LevelInfo{level.Trace, level.Trace}, client)
}

////////////////////////////////////////////////////////////////////////
//
// interface wrapper for the splunk client so that we can mock things out
//
////////////////////////////////////////////////////////////////////////

type splunkClient interface {
	Create(*http.Client, SplunkConnectionInfo) error
	WriteEvent(*hec.Event) error
	WriteBatch([]*hec.Event) error
}

type splunkClientImpl struct {
	hec.HEC
}

func (c *splunkClientImpl) Create(client *http.Client, info SplunkConnectionInfo) error {
	c.HEC = hec.NewClient(info.ServerURL, info.Token)
	if info.Channel != "" {
		c.HEC.SetChannel(info.Channel)
	}

	c.HEC.SetKeepAlive(false)
	c.HEC.SetMaxRetry(2)
	c.HEC.SetHTTPClient(client)

	return nil
}
<|endoftext|>


<commit_before>package command

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/variadico/vbs"
)

// Draft releases and prereleases are not returned by this endpoint.
const githubReleasesURL = "https://api.github.com/repos/variadico/noti/releases/latest"

// notification is the interface for all notifications.
type notification interface {
	Send() error
}

// Root is the root noti command.
var Root = &cobra.Command{
	Use:     "noti [flags] [utility [args...]]",
	Example: "noti tar -cjf music.tar.bz2 Music/",
	RunE:    rootMain,

	SilenceErrors: true,
	SilenceUsage:  true,
}

// Version is the version of noti. This is set at compile time with Make.
var Version string

func init() {
	defineFlags(Root.Flags())
}

func defineFlags(flags *pflag.FlagSet) {
	flags.SetInterspersed(false)
	flags.SortFlags = false

	flags.StringP("title", "t", "", "Set notification title. Default is utility name.")
	flags.StringP("message", "m", "", `Set notification message. Default is "Done!".`)

	flags.BoolP("banner", "b", false, "Trigger a banner notification. This is enabled by default.")
	flags.BoolP("speech", "s", false, "Trigger a speech notification.")
	flags.BoolP("bearychat", "c", false, "Trigger a BearyChat notification.")
	flags.BoolP("hipchat", "i", false, "Trigger a HipChat notification.")
	flags.BoolP("pushbullet", "p", false, "Trigger a Pushbullet notification.")
	flags.BoolP("pushover", "o", false, "Trigger a Pushover notification.")
	flags.BoolP("pushsafer", "u", false, "Trigger a Pushsafer notification.")
	flags.BoolP("simplepush", "l", false, "Trigger a Simplepush notification.")
	flags.BoolP("slack", "k", false, "Trigger a Slack notification.")

	flags.IntP("pwatch", "w", -1, "Monitor a process by PID and trigger a notification when the pid disappears.")

	flags.StringP("file", "f", "", "Path to noti.yaml configuration file.")
	flags.BoolVar(&vbs.Enabled, "verbose", false, "Enable verbose mode.")
	flags.BoolP("version", "v", false, "Print noti version and exit.")
	flags.BoolP("help", "h", false, "Print noti help and exit.")
}

func rootMain(cmd *cobra.Command, args []string) error {
	vbs.Println("os.Args:", os.Args)

	v := viper.New()
	if err := configureApp(v, cmd.Flags()); err != nil {
		vbs.Println("Failed to configure:", err)
	}

	if vbs.Enabled {
		printEnv()
	}

	if showVer, _ := cmd.Flags().GetBool("version"); showVer {
		fmt.Println("noti version", Version)
		if latest, dl, err := latestRelease(githubReleasesURL); err != nil {
			vbs.Println("Failed get latest release:", err)
		} else if latest != Version {
			fmt.Println("Latest:", latest)
			fmt.Println("Download:", dl)
		}
		return nil
	}

	if showHelp, _ := cmd.Flags().GetBool("help"); showHelp {
		return cmd.Help()
	}

	title, err := cmd.Flags().GetString("title")
	if err != nil {
		return err
	}
	if title == "" {
		vbs.Println("Title from flags is empty, getting title from command name")
		title = commandName(args)
	}
	v.Set("title", title)

	if pid, _ := cmd.Flags().GetInt("pwatch"); pid != -1 {
		vbs.Println("Watching PID:", pid)
		err = pollPID(pid, 2*time.Second)
	} else {
		vbs.Println("Running command:", args)
		err = runCommand(args, os.Stdin, os.Stdout, os.Stderr)
	}
	if err != nil {
		v.Set("message", err.Error())
		v.Set("nsuser.soundName", v.GetString("nsuser.soundNameFail"))
	}

	vbs.Println("Title:", v.GetString("title"))
	vbs.Println("Message:", v.GetString("message"))

	enabled := enabledServices(v, cmd.Flags())
	vbs.Println("Services:", enabled)
	vbs.Println("Viper:", v.AllSettings())
	notis := getNotifications(v, enabled)

	vbs.Println(len(notis), "notifications queued")
	for _, n := range notis {
		if err := n.Send(); err != nil {
			log.Println(err)
		} else {
			vbs.Printf("Sent: %T\n", n)
		}
	}

	return nil
}

func latestRelease(u string) (string, string, error) {
	webClient := &http.Client{Timeout: 30 * time.Second}

	resp, err := webClient.Get(u)
	if err != nil {
		return "", "", err
	}
	defer resp.Body.Close()

	var r struct {
		HTMLURL string `json:"html_url"`
		TagName string `json:"tag_name"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return "", "", err
	}

	return r.TagName, r.HTMLURL, nil
}

func commandName(args []string) string {
	switch len(args) {
	case 0:
		return "noti"
	case 1:
		return args[0]
	}

	if args[1][0] != '-' {
		// If the next arg isn't a flag, append a subcommand to the command
		// name.
		return fmt.Sprintf("%s %s", args[0], args[1])
	}

	return args[0]
}

func runCommand(args []string, sin io.Reader, sout, serr io.Writer) error {
	if len(args) == 0 {
		return nil
	}

	var cmd *exec.Cmd
	if _, err := exec.LookPath(args[0]); err != nil {
		// Maybe command is alias or builtin?
		cmd = subshellCommand(args)
		if cmd == nil {
			return err
		}
	} else {
		cmd = exec.Command(args[0], args[1:]...)
	}

	cmd.Stdin = sin
	cmd.Stdout = sout
	cmd.Stderr = serr
	return cmd.Run()
}

func subshellCommand(args []string) *exec.Cmd {
	shell := os.Getenv("SHELL")

	switch filepath.Base(shell) {
	case "bash", "zsh":
		args = append([]string{"-l", "-i", "-c"}, args...)
	default:
		return nil
	}

	return exec.Command(shell, args...)
}

func printEnv() {
	var envs []string
	for _, e := range keyEnvBindings {
		envs = append(envs, e)
	}
	for _, e := range keyEnvBindingsDeprecated {
		envs = append(envs, e)
	}

	for _, env := range envs {
		if val, set := os.LookupEnv(env); set {
			fmt.Printf("%s=%s\n", env, val)
		}
	}
}
<commit_msg>Add example to help<commit_after>package command

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/variadico/vbs"
)

// Draft releases and prereleases are not returned by this endpoint.
const githubReleasesURL = "https://api.github.com/repos/variadico/noti/releases/latest"

// notification is the interface for all notifications.
type notification interface {
	Send() error
}

// Root is the root noti command.
var Root = &cobra.Command{
	Long:    "noti - Monitor a process and trigger a notification",
	Use:     "noti [flags] [utility [args...]]",
	Example: "noti tar -cjf music.tar.bz2 Music/\nclang foo.c; noti",
	RunE:    rootMain,

	SilenceErrors: true,
	SilenceUsage:  true,
}

// Version is the version of noti. This is set at compile time with Make.
var Version string

func init() {
	defineFlags(Root.Flags())
}

func defineFlags(flags *pflag.FlagSet) {
	flags.SetInterspersed(false)
	flags.SortFlags = false

	flags.StringP("title", "t", "", "Set notification title. Default is utility name.")
	flags.StringP("message", "m", "", `Set notification message. Default is "Done!".`)

	flags.BoolP("banner", "b", false, "Trigger a banner notification. This is enabled by default.")
	flags.BoolP("speech", "s", false, "Trigger a speech notification.")
	flags.BoolP("bearychat", "c", false, "Trigger a BearyChat notification.")
	flags.BoolP("hipchat", "i", false, "Trigger a HipChat notification.")
	flags.BoolP("pushbullet", "p", false, "Trigger a Pushbullet notification.")
	flags.BoolP("pushover", "o", false, "Trigger a Pushover notification.")
	flags.BoolP("pushsafer", "u", false, "Trigger a Pushsafer notification.")
	flags.BoolP("simplepush", "l", false, "Trigger a Simplepush notification.")
	flags.BoolP("slack", "k", false, "Trigger a Slack notification.")

	flags.IntP("pwatch", "w", -1, "Monitor a process by PID and trigger a notification when the pid disappears.")

	flags.StringP("file", "f", "", "Path to noti.yaml configuration file.")
	flags.BoolVar(&vbs.Enabled, "verbose", false, "Enable verbose mode.")
	flags.BoolP("version", "v", false, "Print noti version and exit.")
	flags.BoolP("help", "h", false, "Print noti help and exit.")
}

func rootMain(cmd *cobra.Command, args []string) error {
	vbs.Println("os.Args:", os.Args)

	v := viper.New()
	if err := configureApp(v, cmd.Flags()); err != nil {
		vbs.Println("Failed to configure:", err)
	}

	if vbs.Enabled {
		printEnv()
	}

	if showVer, _ := cmd.Flags().GetBool("version"); showVer {
		fmt.Println("noti version", Version)
		if latest, dl, err := latestRelease(githubReleasesURL); err != nil {
			vbs.Println("Failed get latest release:", err)
		} else if latest != Version {
			fmt.Println("Latest:", latest)
			fmt.Println("Download:", dl)
		}
		return nil
	}

	if showHelp, _ := cmd.Flags().GetBool("help"); showHelp {
		return cmd.Help()
	}

	title, err := cmd.Flags().GetString("title")
	if err != nil {
		return err
	}
	if title == "" {
		vbs.Println("Title from flags is empty, getting title from command name")
		title = commandName(args)
	}
	v.Set("title", title)

	if pid, _ := cmd.Flags().GetInt("pwatch"); pid != -1 {
		vbs.Println("Watching PID:", pid)
		err = pollPID(pid, 2*time.Second)
	} else {
		vbs.Println("Running command:", args)
		err = runCommand(args, os.Stdin, os.Stdout, os.Stderr)
	}
	if err != nil {
		v.Set("message", err.Error())
		v.Set("nsuser.soundName", v.GetString("nsuser.soundNameFail"))
	}

	vbs.Println("Title:", v.GetString("title"))
	vbs.Println("Message:", v.GetString("message"))

	enabled := enabledServices(v, cmd.Flags())
	vbs.Println("Services:", enabled)
	vbs.Println("Viper:", v.AllSettings())
	notis := getNotifications(v, enabled)

	vbs.Println(len(notis), "notifications queued")
	for _, n := range notis {
		if err := n.Send(); err != nil {
			log.Println(err)
		} else {
			vbs.Printf("Sent: %T\n", n)
		}
	}

	return nil
}

func latestRelease(u string) (string, string, error) {
	webClient := &http.Client{Timeout: 30 * time.Second}

	resp, err := webClient.Get(u)
	if err != nil {
		return "", "", err
	}
	defer resp.Body.Close()

	var r struct {
		HTMLURL string `json:"html_url"`
		TagName string `json:"tag_name"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return "", "", err
	}

	return r.TagName, r.HTMLURL, nil
}

func commandName(args []string) string {
	switch len(args) {
	case 0:
		return "noti"
	case 1:
		return args[0]
	}

	if args[1][0] != '-' {
		// If the next arg isn't a flag, append a subcommand to the command
		// name.
		return fmt.Sprintf("%s %s", args[0], args[1])
	}

	return args[0]
}

func runCommand(args []string, sin io.Reader, sout, serr io.Writer) error {
	if len(args) == 0 {
		return nil
	}

	var cmd *exec.Cmd
	if _, err := exec.LookPath(args[0]); err != nil {
		// Maybe command is alias or builtin?
		cmd = subshellCommand(args)
		if cmd == nil {
			return err
		}
	} else {
		cmd = exec.Command(args[0], args[1:]...)
	}

	cmd.Stdin = sin
	cmd.Stdout = sout
	cmd.Stderr = serr
	return cmd.Run()
}

func subshellCommand(args []string) *exec.Cmd {
	shell := os.Getenv("SHELL")

	switch filepath.Base(shell) {
	case "bash", "zsh":
		args = append([]string{"-l", "-i", "-c"}, args...)
	default:
		return nil
	}

	return exec.Command(shell, args...)
}

func printEnv() {
	var envs []string
	for _, e := range keyEnvBindings {
		envs = append(envs, e)
	}
	for _, e := range keyEnvBindingsDeprecated {
		envs = append(envs, e)
	}

	for _, env := range envs {
		if val, set := os.LookupEnv(env); set {
			fmt.Printf("%s=%s\n", env, val)
		}
	}
}
<|endoftext|>


<commit_before>package models

import (
	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
	"github.com/jmoiron/sqlx"
)

const (
	Tram int = iota
	Subway
	Rail
	Bus
	Ferry
	CableCar
	Gondola
	Funicular
)

const (
	defaultColor     = "#FFFFFF"
	defaultTextColor = "#000000"
)

var (
	// routeTypeString maps route_id codes to strings
	routeTypeString = map[int]string{
		Tram:      "tram",
		Subway:    "subway",
		Rail:      "rail",
		Bus:       "bus",
		Ferry:     "ferry",
		CableCar:  "cable_car",
		Gondola:   "gondola",
		Funicular: "funicular",
	}

	// routeTypeInt maps route_id strings to int codes
	routeTypeInt = map[string]int{}
)

func init() {
	// initialize routeTypeInt using the reverse mapping in routeTypeString
	for k, v := range routeTypeString {
		routeTypeInt[v] = k
	}
}

// Route is https://developers.google.com/transit/gtfs/reference#routestxt
type Route struct {
	ID        string `json:"route_id" db:"route_id" upsert:"key"`
	Type      int    `json:"route_type" db:"route_type"`
	TypeName  string `json:"route_type_name" db:"-" upsert:"omit"`
	Color     string `json:"route_color" db:"route_color"`
	TextColor string `json:"route_text_color" db:"route_text_color"`
}

// Table returns the table name for the Route struct, implementing the
// upsert.Upserter interface
func (r *Route) Table() string {
	return "route"
}

// NewRoute creates a Route given incoming data, typically from a routes.txt
// file
func NewRoute(id string, rtype int, color, textColor string) (r *Route, err error) {
	var ok bool

	if len(color) < 1 {
		color = defaultColor
	}

	if len(textColor) < 1 {
		textColor = defaultTextColor
	}

	if len(id) < 1 {
		err = ErrNoID
		return
	}

	r = &Route{
		ID:        id,
		Type:      rtype,
		Color:     color,
		TextColor: textColor,
	}

	// Load the string name of the route type, also checking that the incoming
	// rtype was correct
	r.TypeName, ok = routeTypeString[r.Type]
	if !ok {
		r = nil
		err = ErrInvalidRouteType
		return
	}

	return
}

// GetRoute returns a Route with the given ID
func GetRoute(id string) (r *Route, err error) {
	var ok bool

	r = &Route{}
	err = sqlx.Get(etc.DBConn, r, `SELECT * FROM route WHERE route_id = $1`, id)
	if err != nil {
		return
	}

	// Load the string name of the route type
	r.TypeName, ok = routeTypeString[r.Type]
	if !ok {
		r = nil
		err = ErrInvalidRouteType
		return
	}

	return
}

// Save saves a route to the database
func (r *Route) Save() error {
	_, err := upsert.Upsert(etc.DBConn, r)
	return err
}
<commit_msg>change default colors to not include # sign (data files do not have it)<commit_after>package models

import (
	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
	"github.com/jmoiron/sqlx"
)

const (
	Tram int = iota
	Subway
	Rail
	Bus
	Ferry
	CableCar
	Gondola
	Funicular
)

const (
	defaultColor     = "FFFFFF"
	defaultTextColor = "000000"
)

var (
	// routeTypeString maps route_id codes to strings
	routeTypeString = map[int]string{
		Tram:      "tram",
		Subway:    "subway",
		Rail:      "rail",
		Bus:       "bus",
		Ferry:     "ferry",
		CableCar:  "cable_car",
		Gondola:   "gondola",
		Funicular: "funicular",
	}

	// routeTypeInt maps route_id strings to int codes
	routeTypeInt = map[string]int{}
)

func init() {
	// initialize routeTypeInt using the reverse mapping in routeTypeString
	for k, v := range routeTypeString {
		routeTypeInt[v] = k
	}
}

// Route is https://developers.google.com/transit/gtfs/reference#routestxt
type Route struct {
	ID        string `json:"route_id" db:"route_id" upsert:"key"`
	Type      int    `json:"route_type" db:"route_type"`
	TypeName  string `json:"route_type_name" db:"-" upsert:"omit"`
	Color     string `json:"route_color" db:"route_color"`
	TextColor string `json:"route_text_color" db:"route_text_color"`
}

// Table returns the table name for the Route struct, implementing the
// upsert.Upserter interface
func (r *Route) Table() string {
	return "route"
}

// NewRoute creates a Route given incoming data, typically from a routes.txt
// file
func NewRoute(id string, rtype int, color, textColor string) (r *Route, err error) {
	var ok bool

	if len(color) < 1 {
		color = defaultColor
	}

	if len(textColor) < 1 {
		textColor = defaultTextColor
	}

	if len(id) < 1 {
		err = ErrNoID
		return
	}

	r = &Route{
		ID:        id,
		Type:      rtype,
		Color:     color,
		TextColor: textColor,
	}

	// Load the string name of the route type, also checking that the incoming
	// rtype was correct
	r.TypeName, ok = routeTypeString[r.Type]
	if !ok {
		r = nil
		err = ErrInvalidRouteType
		return
	}

	return
}

// GetRoute returns a Route with the given ID
func GetRoute(id string) (r *Route, err error) {
	var ok bool

	r = &Route{}
	err = sqlx.Get(etc.DBConn, r, `SELECT * FROM route WHERE route_id = $1`, id)
	if err != nil {
		return
	}

	// Load the string name of the route type
	r.TypeName, ok = routeTypeString[r.Type]
	if !ok {
		r = nil
		err = ErrInvalidRouteType
		return
	}

	return
}

// Save saves a route to the database
func (r *Route) Save() error {
	_, err := upsert.Upsert(etc.DBConn, r)
	return err
}
<|endoftext|>


<commit_before>package server

import (
	"database/sql"
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"time"

	"github.com/danjac/podbaby/decoders"
	"github.com/danjac/podbaby/models"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

const passwordChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func generateRandomPassword(length int) string {
	b := make([]byte, length)
	numChars := len(passwordChars)
	for i := range b {
		b[i] = passwordChars[rand.Intn(numChars)]
	}
	return string(b)
}

func (s *Server) recoverPassword(w http.ResponseWriter, r *http.Request) {
	// generate a temp password
	decoder := &decoders.RecoverPassword{}

	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}

	user, err := s.DB.Users.GetByNameOrEmail(decoder.Identifier)
	if err != nil {

		if err == sql.ErrNoRows {
			s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("no user found")})
			return
		}
		s.abort(w, r, err)
		return
	}

	tempPassword := generateRandomPassword(6)

	user.SetPassword(tempPassword)

	if err := s.DB.Users.UpdatePassword(user.Password, user.ID); err != nil {
		s.abort(w, r, err)
		return
	}
	msg := fmt.Sprintf(`Hi %s,
We've reset your password so you can sign back in again!

Here is your new temporary password:

%s

You can login here:

%s/#/login/

Change your password as soon as possible!

Thanks,

PodBaby
    `, user.Name, tempPassword, r.Host)

	s.Log.Info(msg)
	go func(msg string) {

		err := s.Mailer.Send(
			"services@podbaby.me",
			[]string{user.Email},
			"Your new password",
			msg,
		)
		if err != nil {
			s.Log.Error(err)
		}

	}(msg)

	s.Render.Text(w, http.StatusOK, "password sent")

}

func (s *Server) signup(w http.ResponseWriter, r *http.Request) {

	decoder := &decoders.Signup{}

	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}

	if exists, _ := s.DB.Users.IsEmail(decoder.Email, 0); exists {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Email taken")})
		return
	}

	if exists, _ := s.DB.Users.IsName(decoder.Name); exists {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Name taken")})
		return
	}

	// make new user

	user := &models.User{
		Name:  decoder.Name,
		Email: decoder.Email,
	}

	if err := user.SetPassword(decoder.Password); err != nil {
		s.abort(w, r, err)
		return
	}

	if err := s.DB.Users.Create(user); err != nil {
		s.abort(w, r, err)
		return
	}
	s.setAuthCookie(w, user.ID)
	// tbd: no need to return user!
	s.Render.JSON(w, http.StatusCreated, user)
}

func (s *Server) login(w http.ResponseWriter, r *http.Request) {
	decoder := &decoders.Login{}
	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}

	user, err := s.DB.Users.GetByNameOrEmail(decoder.Identifier)
	if err != nil {

		if err == sql.ErrNoRows {
			s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("no user found")})
			return
		}
		s.abort(w, r, err)
		return
	}

	if !user.CheckPassword(decoder.Password) {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Invalid password")})
		return
	}
	// login user
	s.setAuthCookie(w, user.ID)

	// tbd: no need to return user!
	s.Render.JSON(w, http.StatusOK, user)

}

func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
	s.setAuthCookie(w, 0)
	s.Render.Text(w, http.StatusOK, "Logged out")
}

func (s *Server) changeEmail(w http.ResponseWriter, r *http.Request) {
	user, _ := getUser(r)
	decoder := &decoders.NewEmail{}
	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}
	// does this email exist?
	if exists, _ := s.DB.Users.IsEmail(decoder.Email, user.ID); exists {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Email taken")})
		return
	}

	if err := s.DB.Users.UpdateEmail(decoder.Email, user.ID); err != nil {
		s.abort(w, r, err)
		return
	}
	s.Render.Text(w, http.StatusOK, "email updated")
}

func (s *Server) changePassword(w http.ResponseWriter, r *http.Request) {
	user, _ := getUser(r)
	decoder := &decoders.NewPassword{}
	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}

	// validate old password first

	if !user.CheckPassword(decoder.OldPassword) {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Invalid password")})
		return
	}
	user.SetPassword(decoder.NewPassword)

	if err := s.DB.Users.UpdatePassword(user.Password, user.ID); err != nil {
		s.abort(w, r, err)
		return
	}
	s.Render.Text(w, http.StatusOK, "password updated")
}

func (s *Server) deleteAccount(w http.ResponseWriter, r *http.Request) {
	user, _ := getUser(r)
	if err := s.DB.Users.DeleteUser(user.ID); err != nil {
		s.abort(w, r, err)
		return
	}
	s.Render.Text(w, http.StatusOK, "account deleted")
}
<commit_msg>recover password msg changes<commit_after>package server

import (
	"database/sql"
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"time"

	"github.com/danjac/podbaby/decoders"
	"github.com/danjac/podbaby/models"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

const passwordChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func generateRandomPassword(length int) string {
	b := make([]byte, length)
	numChars := len(passwordChars)
	for i := range b {
		b[i] = passwordChars[rand.Intn(numChars)]
	}
	return string(b)
}

func (s *Server) recoverPassword(w http.ResponseWriter, r *http.Request) {
	// generate a temp password
	decoder := &decoders.RecoverPassword{}

	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}

	user, err := s.DB.Users.GetByNameOrEmail(decoder.Identifier)
	if err != nil {

		if err == sql.ErrNoRows {
			s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("no user found")})
			return
		}
		s.abort(w, r, err)
		return
	}

	tempPassword := generateRandomPassword(6)

	user.SetPassword(tempPassword)

	if err := s.DB.Users.UpdatePassword(user.Password, user.ID); err != nil {
		s.abort(w, r, err)
		return
	}
	msg := fmt.Sprintf(`Hi %s,

We've reset your password so you can sign back in again!

Here is your new temporary password:

%s

You can login here with this email address or your username:

%s://%s/#/login/

Make sure to change your password as soon as possible!

Thanks,

PodBaby
    `,
		user.Name,
		tempPassword,
		r.URL.Scheme,
		r.URL.Host,
	)

	go func(msg string) {

		err := s.Mailer.Send(
			"services@podbaby.me",
			[]string{user.Email},
			"Your new password",
			msg,
		)
		if err != nil {
			s.Log.Error(err)
		}

	}(msg)

	s.Render.Text(w, http.StatusOK, "password sent")

}

func (s *Server) signup(w http.ResponseWriter, r *http.Request) {

	decoder := &decoders.Signup{}

	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}

	if exists, _ := s.DB.Users.IsEmail(decoder.Email, 0); exists {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Email taken")})
		return
	}

	if exists, _ := s.DB.Users.IsName(decoder.Name); exists {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Name taken")})
		return
	}

	// make new user

	user := &models.User{
		Name:  decoder.Name,
		Email: decoder.Email,
	}

	if err := user.SetPassword(decoder.Password); err != nil {
		s.abort(w, r, err)
		return
	}

	if err := s.DB.Users.Create(user); err != nil {
		s.abort(w, r, err)
		return
	}
	s.setAuthCookie(w, user.ID)
	// tbd: no need to return user!
	s.Render.JSON(w, http.StatusCreated, user)
}

func (s *Server) login(w http.ResponseWriter, r *http.Request) {
	decoder := &decoders.Login{}
	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}

	user, err := s.DB.Users.GetByNameOrEmail(decoder.Identifier)
	if err != nil {

		if err == sql.ErrNoRows {
			s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("no user found")})
			return
		}
		s.abort(w, r, err)
		return
	}

	if !user.CheckPassword(decoder.Password) {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Invalid password")})
		return
	}
	// login user
	s.setAuthCookie(w, user.ID)

	// tbd: no need to return user!
	s.Render.JSON(w, http.StatusOK, user)

}

func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
	s.setAuthCookie(w, 0)
	s.Render.Text(w, http.StatusOK, "Logged out")
}

func (s *Server) changeEmail(w http.ResponseWriter, r *http.Request) {
	user, _ := getUser(r)
	decoder := &decoders.NewEmail{}
	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}
	// does this email exist?
	if exists, _ := s.DB.Users.IsEmail(decoder.Email, user.ID); exists {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Email taken")})
		return
	}

	if err := s.DB.Users.UpdateEmail(decoder.Email, user.ID); err != nil {
		s.abort(w, r, err)
		return
	}
	s.Render.Text(w, http.StatusOK, "email updated")
}

func (s *Server) changePassword(w http.ResponseWriter, r *http.Request) {
	user, _ := getUser(r)
	decoder := &decoders.NewPassword{}
	if err := decoders.Decode(r, decoder); err != nil {
		s.abort(w, r, HTTPError{http.StatusBadRequest, err})
		return
	}

	// validate old password first

	if !user.CheckPassword(decoder.OldPassword) {
		s.abort(w, r, HTTPError{http.StatusBadRequest, errors.New("Invalid password")})
		return
	}
	user.SetPassword(decoder.NewPassword)

	if err := s.DB.Users.UpdatePassword(user.Password, user.ID); err != nil {
		s.abort(w, r, err)
		return
	}
	s.Render.Text(w, http.StatusOK, "password updated")
}

func (s *Server) deleteAccount(w http.ResponseWriter, r *http.Request) {
	user, _ := getUser(r)
	if err := s.DB.Users.DeleteUser(user.ID); err != nil {
		s.abort(w, r, err)
		return
	}
	s.Render.Text(w, http.StatusOK, "account deleted")
}
<|endoftext|>


<commit_before>// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package cookie // import "miniflux.app/http/cookie"

import (
	"net/http"
	"time"
)

// Cookie names.
const (
	CookieAppSessionID  = "MinifluxAppSessionID"
	CookieUserSessionID = "MinifluxUserSessionID"

	// Cookie duration in days.
	cookieDuration = 30
)

// New creates a new cookie.
func New(name, value string, isHTTPS bool, path string) *http.Cookie {
	return &http.Cookie{
		Name:     name,
		Value:    value,
		Path:     basePath(path),
		Secure:   isHTTPS,
		HttpOnly: true,
		Expires:  time.Now().Add(cookieDuration * 24 * time.Hour),
		SameSite: http.SameSiteLaxMode,
	}
}

// Expired returns an expired cookie.
func Expired(name string, isHTTPS bool, path string) *http.Cookie {
	return &http.Cookie{
		Name:     name,
		Value:    "",
		Path:     basePath(path),
		Secure:   isHTTPS,
		HttpOnly: true,
		MaxAge:   -1,
		Expires:  time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
		SameSite: http.SameSiteLaxMode,
	}
}

func basePath(path string) string {
	if path == "" {
		return "/"
	}
	return path
}
<commit_msg>Set SameSite cookie attribute to Strict<commit_after>// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package cookie // import "miniflux.app/http/cookie"

import (
	"net/http"
	"time"
)

// Cookie names.
const (
	CookieAppSessionID  = "MinifluxAppSessionID"
	CookieUserSessionID = "MinifluxUserSessionID"

	// Cookie duration in days.
	cookieDuration = 30
)

// New creates a new cookie.
func New(name, value string, isHTTPS bool, path string) *http.Cookie {
	return &http.Cookie{
		Name:     name,
		Value:    value,
		Path:     basePath(path),
		Secure:   isHTTPS,
		HttpOnly: true,
		Expires:  time.Now().Add(cookieDuration * 24 * time.Hour),
		SameSite: http.SameSiteStrictMode,
	}
}

// Expired returns an expired cookie.
func Expired(name string, isHTTPS bool, path string) *http.Cookie {
	return &http.Cookie{
		Name:     name,
		Value:    "",
		Path:     basePath(path),
		Secure:   isHTTPS,
		HttpOnly: true,
		MaxAge:   -1,
		Expires:  time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
		SameSite: http.SameSiteStrictMode,
	}
}

func basePath(path string) string {
	if path == "" {
		return "/"
	}
	return path
}
<|endoftext|>


<commit_before>package writer

import (
	"bufio"
	"io"
	"net"
	"net/http"
)

// NewResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to
// hook into various parts of the response process.
func NewResponseWriter(w http.ResponseWriter, protoMajor int) ResponseWriter {
	bw := BasicWriter{inner: w}
	if protoMajor == 2 {
		return &HttpTwoWriter{bw}
	}
	return &HttpOneWriter{bw}
}

func SetProxyStatusCode(w ResponseWriter, code int) {
	w.(*BasicWriter).code = code
}

// ResponseWriter is a proxy around an http.ResponseWriter that allows you to hook
// into various parts of the response process.
type ResponseWriter interface {
	http.ResponseWriter
	// Status returns the HTTP status of the request, or 0 if one has not
	// yet been sent.
	Status() int
	// BytesWritten returns the total number of bytes sent to the client.
	BytesWritten() int
	// Tee causes the response body to be written to the given io.Writer in
	// addition to proxying the writes through. Only one io.Writer can be
	// tee'd to at once: setting a second one will overwrite the first.
	// Writes will be sent to the proxy before being written to this
	// io.Writer. It is illegal for the tee'd writer to be modified
	// concurrently with writes.
	Tee(io.Writer)
	// Unwrap returns the original proxied target.
	Unwrap() http.ResponseWriter
	Flush()
}

// basicWriter wraps a http.ResponseWriter that implements the minimal
// http.ResponseWriter interface.
type BasicWriter struct {
	inner         http.ResponseWriter
	wroteHeader   bool
	headerFlushed bool
	code          int
	bytes         int
	tee           io.Writer
}

func (b *BasicWriter) Header() http.Header {
	return b.inner.Header()
}

func (b *BasicWriter) WriteHeader(code int) {
	b.code = code
	if code != 0 {
		b.wroteHeader = true
	} else {
		b.wroteHeader = false
	}
}

func (b *BasicWriter) WriteHeaderImmediate(code int) {
	b.WriteHeader(code)
	if b.wroteHeader {
		b.inner.WriteHeader(code)
		b.headerFlushed = true
	}
}

func (b *BasicWriter) FlushHeadersIfRequired() {
	if !b.headerFlushed {
		b.WriteHeaderImmediate(b.code)
	}
}

func (b *BasicWriter) Write(buf []byte) (int, error) {
	b.FlushHeadersIfRequired()
	n, err := b.inner.Write(buf)
	if b.tee != nil {
		_, err2 := b.tee.Write(buf)
		// Prefer errors generated by the proxied writer.
		if err == nil {
			err = err2
		}
	}
	b.bytes += n
	return n, err
}

func (b *BasicWriter) Status() int {
	// if b.code == 0 {
	// 	return 200
	// }
	return b.code
}

func (b *BasicWriter) BytesWritten() int {
	return b.bytes
}

func (b *BasicWriter) Tee(w io.Writer) {
	b.tee = w
}

func (b *BasicWriter) Unwrap() http.ResponseWriter {
	return b.inner
}

func (b *BasicWriter) CloseNotify() <-chan bool {
	cn := b.inner.(http.CloseNotifier)
	return cn.CloseNotify()
}

func (b *BasicWriter) Flush() {
	b.FlushHeadersIfRequired()
	fl := b.inner.(http.Flusher)
	fl.Flush()
}

// HttpOneWriter is a HTTP writer that additionally satisfies http.Hijacker,
// and io.ReaderFrom.
type HttpOneWriter struct {
	BasicWriter
}

func (f *HttpOneWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hj := f.BasicWriter.inner.(http.Hijacker)
	return hj.Hijack()
}

func (f *HttpOneWriter) ReadFrom(r io.Reader) (int64, error) {
	f.BasicWriter.FlushHeadersIfRequired()
	if f.BasicWriter.tee != nil {
		return io.Copy(&f.BasicWriter, r)
	}
	rf := f.BasicWriter.inner.(io.ReaderFrom)
	n, err := rf.ReadFrom(r)
	f.BasicWriter.bytes += int(n)
	return n, err
}

// HttpTwoWriter is a HTTP2 writer that additionally satisfies
// Push
type HttpTwoWriter struct {
	BasicWriter
}

func (f *HttpTwoWriter) Push(target string, opts *http.PushOptions) error {
	return f.BasicWriter.inner.(http.Pusher).Push(target, opts)
}
<commit_msg>add: IsHijacked to ResponseWriter<commit_after>package writer

import (
	"bufio"
	"io"
	"net"
	"net/http"
)

// NewResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to
// hook into various parts of the response process.
func NewResponseWriter(w http.ResponseWriter, protoMajor int) ResponseWriter {
	bw := BasicWriter{inner: w}
	if protoMajor == 2 {
		return &HttpTwoWriter{bw}
	}
	return &HttpOneWriter{bw, false}
}

// ResponseWriter is a proxy around an http.ResponseWriter that allows you to hook
// into various parts of the response process.
type ResponseWriter interface {
	http.ResponseWriter
	// Status returns the HTTP status of the request, or 0 if one has not
	// yet been sent.
	Status() int
	// BytesWritten returns the total number of bytes sent to the client.
	BytesWritten() int
	// Tee causes the response body to be written to the given io.Writer in
	// addition to proxying the writes through. Only one io.Writer can be
	// tee'd to at once: setting a second one will overwrite the first.
	// Writes will be sent to the proxy before being written to this
	// io.Writer. It is illegal for the tee'd writer to be modified
	// concurrently with writes.
	Tee(io.Writer)
	// Unwrap returns the original proxied target.
	Unwrap() http.ResponseWriter
	Flush()
	IsHijacked() bool
}

// basicWriter wraps a http.ResponseWriter that implements the minimal
// http.ResponseWriter interface.
type BasicWriter struct {
	inner           http.ResponseWriter
	isHeaderSet     bool
	isHeaderFlushed bool
	code            int
	bytes           int
	tee             io.Writer
}

func (b *BasicWriter) Header() http.Header {
	return b.inner.Header()
}

func (b *BasicWriter) WriteHeader(code int) {
	b.code = code
	if code != 0 {
		b.isHeaderSet = true
	} else {
		b.isHeaderSet = false
	}
}

func (b *BasicWriter) WriteHeaderImmediate(code int) {
	b.WriteHeader(code)
	if b.isHeaderSet {
		b.inner.WriteHeader(code)
		b.isHeaderFlushed = true
	}
}

func (b *BasicWriter) FlushHeadersIfRequired() {
	if !b.isHeaderFlushed {
		b.WriteHeaderImmediate(b.code)
	}
}

func (b *BasicWriter) Write(buf []byte) (int, error) {
	b.FlushHeadersIfRequired()
	n, err := b.inner.Write(buf)
	if b.tee != nil {
		_, err2 := b.tee.Write(buf)
		// Prefer errors generated by the proxied writer.
		if err == nil {
			err = err2
		}
	}
	b.bytes += n
	return n, err
}

func (b *BasicWriter) Status() int {
	// if b.code == 0 {
	// 	return 200
	// }
	return b.code
}

func (b *BasicWriter) BytesWritten() int {
	return b.bytes
}

func (b *BasicWriter) Tee(w io.Writer) {
	b.tee = w
}

func (b *BasicWriter) Unwrap() http.ResponseWriter {
	return b.inner
}

func (b *BasicWriter) CloseNotify() <-chan bool {
	cn := b.inner.(http.CloseNotifier)
	return cn.CloseNotify()
}

func (b *BasicWriter) Flush() {
	b.FlushHeadersIfRequired()
	fl := b.inner.(http.Flusher)
	fl.Flush()
}

func (b *BasicWriter) IsHijacked() bool {
	return false
}

// HttpOneWriter is a HTTP writer that additionally satisfies http.Hijacker,
// and io.ReaderFrom.
type HttpOneWriter struct {
	BasicWriter
	isHijacked bool
}

func (f *HttpOneWriter) IsHijacked() bool {
	return f.isHijacked
}

func (f *HttpOneWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hj := f.BasicWriter.inner.(http.Hijacker)
	conn, rw, err := hj.Hijack()
	if err == nil {
		f.isHijacked = true
		return conn, rw, err
	}
	return conn, rw, err
}

func (f *HttpOneWriter) ReadFrom(r io.Reader) (int64, error) {
	f.BasicWriter.FlushHeadersIfRequired()
	if f.BasicWriter.tee != nil {
		return io.Copy(&f.BasicWriter, r)
	}
	rf := f.BasicWriter.inner.(io.ReaderFrom)
	n, err := rf.ReadFrom(r)
	f.BasicWriter.bytes += int(n)
	return n, err
}

// HttpTwoWriter is a HTTP2 writer that additionally satisfies
// Push
type HttpTwoWriter struct {
	BasicWriter
}

func (f *HttpTwoWriter) Push(target string, opts *http.PushOptions) error {
	return f.BasicWriter.inner.(http.Pusher).Push(target, opts)
}
<|endoftext|>


<commit_before>package gorma

const modelTmpl = `// {{if .Description}}{{.Description}}{{else}}app.{{gotypename . 0}} storage type{{end}}
// Identifier: {{ $typeName :=  gotypename . 0}}{{$typeName := demodel $typeName}}
type {{$typeName}} {{ modeldef . }}
{{ $belongsto := metaLookup .Metadata "#belongsto" }}
{{ $m2m := metaLookup .Metadata "#many2many" }}
{{ $nomedia := metaLookup .Metadata "#nomedia" }}
{{ if eq $nomedia "" }}
func {{$typeName}}FromCreatePayload(ctx *app.Create{{demodel $typeName}}Context) {{$typeName}} {
	payload := ctx.Payload
	m := {{$typeName}}{}
	copier.Copy(&m, payload)
{{ if ne $belongsto "" }}{{$barray := split $belongsto ","}}{{ range $idx, $bt := $barray}}
	m.{{ $bt}}ID=int(ctx.{{ demodel $bt}}ID){{end}}{{end}}
	return m
}

func {{$typeName}}FromUpdatePayload(ctx *app.Update{{demodel $typeName}}Context) {{$typeName}} {
	payload := ctx.Payload
	m := {{$typeName}}{}
	copier.Copy(&m, payload)
	return m
}

func (m {{$typeName}}) ToApp() *app.{{demodel $typeName}} {
	target := app.{{demodel $typeName}}{}
	copier.Copy(&target, &m)
	return &target
}
{{ end }}
{{ $tablename := metaLookup .Metadata "#tablename" }}
{{ if ne $tablename "" }}
func (m {{$typeName}}) TableName() string {
	return "{{ $tablename }}"
}
{{ end }}
{{ $roler := metaLookup .Metadata "#roler" }}
{{ if ne $roler "" }}
func (m {{$typeName}}) GetRole() string {
	return m.Role
}
{{end}}

{{ $dyntablename := metaLookup .Metadata "#dyntablename" }}

type {{$typeName}}Storage interface {
	List(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}) []{{$typeName}}
	One(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, id int) ({{$typeName}}, error)
	Add(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, o {{$typeName}}) ({{$typeName}}, error)
	Update(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, o {{$typeName}}) (error)
	Delete(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, id int) (error)
{{ if ne $belongsto "" }}{{$barray := split $belongsto ","}}{{ range $idx, $bt := $barray}}
	ListBy{{$bt}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, parentid int) []{{$typeName}}
	OneBy{{$bt}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, parentid, id int) ({{$typeName}}, error)
{{end}}{{end}}
	{{ storagedef . }}
}
{{ $cached := metaLookup .Metadata "#cached" }}
type {{$typeName}}DB struct {
	DB gorm.DB
	{{ if ne $cached "" }}cache *cache.Cache{{end}}
}
{{ if ne $belongsto "" }}{{$barray := split $belongsto ","}}{{ range $idx, $bt := $barray}}
// would prefer to just pass a context in here, but they're all different, so can't
func {{$typeName}}FilterBy{{$bt}}(parentid int, originaldb *gorm.DB) func(db *gorm.DB) *gorm.DB {
	if parentid > 0 {
		return func(db *gorm.DB) *gorm.DB {
			return db.Where("{{ snake $bt }}_id = ?", parentid)
		}
	} else {
		return func(db *gorm.DB) *gorm.DB {
			return db
		}
	}
}

func (m *{{$typeName}}DB) ListBy{{$bt}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, parentid int) []{{$typeName}} {

	var objs []{{$typeName}}
	m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Scopes({{$typeName}}FilterBy{{$bt}}(parentid, &m.DB)).Find(&objs)
	return objs
}

func (m *{{$typeName}}DB) OneBy{{$bt}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, parentid, id int) ({{$typeName}}, error) {
	{{ if ne $cached "" }}//first attempt to retrieve from cache
	o,found := m.cache.Get(strconv.Itoa(id))
	if found {
		return o.({{$typeName}}), nil
	}
	// fallback to database if not found{{ end }}
	var obj {{$typeName}}

	err := m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Scopes({{$typeName}}FilterBy{{$bt}}(parentid, &m.DB)).Find(&obj, id).Error
	{{ if ne $cached "" }} go m.cache.Set(strconv.Itoa(id), obj, cache.DefaultExpiration) {{ end }}
	return obj, err
}
{{end}}{{end}}

func New{{$typeName}}DB(db gorm.DB) *{{$typeName}}DB {
	{{ if ne $cached "" }}
	return &{{$typeName}}DB{
		DB: db,
		cache: cache.New(5*time.Minute, 30*time.Second),
	}
	{{ else  }}
	return &{{$typeName}}DB{DB: db}

	{{ end  }}
}

func (m *{{$typeName}}DB) List(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}) []{{$typeName}} {

	var objs []{{$typeName}}
	m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Find(&objs)
	return objs
}

func (m *{{$typeName}}DB) One(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, id int) ({{$typeName}}, error) {
	{{ if ne $cached "" }}//first attempt to retrieve from cache
	o,found := m.cache.Get(strconv.Itoa(id))
	if found {
		return o.({{$typeName}}), nil
	}
	// fallback to database if not found{{ end }}
	var obj {{$typeName}}

	err := m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Find(&obj, id).Error
	{{ if ne $cached "" }} go m.cache.Set(strconv.Itoa(id), obj, cache.DefaultExpiration) {{ end }}
	return obj, err
}

func (m *{{$typeName}}DB) Add(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, model {{$typeName}}) ({{$typeName}}, error) {
	err := m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Create(&model).Error
	{{ if ne $cached "" }} go m.cache.Set(strconv.Itoa(model.ID), model, cache.DefaultExpiration) {{ end }}
	return model, err
}

func (m *{{$typeName}}DB) Update(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, model {{$typeName}}) error {
	obj, err := m.One(ctx{{ if ne $dyntablename "" }}, tableName{{ end }}, model.ID)
	if err != nil {
		return  err
	}
	err = m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Model(&obj).Updates(model).Error
	{{ if ne $cached "" }}
	go func(){
	obj, err := m.One(ctx, model.ID)
	if err == nil {
		m.cache.Set(strconv.Itoa(model.ID), obj, cache.DefaultExpiration)
	}
	}()
	{{ end }}

	return err
}

func (m *{{$typeName}}DB) Delete(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, id int)  error {
	var obj {{$typeName}}
	err := m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Delete(&obj, id).Error
	if err != nil {
		return  err
	}
	{{ if ne $cached "" }} go m.cache.Delete(strconv.Itoa(id)) {{ end }}
	return  nil
}

{{ if ne $m2m "" }}{{$barray := split $m2m ","}}{{ range $idx, $bt := $barray}}
{{ $pieces := split $bt ":" }} {{ $lowertype := index $pieces 1  }} {{ $lower := lower $lowertype }}  {{ $lowerplural := index $pieces 0  }} {{ $lowerplural := lower $lowerplural}}
func (m *{{$typeName}}DB) Delete{{index $pieces 1}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }},{{lower $typeName}}ID,  {{$lower}}ID int)  error {
	var obj {{$typeName}}
	obj.ID = {{lower $typeName}}ID
	var assoc {{index $pieces 1}}
	var err error
	assoc.ID = {{$lower}}ID
	if err != nil {
		return err
	}
	err = m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Model(&obj).Association("{{index $pieces 0}}").Delete(assoc).Error
	if err != nil {
		return  err
	}
	return  nil
}
func (m *{{$typeName}}DB) Add{{index $pieces 1}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, {{lower $typeName}}ID, {{$lower}}ID int) error {
	var {{lower $typeName}} {{$typeName}}
	{{lower $typeName}}.ID = {{lower $typeName}}ID
	var assoc {{index $pieces 1}}
	assoc.ID = {{$lower}}ID
	err := m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Model(&{{lower $typeName}}).Association("{{index $pieces 0}}").Append(assoc).Error
	if err != nil {
		return  err
	}
	return  nil
}
func (m *{{$typeName}}DB) List{{index $pieces 0}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, {{lower $typeName}}ID int)  []{{index $pieces 1}} {
	list := make([]{{index $pieces 1}}, 0)
	var obj {{$typeName}}
	obj.ID = {{lower $typeName}}ID
	m.DB{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Model(&obj).Association("{{index $pieces 0}}").Find(&list)
	return  nil
}
{{end}}{{end}}
{{if ne $belongsto ""}}{{$barray := split $belongsto ","}}{{ range $idx, $bt := $barray}}
func Filter{{$typeName}}By{{$bt}}(parent int, list []{{$typeName}}) []{{$typeName}} {
	filtered := make([]{{$typeName}},0)
	for _,o := range list {
		if o.{{$bt}}ID == int(parent) {
			filtered = append(filtered,o)
		}
	}
	return filtered
}
{{end}}{{end}}
`
<commit_msg>Allow access to DB object from Storage Interface<commit_after>package gorma

const modelTmpl = `// {{if .Description}}{{.Description}}{{else}}app.{{gotypename . 0}} storage type{{end}}
// Identifier: {{ $typeName :=  gotypename . 0}}{{$typeName := demodel $typeName}}
type {{$typeName}} {{ modeldef . }}
{{ $belongsto := metaLookup .Metadata "#belongsto" }}
{{ $m2m := metaLookup .Metadata "#many2many" }}
{{ $nomedia := metaLookup .Metadata "#nomedia" }}
{{ if eq $nomedia "" }}
func {{$typeName}}FromCreatePayload(ctx *app.Create{{demodel $typeName}}Context) {{$typeName}} {
	payload := ctx.Payload
	m := {{$typeName}}{}
	copier.Copy(&m, payload)
{{ if ne $belongsto "" }}{{$barray := split $belongsto ","}}{{ range $idx, $bt := $barray}}
	m.{{ $bt}}ID=int(ctx.{{ demodel $bt}}ID){{end}}{{end}}
	return m
}

func {{$typeName}}FromUpdatePayload(ctx *app.Update{{demodel $typeName}}Context) {{$typeName}} {
	payload := ctx.Payload
	m := {{$typeName}}{}
	copier.Copy(&m, payload)
	return m
}

func (m {{$typeName}}) ToApp() *app.{{demodel $typeName}} {
	target := app.{{demodel $typeName}}{}
	copier.Copy(&target, &m)
	return &target
}
{{ end }}
{{ $tablename := metaLookup .Metadata "#tablename" }}
{{ if ne $tablename "" }}
func (m {{$typeName}}) TableName() string {
	return "{{ $tablename }}"
}
{{ end }}
{{ $roler := metaLookup .Metadata "#roler" }}
{{ if ne $roler "" }}
func (m {{$typeName}}) GetRole() string {
	return m.Role
}
{{end}}

{{ $dyntablename := metaLookup .Metadata "#dyntablename" }}

type {{$typeName}}Storage interface {
	DB() *gorm.DB
	List(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}) []{{$typeName}}
	One(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, id int) ({{$typeName}}, error)
	Add(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, o {{$typeName}}) ({{$typeName}}, error)
	Update(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, o {{$typeName}}) (error)
	Delete(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, id int) (error)
{{ if ne $belongsto "" }}{{$barray := split $belongsto ","}}{{ range $idx, $bt := $barray}}
	ListBy{{$bt}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, parentid int) []{{$typeName}}
	OneBy{{$bt}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, parentid, id int) ({{$typeName}}, error)
{{end}}{{end}}
	{{ storagedef . }}
}
{{ $cached := metaLookup .Metadata "#cached" }}
type {{$typeName}}DB struct {
	db gorm.DB
	{{ if ne $cached "" }}cache *cache.Cache{{end}}
}
{{ if ne $belongsto "" }}{{$barray := split $belongsto ","}}{{ range $idx, $bt := $barray}}
// would prefer to just pass a context in here, but they're all different, so can't
func {{$typeName}}FilterBy{{$bt}}(parentid int, originaldb *gorm.DB) func(db *gorm.DB) *gorm.DB {
	if parentid > 0 {
		return func(db *gorm.DB) *gorm.DB {
			return db.Where("{{ snake $bt }}_id = ?", parentid)
		}
	} else {
		return func(db *gorm.DB) *gorm.DB {
			return db
		}
	}
}

func (m *{{$typeName}}DB) ListBy{{$bt}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, parentid int) []{{$typeName}} {

	var objs []{{$typeName}}
	m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Scopes({{$typeName}}FilterBy{{$bt}}(parentid, &m.db)).Find(&objs)
	return objs
}

func (m *{{$typeName}}DB) OneBy{{$bt}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, parentid, id int) ({{$typeName}}, error) {
	{{ if ne $cached "" }}//first attempt to retrieve from cache
	o,found := m.cache.Get(strconv.Itoa(id))
	if found {
		return o.({{$typeName}}), nil
	}
	// fallback to database if not found{{ end }}
	var obj {{$typeName}}

	err := m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Scopes({{$typeName}}FilterBy{{$bt}}(parentid, &m.db)).Find(&obj, id).Error
	{{ if ne $cached "" }} go m.cache.Set(strconv.Itoa(id), obj, cache.DefaultExpiration) {{ end }}
	return obj, err
}
{{end}}{{end}}

func New{{$typeName}}DB(db gorm.DB) *{{$typeName}}DB {
	{{ if ne $cached "" }}
	return &{{$typeName}}DB{
		db: db,
		cache: cache.New(5*time.Minute, 30*time.Second),
	}
	{{ else  }}
	return &{{$typeName}}DB{db: db}

	{{ end  }}
}

func (m *{{$typeName}}DB) DB() *gorm.DB {
	return &m.db
}

func (m *{{$typeName}}DB) List(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}) []{{$typeName}} {

	var objs []{{$typeName}}
	m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Find(&objs)
	return objs
}

func (m *{{$typeName}}DB) One(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, id int) ({{$typeName}}, error) {
	{{ if ne $cached "" }}//first attempt to retrieve from cache
	o,found := m.cache.Get(strconv.Itoa(id))
	if found {
		return o.({{$typeName}}), nil
	}
	// fallback to database if not found{{ end }}
	var obj {{$typeName}}

	err := m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Find(&obj, id).Error
	{{ if ne $cached "" }} go m.cache.Set(strconv.Itoa(id), obj, cache.DefaultExpiration) {{ end }}
	return obj, err
}

func (m *{{$typeName}}DB) Add(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, model {{$typeName}}) ({{$typeName}}, error) {
	err := m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Create(&model).Error
	{{ if ne $cached "" }} go m.cache.Set(strconv.Itoa(model.ID), model, cache.DefaultExpiration) {{ end }}
	return model, err
}

func (m *{{$typeName}}DB) Update(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, model {{$typeName}}) error {
	obj, err := m.One(ctx{{ if ne $dyntablename "" }}, tableName{{ end }}, model.ID)
	if err != nil {
		return  err
	}
	err = m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Model(&obj).Updates(model).Error
	{{ if ne $cached "" }}
	go func(){
	obj, err := m.One(ctx, model.ID)
	if err == nil {
		m.cache.Set(strconv.Itoa(model.ID), obj, cache.DefaultExpiration)
	}
	}()
	{{ end }}

	return err
}

func (m *{{$typeName}}DB) Delete(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, id int)  error {
	var obj {{$typeName}}
	err := m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Delete(&obj, id).Error
	if err != nil {
		return  err
	}
	{{ if ne $cached "" }} go m.cache.Delete(strconv.Itoa(id)) {{ end }}
	return  nil
}

{{ if ne $m2m "" }}{{$barray := split $m2m ","}}{{ range $idx, $bt := $barray}}
{{ $pieces := split $bt ":" }} {{ $lowertype := index $pieces 1  }} {{ $lower := lower $lowertype }}  {{ $lowerplural := index $pieces 0  }} {{ $lowerplural := lower $lowerplural}}
func (m *{{$typeName}}DB) Delete{{index $pieces 1}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }},{{lower $typeName}}ID,  {{$lower}}ID int)  error {
	var obj {{$typeName}}
	obj.ID = {{lower $typeName}}ID
	var assoc {{index $pieces 1}}
	var err error
	assoc.ID = {{$lower}}ID
	if err != nil {
		return err
	}
	err = m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Model(&obj).Association("{{index $pieces 0}}").Delete(assoc).Error
	if err != nil {
		return  err
	}
	return  nil
}
func (m *{{$typeName}}DB) Add{{index $pieces 1}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, {{lower $typeName}}ID, {{$lower}}ID int) error {
	var {{lower $typeName}} {{$typeName}}
	{{lower $typeName}}.ID = {{lower $typeName}}ID
	var assoc {{index $pieces 1}}
	assoc.ID = {{$lower}}ID
	err := m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Model(&{{lower $typeName}}).Association("{{index $pieces 0}}").Append(assoc).Error
	if err != nil {
		return  err
	}
	return  nil
}
func (m *{{$typeName}}DB) List{{index $pieces 0}}(ctx context.Context{{ if ne $dyntablename "" }}, tableName string{{ end }}, {{lower $typeName}}ID int)  []{{index $pieces 1}} {
	list := make([]{{index $pieces 1}}, 0)
	var obj {{$typeName}}
	obj.ID = {{lower $typeName}}ID
	m.db{{ if ne $dyntablename "" }}.Table(tableName){{ end }}.Model(&obj).Association("{{index $pieces 0}}").Find(&list)
	return  nil
}
{{end}}{{end}}
{{if ne $belongsto ""}}{{$barray := split $belongsto ","}}{{ range $idx, $bt := $barray}}
func Filter{{$typeName}}By{{$bt}}(parent int, list []{{$typeName}}) []{{$typeName}} {
	filtered := make([]{{$typeName}},0)
	for _,o := range list {
		if o.{{$bt}}ID == int(parent) {
			filtered = append(filtered,o)
		}
	}
	return filtered
}
{{end}}{{end}}
`
<|endoftext|>


<commit_before>package server

import (
	"crypto/rsa"
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/dgrijalva/jwt-go"
	"github.com/go-errors/errors"
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/revocation"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/sirupsen/logrus"
)

// Configuration contains configuration for the irmaserver library and irmad.
type Configuration struct {
	// irma_configuration. If not given, this will be popupated using SchemesPath.
	IrmaConfiguration *irma.Configuration `json:"-"`
	// Path to IRMA schemes to parse into IrmaConfiguration (only used if IrmaConfiguration == nil).
	// If left empty, default value is taken using DefaultSchemesPath().
	// If an empty folder is specified, default schemes (irma-demo and pbdf) are downloaded into it.
	SchemesPath string `json:"schemes_path" mapstructure:"schemes_path"`
	// If specified, schemes found here are copied into SchemesPath (only used if IrmaConfiguration == nil)
	SchemesAssetsPath string `json:"schemes_assets_path" mapstructure:"schemes_assets_path"`
	// Disable scheme updating
	DisableSchemesUpdate bool `json:"disable_schemes_update" mapstructure:"disable_schemes_update"`
	// Update all schemes every x minutes (default value 0 means 60) (use DisableSchemesUpdate to disable)
	SchemesUpdateInterval int `json:"schemes_update" mapstructure:"schemes_update"`
	// Path to issuer private keys to parse
	IssuerPrivateKeysPath string `json:"privkeys" mapstructure:"privkeys"`
	// URL at which the IRMA app can reach this server during sessions
	URL string `json:"url" mapstructure:"url"`
	// Required to be set to true if URL does not begin with https:// in production mode.
	// In this case, the server would communicate with IRMA apps over plain HTTP. You must otherwise
	// ensure (using eg a reverse proxy with TLS enabled) that the attributes are protected in transit.
	DisableTLS bool `json:"disable_tls" mapstructure:"disable_tls"`
	// (Optional) email address of server admin, for incidental notifications such as breaking API changes
	// See https://github.com/privacybydesign/irmago/tree/master/server#specifying-an-email-address
	// for more information
	Email string `json:"email" mapstructure:"email"`
	// Enable server sent events for status updates (experimental; tends to hang when a reverse proxy is used)
	EnableSSE bool `json:"enable_sse" mapstructure:"enable_sse"`

	// Static session requests that can be created by POST /session/{name}
	StaticSessions map[string]interface{} `json:"static_sessions"`
	// Static session requests after parsing
	StaticSessionRequests map[string]irma.RequestorRequest `json:"-"`

	// Used in the "iss" field of result JWTs from /result-jwt and /getproof
	JwtIssuer string `json:"jwt_issuer" mapstructure:"jwt_issuer"`
	// Private key to sign result JWTs with. If absent, /result-jwt and /getproof are disabled.
	JwtPrivateKey     string `json:"jwt_privkey" mapstructure:"jwt_privkey"`
	JwtPrivateKeyFile string `json:"jwt_privkey_file" mapstructure:"jwt_privkey_file"`
	// Parsed JWT private key
	JwtRSAPrivateKey *rsa.PrivateKey `json:"-"`
	// Whether to allow callbackUrl to be set in session requests when no JWT privatekey is installed
	// (which is potentially unsafe depending on the setup)
	AllowUnsignedCallbacks bool

	// Logging verbosity level: 0 is normal, 1 includes DEBUG level, 2 includes TRACE level
	Verbose int `json:"verbose" mapstructure:"verbose"`
	// Don't log anything at all
	Quiet bool `json:"quiet" mapstructure:"quiet"`
	// Output structured log in JSON format
	LogJSON bool `json:"log_json" mapstructure:"log_json"`
	// Custom logger instance. If specified, Verbose, Quiet and LogJSON are ignored.
	Logger *logrus.Logger `json:"-"`

	// Connection string for revocation database
	RevocationDBConnStr string `json:"revocation_db_str" mapstructure:"revocation_db_str"`
	// Database type for revocation database, supported: postgres, mysql
	RevocationDBType string `json:"revocation_db_type" mapstructure:"revocation_db_type"`
	// Credentials types for which revocation database should be hosted
	RevocationSettings irma.RevocationSettings `json:"revocation_settings" mapstructure:"revocation_settings"`

	// Production mode: enables safer and stricter defaults and config checking
	Production bool `json:"production" mapstructure:"production"`
}

// Check ensures that the Configuration is loaded, usable and free of errors.
func (conf *Configuration) Check() error {
	if conf.Logger == nil {
		conf.Logger = NewLogger(conf.Verbose, conf.Quiet, conf.LogJSON)
	}
	Logger = conf.Logger
	irma.SetLogger(conf.Logger)

	// loop to avoid repetetive err != nil line triplets
	for _, f := range []func() error{
		conf.verifyIrmaConf,
		conf.verifyPrivateKeys,
		conf.verifyURL,
		conf.verifyEmail,
		conf.verifyRevocation,
		conf.verifyStaticSessions,
		conf.verifyJwtPrivateKey,
	} {
		if err := f(); err != nil {
			_ = LogError(err)
			if conf.IrmaConfiguration != nil && conf.IrmaConfiguration.Revocation != nil {
				if e := conf.IrmaConfiguration.Revocation.Close(); e != nil {
					_ = LogError(e)
				}
			}
			return err
		}
	}

	return nil
}

func (conf *Configuration) HavePrivateKeys() bool {
	var err error
	for id := range conf.IrmaConfiguration.Issuers {
		if conf.IrmaConfiguration.SchemeManagers[id.SchemeManagerIdentifier()].Demo {
			continue
		}
		if _, err = conf.IrmaConfiguration.PrivateKeys.Latest(id); err == nil {
			return true
		}
	}
	return false
}

// helpers

func (conf *Configuration) verifyStaticSessions() error {
	conf.StaticSessionRequests = make(map[string]irma.RequestorRequest)
	for name, r := range conf.StaticSessions {
		if !regexp.MustCompile("^[a-zA-Z0-9_]+$").MatchString(name) {
			return errors.Errorf("static session name %s not allowed, must be alphanumeric", name)
		}
		j, err := json.Marshal(r)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		rrequest, err := ParseSessionRequest(j)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		action := rrequest.SessionRequest().Action()
		if action != irma.ActionDisclosing && action != irma.ActionSigning {
			return errors.Errorf("static session %s must be either a disclosing or signing session", name)
		}
		if rrequest.Base().CallbackURL == "" {
			return errors.Errorf("static session %s has no callback URL", name)
		}
		conf.StaticSessionRequests[name] = rrequest
	}
	return nil
}

func (conf *Configuration) verifyIrmaConf() error {
	if conf.IrmaConfiguration == nil {
		var (
			err    error
			exists bool
		)
		if conf.SchemesPath == "" {
			conf.SchemesPath = irma.DefaultSchemesPath() // Returns an existing path
		}
		if exists, err = common.PathExists(conf.SchemesPath); err != nil {
			return err
		}
		if !exists {
			return errors.Errorf("Nonexisting schemes_path provided: %s", conf.SchemesPath)
		}
		conf.Logger.WithField("schemes_path", conf.SchemesPath).Info("Determined schemes path")
		conf.IrmaConfiguration, err = irma.NewConfiguration(conf.SchemesPath, irma.ConfigurationOptions{
			Assets:              conf.SchemesAssetsPath,
			RevocationDBType:    conf.RevocationDBType,
			RevocationDBConnStr: conf.RevocationDBConnStr,
			RevocationSettings:  conf.RevocationSettings,
		})
		if err != nil {
			return err
		}
		if err = conf.IrmaConfiguration.ParseFolder(); err != nil {
			return err
		}
	}

	if len(conf.IrmaConfiguration.SchemeManagers) == 0 {
		conf.Logger.Infof("No schemes found in %s, downloading default (irma-demo and pbdf)", conf.SchemesPath)
		if err := conf.IrmaConfiguration.DownloadDefaultSchemes(); err != nil {
			return err
		}
	}
	if conf.SchemesUpdateInterval == 0 {
		conf.SchemesUpdateInterval = 60
	}
	if !conf.DisableSchemesUpdate {
		conf.IrmaConfiguration.AutoUpdateSchemes(uint(conf.SchemesUpdateInterval))
	}

	return nil
}

func (conf *Configuration) verifyPrivateKeys() error {
	if conf.IssuerPrivateKeysPath == "" {
		return nil
	}
	ring, err := irma.NewPrivateKeyRingFolder(conf.IssuerPrivateKeysPath, conf.IrmaConfiguration)
	if err != nil {
		return err
	}
	return conf.IrmaConfiguration.AddPrivateKeyRing(ring)
}

func (conf *Configuration) prepareRevocation(credid irma.CredentialTypeIdentifier) error {
	var sk *revocation.PrivateKey
	err := conf.IrmaConfiguration.PrivateKeys.Iterate(credid.IssuerIdentifier(), func(isk *gabi.PrivateKey) error {
		if !isk.RevocationSupported() {
			return nil
		}
		s, err := isk.RevocationKey()
		if err != nil {
			return errors.WrapPrefix(err, fmt.Sprintf("failed to load revocation private key %s-%d", credid, isk.Counter), 0)
		}
		sk = s
		exists, err := conf.IrmaConfiguration.Revocation.Exists(credid, isk.Counter)
		if err != nil {
			return errors.WrapPrefix(err, fmt.Sprintf("failed to check if accumulator exists for %s-%d", credid, isk.Counter), 0)
		}
		if !exists {
			conf.Logger.Warnf("Creating initial accumulator for %s-%d", credid, isk.Counter)
			if err := conf.IrmaConfiguration.Revocation.EnableRevocation(credid, sk); err != nil {
				return errors.WrapPrefix(err, fmt.Sprintf("failed create initial accumulator for %s-%d", credid, isk.Counter), 0)
			}
		}
		return nil
	})
	if err != nil {
		return err
	}
	if sk == nil {
		return errors.Errorf("revocation server mode enabled for %s but no private key installed", credid)
	}
	return nil
}

func (conf *Configuration) verifyRevocation() error {
	rev := conf.IrmaConfiguration.Revocation

	// viper lowercases configuration keys, so we have to un-lowercase them back.
	for id := range conf.IrmaConfiguration.CredentialTypes {
		lc := irma.NewCredentialTypeIdentifier(strings.ToLower(id.String()))
		if lc == id {
			continue
		}
		if s, ok := conf.RevocationSettings[lc]; ok {
			delete(conf.RevocationSettings, lc)
			conf.RevocationSettings[id] = s
		}
	}

	for credid, settings := range conf.RevocationSettings {
		if _, known := conf.IrmaConfiguration.CredentialTypes[credid]; !known {
			return errors.Errorf("unknown credential type %s in revocation settings", credid)
		}
		if settings.Authority {
			conf.Logger.Info("authoritative revocation server mode enabled for " + credid.String())
			if err := conf.prepareRevocation(credid); err != nil {
				return err
			}
		} else if settings.Server {
			conf.Logger.Info("revocation server mode enabled for " + credid.String())
		}
		if settings.Server {
			conf.Logger.Info("Being the revocation server for a credential type comes with special responsibilities. Failure can lead to all IRMA apps being unable to disclose credentials of this type. Read more at https://irma.app/docs/revocation/#issuer-responsibilities.")
		}
	}

	for credid, credtype := range conf.IrmaConfiguration.CredentialTypes {
		if !credtype.RevocationSupported() {
			continue
		}
		_, err := rev.Keys.PrivateKeyLatest(credid.IssuerIdentifier())
		haveSK := err == nil
		settings := conf.RevocationSettings[credid]
		if haveSK && (settings == nil || (settings.RevocationServerURL == "" && !settings.Server)) {
			message := "Revocation-supporting private key installed for %s, but no revocation server is configured: issuance sessions will always fail"
			if conf.IrmaConfiguration.SchemeManagers[credid.IssuerIdentifier().SchemeManagerIdentifier()].Demo {
				conf.Logger.Warnf(message, credid)
			} else {
				return errors.Errorf(message, credid)
			}
		}
		if settings != nil && settings.RevocationServerURL != "" {
			url := settings.RevocationServerURL
			if url[len(url)-1] == '/' {
				settings.RevocationServerURL = url[:len(url)-1]
			}
		}
	}

	return nil
}

func (conf *Configuration) verifyURL() error {
	if conf.URL != "" {
		if !strings.HasSuffix(conf.URL, "/") {
			conf.URL = conf.URL + "/"
		}
		if !strings.HasPrefix(conf.URL, "https://") {
			if !conf.Production || conf.DisableTLS {
				conf.DisableTLS = true
				conf.Logger.Warnf("TLS is not enabled on the url \"%s\" to which the IRMA app will connect. "+
					"Ensure that attributes are encrypted in transit by either enabling TLS or adding TLS in a reverse proxy.", conf.URL)
			} else {
				return errors.Errorf("Running without TLS in production mode is unsafe without a reverse proxy. " +
					"Either use a https:// URL or explicitly disable TLS.")
			}
		}
	} else {
		conf.Logger.Warn("No url parameter specified in configuration; unless an url is elsewhere prepended in the QR, the IRMA client will not be able to connect")
	}
	return nil
}

type serverInfo struct {
	Email   string `json:"email"`
	Version string `json:"version"`
}

func (conf *Configuration) verifyEmail() error {
	if conf.Email == "" {
		return nil
	}
	if !strings.Contains(conf.Email, "@") || strings.Contains(conf.Email, "\n") {
		return errors.New("Invalid email address specified")
	}
	t := irma.NewHTTPTransport("https://privacybydesign.foundation/", true)
	t.SetHeader("User-Agent", "irmaserver")
	data := &serverInfo{Email: conf.Email, Version: irma.Version}

	go func() {
		err := t.Post("serverinfo/", nil, data)
		if err != nil {
			conf.Logger.Trace("Failed to send email and version number:", err)
		}
	}()

	return nil
}

func (conf *Configuration) verifyJwtPrivateKey() error {
	if conf.JwtPrivateKey == "" && conf.JwtPrivateKeyFile == "" {
		return nil
	}

	keybytes, err := common.ReadKey(conf.JwtPrivateKey, conf.JwtPrivateKeyFile)
	if err != nil {
		return errors.WrapPrefix(err, "failed to read private key", 0)
	}

	conf.JwtRSAPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(keybytes)
	conf.Logger.Info("Private key parsed, JWT endpoints enabled")
	return err
}
<commit_msg>feat: in static sessions, also require new flag to be set if no JWT private key installed<commit_after>package server

import (
	"crypto/rsa"
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/dgrijalva/jwt-go"
	"github.com/go-errors/errors"
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/revocation"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/sirupsen/logrus"
)

// Configuration contains configuration for the irmaserver library and irmad.
type Configuration struct {
	// irma_configuration. If not given, this will be popupated using SchemesPath.
	IrmaConfiguration *irma.Configuration `json:"-"`
	// Path to IRMA schemes to parse into IrmaConfiguration (only used if IrmaConfiguration == nil).
	// If left empty, default value is taken using DefaultSchemesPath().
	// If an empty folder is specified, default schemes (irma-demo and pbdf) are downloaded into it.
	SchemesPath string `json:"schemes_path" mapstructure:"schemes_path"`
	// If specified, schemes found here are copied into SchemesPath (only used if IrmaConfiguration == nil)
	SchemesAssetsPath string `json:"schemes_assets_path" mapstructure:"schemes_assets_path"`
	// Disable scheme updating
	DisableSchemesUpdate bool `json:"disable_schemes_update" mapstructure:"disable_schemes_update"`
	// Update all schemes every x minutes (default value 0 means 60) (use DisableSchemesUpdate to disable)
	SchemesUpdateInterval int `json:"schemes_update" mapstructure:"schemes_update"`
	// Path to issuer private keys to parse
	IssuerPrivateKeysPath string `json:"privkeys" mapstructure:"privkeys"`
	// URL at which the IRMA app can reach this server during sessions
	URL string `json:"url" mapstructure:"url"`
	// Required to be set to true if URL does not begin with https:// in production mode.
	// In this case, the server would communicate with IRMA apps over plain HTTP. You must otherwise
	// ensure (using eg a reverse proxy with TLS enabled) that the attributes are protected in transit.
	DisableTLS bool `json:"disable_tls" mapstructure:"disable_tls"`
	// (Optional) email address of server admin, for incidental notifications such as breaking API changes
	// See https://github.com/privacybydesign/irmago/tree/master/server#specifying-an-email-address
	// for more information
	Email string `json:"email" mapstructure:"email"`
	// Enable server sent events for status updates (experimental; tends to hang when a reverse proxy is used)
	EnableSSE bool `json:"enable_sse" mapstructure:"enable_sse"`

	// Static session requests that can be created by POST /session/{name}
	StaticSessions map[string]interface{} `json:"static_sessions"`
	// Static session requests after parsing
	StaticSessionRequests map[string]irma.RequestorRequest `json:"-"`

	// Used in the "iss" field of result JWTs from /result-jwt and /getproof
	JwtIssuer string `json:"jwt_issuer" mapstructure:"jwt_issuer"`
	// Private key to sign result JWTs with. If absent, /result-jwt and /getproof are disabled.
	JwtPrivateKey     string `json:"jwt_privkey" mapstructure:"jwt_privkey"`
	JwtPrivateKeyFile string `json:"jwt_privkey_file" mapstructure:"jwt_privkey_file"`
	// Parsed JWT private key
	JwtRSAPrivateKey *rsa.PrivateKey `json:"-"`
	// Whether to allow callbackUrl to be set in session requests when no JWT privatekey is installed
	// (which is potentially unsafe depending on the setup)
	AllowUnsignedCallbacks bool

	// Logging verbosity level: 0 is normal, 1 includes DEBUG level, 2 includes TRACE level
	Verbose int `json:"verbose" mapstructure:"verbose"`
	// Don't log anything at all
	Quiet bool `json:"quiet" mapstructure:"quiet"`
	// Output structured log in JSON format
	LogJSON bool `json:"log_json" mapstructure:"log_json"`
	// Custom logger instance. If specified, Verbose, Quiet and LogJSON are ignored.
	Logger *logrus.Logger `json:"-"`

	// Connection string for revocation database
	RevocationDBConnStr string `json:"revocation_db_str" mapstructure:"revocation_db_str"`
	// Database type for revocation database, supported: postgres, mysql
	RevocationDBType string `json:"revocation_db_type" mapstructure:"revocation_db_type"`
	// Credentials types for which revocation database should be hosted
	RevocationSettings irma.RevocationSettings `json:"revocation_settings" mapstructure:"revocation_settings"`

	// Production mode: enables safer and stricter defaults and config checking
	Production bool `json:"production" mapstructure:"production"`
}

// Check ensures that the Configuration is loaded, usable and free of errors.
func (conf *Configuration) Check() error {
	if conf.Logger == nil {
		conf.Logger = NewLogger(conf.Verbose, conf.Quiet, conf.LogJSON)
	}
	Logger = conf.Logger
	irma.SetLogger(conf.Logger)

	// loop to avoid repetetive err != nil line triplets
	for _, f := range []func() error{
		conf.verifyIrmaConf,
		conf.verifyPrivateKeys,
		conf.verifyURL,
		conf.verifyEmail,
		conf.verifyRevocation,
		conf.verifyStaticSessions,
		conf.verifyJwtPrivateKey,
	} {
		if err := f(); err != nil {
			_ = LogError(err)
			if conf.IrmaConfiguration != nil && conf.IrmaConfiguration.Revocation != nil {
				if e := conf.IrmaConfiguration.Revocation.Close(); e != nil {
					_ = LogError(e)
				}
			}
			return err
		}
	}

	return nil
}

func (conf *Configuration) HavePrivateKeys() bool {
	var err error
	for id := range conf.IrmaConfiguration.Issuers {
		if conf.IrmaConfiguration.SchemeManagers[id.SchemeManagerIdentifier()].Demo {
			continue
		}
		if _, err = conf.IrmaConfiguration.PrivateKeys.Latest(id); err == nil {
			return true
		}
	}
	return false
}

// helpers

func (conf *Configuration) verifyStaticSessions() error {
	conf.StaticSessionRequests = make(map[string]irma.RequestorRequest)
	if len(conf.StaticSessions) > 0 && conf.JwtRSAPrivateKey == nil && !conf.AllowUnsignedCallbacks {
		return errors.New("static sessions configured but no JWT private key is installed: either install JWT or enable allow_unsigned_callbacks in configuration")
	}
	for name, r := range conf.StaticSessions {
		if !regexp.MustCompile("^[a-zA-Z0-9_]+$").MatchString(name) {
			return errors.Errorf("static session name %s not allowed, must be alphanumeric", name)
		}
		j, err := json.Marshal(r)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		rrequest, err := ParseSessionRequest(j)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		action := rrequest.SessionRequest().Action()
		if action != irma.ActionDisclosing && action != irma.ActionSigning {
			return errors.Errorf("static session %s must be either a disclosing or signing session", name)
		}
		if rrequest.Base().CallbackURL == "" {
			return errors.Errorf("static session %s has no callback URL", name)
		}
		conf.StaticSessionRequests[name] = rrequest
	}
	return nil
}

func (conf *Configuration) verifyIrmaConf() error {
	if conf.IrmaConfiguration == nil {
		var (
			err    error
			exists bool
		)
		if conf.SchemesPath == "" {
			conf.SchemesPath = irma.DefaultSchemesPath() // Returns an existing path
		}
		if exists, err = common.PathExists(conf.SchemesPath); err != nil {
			return err
		}
		if !exists {
			return errors.Errorf("Nonexisting schemes_path provided: %s", conf.SchemesPath)
		}
		conf.Logger.WithField("schemes_path", conf.SchemesPath).Info("Determined schemes path")
		conf.IrmaConfiguration, err = irma.NewConfiguration(conf.SchemesPath, irma.ConfigurationOptions{
			Assets:              conf.SchemesAssetsPath,
			RevocationDBType:    conf.RevocationDBType,
			RevocationDBConnStr: conf.RevocationDBConnStr,
			RevocationSettings:  conf.RevocationSettings,
		})
		if err != nil {
			return err
		}
		if err = conf.IrmaConfiguration.ParseFolder(); err != nil {
			return err
		}
	}

	if len(conf.IrmaConfiguration.SchemeManagers) == 0 {
		conf.Logger.Infof("No schemes found in %s, downloading default (irma-demo and pbdf)", conf.SchemesPath)
		if err := conf.IrmaConfiguration.DownloadDefaultSchemes(); err != nil {
			return err
		}
	}
	if conf.SchemesUpdateInterval == 0 {
		conf.SchemesUpdateInterval = 60
	}
	if !conf.DisableSchemesUpdate {
		conf.IrmaConfiguration.AutoUpdateSchemes(uint(conf.SchemesUpdateInterval))
	}

	return nil
}

func (conf *Configuration) verifyPrivateKeys() error {
	if conf.IssuerPrivateKeysPath == "" {
		return nil
	}
	ring, err := irma.NewPrivateKeyRingFolder(conf.IssuerPrivateKeysPath, conf.IrmaConfiguration)
	if err != nil {
		return err
	}
	return conf.IrmaConfiguration.AddPrivateKeyRing(ring)
}

func (conf *Configuration) prepareRevocation(credid irma.CredentialTypeIdentifier) error {
	var sk *revocation.PrivateKey
	err := conf.IrmaConfiguration.PrivateKeys.Iterate(credid.IssuerIdentifier(), func(isk *gabi.PrivateKey) error {
		if !isk.RevocationSupported() {
			return nil
		}
		s, err := isk.RevocationKey()
		if err != nil {
			return errors.WrapPrefix(err, fmt.Sprintf("failed to load revocation private key %s-%d", credid, isk.Counter), 0)
		}
		sk = s
		exists, err := conf.IrmaConfiguration.Revocation.Exists(credid, isk.Counter)
		if err != nil {
			return errors.WrapPrefix(err, fmt.Sprintf("failed to check if accumulator exists for %s-%d", credid, isk.Counter), 0)
		}
		if !exists {
			conf.Logger.Warnf("Creating initial accumulator for %s-%d", credid, isk.Counter)
			if err := conf.IrmaConfiguration.Revocation.EnableRevocation(credid, sk); err != nil {
				return errors.WrapPrefix(err, fmt.Sprintf("failed create initial accumulator for %s-%d", credid, isk.Counter), 0)
			}
		}
		return nil
	})
	if err != nil {
		return err
	}
	if sk == nil {
		return errors.Errorf("revocation server mode enabled for %s but no private key installed", credid)
	}
	return nil
}

func (conf *Configuration) verifyRevocation() error {
	rev := conf.IrmaConfiguration.Revocation

	// viper lowercases configuration keys, so we have to un-lowercase them back.
	for id := range conf.IrmaConfiguration.CredentialTypes {
		lc := irma.NewCredentialTypeIdentifier(strings.ToLower(id.String()))
		if lc == id {
			continue
		}
		if s, ok := conf.RevocationSettings[lc]; ok {
			delete(conf.RevocationSettings, lc)
			conf.RevocationSettings[id] = s
		}
	}

	for credid, settings := range conf.RevocationSettings {
		if _, known := conf.IrmaConfiguration.CredentialTypes[credid]; !known {
			return errors.Errorf("unknown credential type %s in revocation settings", credid)
		}
		if settings.Authority {
			conf.Logger.Info("authoritative revocation server mode enabled for " + credid.String())
			if err := conf.prepareRevocation(credid); err != nil {
				return err
			}
		} else if settings.Server {
			conf.Logger.Info("revocation server mode enabled for " + credid.String())
		}
		if settings.Server {
			conf.Logger.Info("Being the revocation server for a credential type comes with special responsibilities. Failure can lead to all IRMA apps being unable to disclose credentials of this type. Read more at https://irma.app/docs/revocation/#issuer-responsibilities.")
		}
	}

	for credid, credtype := range conf.IrmaConfiguration.CredentialTypes {
		if !credtype.RevocationSupported() {
			continue
		}
		_, err := rev.Keys.PrivateKeyLatest(credid.IssuerIdentifier())
		haveSK := err == nil
		settings := conf.RevocationSettings[credid]
		if haveSK && (settings == nil || (settings.RevocationServerURL == "" && !settings.Server)) {
			message := "Revocation-supporting private key installed for %s, but no revocation server is configured: issuance sessions will always fail"
			if conf.IrmaConfiguration.SchemeManagers[credid.IssuerIdentifier().SchemeManagerIdentifier()].Demo {
				conf.Logger.Warnf(message, credid)
			} else {
				return errors.Errorf(message, credid)
			}
		}
		if settings != nil && settings.RevocationServerURL != "" {
			url := settings.RevocationServerURL
			if url[len(url)-1] == '/' {
				settings.RevocationServerURL = url[:len(url)-1]
			}
		}
	}

	return nil
}

func (conf *Configuration) verifyURL() error {
	if conf.URL != "" {
		if !strings.HasSuffix(conf.URL, "/") {
			conf.URL = conf.URL + "/"
		}
		if !strings.HasPrefix(conf.URL, "https://") {
			if !conf.Production || conf.DisableTLS {
				conf.DisableTLS = true
				conf.Logger.Warnf("TLS is not enabled on the url \"%s\" to which the IRMA app will connect. "+
					"Ensure that attributes are encrypted in transit by either enabling TLS or adding TLS in a reverse proxy.", conf.URL)
			} else {
				return errors.Errorf("Running without TLS in production mode is unsafe without a reverse proxy. " +
					"Either use a https:// URL or explicitly disable TLS.")
			}
		}
	} else {
		conf.Logger.Warn("No url parameter specified in configuration; unless an url is elsewhere prepended in the QR, the IRMA client will not be able to connect")
	}
	return nil
}

type serverInfo struct {
	Email   string `json:"email"`
	Version string `json:"version"`
}

func (conf *Configuration) verifyEmail() error {
	if conf.Email == "" {
		return nil
	}
	if !strings.Contains(conf.Email, "@") || strings.Contains(conf.Email, "\n") {
		return errors.New("Invalid email address specified")
	}
	t := irma.NewHTTPTransport("https://privacybydesign.foundation/", true)
	t.SetHeader("User-Agent", "irmaserver")
	data := &serverInfo{Email: conf.Email, Version: irma.Version}

	go func() {
		err := t.Post("serverinfo/", nil, data)
		if err != nil {
			conf.Logger.Trace("Failed to send email and version number:", err)
		}
	}()

	return nil
}

func (conf *Configuration) verifyJwtPrivateKey() error {
	if conf.JwtPrivateKey == "" && conf.JwtPrivateKeyFile == "" {
		return nil
	}

	keybytes, err := common.ReadKey(conf.JwtPrivateKey, conf.JwtPrivateKeyFile)
	if err != nil {
		return errors.WrapPrefix(err, "failed to read private key", 0)
	}

	conf.JwtRSAPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(keybytes)
	conf.Logger.Info("Private key parsed, JWT endpoints enabled")
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/alecaivazis/survey"
	"github.com/alecaivazis/survey/tests/util"
)

var table = []TestUtil.TestTableEntry{
	{
		"no default", &survey.Input{"Hello world", ""},
	},
	{
		"default", &survey.Input{"Hello world", "default"},
	},
}

func main() {
	TestUtil.RunTable(table)
}
<commit_msg>fixed bug in input tests<commit_after>package main

import (
	"github.com/alecaivazis/survey"
	"github.com/alecaivazis/survey/tests/util"
)

var val = ""

var table = []TestUtil.TestTableEntry{
	{
		"no default", &survey.Input{"Hello world", ""}, &val,
	},
	{
		"default", &survey.Input{"Hello world", "default"}, &val,
	},
}

func main() {
	TestUtil.RunTable(table)
}
<|endoftext|>