<commit_before>package main

import (
	"bufio"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"os"
	"strings"
	"unicode"
)

func main() {
	args := os.Args[1:]
	if len(args) < 2 {
		fmt.Println("Usage : easytags {file_name} {tag_name} \n example: easytags file.go json")
		return
	}

	GenerateTags(args[0], args[1])
}

// generates snake case json tags so that you won't need to write them. Can be also exteded to xml or sql tags
func GenerateTags(fileName, tagName string) {
	fset := token.NewFileSet() // positions are relative to fset
	// Parse the file given in arguments
	f, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)
	if err != nil {
		fmt.Println("Error")
		fmt.Println(err)
		return
	}

	// range over the objects in the scope of this generated AST and check for StructType. Then range over fields
	// contained in that struct.
	for _, d := range f.Scope.Objects {
		if d.Kind == ast.Typ {
			ts, ok := d.Decl.(*ast.TypeSpec)
			if !ok {
				fmt.Printf("Unknown type without TypeSec: %v", d)
				continue
			}

			x, ok := ts.Type.(*ast.StructType)
			if !ok {
				continue
			}
			for _, field := range x.Fields.List {
				if len(field.Names) == 0 {
					continue
				}
				// if tag for field doesn't exists, create one
				if field.Tag == nil {
					name := field.Names[0].String()
					field.Tag = &ast.BasicLit{}
					field.Tag.ValuePos = field.Type.Pos() + 1
					field.Tag.Kind = token.STRING
					field.Tag.Value = fmt.Sprintf("`%s:\"%s\"`", tagName, ToSnake(name))
				} else if !strings.Contains(field.Tag.Value, fmt.Sprintf("%s:", tagName)) {
					// if tag exists, but doesn't contain target tag
					name := field.Names[0].String()
					field.Tag.Value = fmt.Sprintf("`%s:\"%s\" %s`", tagName, ToSnake(name), strings.Replace(field.Tag.Value, "`", "", 2))
				}
			}
		}
	}

	// overwrite the file with modified version of ast.
	write, err := os.Create(fileName)
	if err != nil {
		fmt.Printf("Error opening file %v", err)
		return
	}
	defer write.Close()
	w := bufio.NewWriter(write)
	err = format.Node(w, fset, f)
	if err != nil {
		fmt.Printf("Error formating file", err)
		return
	}
	w.Flush()
}

// ToSnake convert the given string to snake case following the Golang format:
// acronyms are converted to lower-case and preceded by an underscore.
// Original source : https://gist.github.com/elwinar/14e1e897fdbe4d3432e1
func ToSnake(in string) string {
	runes := []rune(in)
	length := len(runes)

	var out []rune
	for i := 0; i < length; i++ {
		if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
			out = append(out, '_')
		}
		out = append(out, unicode.ToLower(runes[i]))
	}
	return string(out)
}
<commit_msg>added support for structs in inner scope and annoymous structs<commit_after>package main

import (
	"bufio"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"os"
	"strings"
	"unicode"
)

func main() {
	args := os.Args[1:]
	if len(args) < 2 {
		fmt.Println("Usage : easytags {file_name} {tag_name} \n example: easytags file.go json")
		return
	}

	GenerateTags(args[0], args[1])
}

// generates snake case json tags so that you won't need to write them. Can be also exteded to xml or sql tags
func GenerateTags(fileName, tagName string) {
	fset := token.NewFileSet() // positions are relative to fset
	// Parse the file given in arguments
	f, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)
	if err != nil {
		fmt.Println("Error")
		fmt.Println(err)
		return
	}

	// range over the objects in the scope of this generated AST and check for StructType. Then range over fields
	// contained in that struct.

	ast.Inspect(f, func(n ast.Node) bool {
		switch t := n.(type) {
		case *ast.StructType:
			processTags(t, tagName)
			return false
		}
		return true
	})

	// overwrite the file with modified version of ast.
	write, err := os.Create(fileName)
	if err != nil {
		fmt.Printf("Error opening file %v", err)
		return
	}
	defer write.Close()
	w := bufio.NewWriter(write)
	err = format.Node(w, fset, f)
	if err != nil {
		fmt.Printf("Error formating file %s", err)
		return
	}
	w.Flush()
}

func processTags(x *ast.StructType, tagName string) {
	for _, field := range x.Fields.List {
		if len(field.Names) == 0 {
			continue
		}
		// if tag for field doesn't exists, create one
		if field.Tag == nil {
			name := field.Names[0].String()
			field.Tag = &ast.BasicLit{}
			field.Tag.ValuePos = field.Type.Pos() + 1
			field.Tag.Kind = token.STRING
			field.Tag.Value = fmt.Sprintf("`%s:\"%s\"`", tagName, ToSnake(name))
		} else if !strings.Contains(field.Tag.Value, fmt.Sprintf("%s:", tagName)) {
			// if tag exists, but doesn't contain target tag
			name := field.Names[0].String()
			field.Tag.Value = fmt.Sprintf("`%s:\"%s\" %s`", tagName, ToSnake(name), strings.Replace(field.Tag.Value, "`", "", 2))
		}
	}
}

// ToSnake convert the given string to snake case following the Golang format:
// acronyms are converted to lower-case and preceded by an underscore.
// Original source : https://gist.github.com/elwinar/14e1e897fdbe4d3432e1
func ToSnake(in string) string {
	runes := []rune(in)
	length := len(runes)

	var out []rune
	for i := 0; i < length; i++ {
		if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
			out = append(out, '_')
		}
		out = append(out, unicode.ToLower(runes[i]))
	}
	return string(out)
}
<|endoftext|>


<commit_before>// Copyright 2014 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

/*
Package ecbrates 0.1.8
This package helps parse the ECB exchange rates and use it for an applications

Example 1:

	package main

	import (
		"fmt"
		"log"

		"github.com/takama/ecbrates"
	)

	func main() {
		r, err := ecbrates.New()
		if err != nil {
			log.Fatal("Error: ", err)
		}

		// Case 1: get dollar rate relative to euro
		if value, ok := r.Rate[ecbrates.USD].(string); ok {
			fmt.Println("Exchange rate", r.Date, ": EUR 1 -> USD", value)
		}

		// Case 2: convert of 100 euros to dollars
		if value, err := r.Convert(100, ecbrates.EUR, ecbrates.USD); err == nil {
			fmt.Println("Exchange rate", r.Date, ": EUR 100.0 -> USD", value)
		}
	}

Example 2:

	package main

	import (
		"fmt"
		"log"

		"github.com/takama/ecbrates"
	)

	func main() {
		rates, err := ecbrates.Load()
		if err != nil {
			log.Fatal("Error: ", err)
		}

		// Show history of exchange rates for EUR -> USD
		for _, r := range rates {
			if value, ok := r.Rate[ecbrates.USD].(string); ok {
				fmt.Println("Exchange rate", r.Date, ": EUR 1 -> USD", value)
			}
		}
	}

The European Central Bank exchange rates
*/
package ecbrates

import (
	"encoding/xml"
	"errors"
	"math"
	"net/http"
	"strconv"
)

// List of all supported currencies
const (
	AUD Currency = "AUD" // Australian Dollar (A$)
	BGN Currency = "BGN" // Bulgarian Lev (BGN)
	BRL Currency = "BRL" // Brazilian Real (R$)
	CAD Currency = "CAD" // Canadian Dollar (CA$)
	CHF Currency = "CHF" // Swiss Franc (CHF)
	CNY Currency = "CNY" // Chinese Yuan (CN¥)
	CZK Currency = "CZK" // Czech Republic Koruna (CZK)
	DKK Currency = "DKK" // Danish Krone (DKK)
	EUR Currency = "EUR" // Euro (€)
	GBP Currency = "GBP" // British Pound Sterling (£)
	HKD Currency = "HKD" // Hong Kong Dollar (HK$)
	HRK Currency = "HRK" // Croatian Kuna (HRK)
	HUF Currency = "HUF" // Hungarian Forint (HUF)
	IDR Currency = "IDR" // Indonesian Rupiah (IDR)
	ILS Currency = "ILS" // Israeli New Sheqel (₪)
	INR Currency = "INR" // Indian Rupee (Rs.)
	JPY Currency = "JPY" // Japanese Yen (¥)
	KRW Currency = "KRW" // South Korean Won (₩)
	LTL Currency = "LTL" // Lithuanian Litas (LTL)
	MXN Currency = "MXN" // Mexican Peso (MX$)
	MYR Currency = "MYR" // Malaysian Ringgit (MYR)
	NOK Currency = "NOK" // Norwegian Krone (NOK)
	NZD Currency = "NZD" // New Zealand Dollar (NZ$)
	PHP Currency = "PHP" // Philippine Peso (Php)
	PLN Currency = "PLN" // Polish Zloty (PLN)
	RON Currency = "RON" // Romanian Leu (RON)
	RUB Currency = "RUB" // Russian Ruble (RUB)
	SEK Currency = "SEK" // Swedish Krona (SEK)
	SGD Currency = "SGD" // Singapore Dollar (SGD)
	THB Currency = "THB" // Thai Baht (฿)
	TRY Currency = "TRY" // Turkish Lira (TRY)
	USD Currency = "USD" // US Dollar ($)
	ZAR Currency = "ZAR" // South African Rand (ZAR)

	ratesLastURL   = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"
	rates90daysUrl = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml"
)

// Currency type as a link to string
type Currency string

// Rates represent date and currency exchange rates
type Rates struct {
	Date string
	Rate map[Currency]interface{}
}

var Currencies = []Currency{
	AUD, BGN, BRL, CAD, CHF, CNY, CZK, DKK, EUR, GBP, HKD,
	HRK, HUF, IDR, ILS, INR, JPY, KRW, LTL, MXN, MYR, NOK,
	NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, USD, ZAR,
}

// New - create a new instance of the rates and fetch a data from ECB
func New() (*Rates, error) {
	r := new(Rates)
	err := r.fetchDay()
	return r, err
}

// Load - create a new instances of the rates and fetch a historical data from ECB
func Load() ([]Rates, error) {
	return fetch90days()
}

// Convert a value "from" one Currency -> "to" other Currency
func (r *Rates) Convert(value float64, from, to Currency) (float64, error) {
	if r.Rate[to] == nil || r.Rate[from] == nil {
		return 0, errors.New("Perhaps one of the values of currencies does not exist")
	}
	errorMessage := "Perhaps one of the values of currencies could not parsed correctly"
	strFrom, okFrom := r.Rate[from].(string)
	strTo, okTo := r.Rate[to].(string)
	if !okFrom || !okTo {
		return 0, errors.New(errorMessage)
	}
	vFrom, err := strconv.ParseFloat(strFrom, 32)
	if err != nil {
		return 0, errors.New(errorMessage)
	}
	vTo, err := strconv.ParseFloat(strTo, 32)
	if err != nil {
		return 0, errors.New(errorMessage)
	}
	return round64(value*round64(vTo, 4)/round64(vFrom, 4), 4), nil

}

// ECB XML envelope
type envelope struct {
	Data []struct {
		Date  string `xml:"time,attr"`
		Rates []struct {
			Currency string `xml:"currency,attr"`
			Rate     string `xml:"rate,attr"`
		} `xml:"Cube"`
	} `xml:"Cube>Cube"`
}

// Fetch an exchange rates
func (r *Rates) fetchDay() error {

	response, err := http.Get(ratesLastURL)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	var raw envelope

	if err := xml.NewDecoder(response.Body).Decode(&raw); err != nil {
		return err
	}

	for _, day := range raw.Data {
		r.Rate = make(map[Currency]interface{})

		// an exchange rates fetched relatively the EUR currency
		r.Rate[EUR] = "1"

		r.Date = day.Date

		for _, item := range day.Rates {
			r.Rate[Currency(item.Currency)] = item.Rate
		}
		break
	}

	return nil
}

// Fetch a lot of exchange rates
func fetch90days() ([]Rates, error) {

	var rates []Rates

	response, err := http.Get(rates90daysUrl)
	if err != nil {
		return rates, err
	}
	defer response.Body.Close()

	var raw envelope

	if err := xml.NewDecoder(response.Body).Decode(&raw); err != nil {
		return rates, err
	}

	for _, day := range raw.Data {

		var r Rates
		r.Rate = make(map[Currency]interface{})

		// an exchange rates fetched relatively the EUR currency
		r.Rate[EUR] = "1"

		r.Date = day.Date
		for _, item := range day.Rates {
			r.Rate[Currency(item.Currency)] = item.Rate
		}
		rates = append(rates, r)
	}
	return rates, nil
}

func round64(x float64, prec int) float64 {
	if math.IsNaN(x) || math.IsInf(x, 0) {
		return x
	}

	sign := 1.0
	if x < 0 {
		sign = -1
		x *= -1
	}

	var rounder float64
	pow := math.Pow(10, float64(prec))
	intermed := x * pow
	_, frac := math.Modf(intermed)

	if frac >= 0.5 {
		rounder = math.Ceil(intermed)
	} else {
		rounder = math.Floor(intermed)
	}

	return rounder / pow * sign
}
<commit_msg>added validation for currency type<commit_after>// Copyright 2015 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

/*
Package ecbrates 0.1.8
This package helps parse the ECB exchange rates and use it for an applications

Example 1:

	package main

	import (
		"fmt"
		"log"

		"github.com/takama/ecbrates"
	)

	func main() {
		r, err := ecbrates.New()
		if err != nil {
			log.Fatal("Error: ", err)
		}

		// Case 1: get dollar rate relative to euro
		if value, ok := r.Rate[ecbrates.USD].(string); ok {
			fmt.Println("Exchange rate", r.Date, ": EUR 1 -> USD", value)
		}

		// Case 2: convert of 100 euros to dollars
		if value, err := r.Convert(100, ecbrates.EUR, ecbrates.USD); err == nil {
			fmt.Println("Exchange rate", r.Date, ": EUR 100.0 -> USD", value)
		}
	}

Example 2:

	package main

	import (
		"fmt"
		"log"

		"github.com/takama/ecbrates"
	)

	func main() {
		rates, err := ecbrates.Load()
		if err != nil {
			log.Fatal("Error: ", err)
		}

		// Show history of exchange rates for EUR -> USD
		for _, r := range rates {
			if value, ok := r.Rate[ecbrates.USD].(string); ok {
				fmt.Println("Exchange rate", r.Date, ": EUR 1 -> USD", value)
			}
		}
	}

The European Central Bank exchange rates
*/
package ecbrates

import (
	"encoding/xml"
	"errors"
	"math"
	"net/http"
	"strconv"
)

// List of all supported currencies
const (
	AUD Currency = "AUD" // Australian Dollar (A$)
	BGN Currency = "BGN" // Bulgarian Lev (BGN)
	BRL Currency = "BRL" // Brazilian Real (R$)
	CAD Currency = "CAD" // Canadian Dollar (CA$)
	CHF Currency = "CHF" // Swiss Franc (CHF)
	CNY Currency = "CNY" // Chinese Yuan (CN¥)
	CZK Currency = "CZK" // Czech Republic Koruna (CZK)
	DKK Currency = "DKK" // Danish Krone (DKK)
	EUR Currency = "EUR" // Euro (€)
	GBP Currency = "GBP" // British Pound Sterling (£)
	HKD Currency = "HKD" // Hong Kong Dollar (HK$)
	HRK Currency = "HRK" // Croatian Kuna (HRK)
	HUF Currency = "HUF" // Hungarian Forint (HUF)
	IDR Currency = "IDR" // Indonesian Rupiah (IDR)
	ILS Currency = "ILS" // Israeli New Sheqel (₪)
	INR Currency = "INR" // Indian Rupee (Rs.)
	JPY Currency = "JPY" // Japanese Yen (¥)
	KRW Currency = "KRW" // South Korean Won (₩)
	LTL Currency = "LTL" // Lithuanian Litas (LTL)
	MXN Currency = "MXN" // Mexican Peso (MX$)
	MYR Currency = "MYR" // Malaysian Ringgit (MYR)
	NOK Currency = "NOK" // Norwegian Krone (NOK)
	NZD Currency = "NZD" // New Zealand Dollar (NZ$)
	PHP Currency = "PHP" // Philippine Peso (Php)
	PLN Currency = "PLN" // Polish Zloty (PLN)
	RON Currency = "RON" // Romanian Leu (RON)
	RUB Currency = "RUB" // Russian Ruble (RUB)
	SEK Currency = "SEK" // Swedish Krona (SEK)
	SGD Currency = "SGD" // Singapore Dollar (SGD)
	THB Currency = "THB" // Thai Baht (฿)
	TRY Currency = "TRY" // Turkish Lira (TRY)
	USD Currency = "USD" // US Dollar ($)
	ZAR Currency = "ZAR" // South African Rand (ZAR)

	ratesLastURL   = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"
	rates90daysURL = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml"
)

// Currency type as a link to string
type Currency string

// Rates represent date and currency exchange rates
type Rates struct {
	Date string
	Rate map[Currency]interface{}
}

// Currencies are valid values for currency
var Currencies = []Currency{
	AUD, BGN, BRL, CAD, CHF, CNY, CZK, DKK, EUR, GBP, HKD,
	HRK, HUF, IDR, ILS, INR, JPY, KRW, LTL, MXN, MYR, NOK,
	NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, USD, ZAR,
}

// IsValid check Currency for valid value
func (c Currency) IsValid() bool {
	for _, value := range Currencies {
		if value == c {
			return true
		}
	}

	return false
}

// New - create a new instance of the rates and fetch a data from ECB
func New() (*Rates, error) {
	r := new(Rates)
	err := r.fetchDay()
	return r, err
}

// Load - create a new instances of the rates and fetch a historical data from ECB
func Load() ([]Rates, error) {
	return fetch90days()
}

// Convert a value "from" one Currency -> "to" other Currency
func (r *Rates) Convert(value float64, from, to Currency) (float64, error) {
	if r.Rate[to] == nil || r.Rate[from] == nil {
		return 0, errors.New("Perhaps one of the values of currencies does not exist")
	}
	errorMessage := "Perhaps one of the values of currencies could not parsed correctly"
	strFrom, okFrom := r.Rate[from].(string)
	strTo, okTo := r.Rate[to].(string)
	if !okFrom || !okTo {
		return 0, errors.New(errorMessage)
	}
	vFrom, err := strconv.ParseFloat(strFrom, 32)
	if err != nil {
		return 0, errors.New(errorMessage)
	}
	vTo, err := strconv.ParseFloat(strTo, 32)
	if err != nil {
		return 0, errors.New(errorMessage)
	}
	return round64(value*round64(vTo, 4)/round64(vFrom, 4), 4), nil

}

// ECB XML envelope
type envelope struct {
	Data []struct {
		Date  string `xml:"time,attr"`
		Rates []struct {
			Currency string `xml:"currency,attr"`
			Rate     string `xml:"rate,attr"`
		} `xml:"Cube"`
	} `xml:"Cube>Cube"`
}

// Fetch an exchange rates
func (r *Rates) fetchDay() error {

	response, err := http.Get(ratesLastURL)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	var raw envelope

	if err := xml.NewDecoder(response.Body).Decode(&raw); err != nil {
		return err
	}

	for _, day := range raw.Data {
		r.Rate = make(map[Currency]interface{})

		// an exchange rates fetched relatively the EUR currency
		r.Rate[EUR] = "1"

		r.Date = day.Date

		for _, item := range day.Rates {
			r.Rate[Currency(item.Currency)] = item.Rate
		}
		break
	}

	return nil
}

// Fetch a lot of exchange rates
func fetch90days() ([]Rates, error) {

	var rates []Rates

	response, err := http.Get(rates90daysURL)
	if err != nil {
		return rates, err
	}
	defer response.Body.Close()

	var raw envelope

	if err := xml.NewDecoder(response.Body).Decode(&raw); err != nil {
		return rates, err
	}

	for _, day := range raw.Data {

		var r Rates
		r.Rate = make(map[Currency]interface{})

		// an exchange rates fetched relatively the EUR currency
		r.Rate[EUR] = "1"

		r.Date = day.Date
		for _, item := range day.Rates {
			r.Rate[Currency(item.Currency)] = item.Rate
		}
		rates = append(rates, r)
	}
	return rates, nil
}

func round64(x float64, prec int) float64 {
	if math.IsNaN(x) || math.IsInf(x, 0) {
		return x
	}

	sign := 1.0
	if x < 0 {
		sign = -1
		x *= -1
	}

	var rounder float64
	pow := math.Pow(10, float64(prec))
	intermed := x * pow
	_, frac := math.Modf(intermed)

	if frac >= 0.5 {
		rounder = math.Ceil(intermed)
	} else {
		rounder = math.Floor(intermed)
	}

	return rounder / pow * sign
}
<|endoftext|>


<commit_before>// History: Sep 03 13 Thibaut Colar Creation

package phpserializer

import(
  "log"
  "io"
  "fmt"
  "errors"
  "reflect"
  "strconv"
  "text/scanner"
)

// Encode / Decode serialized PHP objects from Go / Golang
// Note that there are apparently **NO** PHP serialization format specs, so this might not be perfect.
// Defaults to SnakeToUnderscore & UnderscoreToSnake for Name conversion, but can be replaced.
type PhpSerializer struct{
  EncodeNameConverter NameConverter
  DecodeNameConverter NameConverter
}

// Decode serialized PHP content into target object
// Note: Not closing the reader
func (p PhpSerializer) Decode(reader io.Reader, v interface{}) (err error) {
  if p.DecodeNameConverter == nil{
    p.DecodeNameConverter = &UnderscoreToSnake{}
  }
  s := &scanner.Scanner{}
  s.Init(reader)
  obj := reflect.ValueOf(v)
  err = p.decode(s, obj, false)
  return err
}

func (p PhpSerializer) Encode(v interface{}, writer io.Writer) (err error) {
  if p.EncodeNameConverter == nil{
    p.EncodeNameConverter = &SnakeToUnderscore{}
  }
  return p.encode(v, writer)
}

// #################### Internals ############################################

// Encode a Go object into a serialized PHP and write it to the writer
// Note: Not closing the writer
func (p PhpSerializer) encode(v interface{}, writer io.Writer) (err error) {
  if p.EncodeNameConverter == nil{
    p.EncodeNameConverter = &SnakeToUnderscore{}
  }
  t := reflect.TypeOf(v)
  switch t.Kind(){
    case reflect.Struct:
      val := reflect.ValueOf(v)
      err = p.encodeStruct(val , writer)
    case reflect.Int: // TODO: are int8, int16 etc.. separate ?
      i, _ := v.(int)
      err = p.encodeInt(i , writer)
    case reflect.String:
      str, _ := v.(string)
      err = p.encodeString(str , writer)
    case reflect.Map:
      val := reflect.ValueOf(v)
      err = p.encodeMap(val , writer)
    default:
      return errors.New(fmt.Sprintf("Unsupported element %s - of type %s",
                  reflect.ValueOf(v).String(),
                  t.String()))
  }
  return err
}

// Encode a structure
func (p PhpSerializer) encodeStruct(v reflect.Value, writer io.Writer) (err error) {
  size := v.NumField()
  p.write(writer, fmt.Sprintf("a:%d:{", size))
  for i := 0; i < size; i++ {
    // field name
    fieldName := p.EncodeNameConverter.Convert(v.Type().Field(i).Name)
    err = p.encodeString(fieldName, writer)
    if err != nil {return err}
    // field value
    field := v.Field(i)
    err = p.Encode(field.Interface(), writer)
    if err != nil {return err}
  }
  err = p.write(writer, "}")
  return err
}

// Encode a string
func (p PhpSerializer) encodeString(str string, writer io.Writer) (err error) {
  err = p.write(writer, fmt.Sprintf(`s:%d:"%s";`, len(str), str))
  return err
}

// Encode an int
func (p PhpSerializer) encodeInt(i int, writer io.Writer) (err error) {
  err = p.write(writer, fmt.Sprintf("i:%d;", i))
  return err
}

// Encode a map
func (p PhpSerializer) encodeMap(v reflect.Value, writer io.Writer) (err error) {
  size := len(v.MapKeys())
  p.write(writer, fmt.Sprintf("a:%d:{", size))
  for _, key := range v.MapKeys(){
    err = p.Encode(key.Interface(), writer);
    if err != nil {return err}
    val := v.MapIndex(key)
    err = p.Encode(val.Interface(), writer);
    if err != nil {return err}
  }
  err = p.write(writer, "}")
  return err
}

// write a string to the writer
func (p PhpSerializer) write(writer io.Writer, str string) (err error) {
  _, err = writer.Write([]byte(str))
  return err
}

// Decode the next element found by the scanner and set value into v
func (p PhpSerializer) decode(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
  c := s.Peek()
  switch(c) {
    case 'i': // int
      err = p.decodeInt(s, v, skipVal)
    case 's': // string
      err = p.decodeString(s, v, skipVal)
    case 'a': // map / struct  TODO: can be an array/list too ?
      err = p.decodeMap(s, v, skipVal)
    //case "d": // decimal TODO: 'd' object types (float)
    //case "b": // TODO: 'b' object types (bool ??)
    //case "O": // TODO: 'O' object types
    //case "N": // TODO: 'N' object types
    default:
      s.Scan()
      err = p.error(s, "Unexpected type %s", s.TokenText())
  }
  return err
}

// Decode a map into v (map or struct)
func (p PhpSerializer) decodeMap(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error){
  var size int
  if p.decodeToken(s, "a") != nil {return err}
  size, err = p.decodeSize(s)
  if err != nil {return err}
  if p.decodeToken(s, "{") != nil {return err}
  // decode key/value pairs
  for i := 0; i != size; i++ {
    var key interface{}
    err = p.decode(s, reflect.ValueOf(&key).Elem(), skipVal)
    if err != nil {return err}
    if skipVal {
      // this is just data we need to parse but not do anything with
        err = p.skipValue(s)
        if err != nil {return err}
    } else {
      if v.Kind() == reflect.Map{
        // targetting a map
        if v.IsNil() {
          // Initialize to an empty map if nil
          v.Set(reflect.MakeMap(v.Type()))
        }
        var mapVal interface{}
        err = p.decode(s, reflect.ValueOf(&mapVal).Elem(), skipVal)
        if err != nil {return err}
        v.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(mapVal))
      } else {
        // targetting a struct
        var k string
        k, _ = key.(string)
        if k == "" {return errors.New("Struct Key name is not a string.")}
        target := p.DecodeNameConverter.Convert(k)
        val := v.Elem().FieldByName(target)
        if val.IsValid() {
          err = p.decode(s, val, skipVal)
        } else {
          log.Printf("No targets field found named '%s', skipping it !", target)
          err = p.skipValue(s)
        }
        if err != nil {return err}
      }
    }
  }
  if p.decodeToken(s, "}") != nil {return err}
  return err
}

// Read but discard a value
func (p PhpSerializer) skipValue(s *scanner.Scanner) error {
  var dummy interface{}
  return p.decode(s, reflect.ValueOf(&dummy).Elem(), true)
}

// Decode an int int v
func (p PhpSerializer) decodeInt(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error){
  if p.decodeToken(s, "i") != nil {return err}
  if p.decodeToken(s, ":") != nil {return err}
  s.Scan(); text := s.TokenText()
  var i int
  nb, err := strconv.Atoi(text)
  if err != nil {return err}
  i = nb
  if p.decodeToken(s, ";") != nil {return err}
  if ! skipVal {v.Set(reflect.ValueOf(i))}
  return err
}

// Decode a string into v
func (p PhpSerializer) decodeString(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error){
  if p.decodeToken(s, "s") != nil {return err}
  _, err = p.decodeSize(s)
  if err != nil {return err}
  s.Scan(); str := s.TokenText()
  if str[0] != '"' || str[len(str) - 1] != '"'{
    return p.error(s, "Expected string to be surrounded by quotes !", str)
  }
  if p.decodeToken(s, ";") != nil {return err}
  str = str[1:len(str)-1]
  if ! skipVal {v.Set(reflect.ValueOf(str))}
  return err
}

// Decode a token and check against expected value
func (p PhpSerializer) decodeToken(s *scanner.Scanner, expected string) (err error){
  s.Scan(); text := s.TokenText()
  if text != expected {err = p.error(s, fmt.Sprintf("Expected '%s' !", expected), text)}
  return err
}

// Decode and return a size value (ex: ":12:")
func (p PhpSerializer) decodeSize(s *scanner.Scanner) (size int, err error){
  if p.decodeToken(s, ":") != nil {return size, err}
  s.Scan(); val := s.TokenText()
  size, err = strconv.Atoi(val)
  if err != nil {return size, p.error(s, err.Error(), val)}
  if p.decodeToken(s, ":") != nil {return size, err}
  return size, err
}

// Create a scanning error
func (p PhpSerializer) error(s *scanner.Scanner, msg string, lastTokenText string) error{
  pos := s.Pos()
  msg += fmt.Sprintf(". At Position(%d,%d), got '%s'", pos.Line, pos.Column, lastTokenText)
  return errors.New(msg)
}

<commit_msg>Fixed map support such as mas of mapfs of map ...  works<commit_after>// History: Sep 03 13 Thibaut Colar Creation

package phpserializer

import(
  "log"
  "io"
  "fmt"
  "errors"
  "reflect"
  "strconv"
  "text/scanner"
)

// Encode / Decode serialized PHP objects from Go / Golang
// Note that there are apparently **NO** PHP serialization format specs, so this might not be perfect.
// Defaults to SnakeToUnderscore & UnderscoreToSnake for Name conversion, but can be replaced.
type PhpSerializer struct{
  EncodeNameConverter NameConverter
  DecodeNameConverter NameConverter
}

// Decode serialized PHP content into target object
// Note: Not closing the reader
func (p PhpSerializer) Decode(reader io.Reader, v interface{}) (err error) {
  if p.DecodeNameConverter == nil{
    p.DecodeNameConverter = &UnderscoreToSnake{}
  }
  s := &scanner.Scanner{}
  s.Init(reader)
  obj := reflect.ValueOf(v)
  err = p.decode(s, obj, false)
  return err
}

func (p PhpSerializer) Encode(v interface{}, writer io.Writer) (err error) {
  if p.EncodeNameConverter == nil{
    p.EncodeNameConverter = &SnakeToUnderscore{}
  }
  return p.encode(v, writer)
}

// #################### Internals ############################################

// Encode a Go object into a serialized PHP and write it to the writer
// Note: Not closing the writer
func (p PhpSerializer) encode(v interface{}, writer io.Writer) (err error) {
  if p.EncodeNameConverter == nil{
    p.EncodeNameConverter = &SnakeToUnderscore{}
  }
  t := reflect.TypeOf(v)
  switch t.Kind(){
    case reflect.Struct:
      val := reflect.ValueOf(v)
      err = p.encodeStruct(val , writer)
    case reflect.Int: // TODO: are int8, int16 etc.. separate ?
      i, _ := v.(int)
      err = p.encodeInt(i , writer)
    case reflect.String:
      str, _ := v.(string)
      err = p.encodeString(str , writer)
    case reflect.Map:
      val := reflect.ValueOf(v)
      err = p.encodeMap(val , writer)
    default:
      return errors.New(fmt.Sprintf("Unsupported element %s - of type %s",
                  reflect.ValueOf(v).String(),
                  t.String()))
  }
  return err
}

// Encode a structure
func (p PhpSerializer) encodeStruct(v reflect.Value, writer io.Writer) (err error) {
  size := v.NumField()
  p.write(writer, fmt.Sprintf("a:%d:{", size))
  for i := 0; i < size; i++ {
    // field name
    fieldName := p.EncodeNameConverter.Convert(v.Type().Field(i).Name)
    err = p.encodeString(fieldName, writer)
    if err != nil {return err}
    // field value
    field := v.Field(i)
    err = p.Encode(field.Interface(), writer)
    if err != nil {return err}
  }
  err = p.write(writer, "}")
  return err
}

// Encode a string
func (p PhpSerializer) encodeString(str string, writer io.Writer) (err error) {
  err = p.write(writer, fmt.Sprintf(`s:%d:"%s";`, len(str), str))
  return err
}

// Encode an int
func (p PhpSerializer) encodeInt(i int, writer io.Writer) (err error) {
  err = p.write(writer, fmt.Sprintf("i:%d;", i))
  return err
}

// Encode a map
func (p PhpSerializer) encodeMap(v reflect.Value, writer io.Writer) (err error) {
  size := len(v.MapKeys())
  p.write(writer, fmt.Sprintf("a:%d:{", size))
  for _, key := range v.MapKeys(){
    err = p.Encode(key.Interface(), writer);
    if err != nil {return err}
    val := v.MapIndex(key)
    err = p.Encode(val.Interface(), writer);
    if err != nil {return err}
  }
  err = p.write(writer, "}")
  return err
}

// write a string to the writer
func (p PhpSerializer) write(writer io.Writer, str string) (err error) {
  _, err = writer.Write([]byte(str))
  return err
}

// Decode the next element found by the scanner and set value into v
func (p PhpSerializer) decode(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
  c := s.Peek()
  switch(c) {
    case 'i': // int
      err = p.decodeInt(s, v, skipVal)
    case 's': // string
      err = p.decodeString(s, v, skipVal)
    case 'a': // map / struct  TODO: can be an array/list too ?
      err = p.decodeMap(s, v, skipVal)
    case 'd': // decimal
      err = p.decodeFloat(s, v, skipVal)
    //case "b": // TODO: 'b' object types (bool ??)
    //case "O": // TODO: 'O' object types
    //case "N": // TODO: 'N' object types
    default:
      s.Scan()
      err = p.error(s, "Unexpected type %s", s.TokenText())
  }
  return err
}

// Decode a map into v (map or struct)
func (p PhpSerializer) decodeMap(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error){
  var size int
  if p.decodeToken(s, "a") != nil {return err}
  size, err = p.decodeSize(s)
  if err != nil {return err}
  if p.decodeToken(s, "{") != nil {return err}
  // decode key/value pairs
  for i := 0; i != size; i++ {
    var key interface{}
    err = p.decode(s, reflect.ValueOf(&key).Elem(), skipVal)
    if err != nil {return err}
    if skipVal {
      // this is just data we need to parse but not do anything with
        err = p.skipValue(s)
        if err != nil {return err}
    } else {
      if v.Kind() == reflect.Map{
        // targetting a map
        if v.IsNil() {
          // Initialize to an empty map if nil
          v.Set(reflect.MakeMap(v.Type()))
        }
        // Create an instance of the map value type
        mapVal := reflect.New(v.Type().Elem()).Elem()
        // and then decode that
        err = p.decode(s, mapVal, skipVal)
        if err != nil {return err}
        v.SetMapIndex(reflect.ValueOf(key), mapVal)
      } else {
        // targetting a struct
        var k string
        k, _ = key.(string)
        if k == "" {return errors.New(fmt.Sprintf("Struct Key '%s' is not a string.", key))}
        target := p.DecodeNameConverter.Convert(k)
        var val reflect.Value
        if v.Kind() == reflect.Struct {
          val = v.FieldByName(target)
        } else {
          val = v.Elem().FieldByName(target)
        }
        if val.IsValid() {
          err = p.decode(s, val, skipVal)
        } else {
          log.Printf("No targets field found named '%s', skipping it !", target)
          err = p.skipValue(s)
        }
        if err != nil {return err}
      }
    }
  }
  if p.decodeToken(s, "}") != nil {return err}
  return err
}

// Read but discard a value
func (p PhpSerializer) skipValue(s *scanner.Scanner) error {
  var dummy interface{}
  return p.decode(s, reflect.ValueOf(&dummy).Elem(), true)
}

// Decode an int int v
func (p PhpSerializer) decodeInt(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error){
  if p.decodeToken(s, "i") != nil {return err}
  if p.decodeToken(s, ":") != nil {return err}
  s.Scan(); text := s.TokenText()
  var i int
  nb, err := strconv.Atoi(text)
  if err != nil {return err}
  i = nb
  if p.decodeToken(s, ";") != nil {return err}
  if ! skipVal {v.Set(reflect.ValueOf(i))}
  return err
}

// Decode a float
func (p PhpSerializer) decodeFloat(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error){
  if p.decodeToken(s, "d") != nil {return err}
  if p.decodeToken(s, ":") != nil {return err}
  s.Scan(); text := s.TokenText()
  var f float64
  nb, err := strconv.ParseFloat(text, 64)
  if err != nil {return err}
  f = nb
  if p.decodeToken(s, ";") != nil {return err}
  if ! skipVal {v.Set(reflect.ValueOf(f))}
  return err
}

// Decode a string into v
func (p PhpSerializer) decodeString(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error){
  if p.decodeToken(s, "s") != nil {return err}
  size, err := p.decodeSize(s)
  if err != nil {return err}
  str := "";
  for len(str) < size + 2{
    str += string(s.Next())
  }
  if str[0] != '"' || str[len(str) - 1] != '"'{
    return p.error(s, "Expected string to be surrounded by quotes !", str)
  }
  if p.decodeToken(s, ";") != nil {return err}
  str = str[1:len(str)-1]
  if ! skipVal {v.Set(reflect.ValueOf(str))}
  return err
}

// Decode a token and check against expected value
func (p PhpSerializer) decodeToken(s *scanner.Scanner, expected string) (err error){
  s.Scan(); text := s.TokenText()
  if text != expected {err = p.error(s, fmt.Sprintf("Expected '%s' !", expected), text)}
  return err
}

// Decode and return a size value (ex: ":12:")
func (p PhpSerializer) decodeSize(s *scanner.Scanner) (size int, err error){
  if p.decodeToken(s, ":") != nil {return size, err}
  s.Scan(); val := s.TokenText()
  size, err = strconv.Atoi(val)
  if err != nil {return size, p.error(s, err.Error(), val)}
  if p.decodeToken(s, ":") != nil {return size, err}
  return size, err
}

// Create a scanning error
func (p PhpSerializer) error(s *scanner.Scanner, msg string, lastTokenText string) error{
  pos := s.Pos()
  msg += fmt.Sprintf(". At Position(%d,%d), got '%s'", pos.Line, pos.Column, lastTokenText)
  return errors.New(msg)
}

<|endoftext|>


<commit_before>package main

import "fmt"
import "bytes"
import "io"
import "io/ioutil"
import "errors"
import "encoding/json"
import "github.com/tj/go-debug"
import "github.com/PuerkitoBio/goquery"
import "net/http"

var noMfaError = errors.New("MFA required to use this tool")
var wrongMfaError = errors.New("No valid mfa congfigured for your account!")
var loginFailedError = errors.New("login failed")

var debugOkta = debug.Debug("oktad:okta")

type OktaLoginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
	Options  map[string]interface{}
}

type OktaLoginResponse struct {
	ExpiresAt    string
	SessionToken string
	Status       string
	StateToken   string
	Embedded     struct {
		Factors []OktaMfaFactor
	} `json:"_embedded"`
}

type OktaMfaFactor struct {
	Id         string
	FactorType string
	Provider   string
	Status     string
	Links      map[string]HalLink `json:"_links"`
}

type HalLink struct {
	Href string
}

func newLoginRequest(user, pass string) OktaLoginRequest {
	return OktaLoginRequest{
		user,
		pass,
		map[string]interface{}{
			"multiOptionalFactorEnroll": false,
			"warnBeforePasswordExpired": false,
		},
	}
}

// begins the login process by authenticating
// with okta
func login(cfg OktaConfig, user, pass, destArn string) (*OktaLoginResponse, error) {
	debugOkta("let the login dance begin")

	pr, err := http.NewRequest(
		http.MethodPost,
		fmt.Sprintf(
			// TODO: run some checks on this URL
			"%sapi/v1/authn",
			cfg.BaseURL,
		),
		getOktaLoginBody(cfg, user, pass),
	)

	if err != nil {
		debugOkta("caught an error building the first request to okta")
		return nil, err
	}

	ajs := "application/json"
	pr.Header.Set("Content-Type", ajs)
	pr.Header.Set("Accept", ajs)

	res, err := http.DefaultClient.Do(pr)
	if err != nil {
		debugOkta("caught error on first request to okta")
		return nil, err
	}

	if res.StatusCode != http.StatusOK {
		return nil, loginFailedError
	}

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	debugOkta("login response body %s", string(b))

	var ores OktaLoginResponse
	err = json.Unmarshal(b, &ores)
	if err != nil {
		return nil, err
	}

	return &ores, nil
}

// convenience function to get the request body for the okta request
// just need a buffer, man.
// or, really, an io.Reader
func getOktaLoginBody(cfg OktaConfig, user, pass string) io.Reader {
	return makeRequestBody(newLoginRequest(user, pass))
}

// turns a thing (a variable of some sort) into an io.Reader for
// reading into a request bodygit
func makeRequestBody(t interface{}) io.Reader {
	debug := debug.Debug("oktad:makeRequestBody")
	var b bytes.Buffer
	enc := json.NewEncoder(&b)
	err := enc.Encode(t)
	if err != nil {
		debug("Error encoding json! %s", err)
	}
	return &b
}

// pulls the factor we should use out of the response
func extractTokenFactor(ores *OktaLoginResponse) (*OktaMfaFactor, error) {
	factors := ores.Embedded.Factors
	if len(factors) == 0 {
		return nil, errors.New("MFA factors not present in response")
	}

	var tokenFactor OktaMfaFactor
	for _, factor := range factors {
		// need to assert that this is a map
		// since I don't know the structure enough
		// to make a struct for it
		if factor.FactorType == "token:software:totp" {
			debugOkta("software totp token found!")
			tokenFactor = factor
			break
		}
	}

	if tokenFactor.Id == "" {
		return nil, wrongMfaError
	}

	return &tokenFactor, nil
}

// do that mfa stuff
//
// returns the okta session token and an error if any)
func doMfa(ores *OktaLoginResponse, tf *OktaMfaFactor, mfaToken string) (string, error) {
	var url string
	var st string
	if ores == nil || tf == nil || mfaToken == "" {
		return st, errors.New("invalid params!")
	}

	vObj, ok := tf.Links["verify"]

	if !ok {
		return st, errors.New("Invalid token factor, no 'verify' link found")
	}

	type body struct {
		StateToken string `json:"stateToken"`
		PassCode   string `json:"passCode"`
	}

	url = vObj.Href
	debugOkta("mfa verify url is %s", url)
	req, _ := http.NewRequest(
		"POST",
		url,
		makeRequestBody(body{ores.StateToken, mfaToken}),
	)

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	res, err := http.DefaultClient.Do(req)

	if err != nil {
		return st, err
	}

	var mfares OktaLoginResponse

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return st, err
	}

	err = json.Unmarshal(b, &mfares)
	if err != nil {
		return st, err
	}

	debugOkta("response body when MFAing was %s", string(b))

	if mfares.Status != "SUCCESS" {
		return st, errors.New("MFA did not succeed!")
	}

	st = mfares.SessionToken

	return st, nil
}

// fetches the SAML we need for AWS round 1
func getSaml(cfg OktaConfig, sessionToken string) (string, error) {
	var saml string

	res, err := http.Get(
		fmt.Sprintf(
			"%s?%s=%s",
			cfg.AppURL,
			"onetimetoken",
			sessionToken,
		),
	)
	if err != nil {
		return saml, err
	}

	doc, err := goquery.NewDocumentFromResponse(res)
	if err != nil {
		return saml, err
	}
	sel := doc.Find(`input[name="SAMLResponse"]`)

	if sel.Length() < 1 {
		debugOkta("didn't find saml response element")
		return saml, errors.New("Invalid saml response!")
	}

	saml, ok := sel.First().Attr("value")
	if !ok {
		return saml, errors.New("Invalid saml response!")
	}

	return saml, nil
}
<commit_msg>add okta saml struct for getting the first ARN we need to use with STS<commit_after>package main

import "fmt"
import "bytes"
import "io"
import "io/ioutil"
import "errors"
import "encoding/json"
import "encoding/xml"
import "github.com/tj/go-debug"
import "github.com/PuerkitoBio/goquery"
import "net/http"

var noMfaError = errors.New("MFA required to use this tool")
var wrongMfaError = errors.New("No valid mfa congfigured for your account!")
var loginFailedError = errors.New("login failed")

var debugOkta = debug.Debug("oktad:okta")

type OktaLoginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
	Options  map[string]interface{}
}

type OktaLoginResponse struct {
	ExpiresAt    string
	SessionToken string
	Status       string
	StateToken   string
	Embedded     struct {
		Factors []OktaMfaFactor
	} `json:"_embedded"`
}

type OktaMfaFactor struct {
	Id         string
	FactorType string
	Provider   string
	Status     string
	Links      map[string]HalLink `json:"_links"`
}

type HalLink struct {
	Href string
}

// what the hell XML,
// what the hell.
type OktaSamlResponse struct {
	XMLname    xml.Name `xml:"Response"`
	Attributes []struct {
		Name       string `xml:",attr"`
		NameFormat string `xml:",attr"`
		Value      string `xml:"AttributeValue"`
	} `xml:"Assertion>AttributeStatement>Attribute"`
}

func newLoginRequest(user, pass string) OktaLoginRequest {
	return OktaLoginRequest{
		user,
		pass,
		map[string]interface{}{
			"multiOptionalFactorEnroll": false,
			"warnBeforePasswordExpired": false,
		},
	}
}

// begins the login process by authenticating
// with okta
func login(cfg OktaConfig, user, pass, destArn string) (*OktaLoginResponse, error) {
	debugOkta("let the login dance begin")

	pr, err := http.NewRequest(
		http.MethodPost,
		fmt.Sprintf(
			// TODO: run some checks on this URL
			"%sapi/v1/authn",
			cfg.BaseURL,
		),
		getOktaLoginBody(cfg, user, pass),
	)

	if err != nil {
		debugOkta("caught an error building the first request to okta")
		return nil, err
	}

	ajs := "application/json"
	pr.Header.Set("Content-Type", ajs)
	pr.Header.Set("Accept", ajs)

	res, err := http.DefaultClient.Do(pr)
	if err != nil {
		debugOkta("caught error on first request to okta")
		return nil, err
	}

	if res.StatusCode != http.StatusOK {
		return nil, loginFailedError
	}

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	debugOkta("login response body %s", string(b))

	var ores OktaLoginResponse
	err = json.Unmarshal(b, &ores)
	if err != nil {
		return nil, err
	}

	return &ores, nil
}

// convenience function to get the request body for the okta request
// just need a buffer, man.
// or, really, an io.Reader
func getOktaLoginBody(cfg OktaConfig, user, pass string) io.Reader {
	return makeRequestBody(newLoginRequest(user, pass))
}

// turns a thing (a variable of some sort) into an io.Reader for
// reading into a request bodygit
func makeRequestBody(t interface{}) io.Reader {
	debug := debug.Debug("oktad:makeRequestBody")
	var b bytes.Buffer
	enc := json.NewEncoder(&b)
	err := enc.Encode(t)
	if err != nil {
		debug("Error encoding json! %s", err)
	}
	return &b
}

// pulls the factor we should use out of the response
func extractTokenFactor(ores *OktaLoginResponse) (*OktaMfaFactor, error) {
	factors := ores.Embedded.Factors
	if len(factors) == 0 {
		return nil, errors.New("MFA factors not present in response")
	}

	var tokenFactor OktaMfaFactor
	for _, factor := range factors {
		// need to assert that this is a map
		// since I don't know the structure enough
		// to make a struct for it
		if factor.FactorType == "token:software:totp" {
			debugOkta("software totp token found!")
			tokenFactor = factor
			break
		}
	}

	if tokenFactor.Id == "" {
		return nil, wrongMfaError
	}

	return &tokenFactor, nil
}

// do that mfa stuff
//
// returns the okta session token and an error if any)
func doMfa(ores *OktaLoginResponse, tf *OktaMfaFactor, mfaToken string) (string, error) {
	var url string
	var st string
	if ores == nil || tf == nil || mfaToken == "" {
		return st, errors.New("invalid params!")
	}

	vObj, ok := tf.Links["verify"]

	if !ok {
		return st, errors.New("Invalid token factor, no 'verify' link found")
	}

	type body struct {
		StateToken string `json:"stateToken"`
		PassCode   string `json:"passCode"`
	}

	url = vObj.Href
	debugOkta("mfa verify url is %s", url)
	req, _ := http.NewRequest(
		"POST",
		url,
		makeRequestBody(body{ores.StateToken, mfaToken}),
	)

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	res, err := http.DefaultClient.Do(req)

	if err != nil {
		return st, err
	}

	var mfares OktaLoginResponse

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return st, err
	}

	err = json.Unmarshal(b, &mfares)
	if err != nil {
		return st, err
	}

	debugOkta("response body when MFAing was %s", string(b))

	if mfares.Status != "SUCCESS" {
		return st, errors.New("MFA did not succeed!")
	}

	st = mfares.SessionToken

	return st, nil
}

// fetches the SAML we need for AWS round 1
func getSaml(cfg OktaConfig, sessionToken string) (string, error) {
	var saml string

	res, err := http.Get(
		fmt.Sprintf(
			"%s?%s=%s",
			cfg.AppURL,
			"onetimetoken",
			sessionToken,
		),
	)
	if err != nil {
		return saml, err
	}

	doc, err := goquery.NewDocumentFromResponse(res)
	if err != nil {
		return saml, err
	}
	sel := doc.Find(`input[name="SAMLResponse"]`)

	if sel.Length() < 1 {
		debugOkta("didn't find saml response element")
		return saml, errors.New("Invalid saml response!")
	}

	saml, ok := sel.First().Attr("value")
	if !ok {
		return saml, errors.New("Invalid saml response!")
	}

	return saml, nil
}
<|endoftext|>


<commit_before>package main

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

	"github.com/gorilla/websocket"
	log "gopkg.in/inconshreveable/log15.v2"
)

type ChatMessage struct {
	Text      string `json:"text"`
	FirstName string `json:"firstName"`
	UserName  string `json:"userName"`
	UserId    int    `json:"userId"`
	Phone     string `json:"phone"`
	Webhook   string `json:"webhook"`
}

func readBody(req *http.Request, payload interface{}) error {
	decoder := json.NewDecoder(req.Body)
	err := decoder.Decode(payload)
	if err != nil {
		log.Error("failed to parse body", "err", err)
	}
	return err
}

func forwardMessages(rw http.ResponseWriter, req *http.Request) {
	var upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}

	conn, err := upgrader.Upgrade(rw, req, nil)
	if err != nil {
		log.Error("failed to upgrade to websockets", "err", err)
		return
	}

	go func() {
		defer conn.Close()

		for {
			botResponse := <-messages

			if err := conn.WriteJSON(&botResponse); err != nil {
				log.Error("failed to write data", "err", err)
				break
			}
		}
	}()

	go func() {
		defer conn.Close()

		for {
			var message ChatMessage
			if err := conn.ReadJSON(&message); err != nil {
				log.Error("failed to read data", "err", err)
				break
			} else {
				sendUpdateToBot(message)
			}
		}
	}()
}

func sendUpdateToBot(message ChatMessage) {
	update := Update{
		Message: Message{
			Text: message.Text,
			From: User{
				FirstName: message.FirstName,
				ID:        message.UserId,
				Username:  message.UserName,
			},
		},
		UpdateID: 0,
	}

	if _, err := sendJSON(message.Webhook, &update); err != nil {
		log.Error("failed to send update", "err", err)
	}
}

func mockTelegram(rw http.ResponseWriter, req *http.Request) {
	var botResponse BotResponse
	readBody(req, &botResponse)

	messages <- botResponse

	response := TelegramResponse{
		OK:     true,
		Result: Message{}, // FIXME
	}
	json.NewEncoder(rw).Encode(&response)
}
<commit_msg>Check that connection is open before writing to it<commit_after>package main

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

	"github.com/gorilla/websocket"
	log "gopkg.in/inconshreveable/log15.v2"
)

type ChatMessage struct {
	Text      string `json:"text"`
	FirstName string `json:"firstName"`
	UserName  string `json:"userName"`
	UserId    int    `json:"userId"`
	Phone     string `json:"phone"`
	Webhook   string `json:"webhook"`
}

func readBody(req *http.Request, payload interface{}) error {
	decoder := json.NewDecoder(req.Body)
	err := decoder.Decode(payload)
	if err != nil {
		log.Error("failed to parse body", "err", err)
	}
	return err
}

func forwardMessages(rw http.ResponseWriter, req *http.Request) {
	var upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}

	conn, err := upgrader.Upgrade(rw, req, nil)
	if err != nil {
		log.Error("failed to upgrade to websockets", "err", err)
		return
	}
	isOpen := true

	go func() {
		defer conn.Close()

		for {
			botResponse := <-messages

			if !isOpen {
				messages <- botResponse
				break
			}

			if err := conn.WriteJSON(&botResponse); err != nil {
				log.Error("failed to write data", "err", err)
				break
			}
		}
	}()

	go func() {
		defer func() {
			isOpen = false
			conn.Close()
		}()

		for {
			var message ChatMessage
			if err := conn.ReadJSON(&message); err != nil {
				log.Error("failed to read data", "err", err)
				break
			}
			sendUpdateToBot(message)
		}
	}()
}

func sendUpdateToBot(message ChatMessage) {
	update := Update{
		Message: Message{
			Text: message.Text,
			From: User{
				FirstName: message.FirstName,
				ID:        message.UserId,
				Username:  message.UserName,
			},
		},
		UpdateID: 0,
	}

	if _, err := sendJSON(message.Webhook, &update); err != nil {
		log.Error("failed to send update", "err", err)
	}
}

func mockTelegram(rw http.ResponseWriter, req *http.Request) {
	var botResponse BotResponse
	readBody(req, &botResponse)

	messages <- botResponse

	response := TelegramResponse{
		OK:     true,
		Result: Message{}, // FIXME
	}
	json.NewEncoder(rw).Encode(&response)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/jumoel/bitbucket-enforcer/gobucket"
	"github.com/jumoel/bitbucket-enforcer/log"
	dotenv "github.com/jumoel/bitbucket-enforcer/vendor/godotenv"
)

type branchManagement struct {
	PreventDelete []string
	PreventRebase []string
	AllowPushes   map[string]struct {
		Groups []string
		Users  []string
	}
}

type accessManagement struct {
	Users  map[string]string // usernames => permissions
	Groups map[string]string // groupnames => permissions
}

type repositorySettings struct {
	LandingPage      string
	Private          interface{}
	Forks            string
	IssueTracker     string
	DeployKeys       publicKeyList
	PostHooks        []string
	BranchManagement branchManagement
	AccessManagement accessManagement
}

type publicKey struct {
	Name string
	Key  string
}

type publicKeyList []publicKey

type bbServices []gobucket.Service
type matchType int

const (
	matchNone matchType = iota
	matchContent
	matchExact
)

const sleepTime = 1 * time.Second

var configDir = flag.String("configdir", "configs", "the folder containing repository configrations")
var verbose = flag.Bool("v", false, "print more output")
var bbAPI *gobucket.APIClient

func main() {
	log.SetPrefix("bitbucket-enforcer")

	flag.Parse()

	err := dotenv.Load()
	if err != nil {
		log.Notice(".env error", err)
	}

	bbUsername := os.Getenv("BITBUCKET_ENFORCER_USERNAME")
	bbKey := os.Getenv("BITBUCKET_ENFORCER_API_KEY")

	bbAPI = gobucket.New(bbUsername, bbKey)

	scanRepositories(bbUsername)

	//fmt.Println(bbAPI.RepositoriesChanged(bbUsername, ""))
	//fmt.Println(bbAPI.GetRepositories(bbUsername))
}

func scanRepositories(bbUsername string) {
	var enforcementMatcher = regexp.MustCompile(`-enforce(?:=([a-zA-Z0-9]+))?`)

	var lastEtag string
	var changed bool

	for _ = range time.Tick(sleepTime) {
		var err error
		if changed, lastEtag, err = bbAPI.RepositoriesChanged(bbUsername, lastEtag); err != nil {
			log.Error("Error determining if repository list has changed", err)
		}

		if !changed {
			if *verbose {
				log.Info("No repository changes, sleeping.")
			}
			continue
		}

		log.Info("Repository list changed")

		repos, err := bbAPI.GetRepositories(bbUsername)

		if err != nil {
			log.Error("Error getting repository list", err)
			continue
		}

		for _, repo := range repos {
			if strings.Contains(repo.Description, "-noenforce") {
				if *verbose {
					log.Info(fmt.Sprintf("Skipping <%s> because of '-noenforce'\n", repo.FullName))
				}
				continue
			}

			if strings.Contains(repo.Description, "-enforced") {
				if *verbose {
					log.Info(fmt.Sprintf("Skipping <%s> because of '-enforced'\n", repo.FullName))
				}
				continue
			}

			matches := enforcementMatcher.FindStringSubmatch(repo.Description)

			enforcementPolicy := "default"
			if len(matches) > 0 {
				enforcementPolicy = matches[1]
			}

			log.Info(fmt.Sprintf("Enforcing repo '%s' with policy '%s'", repo.FullName, enforcementPolicy))

			if repo.FullName == "omi-nu/testnutnutnutasdasd" {
				enforcePolicy(repo.FullName, enforcementPolicy)
			}
		}
	}
}

func enforcePolicy(repoFullname string, policyname string) {
	parts := strings.Split(repoFullname, "/")
	policy, err := parseConfig(policyname)

	if err != nil {
		log.Error(fmt.Sprintf("Error parsing parsing policy '%s': ", policyname), err)
	}

	if policy.Private != nil {
		if err := bbAPI.SetPrivacy(parts[0], parts[1], policy.Private.(bool)); err != nil {
			log.Warning("Error setting privacy: ", err)
		}
	}

	if policy.Forks != "" {
		if err := bbAPI.SetForks(parts[0], parts[1], policy.Forks); err != nil {
			log.Warning("Error fork policy: ", err)
		}
	}

	if policy.LandingPage != "" {
		if err := bbAPI.SetLandingPage(parts[0], parts[1], policy.LandingPage); err != nil {
			log.Warning("Error setting landing page: ", err)
		}
	}

	if len(policy.DeployKeys) > 0 {
		if err := enforceDeployKeys(parts[0], parts[1], policy.DeployKeys); err != nil {
			log.Warning("Error setting deploy keys: ", err)
		}
	}

	if len(policy.PostHooks) > 0 {
		if err := enforcePOSTHooks(parts[0], parts[1], policy.PostHooks); err != nil {
			log.Warning("Error setting POST hooks: ", err)
		}
	}

	if policy.IssueTracker != "" {
		if err := bbAPI.SetIssueTracker(parts[0], parts[1], policy.IssueTracker); err != nil {
			log.Warning("Error setting issue tracker: ", err)
		}
	}

	if err := enforceBranchManagement(parts[0], parts[1], policy.BranchManagement); err != nil {
		log.Warning("Error setting branch policies: ", err)
	}

	if err := enforceAccessManagement(parts[0], parts[1], policy.AccessManagement); err != nil {
		log.Warning("Error setting access policies: ", err)
	}
}

func enforceAccessManagement(owner string, repo string, policies accessManagement) error {
	for username, privilege := range policies.Users {
		if err := bbAPI.AddUserPrivilege(owner, repo, username, privilege); err != nil {
			return err
		}
	}

	for groupname, privilege := range policies.Groups {
		if err := bbAPI.AddGroupPrivilege(owner, repo, groupname, privilege); err != nil {
			return err
		}
	}

	return nil
}

func enforceBranchManagement(owner string, repo string, policies branchManagement) error {
	for _, branch := range policies.PreventDelete {
		if err := bbAPI.AddBranchRestriction(owner, repo, "delete", branch, nil, nil); err != nil {
			return err
		}
	}

	for _, branch := range policies.PreventRebase {
		if err := bbAPI.AddBranchRestriction(owner, repo, "force", branch, nil, nil); err != nil {
			return err
		}
	}

	for branch, permissions := range policies.AllowPushes {
		if err := bbAPI.AddBranchRestriction(owner, repo, "push", branch, permissions.Users, permissions.Groups); err != nil {
			return err
		}
	}

	return nil
}

func (hooks *bbServices) hasPOSTHook(URL string) bool {
	for _, hook := range *hooks {
		if hook.Service.Type == "POST" {
			for _, field := range hook.Service.Fields {
				if field.Name == "URL" && field.Value == URL {
					return true
				}
			}
		}
	}

	return false
}

func enforcePOSTHooks(owner string, repo string, hookURLs []string) error {
	hookList, err := bbAPI.GetServices(owner, repo)

	if err != nil {
		return err
	}

	var currentHooks bbServices = hookList

	for _, url := range hookURLs {
		if !currentHooks.hasPOSTHook(url) {
			if err := bbAPI.AddService(owner, repo, "POST", map[string]string{"URL": url}); err != nil {
				return err
			}
		}
	}

	return nil
}

func (keys *publicKeyList) hasKey(needle gobucket.DeployKey) (matchType, int) {
	for index, key := range *keys {
		if key.Key == needle.Key && key.Name == needle.Label {
			return matchExact, index
		} else if key.Key == needle.Key {
			return matchContent, index
		}
	}

	return matchNone, -1
}

/*
This method ensures the presence of all required keys.
- It removes keys with matching content but mismatching names. Afterwards they
  are added again, this time with the correct name.
- It adds keys that are not present.
- It doesn't remove keys that are present in Bitbucket but not in the policy
  file.
*/
func enforceDeployKeys(owner string, repo string, keys publicKeyList) error {
	currkeys, _ := bbAPI.GetDeployKeys(owner, repo)

	newkeys := make(publicKeyList, len(keys))
	copy(newkeys, keys)

	for _, key := range currkeys {
		match, matchIndex := newkeys.hasKey(key)

		if match == matchContent {
			// Delete the key from BB so it can be reuploaded with proper name
			if err := bbAPI.DeleteDeployKey(owner, repo, key.ID); err != nil {
				return err
			}
		} else if match == matchExact {
			// Don't waste time reuploading key as it is an exact match
			newkeys = append(newkeys[:matchIndex], newkeys[(matchIndex+1):]...)
		}
	}

	for _, key := range newkeys {
		if err := bbAPI.AddDeployKey(owner, repo, key.Name, key.Key); err != nil {
			return err
		}
	}

	return nil
}

func parseConfig(configFile string) (repositorySettings, error) {
	rawConfig, err := ioutil.ReadFile(fmt.Sprintf("%s/%s.json", *configDir, configFile))
	if err != nil {
		return repositorySettings{}, err
	}

	var config repositorySettings
	if err := json.Unmarshal(rawConfig, &config); err != nil {
		return repositorySettings{}, err
	}

	if *verbose {
		log.Info("Loaded config: ", config)
	}

	return config, nil
}
<commit_msg>More error handling<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/jumoel/bitbucket-enforcer/gobucket"
	"github.com/jumoel/bitbucket-enforcer/log"
	dotenv "github.com/jumoel/bitbucket-enforcer/vendor/godotenv"
)

type branchManagement struct {
	PreventDelete []string
	PreventRebase []string
	AllowPushes   map[string]struct {
		Groups []string
		Users  []string
	}
}

type accessManagement struct {
	Users  map[string]string // usernames => permissions
	Groups map[string]string // groupnames => permissions
}

type repositorySettings struct {
	LandingPage      string
	Private          interface{}
	Forks            string
	IssueTracker     string
	DeployKeys       publicKeyList
	PostHooks        []string
	BranchManagement branchManagement
	AccessManagement accessManagement
}

type publicKey struct {
	Name string
	Key  string
}

type publicKeyList []publicKey

type bbServices []gobucket.Service
type matchType int

const (
	matchNone matchType = iota
	matchContent
	matchExact
)

const sleepTime = 1 * time.Second

var configDir = flag.String("configdir", "configs", "the folder containing repository configrations")
var verbose = flag.Bool("v", false, "print more output")
var bbAPI *gobucket.APIClient

func main() {
	log.SetPrefix("bitbucket-enforcer")

	flag.Parse()

	err := dotenv.Load()
	if err != nil {
		log.Notice(".env error", err)
	}

	bbUsername := os.Getenv("BITBUCKET_ENFORCER_USERNAME")
	bbKey := os.Getenv("BITBUCKET_ENFORCER_API_KEY")

	bbAPI = gobucket.New(bbUsername, bbKey)

	scanRepositories(bbUsername)
}

func scanRepositories(bbUsername string) {
	var enforcementMatcher = regexp.MustCompile(`-enforce(?:=([a-zA-Z0-9]+))?`)

	var lastEtag string
	var changed bool

	for _ = range time.Tick(sleepTime) {
		var err error
		if changed, lastEtag, err = bbAPI.RepositoriesChanged(bbUsername, lastEtag); err != nil {
			log.Error("Error determining if repository list has changed", err)
		}

		if !changed {
			if *verbose {
				log.Info("No repository changes, sleeping.")
			}
			continue
		}

		log.Info("Repository list changed")

		repos, err := bbAPI.GetRepositories(bbUsername)

		if err != nil {
			log.Error("Error getting repository list", err)
			continue
		}

		for _, repo := range repos {
			if strings.Contains(repo.Description, "-noenforce") {
				if *verbose {
					log.Info(fmt.Sprintf("Skipping <%s> because of '-noenforce'\n", repo.FullName))
				}
				continue
			}

			if strings.Contains(repo.Description, "-enforced") {
				if *verbose {
					log.Info(fmt.Sprintf("Skipping <%s> because of '-enforced'\n", repo.FullName))
				}
				continue
			}

			matches := enforcementMatcher.FindStringSubmatch(repo.Description)

			enforcementPolicy := "default"
			if len(matches) > 0 {
				enforcementPolicy = matches[1]
			}

			log.Info(fmt.Sprintf("Enforcing repo '%s' with policy '%s'", repo.FullName, enforcementPolicy))

			if repo.FullName == "omi-nu/testnutnutnutasdasd" {
				parts := strings.Split(repo.FullName, "/")
				err := enforcePolicy(parts[0], parts[1], enforcementPolicy)

				if err != nil {
					log.Warning(fmt.Sprintf("Could not enforce policy '%s' on repo '%s'. Will be processed again next cycle. (%s)", enforcementPolicy, repo.FullName, err))
				} else {
					newDescription := strings.TrimSpace(fmt.Sprintf("%s\n\n-enforced", repo.Description))

					if err := bbAPI.SetDescription(parts[0], parts[1], newDescription); err != nil {
						log.Warning("Could not set description on repo '%s'. Will be processed again next cycle.", repo.FullName)
					}
				}
			}
		}
	}
}

func enforcePolicy(owner string, repo string, policyname string) error {
	policy, err := parseConfig(policyname)

	if err != nil {
		log.Error(fmt.Sprintf("Error parsing parsing policy '%s': ", policyname), err)
		return err
	}

	if policy.Private != nil {
		if err := bbAPI.SetPrivacy(owner, repo, policy.Private.(bool)); err != nil {
			log.Warning("Error setting privacy: ", err)
			return err
		}
	}

	if policy.Forks != "" {
		if err := bbAPI.SetForks(owner, repo, policy.Forks); err != nil {
			log.Warning("Error fork policy: ", err)
			return err
		}
	}

	if policy.LandingPage != "" {
		if err := bbAPI.SetLandingPage(owner, repo, policy.LandingPage); err != nil {
			log.Warning("Error setting landing page: ", err)
			return err
		}
	}

	if len(policy.DeployKeys) > 0 {
		if err := enforceDeployKeys(owner, repo, policy.DeployKeys); err != nil {
			log.Warning("Error setting deploy keys: ", err)
			return err
		}
	}

	if len(policy.PostHooks) > 0 {
		if err := enforcePOSTHooks(owner, repo, policy.PostHooks); err != nil {
			log.Warning("Error setting POST hooks: ", err)
			return err
		}
	}

	if policy.IssueTracker != "" {
		if err := bbAPI.SetIssueTracker(owner, repo, policy.IssueTracker); err != nil {
			log.Warning("Error setting issue tracker: ", err)
			return err
		}
	}

	if err := enforceBranchManagement(owner, repo, policy.BranchManagement); err != nil {
		log.Warning("Error setting branch policies: ", err)
		return err
	}

	if err := enforceAccessManagement(owner, repo, policy.AccessManagement); err != nil {
		log.Warning("Error setting access policies: ", err)
		return err
	}

	return nil
}

func enforceAccessManagement(owner string, repo string, policies accessManagement) error {
	for username, privilege := range policies.Users {
		if err := bbAPI.AddUserPrivilege(owner, repo, username, privilege); err != nil {
			return err
		}
	}

	for groupname, privilege := range policies.Groups {
		if err := bbAPI.AddGroupPrivilege(owner, repo, groupname, privilege); err != nil {
			return err
		}
	}

	return nil
}

func enforceBranchManagement(owner string, repo string, policies branchManagement) error {
	for _, branch := range policies.PreventDelete {
		if err := bbAPI.AddBranchRestriction(owner, repo, "delete", branch, nil, nil); err != nil {
			return err
		}
	}

	for _, branch := range policies.PreventRebase {
		if err := bbAPI.AddBranchRestriction(owner, repo, "force", branch, nil, nil); err != nil {
			return err
		}
	}

	for branch, permissions := range policies.AllowPushes {
		if err := bbAPI.AddBranchRestriction(owner, repo, "push", branch, permissions.Users, permissions.Groups); err != nil {
			return err
		}
	}

	return nil
}

func (hooks *bbServices) hasPOSTHook(URL string) bool {
	for _, hook := range *hooks {
		if hook.Service.Type == "POST" {
			for _, field := range hook.Service.Fields {
				if field.Name == "URL" && field.Value == URL {
					return true
				}
			}
		}
	}

	return false
}

func enforcePOSTHooks(owner string, repo string, hookURLs []string) error {
	hookList, err := bbAPI.GetServices(owner, repo)

	if err != nil {
		return err
	}

	var currentHooks bbServices = hookList

	for _, url := range hookURLs {
		if !currentHooks.hasPOSTHook(url) {
			if err := bbAPI.AddService(owner, repo, "POST", map[string]string{"URL": url}); err != nil {
				return err
			}
		}
	}

	return nil
}

func (keys *publicKeyList) hasKey(needle gobucket.DeployKey) (matchType, int) {
	for index, key := range *keys {
		if key.Key == needle.Key && key.Name == needle.Label {
			return matchExact, index
		} else if key.Key == needle.Key {
			return matchContent, index
		}
	}

	return matchNone, -1
}

/*
This method ensures the presence of all required keys.
- It removes keys with matching content but mismatching names. Afterwards they
  are added again, this time with the correct name.
- It adds keys that are not present.
- It doesn't remove keys that are present in Bitbucket but not in the policy
  file.
*/
func enforceDeployKeys(owner string, repo string, keys publicKeyList) error {
	currkeys, _ := bbAPI.GetDeployKeys(owner, repo)

	newkeys := make(publicKeyList, len(keys))
	copy(newkeys, keys)

	for _, key := range currkeys {
		match, matchIndex := newkeys.hasKey(key)

		if match == matchContent {
			// Delete the key from BB so it can be reuploaded with proper name
			if err := bbAPI.DeleteDeployKey(owner, repo, key.ID); err != nil {
				return err
			}
		} else if match == matchExact {
			// Don't waste time reuploading key as it is an exact match
			newkeys = append(newkeys[:matchIndex], newkeys[(matchIndex+1):]...)
		}
	}

	for _, key := range newkeys {
		if err := bbAPI.AddDeployKey(owner, repo, key.Name, key.Key); err != nil {
			return err
		}
	}

	return nil
}

func parseConfig(configFile string) (repositorySettings, error) {
	rawConfig, err := ioutil.ReadFile(fmt.Sprintf("%s/%s.json", *configDir, configFile))
	if err != nil {
		return repositorySettings{}, err
	}

	var config repositorySettings
	if err := json.Unmarshal(rawConfig, &config); err != nil {
		return repositorySettings{}, err
	}

	if *verbose {
		log.Info("Loaded config: ", config)
	}

	return config, nil
}
<|endoftext|>


<commit_before>package main

import (
	"strings"
)

// A list of keys we don't want to deal with
var IGNORED_KEYS = map[string]bool{
	// direnv env config
	"DIRENV_CONFIG": true,
	"DIRENV_BASH":   true,

	"COMP_WORDBREAKS": true, // Avoids segfaults in bash
	"PS1":             true, // PS1 should not be exported, fixes problem in bash

	// variables that should change freely
	"OLDPWD": true,
	"PWD":    true,
	"SHELL":  true,
	"SHLVL":  true,
	"_":      true,
}

type EnvDiff struct {
	Prev map[string]string `json:"p"`
	Next map[string]string `json:"n"`
}

func BuildEnvDiff(e1, e2 Env) *EnvDiff {
	diff := &EnvDiff{make(map[string]string), make(map[string]string)}

	for key := range e1 {
		if e2[key] != e1[key] && !IgnoredEnv(key) {
			diff.Prev[key] = e1[key]
		}
	}

	for key := range e2 {
		if e2[key] != e1[key] && !IgnoredEnv(key) {
			diff.Next[key] = e2[key]
		}
	}

	return diff
}

func LoadEnvDiff(base64env string) (diff *EnvDiff, err error) {
	diff = new(EnvDiff)
	err = unmarshal(base64env, diff)
	return
}

func (self *EnvDiff) Any() bool {
	return len(self.Prev) > 0 || len(self.Next) > 0
}

func (self *EnvDiff) ToShell(shell Shell) string {
	str := ""

	for key := range self.Prev {
		_, ok := self.Next[key]
		if !ok {
			str += shell.Unset(key)
		}
	}

	for key, value := range self.Next {
		str += shell.Export(key, value)
	}

	return str
}

func (self *EnvDiff) Patch(env Env) (newEnv Env) {
	newEnv = make(Env)

	for k, v := range env {
		newEnv[k] = v
	}

	for key := range self.Prev {
		delete(newEnv, key)
	}

	for key, value := range self.Next {
		newEnv[key] = value
	}

	return newEnv
}

func (self *EnvDiff) Reverse() *EnvDiff {
	return &EnvDiff{self.Next, self.Prev}
}

func (self *EnvDiff) Serialize() string {
	return marshal(self)
}

//// Utils

func IgnoredEnv(key string) bool {
	if strings.HasPrefix(key, "__fish") {
		return true
	}
	_, found := IGNORED_KEYS[key]
	return found
}
<commit_msg>Fix issue #114 - work for blank envs<commit_after>package main

import (
	"strings"
)

// A list of keys we don't want to deal with
var IGNORED_KEYS = map[string]bool{
	// direnv env config
	"DIRENV_CONFIG": true,
	"DIRENV_BASH":   true,

	"COMP_WORDBREAKS": true, // Avoids segfaults in bash
	"PS1":             true, // PS1 should not be exported, fixes problem in bash

	// variables that should change freely
	"OLDPWD": true,
	"PWD":    true,
	"SHELL":  true,
	"SHLVL":  true,
	"_":      true,
}

type EnvDiff struct {
	Prev map[string]string `json:"p"`
	Next map[string]string `json:"n"`
}

func BuildEnvDiff(e1, e2 Env) *EnvDiff {
	diff := &EnvDiff{make(map[string]string), make(map[string]string)}

	in := func(key string, e Env) bool {
		_, ok := e[key]
		return ok
	}

	for key := range e1 {
		if IgnoredEnv(key) {
			continue
		}
		if e2[key] != e1[key] || !in(key, e2) {
			diff.Prev[key] = e1[key]
		}
	}

	for key := range e2 {
		if IgnoredEnv(key) {
			continue
		}
		if e2[key] != e1[key] || !in(key, e1) {
			diff.Next[key] = e2[key]
		}
	}

	return diff
}

func LoadEnvDiff(base64env string) (diff *EnvDiff, err error) {
	diff = new(EnvDiff)
	err = unmarshal(base64env, diff)
	return
}

func (self *EnvDiff) Any() bool {
	return len(self.Prev) > 0 || len(self.Next) > 0
}

func (self *EnvDiff) ToShell(shell Shell) string {
	str := ""

	for key := range self.Prev {
		_, ok := self.Next[key]
		if !ok {
			str += shell.Unset(key)
		}
	}

	for key, value := range self.Next {
		str += shell.Export(key, value)
	}

	return str
}

func (self *EnvDiff) Patch(env Env) (newEnv Env) {
	newEnv = make(Env)

	for k, v := range env {
		newEnv[k] = v
	}

	for key := range self.Prev {
		delete(newEnv, key)
	}

	for key, value := range self.Next {
		newEnv[key] = value
	}

	return newEnv
}

func (self *EnvDiff) Reverse() *EnvDiff {
	return &EnvDiff{self.Next, self.Prev}
}

func (self *EnvDiff) Serialize() string {
	return marshal(self)
}

//// Utils

func IgnoredEnv(key string) bool {
	if strings.HasPrefix(key, "__fish") {
		return true
	}
	_, found := IGNORED_KEYS[key]
	return found
}
<|endoftext|>


<commit_before>package envplate

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
)

const (
	NoDefaultDefined = ""
	NoKeyDefined     = ""
)

var exp = regexp.MustCompile(`(.?)\$\{(.+?)(?:\:\-(.+?))?\}`)

func Apply(globs []string) {

	matches := false

	for _, pattern := range globs {

		files, err := filepath.Glob(pattern)

		if err != nil {
			Log(ERROR, err.Error())
		}

		for _, name := range files {

			if info, _ := os.Stat(name); info.IsDir() {
				continue
			}

			matches = true

			if err := parse(name); err != nil {
				Log(ERROR, "Error while parsing '%s': %v", name, err)
			}

		}

	}

	if !matches {
		Log(ERROR, "Zero files matched passed globs '%v'", globs)
	}

}

func createBackup(file string) error {

	source, err := os.Open(file)

	if err != nil {
		return err
	}

	defer source.Close()

	target, err := os.Create(fmt.Sprintf("%s.bak", file))

	if err != nil {
		return err
	}

	defer target.Close()

	if _, err := io.Copy(target, source); err != nil {
		return err
	}

	if err := os.Chmod(target.Name(), filemode(source.Name())); err != nil {
		return err
	}

	return nil

}

func parse(file string) error {

	content, err := ioutil.ReadFile(file)

	if err != nil {
		return fmt.Errorf("Cannot open %s: %v", file, err)
	}

	Log(DEBUG, "Parsing environment references in '%s'", file)

	parsed := exp.ReplaceAllStringFunc(string(content), func(match string) string {

		var (
			pre, key, def = capture(match)
			value    = os.Getenv(key)
		)

                if pre == `\` {

                  return match[1:len(match)]

                }

		if value == NoKeyDefined {

			if def == NoDefaultDefined {
				Log(ERROR, "'%s' requires undeclared environment variable '%s', no default is given", file, key)
			} else {

				if Config.Strict {
					Log(ERROR, "'%s' requires undeclared environment variable '%s', but cannot use default '%s' (strict-mode)", file, key, def)
				} else {
					Log(DEBUG, "'%s' requires undeclared environment variable '%s', using default '%s'", file, key, def)
					value = def
				}

			}

		} else {
			Log(DEBUG, "Expanding reference to '%s' to value '%s'", key, value)
		}

		return pre + value

	})

	if Config.DryRun {
		Log(INFO, "Expanding all references in '%s' would look like this:\n%s", file, parsed)
	} else {

		if Config.Backup {

			Log(DEBUG, "Creating backup of '%s'", file)

			if err := createBackup(file); err != nil {
				return err
			}

		}

		return ioutil.WriteFile(file, []byte(parsed), filemode(file))

	}

	return nil

}

func capture(s string) (pre, key, def string) {

	matches := exp.FindStringSubmatch(s)

        pre = matches[1]
	key = matches[2]
	def = matches[3]

	return pre, key, def

}

func filemode(file string) os.FileMode {

	fileinfo, err := os.Stat(file)

	if err != nil {
		Log(ERROR, "Cannot stat '%s': %v", file, err)
	}

	return fileinfo.Mode()

}
<commit_msg>Improve escaping<commit_after>package envplate

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
)

const (
	NoDefaultDefined    = ""
	NoKeyDefined        = ""
	NotAnEscapeSequence = ""
)

var exp = regexp.MustCompile(`(\\*)\$\{(.+?)(?:\:\-(.+?))?\}`)

func Apply(globs []string) {

	matches := false

	for _, pattern := range globs {

		files, err := filepath.Glob(pattern)

		if err != nil {
			Log(ERROR, err.Error())
		}

		for _, name := range files {

			if info, _ := os.Stat(name); info.IsDir() {
				continue
			}

			matches = true

			if err := parse(name); err != nil {
				Log(ERROR, "Error while parsing '%s': %v", name, err)
			}

		}

	}

	if !matches {
		Log(ERROR, "Zero files matched passed globs '%v'", globs)
	}

}

func createBackup(file string) error {

	source, err := os.Open(file)

	if err != nil {
		return err
	}

	defer source.Close()

	target, err := os.Create(fmt.Sprintf("%s.bak", file))

	if err != nil {
		return err
	}

	defer target.Close()

	if _, err := io.Copy(target, source); err != nil {
		return err
	}

	if err := os.Chmod(target.Name(), filemode(source.Name())); err != nil {
		return err
	}

	return nil

}

func parse(file string) error {

	content, err := ioutil.ReadFile(file)

	if err != nil {
		return fmt.Errorf("Cannot open %s: %v", file, err)
	}

	Log(DEBUG, "Parsing environment references in '%s'", file)

	parsed := exp.ReplaceAllStringFunc(string(content), func(match string) string {

		var (
			esc, key, def = capture(match)
			value         = os.Getenv(key)
		)

		if len(esc)%2 == 1 {

			escaped := escape(match)

			if escaped == NotAnEscapeSequence {

				Log(ERROR, "Tried to escape '%s', but was no escape sequence")

			}

			return escaped

		}

		if value == NoKeyDefined {

			if def == NoDefaultDefined {
				Log(ERROR, "'%s' requires undeclared environment variable '%s', no default is given", file, key)
			} else {

				if Config.Strict {
					Log(ERROR, "'%s' requires undeclared environment variable '%s', but cannot use default '%s' (strict-mode)", file, key, def)
				} else {
					Log(DEBUG, "'%s' requires undeclared environment variable '%s', using default '%s'", file, key, def)
					value = def
				}

			}

		} else {
			Log(DEBUG, "Expanding reference to '%s' to value '%s'", key, value)
		}

		if len(esc) > 0 {

			value = esc[:len(esc)/2] + value

		}

		return value

	})

	if Config.DryRun {
		Log(INFO, "Expanding all references in '%s' would look like this:\n%s", file, parsed)
	} else {

		if Config.Backup {

			Log(DEBUG, "Creating backup of '%s'", file)

			if err := createBackup(file); err != nil {
				return err
			}

		}

		return ioutil.WriteFile(file, []byte(parsed), filemode(file))

	}

	return nil

}

func capture(s string) (esc, key, def string) {

	matches := exp.FindStringSubmatch(s)

	esc = matches[1]
	key = matches[2]
	def = matches[3]

	return esc, key, def

}

func filemode(file string) os.FileMode {

	fileinfo, err := os.Stat(file)

	if err != nil {
		Log(ERROR, "Cannot stat '%s': %v", file, err)
	}

	return fileinfo.Mode()

}

func escape(s string) (escaped string) {

	expEscaped := regexp.MustCompile(`(\\+)(.*)`)
	matches := expEscaped.FindStringSubmatch(s)

	if matches == nil {

		return NotAnEscapeSequence

	}

	bss := matches[1]

	if len(bss)%2 != 1 {

		return NotAnEscapeSequence

	}

	parsedBss := bss[:len(bss)-1][:(len(bss)-1)/2]

	escaped = parsedBss + matches[2]

	Log(DEBUG, "Substituting escaped sequence '%s' with '%s'", s, escaped)

	return escaped

}
<|endoftext|>


<commit_before>// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"

	"github.com/gorilla/websocket"
)

type OnMessage func(fromAddr string, msg []byte)

// Client is a middleman between the websocket connection and the hub.
type RequestMsg struct {
	// The websocket connection.
	conn *websocket.Conn
	msg  []byte
}

// hub maintains the set of active clients and broadcasts messages to the
// clients.
type Hub struct {
	// Registered clients.
	clients map[*Client]bool

	// Inbound messages from the clients.
	request chan *RequestMsg

	// Register requests from the clients.
	register chan *Client

	// Unregister requests from clients.
	unregister chan *Client
}

func newHub() *Hub {
	return &Hub{
		request:    make(chan *RequestMsg),
		register:   make(chan *Client),
		unregister: make(chan *Client),
		clients:    make(map[*Client]bool),
	}
}

func (h *Hub) sendMessageToAddr(sendToIP string, message []byte) {
	// TODO: make this a hashtable to avoid iterating over all clients
	for client := range h.clients {
		fmt.Println("Finding client to forward to...")
		if client.conn.RemoteAddr().String() == sendToIP {
			client.send <- message
			fmt.Println("...done")
			break
		}
	}
}

func (h *Hub) run(cb OnMessage) {
	for {
		select {
		case client := <-h.register:
			h.clients[client] = true
		case client := <-h.unregister:
			if _, ok := h.clients[client]; ok {
				delete(h.clients, client)
				close(client.send)
			}
		case req := <-h.request:
			cb(req.conn.RemoteAddr().String(), req.msg)
		}
	}
}
<commit_msg>print clients<commit_after>// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"

	"github.com/gorilla/websocket"
)

type OnMessage func(fromAddr string, msg []byte)

// Client is a middleman between the websocket connection and the hub.
type RequestMsg struct {
	// The websocket connection.
	conn *websocket.Conn
	msg  []byte
}

// hub maintains the set of active clients and broadcasts messages to the
// clients.
type Hub struct {
	// Registered clients.
	clients map[*Client]bool

	// Inbound messages from the clients.
	request chan *RequestMsg

	// Register requests from the clients.
	register chan *Client

	// Unregister requests from clients.
	unregister chan *Client
}

func newHub() *Hub {
	return &Hub{
		request:    make(chan *RequestMsg),
		register:   make(chan *Client),
		unregister: make(chan *Client),
		clients:    make(map[*Client]bool),
	}
}

func (h *Hub) sendMessageToAddr(sendToIP string, message []byte) {
	// TODO: make this a hashtable to avoid iterating over all clients

	fmt.Println("Finding client to forward to...")
	for client := range h.clients {
		fmt.Println("..." + client.conn.RemoteAddr().String())
		if client.conn.RemoteAddr().String() == sendToIP {
			client.send <- message
			fmt.Println("...done")
			break
		}
	}
}

func (h *Hub) run(cb OnMessage) {
	for {
		select {
		case client := <-h.register:
			h.clients[client] = true
		case client := <-h.unregister:
			if _, ok := h.clients[client]; ok {
				delete(h.clients, client)
				close(client.send)
			}
		case req := <-h.request:
			cb(req.conn.RemoteAddr().String(), req.msg)
		}
	}
}
<|endoftext|>