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

package beat

import (
	"fmt"
	"io/ioutil"
	"time"

	"github.com/elastic/beats/libbeat/beat"
	"github.com/elastic/beats/libbeat/cfgfile"
	//"github.com/elastic/beats/libbeat/common"
	"github.com/elastic/beats/libbeat/logp"
	"github.com/elastic/beats/libbeat/publisher"
	"github.com/mheese/go-systemd/sdjournal"
)

var SeekPositions = map[string]bool{
	"cursor": true,
	"head":   true,
	"tail":   true,
}

var SeekFallbackPositions = map[string]bool{
	"none": true,
	"head": true,
	"tail": true,
}

// Journalbeat is the main Journalbeat struct
type Journalbeat struct {
	JbConfig             ConfigSettings
	writeCursorState     bool
	cursorStateFile      string
	cursorFlushSecs      int
	seekPosition         string
	cursorSeekFallback   string
	convertToNumbers     bool
	cleanFieldnames      bool
	moveMetadataLocation string
	defaultType          string

	jr   *sdjournal.JournalReader
	done chan int
	recv chan sdjournal.JournalEntry

	cursorChan      chan string
	cursorChanFlush chan int

	output publisher.Client
}

// New creates a new Journalbeat object and returns. Should be done once in main
func New() *Journalbeat {
	logp.Info("New Journalbeat")
	return &Journalbeat{}
}

// Config parses configuration data and prepares for Setup
func (jb *Journalbeat) Config(b *beat.Beat) error {
	logp.Info("Journalbeat Config")
	err := cfgfile.Read(&jb.JbConfig, "")
	if err != nil {
		logp.Err("Error reading configuration file: %v", err)
		return err
	}

	if jb.JbConfig.Input.WriteCursorState != nil {
		jb.writeCursorState = *jb.JbConfig.Input.WriteCursorState
	} else {
		jb.writeCursorState = false
	}

	if jb.JbConfig.Input.CursorStateFile != nil {
		jb.cursorStateFile = *jb.JbConfig.Input.CursorStateFile
	} else {
		jb.cursorStateFile = ".journalbeat-cursor-state"
	}

	if jb.JbConfig.Input.FlushCursorSecs != nil {
		jb.cursorFlushSecs = *jb.JbConfig.Input.FlushCursorSecs
	} else {
		jb.cursorFlushSecs = 5
	}

	if jb.JbConfig.Input.SeekPosition != nil {
		jb.seekPosition = *jb.JbConfig.Input.SeekPosition
	} else {
		jb.seekPosition = "tail"
	}

	if jb.JbConfig.Input.CursorSeekFallback != nil {
		jb.cursorSeekFallback = *jb.JbConfig.Input.CursorSeekFallback
	} else {
		jb.cursorSeekFallback = "tail"
	}

	if jb.JbConfig.Input.ConvertToNumbers != nil {
		jb.convertToNumbers = *jb.JbConfig.Input.ConvertToNumbers
	} else {
		jb.convertToNumbers = false
	}

	if jb.JbConfig.Input.CleanFieldNames != nil {
		jb.cleanFieldnames = *jb.JbConfig.Input.CleanFieldNames
	} else {
		jb.cleanFieldnames = false
	}

	if jb.JbConfig.Input.MoveMetadataLocation != nil {
		jb.moveMetadataLocation = *jb.JbConfig.Input.MoveMetadataLocation
	} else {
		jb.moveMetadataLocation = ""
	}

	if jb.JbConfig.Input.DefaultType != nil {
		jb.defaultType = *jb.JbConfig.Input.DefaultType
	} else {
		jb.defaultType = "journal"
	}

	if _, ok := SeekPositions[jb.seekPosition]; !ok {
		errMsg := "seek_position must be either cursor, head, or tail"
		logp.Err(errMsg)
		return fmt.Errorf("%s", errMsg)
	}

	if _, ok := SeekFallbackPositions[jb.cursorSeekFallback]; !ok {
		errMsg := "cursor_seek_fallback must be either head, tail, or none"
		logp.Err(errMsg)
		return fmt.Errorf("%s", errMsg)
	}

	return nil
}

func (jb *Journalbeat) seekToPosition() error {
	position := jb.seekPosition
	// try seekToCursor first, if that is requested
	if position == "cursor" {
		cursor, err := ioutil.ReadFile(jb.cursorStateFile)
		if err != nil {
			logp.Warn("Could not seek to cursor: reading cursor state file failed: %v", err)
		} else {
			// try to seek to cursor and if successful return
			err = seekToHelper("cursor", jb.jr.Journal.SeekCursor(string(cursor)))
			if err == nil {
				return nil
			}
		}

		if jb.cursorSeekFallback == "none" {
			return err
		}

		position = jb.cursorSeekFallback
	}

	var err error
	switch position {
	case "head":
		err = seekToHelper("head", jb.jr.Journal.SeekHead())
	case "tail":
		err = seekToHelper("tail", jb.jr.Journal.SeekTail())
	}
	return err
}

func seekToHelper(position string, err error) error {
	if err == nil {
		logp.Info("Seek to " + position + " successful")
	} else {
		logp.Warn("Could not seek to %s: %v", position, err)
	}
	return err
}

// Setup prepares Journalbeat for the main loop (starts journalreader, etc.)
func (jb *Journalbeat) Setup(b *beat.Beat) error {
	logp.Info("Journalbeat Setup")
	jb.output = b.Publisher.Connect()
	// Buffer channel else write to it blocks when Stop is called while
	// FollowJournal waits to write next  event
	jb.done = make(chan int, 1)
	jb.recv = make(chan sdjournal.JournalEntry)
	jb.cursorChan = make(chan string)
	jb.cursorChanFlush = make(chan int)

	jr, err := sdjournal.NewJournalReader(sdjournal.JournalReaderConfig{
		Path:  jb.JbConfig.Input.JournalDir,
		Since: time.Duration(1),
		//          NumFromTail: 0,
	})
	if err != nil {
		logp.Err("Could not create JournalReader")
		return err
	}

	jb.jr = jr

	// seek to position
	err = jb.seekToPosition()
	if err != nil {
		errMsg := fmt.Sprintf("seeking to a good position in journal failed: %v", err)
		logp.Err(errMsg)
		return fmt.Errorf("%s", errMsg)
	}

	// done with setup
	return nil
}

// Cleanup cleans up resources
func (jb *Journalbeat) Cleanup(b *beat.Beat) error {
	logp.Info("Journalbeat Cleanup")
	jb.jr.Close()
	jb.output.Close()
	if jb.writeCursorState {
		jb.cursorChanFlush <- 1
	}
	close(jb.done)
	close(jb.recv)
	close(jb.cursorChan)
	close(jb.cursorChanFlush)
	return nil
}

// Run is the main event loop: read from journald and pass it to Publish
func (jb *Journalbeat) Run(b *beat.Beat) error {
	logp.Info("Journalbeat Run")

	// if requested, start the WriteCursorLoop
	if jb.writeCursorState {
		go WriteCursorLoop(jb)
	}

	// Publishes event to output
	go Publish(b, jb)

	// Blocks progressing
	jb.jr.FollowJournal(jb.done, jb.recv)
	return nil
}

// Stop stops the journalbeat
func (jb *Journalbeat) Stop() {
	logp.Info("Journalbeat Stop")
	// A little hack to get Followjournal to close correctly.
	// Write to buffered close channel and then read next event
	// else if Publish is stuck on a send it hangs
	jb.done <- 1
	select {
	case <-jb.recv:
	}
}

// Publish is used to publish read events to the beat output chain
func Publish(beat *beat.Beat, jb *Journalbeat) {
	logp.Info("Start sending events to output")
	for {
		ev := <-jb.recv

		// do some conversion, etc.
		m := MapStrFromJournalEntry(ev, jb.cleanFieldnames, jb.convertToNumbers)
		if jb.moveMetadataLocation != "" {
			m = MapStrMoveJournalMetadata(m, jb.moveMetadataLocation)
		}

		// add type if it does not exist yet (or if it is not a string)
		// TODO: type should be derived from the system journal
		_, ok := m["type"].(string)
		if !ok {
			m["type"] = jb.defaultType
		}

		// add input_type if it does not exist yet (or if it is not a string)
		// TODO: input_type should be derived from the system journal
		_, ok = m["input_type"].(string)
		if !ok {
			m["input_type"] = "journal"
		}

		// publish the event now
		//m := (common.MapStr)(ev)
		success := jb.output.PublishEvent(m, publisher.Sync, publisher.Guaranteed)
		// should never happen but if it does should definitely log an not save cursor
		if !success {
			logp.Err("PublishEvent returned false for cursor %s", ev["__CURSOR"])
			continue
		}

		// save cursor
		if jb.writeCursorState {
			cursor, ok := ev["__CURSOR"].(string)
			if ok {
				jb.cursorChan <- cursor
			}
		}
	}
}

// WriteCursorLoop runs the loop which flushes the current cursor position to
// a file
func WriteCursorLoop(jb *Journalbeat) {
	var cursor, oldCursor string
	before := time.Now()
	stop := false
	for {
		// select next event
		select {
		case <-jb.cursorChanFlush:
			stop = true
		case c := <-jb.cursorChan:
			cursor = c
		case <-time.After(time.Duration(jb.cursorFlushSecs) * time.Second):
		}

		// stop immediately if we are supposed to
		if stop {
			break
		}

		// check if we need to flush
		now := time.Now()
		if now.Sub(before) > time.Duration(jb.cursorFlushSecs)*time.Second {
			before = now
			if cursor != oldCursor {
				jb.saveCursorState(cursor)
				oldCursor = cursor
			}
		}
	}

	logp.Info("flushing cursor state for the last time")
	jb.saveCursorState(cursor)
}

func (jb *Journalbeat) saveCursorState(cursor string) {
	if cursor != "" {
		err := ioutil.WriteFile(jb.cursorStateFile, []byte(cursor), 0644)
		if err != nil {
			logp.Err("Could not write to cursor state file: %v", err)
		}
	}
}
<commit_msg>Check for nil; deref<commit_after>// Copyright 2016 Marcus Heese
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package beat

import (
	"fmt"
	"io/ioutil"
	"time"

	"github.com/elastic/beats/libbeat/beat"
	"github.com/elastic/beats/libbeat/cfgfile"
	//"github.com/elastic/beats/libbeat/common"
	"github.com/elastic/beats/libbeat/logp"
	"github.com/elastic/beats/libbeat/publisher"
	"github.com/mheese/go-systemd/sdjournal"
)

var SeekPositions = map[string]bool{
	"cursor": true,
	"head":   true,
	"tail":   true,
}

var SeekFallbackPositions = map[string]bool{
	"none": true,
	"head": true,
	"tail": true,
}

// Journalbeat is the main Journalbeat struct
type Journalbeat struct {
	JbConfig             ConfigSettings
	writeCursorState     bool
	cursorStateFile      string
	cursorFlushSecs      int
	seekPosition         string
	cursorSeekFallback   string
	convertToNumbers     bool
	cleanFieldnames      bool
	moveMetadataLocation string
	defaultType          string

	jr   *sdjournal.JournalReader
	done chan int
	recv chan sdjournal.JournalEntry

	cursorChan      chan string
	cursorChanFlush chan int

	output publisher.Client
}

// New creates a new Journalbeat object and returns. Should be done once in main
func New() *Journalbeat {
	logp.Info("New Journalbeat")
	return &Journalbeat{}
}

// Config parses configuration data and prepares for Setup
func (jb *Journalbeat) Config(b *beat.Beat) error {
	logp.Info("Journalbeat Config")
	err := cfgfile.Read(&jb.JbConfig, "")
	if err != nil {
		logp.Err("Error reading configuration file: %v", err)
		return err
	}

	if jb.JbConfig.Input.WriteCursorState != nil {
		jb.writeCursorState = *jb.JbConfig.Input.WriteCursorState
	} else {
		jb.writeCursorState = false
	}

	if jb.JbConfig.Input.CursorStateFile != nil {
		jb.cursorStateFile = *jb.JbConfig.Input.CursorStateFile
	} else {
		jb.cursorStateFile = ".journalbeat-cursor-state"
	}

	if jb.JbConfig.Input.FlushCursorSecs != nil {
		jb.cursorFlushSecs = *jb.JbConfig.Input.FlushCursorSecs
	} else {
		jb.cursorFlushSecs = 5
	}

	if jb.JbConfig.Input.SeekPosition != nil {
		jb.seekPosition = *jb.JbConfig.Input.SeekPosition
	} else {
		jb.seekPosition = "tail"
	}

	if jb.JbConfig.Input.CursorSeekFallback != nil {
		jb.cursorSeekFallback = *jb.JbConfig.Input.CursorSeekFallback
	} else {
		jb.cursorSeekFallback = "tail"
	}

	if jb.JbConfig.Input.ConvertToNumbers != nil {
		jb.convertToNumbers = *jb.JbConfig.Input.ConvertToNumbers
	} else {
		jb.convertToNumbers = false
	}

	if jb.JbConfig.Input.CleanFieldNames != nil {
		jb.cleanFieldnames = *jb.JbConfig.Input.CleanFieldNames
	} else {
		jb.cleanFieldnames = false
	}

	if jb.JbConfig.Input.MoveMetadataLocation != nil {
		jb.moveMetadataLocation = *jb.JbConfig.Input.MoveMetadataLocation
	} else {
		jb.moveMetadataLocation = ""
	}

	if jb.JbConfig.Input.DefaultType != nil {
		jb.defaultType = *jb.JbConfig.Input.DefaultType
	} else {
		jb.defaultType = "journal"
	}

	if _, ok := SeekPositions[jb.seekPosition]; !ok {
		errMsg := "seek_position must be either cursor, head, or tail"
		logp.Err(errMsg)
		return fmt.Errorf("%s", errMsg)
	}

	if _, ok := SeekFallbackPositions[jb.cursorSeekFallback]; !ok {
		errMsg := "cursor_seek_fallback must be either head, tail, or none"
		logp.Err(errMsg)
		return fmt.Errorf("%s", errMsg)
	}

	return nil
}

func (jb *Journalbeat) seekToPosition() error {
	position := jb.seekPosition
	// try seekToCursor first, if that is requested
	if position == "cursor" {
		cursor, err := ioutil.ReadFile(jb.cursorStateFile)
		if err != nil {
			logp.Warn("Could not seek to cursor: reading cursor state file failed: %v", err)
		} else {
			// try to seek to cursor and if successful return
			err = seekToHelper("cursor", jb.jr.Journal.SeekCursor(string(cursor)))
			if err == nil {
				return nil
			}
		}

		if jb.cursorSeekFallback == "none" {
			return err
		}

		position = jb.cursorSeekFallback
	}

	var err error
	switch position {
	case "head":
		err = seekToHelper("head", jb.jr.Journal.SeekHead())
	case "tail":
		err = seekToHelper("tail", jb.jr.Journal.SeekTail())
	}
	return err
}

func seekToHelper(position string, err error) error {
	if err == nil {
		logp.Info("Seek to " + position + " successful")
	} else {
		logp.Warn("Could not seek to %s: %v", position, err)
	}
	return err
}

// Setup prepares Journalbeat for the main loop (starts journalreader, etc.)
func (jb *Journalbeat) Setup(b *beat.Beat) error {
	logp.Info("Journalbeat Setup")
	jb.output = b.Publisher.Connect()
	// Buffer channel else write to it blocks when Stop is called while
	// FollowJournal waits to write next  event
	jb.done = make(chan int, 1)
	jb.recv = make(chan sdjournal.JournalEntry)
	jb.cursorChan = make(chan string)
	jb.cursorChanFlush = make(chan int)

	var dir string
	if jb.JbConfig.Input.JournalDir != nil {
		dir = *jb.JbConfig.Input.JournalDir
	}

	jr, err := sdjournal.NewJournalReader(sdjournal.JournalReaderConfig{
		Path:  dir,
		Since: time.Duration(1),
		//          NumFromTail: 0,
	})
	if err != nil {
		logp.Err("Could not create JournalReader")
		return err
	}

	jb.jr = jr

	// seek to position
	err = jb.seekToPosition()
	if err != nil {
		errMsg := fmt.Sprintf("seeking to a good position in journal failed: %v", err)
		logp.Err(errMsg)
		return fmt.Errorf("%s", errMsg)
	}

	// done with setup
	return nil
}

// Cleanup cleans up resources
func (jb *Journalbeat) Cleanup(b *beat.Beat) error {
	logp.Info("Journalbeat Cleanup")
	jb.jr.Close()
	jb.output.Close()
	if jb.writeCursorState {
		jb.cursorChanFlush <- 1
	}
	close(jb.done)
	close(jb.recv)
	close(jb.cursorChan)
	close(jb.cursorChanFlush)
	return nil
}

// Run is the main event loop: read from journald and pass it to Publish
func (jb *Journalbeat) Run(b *beat.Beat) error {
	logp.Info("Journalbeat Run")

	// if requested, start the WriteCursorLoop
	if jb.writeCursorState {
		go WriteCursorLoop(jb)
	}

	// Publishes event to output
	go Publish(b, jb)

	// Blocks progressing
	jb.jr.FollowJournal(jb.done, jb.recv)
	return nil
}

// Stop stops the journalbeat
func (jb *Journalbeat) Stop() {
	logp.Info("Journalbeat Stop")
	// A little hack to get Followjournal to close correctly.
	// Write to buffered close channel and then read next event
	// else if Publish is stuck on a send it hangs
	jb.done <- 1
	select {
	case <-jb.recv:
	}
}

// Publish is used to publish read events to the beat output chain
func Publish(beat *beat.Beat, jb *Journalbeat) {
	logp.Info("Start sending events to output")
	for {
		ev := <-jb.recv

		// do some conversion, etc.
		m := MapStrFromJournalEntry(ev, jb.cleanFieldnames, jb.convertToNumbers)
		if jb.moveMetadataLocation != "" {
			m = MapStrMoveJournalMetadata(m, jb.moveMetadataLocation)
		}

		// add type if it does not exist yet (or if it is not a string)
		// TODO: type should be derived from the system journal
		_, ok := m["type"].(string)
		if !ok {
			m["type"] = jb.defaultType
		}

		// add input_type if it does not exist yet (or if it is not a string)
		// TODO: input_type should be derived from the system journal
		_, ok = m["input_type"].(string)
		if !ok {
			m["input_type"] = "journal"
		}

		// publish the event now
		//m := (common.MapStr)(ev)
		success := jb.output.PublishEvent(m, publisher.Sync, publisher.Guaranteed)
		// should never happen but if it does should definitely log an not save cursor
		if !success {
			logp.Err("PublishEvent returned false for cursor %s", ev["__CURSOR"])
			continue
		}

		// save cursor
		if jb.writeCursorState {
			cursor, ok := ev["__CURSOR"].(string)
			if ok {
				jb.cursorChan <- cursor
			}
		}
	}
}

// WriteCursorLoop runs the loop which flushes the current cursor position to
// a file
func WriteCursorLoop(jb *Journalbeat) {
	var cursor, oldCursor string
	before := time.Now()
	stop := false
	for {
		// select next event
		select {
		case <-jb.cursorChanFlush:
			stop = true
		case c := <-jb.cursorChan:
			cursor = c
		case <-time.After(time.Duration(jb.cursorFlushSecs) * time.Second):
		}

		// stop immediately if we are supposed to
		if stop {
			break
		}

		// check if we need to flush
		now := time.Now()
		if now.Sub(before) > time.Duration(jb.cursorFlushSecs)*time.Second {
			before = now
			if cursor != oldCursor {
				jb.saveCursorState(cursor)
				oldCursor = cursor
			}
		}
	}

	logp.Info("flushing cursor state for the last time")
	jb.saveCursorState(cursor)
}

func (jb *Journalbeat) saveCursorState(cursor string) {
	if cursor != "" {
		err := ioutil.WriteFile(jb.cursorStateFile, []byte(cursor), 0644)
		if err != nil {
			logp.Err("Could not write to cursor state file: %v", err)
		}
	}
}
<|endoftext|>


<commit_before>package adapter

import (
	"github.com/pivotal-cf/on-demand-services-sdk/bosh"
	"github.com/pivotal-cf/on-demand-services-sdk/serviceadapter"
)

type DashboardURLGenerator struct {}

func (d *DashboardURLGenerator) DashboardUrl(instanceID string, plan serviceadapter.Plan, manifest bosh.BoshManifest) (serviceadapter.DashboardUrl, error) {
	// TODO: implement url generator
	return serviceadapter.DashboardUrl{DashboardUrl: "http://todo.com"}, nil
}
<commit_msg>Implement dashboard url generation<commit_after>package adapter

import (
	"fmt"

	"github.com/pivotal-cf/on-demand-services-sdk/bosh"
	"github.com/pivotal-cf/on-demand-services-sdk/serviceadapter"
)

type DashboardURLGenerator struct{}

func (d *DashboardURLGenerator) DashboardUrl(instanceID string, plan serviceadapter.Plan, manifest bosh.BoshManifest) (serviceadapter.DashboardUrl, error) {
	properties := manifest.Properties["mongo_ops"].(map[interface{}]interface{})
	url := properties["url"].(string)
	groupID := properties["group_id"].(string)

	return serviceadapter.DashboardUrl{
		DashboardUrl: fmt.Sprintf("%s/v2/%s", url, groupID),
	}, nil
}
<|endoftext|>


<commit_before>package blockchain

// BlockHeader contains metadata about a block
import (
	"encoding/gob"
	"fmt"
	"io"

	"github.com/ubclaunchpad/cumulus/common/util"
)

// UserBlockSize is the maximum size of a block in bytes when marshaled
// as specifiedd by the user (about 250K by default).
const UserBlockSize = 1 << 18

// BlockHeader contains metadata about a block
type BlockHeader struct {
	// BlockNumber is the position of the block within the blockchain
	BlockNumber uint32
	// LastBlock is the hash of the previous block
	LastBlock Hash
	// Target is the current target
	Target Hash
	// Time is represented as the number of seconds elapsed
	// since January 1, 1970 UTC. It increments every second when mining.
	Time uint32
	// Nonce starts at 0 and increments by 1 for every hash when mining
	Nonce uint64
	// ExtraData is an extra field that can be filled with arbitrary data to
	// be stored in the block
	ExtraData []byte
}

// Marshal converts a BlockHeader to a byte slice
func (bh *BlockHeader) Marshal() []byte {
	var buf []byte
	buf = util.AppendUint32(buf, bh.BlockNumber)
	buf = append(buf, bh.LastBlock.Marshal()...)
	buf = append(buf, bh.Target.Marshal()...)
	buf = util.AppendUint32(buf, bh.Time)
	buf = util.AppendUint64(buf, bh.Nonce)
	buf = append(buf, bh.ExtraData...)

	return buf
}

// Len returns the length in bytes of the BlockHeader.
func (bh *BlockHeader) Len() int {
	return len(bh.Marshal())
}

// Block represents a block in the blockchain. Contains transactions and header metadata.
type Block struct {
	BlockHeader
	Transactions []*Transaction
}

// Len returns the length in bytes of the Block.
func (b *Block) Len() int {
	return len(b.Marshal())
}

// Marshal converts a Block to a byte slice.
func (b *Block) Marshal() []byte {
	var buf []byte
	buf = append(buf, b.BlockHeader.Marshal()...)
	for _, t := range b.Transactions {
		buf = append(buf, t.Marshal()...)
	}
	return buf
}

// Encode writes the marshalled block to the given io.Writer
func (b *Block) Encode(w io.Writer) {
	err := gob.NewEncoder(w).Encode(b)
	if err != nil {
		fmt.Println(err.Error())
	}
}

// DecodeBlock reads the marshalled block from the given io.Reader and populates b
func DecodeBlock(r io.Reader) *Block {
	var b Block
	gob.NewDecoder(r).Decode(&b)
	return &b
}

// ContainsTransaction returns true and the transaction itself if the Block
// contains the transaction.
func (b *Block) ContainsTransaction(t *Transaction) (bool, uint32) {
	for i, tr := range b.Transactions {
		if HashSum(t) == HashSum(tr) {
			return true, uint32(i)
		}
	}
	return false, 0
}

// GetCloudBaseTransaction returns the CloudBase transaction within a block
func (b *Block) GetCloudBaseTransaction() *Transaction {
	return b.Transactions[0]
}
<commit_msg>remove block size<commit_after>package blockchain

// BlockHeader contains metadata about a block
import (
	"encoding/gob"
	"fmt"
	"io"

	"github.com/ubclaunchpad/cumulus/common/util"
)

// BlockHeader contains metadata about a block
type BlockHeader struct {
	// BlockNumber is the position of the block within the blockchain
	BlockNumber uint32
	// LastBlock is the hash of the previous block
	LastBlock Hash
	// Target is the current target
	Target Hash
	// Time is represented as the number of seconds elapsed
	// since January 1, 1970 UTC. It increments every second when mining.
	Time uint32
	// Nonce starts at 0 and increments by 1 for every hash when mining
	Nonce uint64
	// ExtraData is an extra field that can be filled with arbitrary data to
	// be stored in the block
	ExtraData []byte
}

// Marshal converts a BlockHeader to a byte slice
func (bh *BlockHeader) Marshal() []byte {
	var buf []byte
	buf = util.AppendUint32(buf, bh.BlockNumber)
	buf = append(buf, bh.LastBlock.Marshal()...)
	buf = append(buf, bh.Target.Marshal()...)
	buf = util.AppendUint32(buf, bh.Time)
	buf = util.AppendUint64(buf, bh.Nonce)
	buf = append(buf, bh.ExtraData...)

	return buf
}

// Len returns the length in bytes of the BlockHeader.
func (bh *BlockHeader) Len() int {
	return len(bh.Marshal())
}

// Block represents a block in the blockchain. Contains transactions and header metadata.
type Block struct {
	BlockHeader
	Transactions []*Transaction
}

// Len returns the length in bytes of the Block.
func (b *Block) Len() int {
	return len(b.Marshal())
}

// Marshal converts a Block to a byte slice.
func (b *Block) Marshal() []byte {
	var buf []byte
	buf = append(buf, b.BlockHeader.Marshal()...)
	for _, t := range b.Transactions {
		buf = append(buf, t.Marshal()...)
	}
	return buf
}

// Encode writes the marshalled block to the given io.Writer
func (b *Block) Encode(w io.Writer) {
	err := gob.NewEncoder(w).Encode(b)
	if err != nil {
		fmt.Println(err.Error())
	}
}

// DecodeBlock reads the marshalled block from the given io.Reader and populates b
func DecodeBlock(r io.Reader) *Block {
	var b Block
	gob.NewDecoder(r).Decode(&b)
	return &b
}

// ContainsTransaction returns true and the transaction itself if the Block
// contains the transaction.
func (b *Block) ContainsTransaction(t *Transaction) (bool, uint32) {
	for i, tr := range b.Transactions {
		if HashSum(t) == HashSum(tr) {
			return true, uint32(i)
		}
	}
	return false, 0
}

// GetCloudBaseTransaction returns the CloudBase transaction within a block
func (b *Block) GetCloudBaseTransaction() *Transaction {
	return b.Transactions[0]
}
<|endoftext|>


<commit_before>package lnwallet

import (
	"encoding/hex"

	"github.com/btcsuite/btcd/btcjson"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/btcsuite/btcutil/coinset"
)

// lnCoin...
// to adhere to the coinset.Coin interface
type lnCoin struct {
	hash     *wire.ShaHash
	index    uint32
	value    btcutil.Amount
	pkScript []byte
	numConfs int64
	valueAge int64
}

func (l *lnCoin) Hash() *wire.ShaHash   { return l.hash }
func (l *lnCoin) Index() uint32         { return l.index }
func (l *lnCoin) Value() btcutil.Amount { return l.value }
func (l *lnCoin) PkScript() []byte      { return l.pkScript }
func (l *lnCoin) NumConfs() int64       { return l.numConfs }
func (l *lnCoin) ValueAge() int64       { return l.valueAge }

// Ensure lnCoin adheres to the coinset.Coin interface.
var _ coinset.Coin = (*lnCoin)(nil)

// newLnCoin...
func newLnCoin(output *btcjson.ListUnspentResult) (coinset.Coin, error) {
	txid, err := wire.NewShaHashFromStr(output.TxID)
	if err != nil {
		return nil, err
	}

	pkScript, err := hex.DecodeString(output.ScriptPubKey)
	if err != nil {
		return nil, err
	}

	return &lnCoin{
		hash: txid,
		// btcjson.ListUnspentResult shows the amount in BTC,
		// translate into Satoshi so coin selection can work properly.
		value:    btcutil.Amount(output.Amount * 1e8),
		index:    output.Vout,
		pkScript: pkScript,
		numConfs: output.Confirmations,
		// TODO(roasbeef): output.Amount should be a int64, damn json-RPC :/
		valueAge: output.Confirmations * int64(output.Amount),
	}, nil
}

// outputsToCoins...
func outputsToCoins(outputs []*btcjson.ListUnspentResult) ([]coinset.Coin, error) {
	coins := make([]coinset.Coin, len(outputs))
	for i, output := range outputs {
		coin, err := newLnCoin(output)
		if err != nil {
			return nil, err
		}

		coins[i] = coin
	}

	return coins, nil
}
<commit_msg>lnwallet: finish docstrings within coin select code<commit_after>package lnwallet

import (
	"encoding/hex"

	"github.com/btcsuite/btcd/btcjson"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/btcsuite/btcutil/coinset"
)

// lnCoin represents a single unspet output. Its purpose is to convert a regular
// output to a struct adhering to the coinset.Coin interface
type lnCoin struct {
	hash     *wire.ShaHash
	index    uint32
	value    btcutil.Amount
	pkScript []byte
	numConfs int64
	valueAge int64
}

func (l *lnCoin) Hash() *wire.ShaHash   { return l.hash }
func (l *lnCoin) Index() uint32         { return l.index }
func (l *lnCoin) Value() btcutil.Amount { return l.value }
func (l *lnCoin) PkScript() []byte      { return l.pkScript }
func (l *lnCoin) NumConfs() int64       { return l.numConfs }
func (l *lnCoin) ValueAge() int64       { return l.valueAge }

// Ensure lnCoin adheres to the coinset.Coin interface.
var _ coinset.Coin = (*lnCoin)(nil)

// newLnCoin creates a new "coin" from the passed output. Coins are required
// in order to perform coin selection upon.
func newLnCoin(output *btcjson.ListUnspentResult) (coinset.Coin, error) {
	txid, err := wire.NewShaHashFromStr(output.TxID)
	if err != nil {
		return nil, err
	}

	pkScript, err := hex.DecodeString(output.ScriptPubKey)
	if err != nil {
		return nil, err
	}

	return &lnCoin{
		hash: txid,
		// btcjson.ListUnspentResult shows the amount in BTC,
		// translate into Satoshi so coin selection can work properly.
		value:    btcutil.Amount(output.Amount * 1e8),
		index:    output.Vout,
		pkScript: pkScript,
		numConfs: output.Confirmations,
		// TODO(roasbeef): output.Amount should be a int64, damn json-RPC :/
		valueAge: output.Confirmations * int64(output.Amount),
	}, nil
}

// outputsToCoins converts a slice of transaction outputs to a coin-selectable
// slice of "Coins"s.
func outputsToCoins(outputs []*btcjson.ListUnspentResult) ([]coinset.Coin, error) {
	coins := make([]coinset.Coin, len(outputs))
	for i, output := range outputs {
		coin, err := newLnCoin(output)
		if err != nil {
			return nil, err
		}

		coins[i] = coin
	}

	return coins, nil
}
<|endoftext|>


<commit_before>package login

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/Bplotka/go-httpt"
	"github.com/Bplotka/go-httpt/rt"
	"github.com/Bplotka/go-jwt"
	"github.com/Bplotka/oidc"
	"github.com/Bplotka/oidc/login/mocks"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"golang.org/x/net/context"
	"gopkg.in/square/go-jose.v2"
)

const (
	testIssuer       = "https://issuer.org"
	testBindAddress  = "http://127.0.0.1:0/something"
	testClientID     = "clientID1"
	testClientSecret = "secret1"
	testNonce        = "nonce1"
)

var (
	testToken = oidc.Token{
		AccessToken:  "access1",
		RefreshToken: "refresh1",
		IDToken:      "idtoken1",
	}
)

type TokenSourceTestSuite struct {
	suite.Suite

	testDiscovery oidc.DiscoveryJSON
	testCfg       Config

	s *httpt.Server

	cache      *mocks.TokenCache
	oidcSource *OIDCTokenSource

	testCtx context.Context
}

func (s *TokenSourceTestSuite) validIDToken(nonce string) (idToken string, jwkSetJSON []byte) {
	builder, err := jwt.NewDefaultBuilder()
	s.Require().NoError(err)

	issuedAt := time.Now()
	token, err := builder.JWS().Claims(&oidc.IDToken{
		Issuer:   testIssuer,
		Nonce:    nonce,
		Expiry:   oidc.NewNumericDate(issuedAt.Add(1 * time.Hour)),
		IssuedAt: oidc.NewNumericDate(issuedAt),
		Subject:  "subject1",
		Audience: []string{testClientID},
	}).CompactSerialize()
	s.Require().NoError(err)

	set := jose.JSONWebKeySet{
		Keys: []jose.JSONWebKey{builder.PublicJWK()},
	}

	jwkSetJSON, err = json.Marshal(&set)
	s.Require().NoError(err)
	return token, jwkSetJSON
}

func (s *TokenSourceTestSuite) SetupSuite() {
	s.testDiscovery = oidc.DiscoveryJSON{
		Issuer:   testIssuer,
		AuthURL:  testIssuer + "/auth1",
		TokenURL: testIssuer + "/token1",
		JWKSURL:  testIssuer + "/jwks1",
	}

	jsonDiscovery, err := json.Marshal(s.testDiscovery)
	s.Require().NoError(err)

	s.s = httpt.NewServer(s.T())
	s.testCtx = context.WithValue(context.TODO(), oidc.HTTPClientCtxKey, s.s.HTTPClient())

	s.s.On("GET", testIssuer+oidc.DiscoveryEndpoint).
		Push(rt.JSONResponseFunc(http.StatusOK, jsonDiscovery))

	s.testCfg = Config{
		Provider:    testIssuer,
		BindAddress: testBindAddress,

		ClientID:     testClientID,
		ClientSecret: testClientSecret,
		Scopes:       []string{oidc.ScopeOpenID, oidc.ScopeEmail},

		NonceCheck: true,
	}

	s.cache = new(mocks.TokenCache)
	bindURL, err := url.Parse(s.testCfg.BindAddress)
	s.Require().NoError(err)

	oidcClient, err := oidc.NewClient(s.testCtx, s.testCfg.Provider)
	s.Require().NoError(err)

	s.oidcSource = &OIDCTokenSource{
		ctx:    s.testCtx,
		logger: log.New(os.Stdout, "", 0),

		oidcClient:  oidcClient,
		tokenCache:  s.cache,
		cfg:         s.testCfg,
		bindURL:     bindURL,
		openBrowser: openBrowser,

		nonce: testNonce,
	}
}

func (s *TokenSourceTestSuite) SetupTest() {
	s.s.Reset()

	s.oidcSource.openBrowser = func(string) error {
		s.T().Errorf("OpenBrowser Not mocked")
		s.T().FailNow()
		return nil
	}
	s.oidcSource.genRandToken = func() string {
		s.T().Errorf("GenState Not mocked")
		s.T().FailNow()
		return ""
	}

	s.cache = new(mocks.TokenCache)
	s.oidcSource.tokenCache = s.cache
}

func TestTokenSourceTestSuite(t *testing.T) {
	suite.Run(t, &TokenSourceTestSuite{})
}

func TestCallbackURL(t *testing.T) {
	bindURL, err := url.Parse(testBindAddress)
	require.NoError(t, err)
	assert.Equal(t, "127.0.0.1:0", bindURL.Host)
	assert.Equal(t, "/something/callback", callbackURL(bindURL))
}

// Below tests invokes local server - can be flaky due to timing issues. (Server did not have time to be closed).

func (s *TokenSourceTestSuite) Test_CacheOK() {
	idToken, jwkSetJSON := s.validIDToken(s.oidcSource.nonce)
	expectedToken := testToken
	expectedToken.IDToken = idToken
	s.cache.On("Token").Return(&expectedToken, nil)

	s.s.Push(rt.JSONResponseFunc(http.StatusOK, jwkSetJSON))

	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(expectedToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

func stripRedirectURL(urlToStrip string) (string, error) {
	// Strip out redirectURL from URL.
	var redirectURL string
	splittedURL := strings.Split(urlToStrip, "&")
	for _, arg := range splittedURL {
		if !strings.HasPrefix(arg, "redirect_uri=") {
			continue
		}
		redirectArg := strings.Split(arg, "=")
		if len(redirectArg) != 2 {
			return "", errors.New("More or less than two args after splitting by `=`")
		}
		var err error
		redirectURL, err = url.QueryUnescape(redirectArg[1])
		if err != nil {
			return "", err
		}
	}
	if redirectURL == "" {
		return "", errors.New("RedirectURL not found in given URL.")
	}
	return redirectURL, nil
}

func (s *TokenSourceTestSuite) callSuccessfulCallback(expectedWord string) func(string) error {
	return func(urlToGet string) error {
		redirectURL, err := stripRedirectURL(urlToGet)
		s.Require().NoError(err)

		s.Equal(fmt.Sprintf(
			"https://issuer.org/auth1?client_id=%s&nonce=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s",
			testClientID,
			expectedWord,
			url.QueryEscape(redirectURL),
			strings.Join(s.testCfg.Scopes, "+"),
			expectedWord,
		), urlToGet)

		t := oidc.TokenResponse{
			AccessToken:  testToken.AccessToken,
			RefreshToken: testToken.RefreshToken,
			IDToken:      testToken.IDToken,
			TokenType:    "Bearer",
		}
		tokenJSON, err := json.Marshal(t)
		s.Require().NoError(err)

		s.s.Push(rt.JSONResponseFunc(http.StatusOK, tokenJSON))

		req, err := http.NewRequest("GET", fmt.Sprintf(
			"%s?code=%s&state=%s",
			redirectURL,
			"code1",
			expectedWord,
		), nil)
		s.Require().NoError(err)

		u, err := url.Parse(redirectURL)
		s.Require().NoError(err)
		for i := 0; i <= 5; i++ {
			_, err = net.Dial("tcp", u.Host)
			if err == nil {
				break
			}
			time.Sleep(100 * time.Millisecond)
		}
		s.Require().NoError(err, "Server should be able to start and listen on provided address.")

		res, err := http.DefaultClient.Do(req)
		s.Require().NoError(err)

		s.Equal(http.StatusOK, res.StatusCode)
		return nil
	}
}

func (s *TokenSourceTestSuite) Test_CacheErr_NewToken_OKCallback() {
	s.cache.On("Token").Return(nil, errors.New("test_err"))
	s.cache.On("SetToken", &testToken).Return(nil)

	const expectedWord = "secret_token"
	s.oidcSource.genRandToken = func() string {
		return expectedWord
	}

	s.oidcSource.openBrowser = s.callSuccessfulCallback(expectedWord)
	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(testToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

func (s *TokenSourceTestSuite) Test_CacheEmpty_NewToken_OKCallback() {
	s.cache.On("Token").Return(nil, nil)
	s.cache.On("SetToken", &testToken).Return(nil)

	const expectedWord = "secret_token"
	s.oidcSource.genRandToken = func() string {
		return expectedWord
	}

	s.oidcSource.openBrowser = s.callSuccessfulCallback(expectedWord)
	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(testToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

func (s *TokenSourceTestSuite) Test_IDTokenWrongNonce_RefreshToken_OK() {
	idToken, jwkSetJSON := s.validIDToken("wrongNonce")
	invalidToken := testToken
	invalidToken.IDToken = idToken
	s.cache.On("Token").Return(&invalidToken, nil)

	idTokenOkNonce, jwkSetJSON2 := s.validIDToken(s.oidcSource.nonce)
	expectedToken := invalidToken
	expectedToken.IDToken = idTokenOkNonce
	s.cache.On("SetToken", &expectedToken).Return(nil)

	// For first verification inside OIDC TokenSource.
	s.s.Push(rt.JSONResponseFunc(http.StatusOK, jwkSetJSON))

	// OK Refresh response.
	t := oidc.TokenResponse{
		AccessToken:  expectedToken.AccessToken,
		RefreshToken: expectedToken.RefreshToken,
		IDToken:      expectedToken.IDToken,
		TokenType:    "Bearer",
	}
	tokenJSON, err := json.Marshal(t)
	s.Require().NoError(err)

	s.s.Push(rt.JSONResponseFunc(http.StatusOK, tokenJSON))

	// For 2th verification inside reuse TokenSource.
	s.s.Push(rt.JSONResponseFunc(http.StatusOK, jwkSetJSON2))

	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(expectedToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

func (s *TokenSourceTestSuite) Test_IDTokenWrongNonce_RefreshTokenErr_NewToken_OK() {
	idToken, jwkSetJSON := s.validIDToken("wrongNonce")
	invalidToken := testToken
	invalidToken.IDToken = idToken
	s.cache.On("Token").Return(&invalidToken, nil)
	s.cache.On("SetToken", &testToken).Return(nil)

	// For first verification inside OIDC TokenSource.
	s.s.Push(rt.JSONResponseFunc(http.StatusOK, jwkSetJSON))

	s.s.Push(rt.JSONResponseFunc(http.StatusBadRequest, []byte(`{"error": "bad_request"}`)))

	const expectedWord = "secret_token"
	s.oidcSource.genRandToken = func() string {
		return expectedWord
	}
	s.oidcSource.openBrowser = s.callSuccessfulCallback(expectedWord)

	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(testToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

func (s *TokenSourceTestSuite) Test_CacheEmpty_NewToken_ErrCallback() {
	s.cache.On("Token").Return(nil, nil)

	const expectedWord = "secret_token"
	s.oidcSource.genRandToken = func() string {
		return expectedWord
	}

	s.oidcSource.openBrowser = func(urlToGet string) error {
		redirectURL, err := stripRedirectURL(urlToGet)
		s.Require().NoError(err)

		s.Equal(fmt.Sprintf(
			"https://issuer.org/auth1?client_id=%s&nonce=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s",
			testClientID,
			expectedWord,
			url.QueryEscape(redirectURL),
			strings.Join(s.testCfg.Scopes, "+"),
			expectedWord,
		), urlToGet)

		s.s.Push(rt.JSONResponseFunc(http.StatusGatewayTimeout, []byte(`{"error": "temporary unavailable"}`)))

		req, err := http.NewRequest("GET", fmt.Sprintf(
			"%s?code=%s&state=%s",
			redirectURL,
			"code1",
			expectedWord,
		), nil)
		s.Require().NoError(err)

		u, err := url.Parse(redirectURL)
		s.Require().NoError(err)
		for i := 0; i <= 5; i++ {
			_, err = net.Dial("tcp", u.Host)
			if err == nil {
				break
			}
			time.Sleep(100 * time.Millisecond)
		}
		s.Require().NoError(err)

		res, err := http.DefaultClient.Do(req)
		s.Require().NoError(err)

		// Still it should be ok.
		s.Equal(http.StatusOK, res.StatusCode)
		return nil
	}

	_, err := s.oidcSource.OIDCToken()
	s.Require().Error(err)
	s.Equal("Failed to obtain new token. Err: oidc: Callback error: oauth2: cannot fetch token: \nResponse: {\"error\": \"temporary unavailable\"}", err.Error())

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}
<commit_msg>Refactored stripArg method.<commit_after>package login

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/Bplotka/go-httpt"
	"github.com/Bplotka/go-httpt/rt"
	"github.com/Bplotka/go-jwt"
	"github.com/Bplotka/oidc"
	"github.com/Bplotka/oidc/login/mocks"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"golang.org/x/net/context"
	"gopkg.in/square/go-jose.v2"
)

const (
	testIssuer       = "https://issuer.org"
	testBindAddress  = "http://127.0.0.1:0/something"
	testClientID     = "clientID1"
	testClientSecret = "secret1"
	testNonce        = "nonce1"
)

var (
	testToken = oidc.Token{
		AccessToken:  "access1",
		RefreshToken: "refresh1",
		IDToken:      "idtoken1",
	}
)

type TokenSourceTestSuite struct {
	suite.Suite

	testDiscovery oidc.DiscoveryJSON
	testCfg       Config

	s *httpt.Server

	cache      *mocks.TokenCache
	oidcSource *OIDCTokenSource

	testCtx context.Context
}

func (s *TokenSourceTestSuite) validIDToken(nonce string) (idToken string, jwkSetJSON []byte) {
	builder, err := jwt.NewDefaultBuilder()
	s.Require().NoError(err)

	issuedAt := time.Now()
	token, err := builder.JWS().Claims(&oidc.IDToken{
		Issuer:   testIssuer,
		Nonce:    nonce,
		Expiry:   oidc.NewNumericDate(issuedAt.Add(1 * time.Hour)),
		IssuedAt: oidc.NewNumericDate(issuedAt),
		Subject:  "subject1",
		Audience: []string{testClientID},
	}).CompactSerialize()
	s.Require().NoError(err)

	set := jose.JSONWebKeySet{
		Keys: []jose.JSONWebKey{builder.PublicJWK()},
	}

	jwkSetJSON, err = json.Marshal(&set)
	s.Require().NoError(err)
	return token, jwkSetJSON
}

func (s *TokenSourceTestSuite) SetupSuite() {
	s.testDiscovery = oidc.DiscoveryJSON{
		Issuer:   testIssuer,
		AuthURL:  testIssuer + "/auth1",
		TokenURL: testIssuer + "/token1",
		JWKSURL:  testIssuer + "/jwks1",
	}

	jsonDiscovery, err := json.Marshal(s.testDiscovery)
	s.Require().NoError(err)

	s.s = httpt.NewServer(s.T())
	s.testCtx = context.WithValue(context.TODO(), oidc.HTTPClientCtxKey, s.s.HTTPClient())

	s.s.On("GET", testIssuer+oidc.DiscoveryEndpoint).
		Push(rt.JSONResponseFunc(http.StatusOK, jsonDiscovery))

	s.testCfg = Config{
		Provider:    testIssuer,
		BindAddress: testBindAddress,

		ClientID:     testClientID,
		ClientSecret: testClientSecret,
		Scopes:       []string{oidc.ScopeOpenID, oidc.ScopeEmail},

		NonceCheck: true,
	}

	s.cache = new(mocks.TokenCache)
	bindURL, err := url.Parse(s.testCfg.BindAddress)
	s.Require().NoError(err)

	oidcClient, err := oidc.NewClient(s.testCtx, s.testCfg.Provider)
	s.Require().NoError(err)

	s.oidcSource = &OIDCTokenSource{
		ctx:    s.testCtx,
		logger: log.New(os.Stdout, "", 0),

		oidcClient:  oidcClient,
		tokenCache:  s.cache,
		cfg:         s.testCfg,
		bindURL:     bindURL,
		openBrowser: openBrowser,

		nonce: testNonce,
	}
}

func (s *TokenSourceTestSuite) SetupTest() {
	s.s.Reset()

	s.oidcSource.openBrowser = func(string) error {
		s.T().Errorf("OpenBrowser Not mocked")
		s.T().FailNow()
		return nil
	}
	s.oidcSource.genRandToken = func() string {
		s.T().Errorf("GenState Not mocked")
		s.T().FailNow()
		return ""
	}

	s.cache = new(mocks.TokenCache)
	s.oidcSource.tokenCache = s.cache
}

func TestTokenSourceTestSuite(t *testing.T) {
	suite.Run(t, &TokenSourceTestSuite{})
}

func TestCallbackURL(t *testing.T) {
	bindURL, err := url.Parse(testBindAddress)
	require.NoError(t, err)
	assert.Equal(t, "127.0.0.1:0", bindURL.Host)
	assert.Equal(t, "/something/callback", callbackURL(bindURL))
}

// Below tests invokes local server - can be flaky due to timing issues. (Server did not have time to be closed).

func (s *TokenSourceTestSuite) Test_CacheOK() {
	idToken, jwkSetJSON := s.validIDToken(s.oidcSource.nonce)
	expectedToken := testToken
	expectedToken.IDToken = idToken
	s.cache.On("Token").Return(&expectedToken, nil)

	s.s.Push(rt.JSONResponseFunc(http.StatusOK, jwkSetJSON))

	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(expectedToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

// stripArgFromURL strips out arg value from URL.
func stripArgFromURL(arg string, urlToStrip string) (string, error) {
	var argValue string
	splittedURL := strings.Split(urlToStrip, "&")
	for _, a := range splittedURL {
		if !strings.HasPrefix(a, arg+"=") {
			continue
		}
		splittedArg := strings.Split(a, "=")
		if len(splittedArg) != 2 {
			return "", errors.New("More or less than two args after splitting by `=`")
		}
		var err error
		argValue, err = url.QueryUnescape(splittedArg[1])
		if err != nil {
			return "", err
		}
	}
	if argValue == "" {
		return "", fmt.Errorf("%s not found in given URL.", arg)
	}
	return argValue, nil
}

func (s *TokenSourceTestSuite) callSuccessfulCallback(expectedWord string) func(string) error {
	return func(urlToGet string) error {
		redirectURL, err := stripArgFromURL("redirect_uri", urlToGet)
		s.Require().NoError(err)

		s.Equal(fmt.Sprintf(
			"https://issuer.org/auth1?client_id=%s&nonce=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s",
			testClientID,
			expectedWord,
			url.QueryEscape(redirectURL),
			strings.Join(s.testCfg.Scopes, "+"),
			expectedWord,
		), urlToGet)

		t := oidc.TokenResponse{
			AccessToken:  testToken.AccessToken,
			RefreshToken: testToken.RefreshToken,
			IDToken:      testToken.IDToken,
			TokenType:    "Bearer",
		}
		tokenJSON, err := json.Marshal(t)
		s.Require().NoError(err)

		s.s.Push(rt.JSONResponseFunc(http.StatusOK, tokenJSON))

		req, err := http.NewRequest("GET", fmt.Sprintf(
			"%s?code=%s&state=%s",
			redirectURL,
			"code1",
			expectedWord,
		), nil)
		s.Require().NoError(err)

		u, err := url.Parse(redirectURL)
		s.Require().NoError(err)
		for i := 0; i <= 5; i++ {
			_, err = net.Dial("tcp", u.Host)
			if err == nil {
				break
			}
			time.Sleep(100 * time.Millisecond)
		}
		s.Require().NoError(err, "Server should be able to start and listen on provided address.")

		res, err := http.DefaultClient.Do(req)
		s.Require().NoError(err)

		s.Equal(http.StatusOK, res.StatusCode)
		return nil
	}
}

func (s *TokenSourceTestSuite) Test_CacheErr_NewToken_OKCallback() {
	s.cache.On("Token").Return(nil, errors.New("test_err"))
	s.cache.On("SetToken", &testToken).Return(nil)

	const expectedWord = "secret_token"
	s.oidcSource.genRandToken = func() string {
		return expectedWord
	}

	s.oidcSource.openBrowser = s.callSuccessfulCallback(expectedWord)
	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(testToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

func (s *TokenSourceTestSuite) Test_CacheEmpty_NewToken_OKCallback() {
	s.cache.On("Token").Return(nil, nil)
	s.cache.On("SetToken", &testToken).Return(nil)

	const expectedWord = "secret_token"
	s.oidcSource.genRandToken = func() string {
		return expectedWord
	}

	s.oidcSource.openBrowser = s.callSuccessfulCallback(expectedWord)
	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(testToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

func (s *TokenSourceTestSuite) Test_IDTokenWrongNonce_RefreshToken_OK() {
	idToken, jwkSetJSON := s.validIDToken("wrongNonce")
	invalidToken := testToken
	invalidToken.IDToken = idToken
	s.cache.On("Token").Return(&invalidToken, nil)

	idTokenOkNonce, jwkSetJSON2 := s.validIDToken(s.oidcSource.nonce)
	expectedToken := invalidToken
	expectedToken.IDToken = idTokenOkNonce
	s.cache.On("SetToken", &expectedToken).Return(nil)

	// For first verification inside OIDC TokenSource.
	s.s.Push(rt.JSONResponseFunc(http.StatusOK, jwkSetJSON))

	// OK Refresh response.
	t := oidc.TokenResponse{
		AccessToken:  expectedToken.AccessToken,
		RefreshToken: expectedToken.RefreshToken,
		IDToken:      expectedToken.IDToken,
		TokenType:    "Bearer",
	}
	tokenJSON, err := json.Marshal(t)
	s.Require().NoError(err)

	s.s.Push(rt.JSONResponseFunc(http.StatusOK, tokenJSON))

	// For 2th verification inside reuse TokenSource.
	s.s.Push(rt.JSONResponseFunc(http.StatusOK, jwkSetJSON2))

	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(expectedToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

func (s *TokenSourceTestSuite) Test_IDTokenWrongNonce_RefreshTokenErr_NewToken_OK() {
	idToken, jwkSetJSON := s.validIDToken("wrongNonce")
	invalidToken := testToken
	invalidToken.IDToken = idToken
	s.cache.On("Token").Return(&invalidToken, nil)
	s.cache.On("SetToken", &testToken).Return(nil)

	// For first verification inside OIDC TokenSource.
	s.s.Push(rt.JSONResponseFunc(http.StatusOK, jwkSetJSON))

	s.s.Push(rt.JSONResponseFunc(http.StatusBadRequest, []byte(`{"error": "bad_request"}`)))

	const expectedWord = "secret_token"
	s.oidcSource.genRandToken = func() string {
		return expectedWord
	}
	s.oidcSource.openBrowser = s.callSuccessfulCallback(expectedWord)

	token, err := s.oidcSource.OIDCToken()
	s.Require().NoError(err)

	s.Equal(testToken, *token)

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}

func (s *TokenSourceTestSuite) Test_CacheEmpty_NewToken_ErrCallback() {
	s.cache.On("Token").Return(nil, nil)

	const expectedWord = "secret_token"
	s.oidcSource.genRandToken = func() string {
		return expectedWord
	}

	s.oidcSource.openBrowser = func(urlToGet string) error {
		redirectURL, err := stripArgFromURL("redirect_uri", urlToGet)
		s.Require().NoError(err)

		s.Equal(fmt.Sprintf(
			"https://issuer.org/auth1?client_id=%s&nonce=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s",
			testClientID,
			expectedWord,
			url.QueryEscape(redirectURL),
			strings.Join(s.testCfg.Scopes, "+"),
			expectedWord,
		), urlToGet)

		s.s.Push(rt.JSONResponseFunc(http.StatusGatewayTimeout, []byte(`{"error": "temporary unavailable"}`)))

		req, err := http.NewRequest("GET", fmt.Sprintf(
			"%s?code=%s&state=%s",
			redirectURL,
			"code1",
			expectedWord,
		), nil)
		s.Require().NoError(err)

		u, err := url.Parse(redirectURL)
		s.Require().NoError(err)
		for i := 0; i <= 5; i++ {
			_, err = net.Dial("tcp", u.Host)
			if err == nil {
				break
			}
			time.Sleep(100 * time.Millisecond)
		}
		s.Require().NoError(err)

		res, err := http.DefaultClient.Do(req)
		s.Require().NoError(err)

		// Still it should be ok.
		s.Equal(http.StatusOK, res.StatusCode)
		return nil
	}

	_, err := s.oidcSource.OIDCToken()
	s.Require().Error(err)
	s.Equal("Failed to obtain new token. Err: oidc: Callback error: oauth2: cannot fetch token: \nResponse: {\"error\": \"temporary unavailable\"}", err.Error())

	s.cache.AssertExpectations(s.T())
	s.Equal(0, s.s.Len())
}
<|endoftext|>


<commit_before>package checks

import (
	"math/rand"
	"sync"
	"time"

	"github.com/gansoi/gansoi/database"
	"github.com/gansoi/gansoi/stats"
)

type (
	// MetaStore will keep a list of checks to execute.
	MetaStore struct {
		sync.RWMutex
		store map[metaKey]*checkMeta
	}

	metaKey struct {
		checkID string
		hostID  string
	}

	checkMeta struct {
		check     *Check
		key       *metaKey
		running   bool
		runs      int
		NextCheck time.Time
		interval  time.Duration
	}
)

func newMetaStore(db database.Database) (*MetaStore, error) {
	s := &MetaStore{
		store: make(map[metaKey]*checkMeta),
	}

	db.RegisterListener(s)

	s.populate(db)

	return s, nil
}

// PostApply implements database.Listener.
func (s *MetaStore) PostApply(leader bool, command database.Command, data interface{}, err error) {
	clock := time.Now()

	check, isCheck := data.(*Check)
	if !isCheck {
		return
	}

	// We have no way of knowing if the check is new or not, so we have to
	// always delete the known metadata.
	s.removeCheck(check)

	if command == database.CommandSave {
		s.addCheck(clock, check)
	}
}

func (s *MetaStore) populate(db database.Database) error {
	clock := time.Now()
	var allChecks []Check
	err := db.All(&allChecks, -1, 0, false)
	if err != nil {
		return err
	}

	for _, c := range allChecks {
		s.addCheck(clock, &c)
	}

	return nil
}

func (s *MetaStore) removeCheck(check *Check) {
	s.Lock()

	for key := range s.store {
		if key.checkID == check.ID {
			delete(s.store, key)
		}
	}

	s.Unlock()
}

func (s *MetaStore) addCheck(clock time.Time, check *Check) {
	key := metaKey{
		checkID: check.ID,
	}

	if len(check.Hosts) == 0 {
		meta := &checkMeta{
			NextCheck: randomStartTime(clock, check.Interval),
			interval:  check.Interval,
			check:     check,
			key:       &key,
		}

		s.Lock()
		s.store[key] = meta
		s.Unlock()
	}

	for _, hostID := range check.Hosts {
		meta := &checkMeta{
			NextCheck: randomStartTime(clock, check.Interval),
			interval:  check.Interval,
			check:     check,
			key:       &key,
		}

		key.hostID = hostID

		s.Lock()
		s.store[key] = meta
		s.Unlock()
	}
}

// Next returns the next check to execute. Done() must be called when the check
// is done executing.
func (s *MetaStore) Next(clock time.Time) *checkMeta {
	var winner *checkMeta

	s.RLock()
	for _, meta := range s.store {
		// Calculate how much we should wait before executing the check. If
		// the value is positive, it's in the future.
		wait := meta.NextCheck.Sub(clock)

		// ... and if the value is negative, we better get on with it :)
		if wait < 0 {
			if meta.running {
				stats.CounterInc("scheduler_inflight_overrun", 1)
				continue
			}

			winner = meta

			break
		}
	}

	s.RUnlock()

	if winner != nil {
		// We lock the MetaStore just to change a single checkMeta. That is a
		// bit excessive, but it'll do for now.
		s.Lock()
		winner.runs++
		winner.running = true
		winner.NextCheck = clock.Add(winner.interval)
		s.Unlock()
	}

	return winner
}

// Done must be called to signal that a check is done. After Done() the
// checkMeta can again be returned from Next().
func (s *MetaStore) Done(meta *checkMeta) {
	s.Lock()
	meta.running = false
	s.Unlock()
}

// randomStartTime will try to find a random start time for a check. It should
// be randomized to distribute load for heavy checks. We could be checking 100
// frontend servers that will all hit the same backend database - and we want
// to monitor downtime, not create downtime by DOS'ing :)
func randomStartTime(clock time.Time, delay time.Duration) time.Time {
	// If the delay is more than one minute, we clamp it. First check should
	// be fairly quick.
	if delay > time.Second*60 {
		delay = time.Second * 60
	}

	// This will not distribute all checks evenly in the "delay" space, but
	// it's good enough for now.
	delay = time.Duration(rand.Int63n(int64(delay)))

	return clock.Add(delay)
}
<commit_msg>Do not use pointers to the same key.<commit_after>package checks

import (
	"math/rand"
	"sync"
	"time"

	"github.com/gansoi/gansoi/database"
	"github.com/gansoi/gansoi/stats"
)

type (
	// MetaStore will keep a list of checks to execute.
	MetaStore struct {
		sync.RWMutex
		store map[metaKey]*checkMeta
	}

	metaKey struct {
		checkID string
		hostID  string
	}

	checkMeta struct {
		check     *Check
		key       *metaKey
		running   bool
		runs      int
		NextCheck time.Time
		interval  time.Duration
	}
)

func newMetaStore(db database.Database) (*MetaStore, error) {
	s := &MetaStore{
		store: make(map[metaKey]*checkMeta),
	}

	db.RegisterListener(s)

	s.populate(db)

	return s, nil
}

// PostApply implements database.Listener.
func (s *MetaStore) PostApply(leader bool, command database.Command, data interface{}, err error) {
	clock := time.Now()

	check, isCheck := data.(*Check)
	if !isCheck {
		return
	}

	// We have no way of knowing if the check is new or not, so we have to
	// always delete the known metadata.
	s.removeCheck(check)

	if command == database.CommandSave {
		s.addCheck(clock, check)
	}
}

func (s *MetaStore) populate(db database.Database) error {
	clock := time.Now()
	var allChecks []Check
	err := db.All(&allChecks, -1, 0, false)
	if err != nil {
		return err
	}

	for _, c := range allChecks {
		s.addCheck(clock, &c)
	}

	return nil
}

func (s *MetaStore) removeCheck(check *Check) {
	s.Lock()

	for key := range s.store {
		if key.checkID == check.ID {
			delete(s.store, key)
		}
	}

	s.Unlock()
}

func (s *MetaStore) addCheck(clock time.Time, check *Check) {
	if len(check.Hosts) == 0 {
		key := metaKey{
			checkID: check.ID,
		}

		meta := &checkMeta{
			NextCheck: randomStartTime(clock, check.Interval),
			interval:  check.Interval,
			check:     check,
			key:       &key,
		}

		s.Lock()
		s.store[key] = meta
		s.Unlock()
	}

	for _, hostID := range check.Hosts {
		key := metaKey{
			checkID: check.ID,
			hostID:  hostID,
		}

		meta := &checkMeta{
			NextCheck: randomStartTime(clock, check.Interval),
			interval:  check.Interval,
			check:     check,
			key:       &key,
		}

		s.Lock()
		s.store[key] = meta
		s.Unlock()
	}
}

// Next returns the next check to execute. Done() must be called when the check
// is done executing.
func (s *MetaStore) Next(clock time.Time) *checkMeta {
	var winner *checkMeta

	s.RLock()
	for _, meta := range s.store {
		// Calculate how much we should wait before executing the check. If
		// the value is positive, it's in the future.
		wait := meta.NextCheck.Sub(clock)

		// ... and if the value is negative, we better get on with it :)
		if wait < 0 {
			if meta.running {
				stats.CounterInc("scheduler_inflight_overrun", 1)
				continue
			}

			winner = meta

			break
		}
	}

	s.RUnlock()

	if winner != nil {
		// We lock the MetaStore just to change a single checkMeta. That is a
		// bit excessive, but it'll do for now.
		s.Lock()
		winner.runs++
		winner.running = true
		winner.NextCheck = clock.Add(winner.interval)
		s.Unlock()
	}

	return winner
}

// Done must be called to signal that a check is done. After Done() the
// checkMeta can again be returned from Next().
func (s *MetaStore) Done(meta *checkMeta) {
	s.Lock()
	meta.running = false
	s.Unlock()
}

// randomStartTime will try to find a random start time for a check. It should
// be randomized to distribute load for heavy checks. We could be checking 100
// frontend servers that will all hit the same backend database - and we want
// to monitor downtime, not create downtime by DOS'ing :)
func randomStartTime(clock time.Time, delay time.Duration) time.Time {
	// If the delay is more than one minute, we clamp it. First check should
	// be fairly quick.
	if delay > time.Second*60 {
		delay = time.Second * 60
	}

	// This will not distribute all checks evenly in the "delay" space, but
	// it's good enough for now.
	delay = time.Duration(rand.Int63n(int64(delay)))

	return clock.Add(delay)
}
<|endoftext|>


<commit_before>package cienv

import (
	"os"
	"reflect"
	"testing"
)

func setupEnvs() (cleanup func()) {
	var cleanEnvs = []string{
		"TRAVIS_PULL_REQUEST",
		"TRAVIS_REPO_SLUG",
		"TRAVIS_PULL_REQUEST_SHA",
		"CIRCLE_PR_NUMBER",
		"CIRCLE_PROJECT_USERNAME",
		"CIRCLE_PROJECT_REPONAME",
		"CIRCLE_SHA1",
		"DRONE_PULL_REQUEST",
		"DRONE_REPO",
		"DRONE_REPO_OWNER",
		"DRONE_REPO_NAME",
		"DRONE_COMMIT",
		"CI_PULL_REQUEST",
		"CI_COMMIT",
		"CI_REPO_OWNER",
		"CI_REPO_NAME",
		"CI_BRANCH",
		"TRAVIS_PULL_REQUEST_BRANCH",
		"CIRCLE_BRANCH",
		"DRONE_COMMIT_BRANCH",
	}
	saveEnvs := make(map[string]string)
	for _, key := range cleanEnvs {
		saveEnvs[key] = os.Getenv(key)
		os.Unsetenv(key)
	}
	return func() {
		for key, value := range saveEnvs {
			os.Setenv(key, value)
		}
	}
}

func TestGetPullRequestInfo_travis(t *testing.T) {
	cleanup := setupEnvs()
	defer cleanup()

	os.Setenv("TRAVIS_REPO_SLUG", "invalid repo slug")

	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("TRAVIS_REPO_SLUG", "haya14busa/reviewdog")

	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("TRAVIS_PULL_REQUEST_SHA", "sha")

	_, isPR, err := GetPullRequestInfo()
	if err != nil {
		t.Errorf("got unexpected err: %v", err)
	}
	if isPR {
		t.Errorf("isPR = %v, want false", isPR)
	}

	os.Setenv("TRAVIS_PULL_REQUEST", "str")

	_, isPR, err = GetPullRequestInfo()
	if err != nil {
		t.Errorf("got unexpected error: %v", err)
	}
	if isPR {
		t.Errorf("isPR = %v, want false", isPR)
	}

	os.Setenv("TRAVIS_PULL_REQUEST", "1")

	if _, isPR, err = GetPullRequestInfo(); err != nil {
		t.Errorf("got unexpected err: %v", err)
	}
	if !isPR {
		t.Error("should be pull request build")
	}

	os.Setenv("TRAVIS_PULL_REQUEST", "false")

	_, isPR, err = GetPullRequestInfo()
	if err != nil {
		t.Errorf("got unexpected err: %v", err)
	}
	if isPR {
		t.Errorf("isPR = %v, want false", isPR)
	}
}

func TestGetPullRequestInfo_circleci(t *testing.T) {
	cleanup := setupEnvs()
	defer cleanup()

	if _, isPR, err := GetPullRequestInfo(); isPR {
		t.Errorf("should be non pull-request build. error: %v", err)
	}

	os.Setenv("CIRCLE_PR_NUMBER", "1")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CIRCLE_PROJECT_USERNAME", "haya14busa")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CIRCLE_PROJECT_REPONAME", "reviewdog")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CIRCLE_SHA1", "sha1")
	g, isPR, err := GetPullRequestInfo()
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if !isPR {
		t.Error("should be pull request build")
	}
	want := &PullRequestInfo{
		Owner:       "haya14busa",
		Repo:        "reviewdog",
		PullRequest: 1,
		SHA:         "sha1",
	}
	if !reflect.DeepEqual(g, want) {
		t.Errorf("got: %#v, want: %#v", g, want)
	}
}

func TestGetPullRequestInfo_droneio(t *testing.T) {
	cleanup := setupEnvs()
	defer cleanup()

	if _, isPR, err := GetPullRequestInfo(); isPR {
		t.Errorf("should be non pull-request build. error: %v", err)
	}

	os.Setenv("DRONE_PULL_REQUEST", "1")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	// Drone <= 0.4 without valid repo
	os.Setenv("DRONE_REPO", "invalid")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}
	os.Unsetenv("DRONE_REPO")

	// Drone > 0.4 without DRONE_REPO_NAME
	os.Setenv("DRONE_REPO_OWNER", "haya14busa")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}
	os.Unsetenv("DRONE_REPO_OWNER")

	// Drone > 0.4 without DRONE_REPO_OWNER
	os.Setenv("DRONE_REPO_NAME", "reviewdog")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	// Drone > 0.4 have valid variables
	os.Setenv("DRONE_REPO_NAME", "reviewdog")
	os.Setenv("DRONE_REPO_OWNER", "haya14busa")

	os.Setenv("DRONE_COMMIT", "sha1")
	g, isPR, err := GetPullRequestInfo()
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if !isPR {
		t.Error("should be pull request build")
	}
	want := &PullRequestInfo{
		Owner:       "haya14busa",
		Repo:        "reviewdog",
		PullRequest: 1,
		SHA:         "sha1",
	}
	if !reflect.DeepEqual(g, want) {
		t.Errorf("got: %#v, want: %#v", g, want)
	}
}

func TestGetPullRequestInfo_common(t *testing.T) {
	cleanup := setupEnvs()
	defer cleanup()

	if _, isPR, err := GetPullRequestInfo(); isPR {
		t.Errorf("should be non pull-request build. error: %v", err)
	}

	os.Setenv("CI_PULL_REQUEST", "1")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CI_REPO_OWNER", "haya14busa")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CI_REPO_NAME", "reviewdog")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CI_COMMIT", "sha1")
	g, isPR, err := GetPullRequestInfo()
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if !isPR {
		t.Error("should be pull request build")
	}
	want := &PullRequestInfo{
		Owner:       "haya14busa",
		Repo:        "reviewdog",
		PullRequest: 1,
		SHA:         "sha1",
	}
	if !reflect.DeepEqual(g, want) {
		t.Errorf("got: %#v, want: %#v", g, want)
	}
}
<commit_msg>cienv: update cleanup envs<commit_after>package cienv

import (
	"os"
	"reflect"
	"testing"
)

func setupEnvs() (cleanup func()) {
	var cleanEnvs = []string{
		"CIRCLE_BRANCH",
		"CIRCLE_PROJECT_REPONAME",
		"CIRCLE_PROJECT_USERNAME",
		"CIRCLE_PR_NUMBER",
		"CIRCLE_SHA1",
		"CI_BRANCH",
		"CI_COMMIT",
		"CI_COMMIT_SHA",
		"CI_PROJECT_NAME",
		"CI_PROJECT_NAMESPACE",
		"CI_PULL_REQUEST",
		"CI_REPO_NAME",
		"CI_REPO_OWNER",
		"DRONE_COMMIT",
		"DRONE_COMMIT_BRANCH",
		"DRONE_PULL_REQUEST",
		"DRONE_REPO",
		"DRONE_REPO_NAME",
		"DRONE_REPO_OWNER",
		"TRAVIS_COMMIT",
		"TRAVIS_PULL_REQUEST",
		"TRAVIS_PULL_REQUEST_BRANCH",
		"TRAVIS_PULL_REQUEST_SHA",
		"TRAVIS_REPO_SLUG",
	}
	saveEnvs := make(map[string]string)
	for _, key := range cleanEnvs {
		saveEnvs[key] = os.Getenv(key)
		os.Unsetenv(key)
	}
	return func() {
		for key, value := range saveEnvs {
			os.Setenv(key, value)
		}
	}
}

func TestGetPullRequestInfo_travis(t *testing.T) {
	cleanup := setupEnvs()
	defer cleanup()

	os.Setenv("TRAVIS_REPO_SLUG", "invalid repo slug")

	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("TRAVIS_REPO_SLUG", "haya14busa/reviewdog")

	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("TRAVIS_PULL_REQUEST_SHA", "sha")

	_, isPR, err := GetPullRequestInfo()
	if err != nil {
		t.Errorf("got unexpected err: %v", err)
	}
	if isPR {
		t.Errorf("isPR = %v, want false", isPR)
	}

	os.Setenv("TRAVIS_PULL_REQUEST", "str")

	_, isPR, err = GetPullRequestInfo()
	if err != nil {
		t.Errorf("got unexpected error: %v", err)
	}
	if isPR {
		t.Errorf("isPR = %v, want false", isPR)
	}

	os.Setenv("TRAVIS_PULL_REQUEST", "1")

	if _, isPR, err = GetPullRequestInfo(); err != nil {
		t.Errorf("got unexpected err: %v", err)
	}
	if !isPR {
		t.Error("should be pull request build")
	}

	os.Setenv("TRAVIS_PULL_REQUEST", "false")

	_, isPR, err = GetPullRequestInfo()
	if err != nil {
		t.Errorf("got unexpected err: %v", err)
	}
	if isPR {
		t.Errorf("isPR = %v, want false", isPR)
	}
}

func TestGetPullRequestInfo_circleci(t *testing.T) {
	cleanup := setupEnvs()
	defer cleanup()

	if _, isPR, err := GetPullRequestInfo(); isPR {
		t.Errorf("should be non pull-request build. error: %v", err)
	}

	os.Setenv("CIRCLE_PR_NUMBER", "1")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CIRCLE_PROJECT_USERNAME", "haya14busa")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CIRCLE_PROJECT_REPONAME", "reviewdog")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CIRCLE_SHA1", "sha1")
	g, isPR, err := GetPullRequestInfo()
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if !isPR {
		t.Error("should be pull request build")
	}
	want := &PullRequestInfo{
		Owner:       "haya14busa",
		Repo:        "reviewdog",
		PullRequest: 1,
		SHA:         "sha1",
	}
	if !reflect.DeepEqual(g, want) {
		t.Errorf("got: %#v, want: %#v", g, want)
	}
}

func TestGetPullRequestInfo_droneio(t *testing.T) {
	cleanup := setupEnvs()
	defer cleanup()

	if _, isPR, err := GetPullRequestInfo(); isPR {
		t.Errorf("should be non pull-request build. error: %v", err)
	}

	os.Setenv("DRONE_PULL_REQUEST", "1")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	// Drone <= 0.4 without valid repo
	os.Setenv("DRONE_REPO", "invalid")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}
	os.Unsetenv("DRONE_REPO")

	// Drone > 0.4 without DRONE_REPO_NAME
	os.Setenv("DRONE_REPO_OWNER", "haya14busa")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}
	os.Unsetenv("DRONE_REPO_OWNER")

	// Drone > 0.4 without DRONE_REPO_OWNER
	os.Setenv("DRONE_REPO_NAME", "reviewdog")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	// Drone > 0.4 have valid variables
	os.Setenv("DRONE_REPO_NAME", "reviewdog")
	os.Setenv("DRONE_REPO_OWNER", "haya14busa")

	os.Setenv("DRONE_COMMIT", "sha1")
	g, isPR, err := GetPullRequestInfo()
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if !isPR {
		t.Error("should be pull request build")
	}
	want := &PullRequestInfo{
		Owner:       "haya14busa",
		Repo:        "reviewdog",
		PullRequest: 1,
		SHA:         "sha1",
	}
	if !reflect.DeepEqual(g, want) {
		t.Errorf("got: %#v, want: %#v", g, want)
	}
}

func TestGetPullRequestInfo_common(t *testing.T) {
	cleanup := setupEnvs()
	defer cleanup()

	if _, isPR, err := GetPullRequestInfo(); isPR {
		t.Errorf("should be non pull-request build. error: %v", err)
	}

	os.Setenv("CI_PULL_REQUEST", "1")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CI_REPO_OWNER", "haya14busa")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CI_REPO_NAME", "reviewdog")
	if _, _, err := GetPullRequestInfo(); err == nil {
		t.Error("error expected but got nil")
	} else {
		t.Log(err)
	}

	os.Setenv("CI_COMMIT", "sha1")
	g, isPR, err := GetPullRequestInfo()
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if !isPR {
		t.Error("should be pull request build")
	}
	want := &PullRequestInfo{
		Owner:       "haya14busa",
		Repo:        "reviewdog",
		PullRequest: 1,
		SHA:         "sha1",
	}
	if !reflect.DeepEqual(g, want) {
		t.Errorf("got: %#v, want: %#v", g, want)
	}
}
<|endoftext|>


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

package nodes

// TODO be consistent between Node/*Node

import (
	"sort"
)

// NodeType is type for parsed codelab nodes tree.
type NodeType uint32

// Codelab node kinds.
const (
	NodeInvalid     NodeType = 1 << iota
	NodeList                 // A node which contains a list of other nodes
	NodeGrid                 // Table
	NodeText                 // Simple node with a string as the value
	NodeCode                 // Source code or console (terminal) output
	NodeInfobox              // An aside box for notes or warnings
	NodeSurvey               // Sets of grouped questions
	NodeURL                  // Represents elements such as
	NodeImage                // Image
	NodeButton               // Button
	NodeItemsList            // Set of NodeList items
	NodeItemsCheck           // Special kind of NodeItemsList, checklist
	NodeItemsFAQ             // Special kind of NodeItemsList, FAQ
	NodeHeader               // A header text node
	NodeHeaderCheck          // Special kind of header, checklist
	NodeHeaderFAQ            // Special kind of header, FAQ
	NodeYouTube              // YouTube video
	NodeIframe               // Embedded iframe
	NodeImport               // A node which holds content imported from another resource
)

// Node is an interface common to all node types.
type Node interface {
	// Type returns node type.
	Type() NodeType
	// MutateType changes node type where possible.
	// Only changes within this same category are allowed.
	// For instance, items list or header nodes can change their types
	// to another kind of items list or header.
	MutateType(NodeType)
	// Block returns a source reference of the node.
	Block() interface{}
	// MutateBlock updates source reference of the node.
	MutateBlock(interface{})
	// Empty returns true if the node has no content.
	Empty() bool
	// Env returns node environment
	Env() []string
	// MutateEnv replaces current node environment tags with env.
	MutateEnv(env []string)
}

// IsItemsList returns true if t is one of ItemsListNode types.
func IsItemsList(t NodeType) bool {
	return t&(NodeItemsList|NodeItemsCheck|NodeItemsFAQ) != 0
}

// IsHeader returns true if t is one of header types.
func IsHeader(t NodeType) bool {
	return t&(NodeHeader|NodeHeaderCheck|NodeHeaderFAQ) != 0
}

// IsInline returns true if t is an inline node type.
func IsInline(t NodeType) bool {
	return t&(NodeText|NodeURL|NodeImage|NodeButton) != 0
}

// EmptyNodes returns true if all of nodes are empty.
func EmptyNodes(nodes []Node) bool {
	for _, n := range nodes {
		if !n.Empty() {
			return false
		}
	}
	return true
}

type node struct {
	typ   NodeType
	block interface{}
	env   []string
}

func (b *node) Type() NodeType {
	return b.typ
}

func (b *node) MutateType(t NodeType) {
	if IsItemsList(b.typ) && IsItemsList(t) || IsHeader(b.typ) && IsHeader(t) {
		b.typ = t
	}
}

func (b *node) Block() interface{} {
	return b.block
}

func (b *node) MutateBlock(v interface{}) {
	b.block = v
}

func (b *node) Env() []string {
	return b.env
}

func (b *node) MutateEnv(e []string) {
	b.env = make([]string, len(e))
	copy(b.env, e)
	sort.Strings(b.env)
}

// NewImportNode creates a new Node of type NodeImport,
// with initialized ImportNode.Content.
func NewImportNode(url string) *ImportNode {
	return &ImportNode{
		node:    node{typ: NodeImport},
		Content: NewListNode(),
		URL:     url,
	}
}

// ImportNode indicates a remote resource available at ImportNode.URL.
type ImportNode struct {
	node
	URL     string
	Content *ListNode
}

// Empty returns the result of in.Content.Empty method.
func (in *ImportNode) Empty() bool {
	return in.Content.Empty()
}

// MutateBlock mutates both in's block marker and that of in.Content.
func (in *ImportNode) MutateBlock(v interface{}) {
	in.node.MutateBlock(v)
	in.Content.MutateBlock(v)
}

// ImportNodes extracts everything except NodeImport nodes, recursively.
func ImportNodes(nodes []Node) []*ImportNode {
	var imps []*ImportNode
	for _, n := range nodes {
		switch n := n.(type) {
		case *ImportNode:
			imps = append(imps, n)
		case *ListNode:
			imps = append(imps, ImportNodes(n.Nodes)...)
		case *InfoboxNode:
			imps = append(imps, ImportNodes(n.Content.Nodes)...)
		case *GridNode:
			for _, r := range n.Rows {
				for _, c := range r {
					imps = append(imps, ImportNodes(c.Content.Nodes)...)
				}
			}
		}
	}
	return imps
}

// NewItemsListNode creates a new ItemsListNode of type NodeItemsList,
// which defaults to an unordered list.
// Provide a positive start to make this a numbered list.
// NodeItemsCheck and NodeItemsFAQ are always unnumbered.
func NewItemsListNode(typ string, start int) *ItemsListNode {
	iln := ItemsListNode{
		node: node{typ: NodeItemsList},
		// TODO document this
		ListType: typ,
		Start:    start,
	}
	iln.MutateBlock(true)
	return &iln
}

// ItemsListNode containts sets of ListNode.
// Non-zero ListType indicates an ordered list.
type ItemsListNode struct {
	node
	ListType string
	Start    int
	Items    []*ListNode
}

// Empty returns true if every item has empty content.
func (il *ItemsListNode) Empty() bool {
	for _, i := range il.Items {
		if !i.Empty() {
			return false
		}
	}
	return true
}

// NewItem creates a new ListNode and adds it to il.Items.
func (il *ItemsListNode) NewItem(nodes ...Node) *ListNode {
	n := NewListNode(nodes...)
	il.Items = append(il.Items, n)
	return n
}
<commit_msg>Remove TODO<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package nodes

import (
	"sort"
)

// NodeType is type for parsed codelab nodes tree.
type NodeType uint32

// Codelab node kinds.
const (
	NodeInvalid     NodeType = 1 << iota
	NodeList                 // A node which contains a list of other nodes
	NodeGrid                 // Table
	NodeText                 // Simple node with a string as the value
	NodeCode                 // Source code or console (terminal) output
	NodeInfobox              // An aside box for notes or warnings
	NodeSurvey               // Sets of grouped questions
	NodeURL                  // Represents elements such as
	NodeImage                // Image
	NodeButton               // Button
	NodeItemsList            // Set of NodeList items
	NodeItemsCheck           // Special kind of NodeItemsList, checklist
	NodeItemsFAQ             // Special kind of NodeItemsList, FAQ
	NodeHeader               // A header text node
	NodeHeaderCheck          // Special kind of header, checklist
	NodeHeaderFAQ            // Special kind of header, FAQ
	NodeYouTube              // YouTube video
	NodeIframe               // Embedded iframe
	NodeImport               // A node which holds content imported from another resource
)

// Node is an interface common to all node types.
type Node interface {
	// Type returns node type.
	Type() NodeType
	// MutateType changes node type where possible.
	// Only changes within this same category are allowed.
	// For instance, items list or header nodes can change their types
	// to another kind of items list or header.
	MutateType(NodeType)
	// Block returns a source reference of the node.
	Block() interface{}
	// MutateBlock updates source reference of the node.
	MutateBlock(interface{})
	// Empty returns true if the node has no content.
	Empty() bool
	// Env returns node environment
	Env() []string
	// MutateEnv replaces current node environment tags with env.
	MutateEnv(env []string)
}

// IsItemsList returns true if t is one of ItemsListNode types.
func IsItemsList(t NodeType) bool {
	return t&(NodeItemsList|NodeItemsCheck|NodeItemsFAQ) != 0
}

// IsHeader returns true if t is one of header types.
func IsHeader(t NodeType) bool {
	return t&(NodeHeader|NodeHeaderCheck|NodeHeaderFAQ) != 0
}

// IsInline returns true if t is an inline node type.
func IsInline(t NodeType) bool {
	return t&(NodeText|NodeURL|NodeImage|NodeButton) != 0
}

// EmptyNodes returns true if all of nodes are empty.
func EmptyNodes(nodes []Node) bool {
	for _, n := range nodes {
		if !n.Empty() {
			return false
		}
	}
	return true
}

type node struct {
	typ   NodeType
	block interface{}
	env   []string
}

func (b *node) Type() NodeType {
	return b.typ
}

func (b *node) MutateType(t NodeType) {
	if IsItemsList(b.typ) && IsItemsList(t) || IsHeader(b.typ) && IsHeader(t) {
		b.typ = t
	}
}

func (b *node) Block() interface{} {
	return b.block
}

func (b *node) MutateBlock(v interface{}) {
	b.block = v
}

func (b *node) Env() []string {
	return b.env
}

func (b *node) MutateEnv(e []string) {
	b.env = make([]string, len(e))
	copy(b.env, e)
	sort.Strings(b.env)
}

// NewImportNode creates a new Node of type NodeImport,
// with initialized ImportNode.Content.
func NewImportNode(url string) *ImportNode {
	return &ImportNode{
		node:    node{typ: NodeImport},
		Content: NewListNode(),
		URL:     url,
	}
}

// ImportNode indicates a remote resource available at ImportNode.URL.
type ImportNode struct {
	node
	URL     string
	Content *ListNode
}

// Empty returns the result of in.Content.Empty method.
func (in *ImportNode) Empty() bool {
	return in.Content.Empty()
}

// MutateBlock mutates both in's block marker and that of in.Content.
func (in *ImportNode) MutateBlock(v interface{}) {
	in.node.MutateBlock(v)
	in.Content.MutateBlock(v)
}

// ImportNodes extracts everything except NodeImport nodes, recursively.
func ImportNodes(nodes []Node) []*ImportNode {
	var imps []*ImportNode
	for _, n := range nodes {
		switch n := n.(type) {
		case *ImportNode:
			imps = append(imps, n)
		case *ListNode:
			imps = append(imps, ImportNodes(n.Nodes)...)
		case *InfoboxNode:
			imps = append(imps, ImportNodes(n.Content.Nodes)...)
		case *GridNode:
			for _, r := range n.Rows {
				for _, c := range r {
					imps = append(imps, ImportNodes(c.Content.Nodes)...)
				}
			}
		}
	}
	return imps
}

// NewItemsListNode creates a new ItemsListNode of type NodeItemsList,
// which defaults to an unordered list.
// Provide a positive start to make this a numbered list.
// NodeItemsCheck and NodeItemsFAQ are always unnumbered.
func NewItemsListNode(typ string, start int) *ItemsListNode {
	iln := ItemsListNode{
		node: node{typ: NodeItemsList},
		// TODO document this
		ListType: typ,
		Start:    start,
	}
	iln.MutateBlock(true)
	return &iln
}

// ItemsListNode containts sets of ListNode.
// Non-zero ListType indicates an ordered list.
type ItemsListNode struct {
	node
	ListType string
	Start    int
	Items    []*ListNode
}

// Empty returns true if every item has empty content.
func (il *ItemsListNode) Empty() bool {
	for _, i := range il.Items {
		if !i.Empty() {
			return false
		}
	}
	return true
}

// NewItem creates a new ListNode and adds it to il.Items.
func (il *ItemsListNode) NewItem(nodes ...Node) *ListNode {
	n := NewListNode(nodes...)
	il.Items = append(il.Items, n)
	return n
}
<|endoftext|>


<commit_before>package auth

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/cozy/cozy-stack/client/request"
	"github.com/cozy/cozy-stack/pkg/config"
)

type (
	// Client describes the data of an OAuth client
	Client struct {
		ClientID          string   `json:"client_id,omitempty"`
		ClientSecret      string   `json:"client_secret"`
		SecretExpiresAt   int      `json:"client_secret_expires_at"`
		RegistrationToken string   `json:"registration_access_token"`
		RedirectURIs      []string `json:"redirect_uris"`
		ClientName        string   `json:"client_name"`
		ClientKind        string   `json:"client_kind,omitempty"`
		ClientURI         string   `json:"client_uri,omitempty"`
		LogoURI           string   `json:"logo_uri,omitempty"`
		PolicyURI         string   `json:"policy_uri,omitempty"`
		SoftwareID        string   `json:"software_id"`
		SoftwareVersion   string   `json:"software_version,omitempty"`
	}

	// AccessToken describes the content of an access token
	AccessToken struct {
		TokenType    string `json:"token_type"`
		AccessToken  string `json:"access_token"`
		RefreshToken string `json:"refresh_token"`
		Scope        string `json:"scope"`
	}

	// UserAcceptFunc is a function that can be defined by the user of this
	// library to describe how to ask the user for authorizing the client to
	// access to its data.
	//
	// The method should return the url on which the user has been redirected
	// which should contain a registering code and state, or an error .
	UserAcceptFunc func(accessURL string) (*url.URL, error)

	// Request represents an OAuth request with client parameters (*Client) and
	// list of scopes that the application wants to access.
	Request struct {
		ClientParams *Client
		Scopes       []string
		Domain       string
		HTTPClient   *http.Client
		UserAgent    string
		UserAccept   UserAcceptFunc
		Storage      Storage

		token  *AccessToken
		client *Client
	}

	// Error represents a client registration error returned by the OAuth server
	Error struct {
		Value       string `json:"error"`
		Description string `json:"error_description,omitempty"`
	}
)

func (e *Error) Error() string {
	return fmt.Sprintf("Authentication error: %s (%s)", e.Description, e.Value)
}

// AuthHeader implements the Tokener interface for the client
func (c *Client) AuthHeader() string {
	return "Bearer " + c.RegistrationToken
}

// AuthHeader implements the Tokener interface for the access token
func (t *AccessToken) AuthHeader() string {
	return "Bearer " + t.AccessToken
}

// AuthHeader implements the Tokener interface for the request
func (r *Request) AuthHeader() string {
	return r.token.AuthHeader()
}

// defaultClient defaults some values of the given client
func defaultClient(c *Client) *Client {
	if c == nil {
		c = &Client{}
	}
	if c.SoftwareID == "" {
		c.SoftwareID = "github.com/cozy/cozy-stack"
	}
	if c.SoftwareVersion == "" {
		c.SoftwareVersion = config.Version
	}
	if c.ClientName == "" {
		c.ClientName = "Cozy Go client"
	}
	if c.ClientKind == "" {
		c.ClientKind = "unknown"
	}
	return c
}

// Authenticate will start the authentication flow.
//
// If the storage has a client and token stored, it is reused and no
// authentication flow is started. Otherwise, a new client is registered and
// the authentication process is started.
func (r *Request) Authenticate() error {
	client, token, err := r.Storage.Load(r.Domain)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if client != nil && token != nil {
		r.client, r.token = client, token
		return nil
	}
	if client == nil {
		client, err = r.RegisterClient(defaultClient(r.ClientParams))
		if err != nil {
			return err
		}
	}
	b := make([]byte, 32)
	if _, err = io.ReadFull(rand.Reader, b); err != nil {
		return err
	}
	state := base64.StdEncoding.EncodeToString(b)
	if err = r.Storage.Save(r.Domain, client, nil); err != nil {
		return err
	}
	codeURL, err := r.AuthCodeURL(client, state)
	if err != nil {
		return err
	}
	receivedURL, err := r.UserAccept(codeURL)
	if err != nil {
		return err
	}
	query := receivedURL.Query()
	if state != query.Get("state") {
		return errors.New("Non matching states")
	}
	token, err = r.GetAccessToken(client, query.Get("access_code"))
	if err != nil {
		return err
	}
	if err = r.Storage.Save(r.Domain, client, token); err != nil {
		return err
	}
	r.client, r.token = client, token
	return nil
}

// AuthCodeURL returns the url on which the user is asked to authorize the
// application.
func (r *Request) AuthCodeURL(c *Client, state string) (string, error) {
	query := url.Values{
		"client_id":     {c.ClientID},
		"redirect_uri":  {c.RedirectURIs[0]},
		"state":         {state},
		"response_type": {"code"},
		"scope":         {strings.Join(r.Scopes, " ")},
	}
	u := url.URL{
		Scheme:   "https",
		Host:     r.Domain,
		Path:     "/auth/authorize",
		RawQuery: query.Encode(),
	}
	return u.String(), nil
}

// req performs an authentication HTTP request
func (r *Request) req(opts *request.Options) (*http.Response, error) {
	opts.Domain = r.Domain
	opts.Client = r.HTTPClient
	opts.ParseError = parseError
	return request.Req(opts)
}

// RegisterClient performs the registration of the specified client.
func (r *Request) RegisterClient(c *Client) (*Client, error) {
	body, err := request.WriteJSON(c)
	if err != nil {
		return nil, err
	}
	res, err := r.req(&request.Options{
		Method: "POST",
		Path:   "/auth/register",
		Headers: request.Headers{
			"Content-Type": "application/json",
			"Accept":       "application/json",
		},
		Body: body,
	})
	if err != nil {
		return nil, err
	}
	return readClient(res.Body)
}

// GetAccessToken fetch the access token using the specified authorization
// code.
func (r *Request) GetAccessToken(c *Client, code string) (*AccessToken, error) {
	q := url.Values{
		"grant_type": {"authorization_code"},
		"code":       {code},
	}
	return r.retrieveToken(c, nil, q)
}

// RefreshToken performs a token refresh using the specified client and current
// access token.
func (r *Request) RefreshToken(c *Client, t *AccessToken) (*AccessToken, error) {
	q := url.Values{
		"grant_type": {"refresh_token"},
		"code":       {t.RefreshToken},
	}
	return r.retrieveToken(c, t, q)
}

func (r *Request) retrieveToken(c *Client, t *AccessToken, q url.Values) (*AccessToken, error) {
	res, err := r.req(&request.Options{
		Method:     "GET",
		Path:       "/auth/access_token",
		Authorizer: t,
		Body:       strings.NewReader(q.Encode()),
		Headers: request.Headers{
			"Content-Type": "application/x-www-form-urlencoded",
			"Accept":       "application/json",
		},
	})
	if err != nil {
		return nil, err
	}
	token := &AccessToken{}
	if err := request.ReadJSON(res.Body, token); err != nil {
		return nil, err
	}
	return token, nil
}

func parseError(res *http.Response, b []byte) error {
	var err Error
	if err := json.Unmarshal(b, &err); err != nil {
		return &request.Error{
			Status: http.StatusText(res.StatusCode),
			Title:  http.StatusText(res.StatusCode),
			Detail: string(b),
		}
	}
	// TODO: handle multi-error
	return &err
}

func readClient(r io.ReadCloser) (*Client, error) {
	client := &Client{}
	if err := request.ReadJSON(r, client); err != nil {
		return nil, err
	}
	return defaultClient(client), nil
}
<commit_msg>Fix access_token request<commit_after>package auth

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/cozy/cozy-stack/client/request"
	"github.com/cozy/cozy-stack/pkg/config"
)

type (
	// Client describes the data of an OAuth client
	Client struct {
		ClientID          string   `json:"client_id,omitempty"`
		ClientSecret      string   `json:"client_secret"`
		SecretExpiresAt   int      `json:"client_secret_expires_at"`
		RegistrationToken string   `json:"registration_access_token"`
		RedirectURIs      []string `json:"redirect_uris"`
		ClientName        string   `json:"client_name"`
		ClientKind        string   `json:"client_kind,omitempty"`
		ClientURI         string   `json:"client_uri,omitempty"`
		LogoURI           string   `json:"logo_uri,omitempty"`
		PolicyURI         string   `json:"policy_uri,omitempty"`
		SoftwareID        string   `json:"software_id"`
		SoftwareVersion   string   `json:"software_version,omitempty"`
	}

	// AccessToken describes the content of an access token
	AccessToken struct {
		TokenType    string `json:"token_type"`
		AccessToken  string `json:"access_token"`
		RefreshToken string `json:"refresh_token"`
		Scope        string `json:"scope"`
	}

	// UserAcceptFunc is a function that can be defined by the user of this
	// library to describe how to ask the user for authorizing the client to
	// access to its data.
	//
	// The method should return the url on which the user has been redirected
	// which should contain a registering code and state, or an error .
	UserAcceptFunc func(accessURL string) (*url.URL, error)

	// Request represents an OAuth request with client parameters (*Client) and
	// list of scopes that the application wants to access.
	Request struct {
		ClientParams *Client
		Scopes       []string
		Domain       string
		HTTPClient   *http.Client
		UserAgent    string
		UserAccept   UserAcceptFunc
		Storage      Storage

		token  *AccessToken
		client *Client
	}

	// Error represents a client registration error returned by the OAuth server
	Error struct {
		Value       string `json:"error"`
		Description string `json:"error_description,omitempty"`
	}
)

func (e *Error) Error() string {
	return fmt.Sprintf("Authentication error: %s (%s)", e.Description, e.Value)
}

// AuthHeader implements the Tokener interface for the client
func (c *Client) AuthHeader() string {
	return "Bearer " + c.RegistrationToken
}

// AuthHeader implements the Tokener interface for the access token
func (t *AccessToken) AuthHeader() string {
	return "Bearer " + t.AccessToken
}

// AuthHeader implements the Tokener interface for the request
func (r *Request) AuthHeader() string {
	return r.token.AuthHeader()
}

// defaultClient defaults some values of the given client
func defaultClient(c *Client) *Client {
	if c == nil {
		c = &Client{}
	}
	if c.SoftwareID == "" {
		c.SoftwareID = "github.com/cozy/cozy-stack"
	}
	if c.SoftwareVersion == "" {
		c.SoftwareVersion = config.Version
	}
	if c.ClientName == "" {
		c.ClientName = "Cozy Go client"
	}
	if c.ClientKind == "" {
		c.ClientKind = "unknown"
	}
	return c
}

// Authenticate will start the authentication flow.
//
// If the storage has a client and token stored, it is reused and no
// authentication flow is started. Otherwise, a new client is registered and
// the authentication process is started.
func (r *Request) Authenticate() error {
	client, token, err := r.Storage.Load(r.Domain)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if client != nil && token != nil {
		r.client, r.token = client, token
		return nil
	}
	if client == nil {
		client, err = r.RegisterClient(defaultClient(r.ClientParams))
		if err != nil {
			return err
		}
	}
	b := make([]byte, 32)
	if _, err = io.ReadFull(rand.Reader, b); err != nil {
		return err
	}
	state := base64.StdEncoding.EncodeToString(b)
	if err = r.Storage.Save(r.Domain, client, nil); err != nil {
		return err
	}
	codeURL, err := r.AuthCodeURL(client, state)
	if err != nil {
		return err
	}
	receivedURL, err := r.UserAccept(codeURL)
	if err != nil {
		return err
	}
	query := receivedURL.Query()
	if state != query.Get("state") {
		return errors.New("Non matching states")
	}
	token, err = r.GetAccessToken(client, query.Get("access_code"))
	if err != nil {
		return err
	}
	if err = r.Storage.Save(r.Domain, client, token); err != nil {
		return err
	}
	r.client, r.token = client, token
	return nil
}

// AuthCodeURL returns the url on which the user is asked to authorize the
// application.
func (r *Request) AuthCodeURL(c *Client, state string) (string, error) {
	query := url.Values{
		"client_id":     {c.ClientID},
		"redirect_uri":  {c.RedirectURIs[0]},
		"state":         {state},
		"response_type": {"code"},
		"scope":         {strings.Join(r.Scopes, " ")},
	}
	u := url.URL{
		Scheme:   "https",
		Host:     r.Domain,
		Path:     "/auth/authorize",
		RawQuery: query.Encode(),
	}
	return u.String(), nil
}

// req performs an authentication HTTP request
func (r *Request) req(opts *request.Options) (*http.Response, error) {
	opts.Domain = r.Domain
	opts.Client = r.HTTPClient
	opts.ParseError = parseError
	return request.Req(opts)
}

// RegisterClient performs the registration of the specified client.
func (r *Request) RegisterClient(c *Client) (*Client, error) {
	body, err := request.WriteJSON(c)
	if err != nil {
		return nil, err
	}
	res, err := r.req(&request.Options{
		Method: "POST",
		Path:   "/auth/register",
		Headers: request.Headers{
			"Content-Type": "application/json",
			"Accept":       "application/json",
		},
		Body: body,
	})
	if err != nil {
		return nil, err
	}
	return readClient(res.Body)
}

// GetAccessToken fetch the access token using the specified authorization
// code.
func (r *Request) GetAccessToken(c *Client, code string) (*AccessToken, error) {
	q := url.Values{
		"grant_type":    {"authorization_code"},
		"code":          {code},
		"client_id":     {c.ClientID},
		"client_secret": {c.ClientSecret},
	}
	return r.retrieveToken(c, nil, q)
}

// RefreshToken performs a token refresh using the specified client and current
// access token.
func (r *Request) RefreshToken(c *Client, t *AccessToken) (*AccessToken, error) {
	q := url.Values{
		"grant_type":    {"refresh_token"},
		"code":          {t.RefreshToken},
		"client_id":     {c.ClientID},
		"client_secret": {c.ClientSecret},
	}
	return r.retrieveToken(c, t, q)
}

func (r *Request) retrieveToken(c *Client, t *AccessToken, q url.Values) (*AccessToken, error) {
	opts := &request.Options{
		Method: "POST",
		Path:   "/auth/access_token",
		Body:   strings.NewReader(q.Encode()),
		Headers: request.Headers{
			"Content-Type": "application/x-www-form-urlencoded",
			"Accept":       "application/json",
		},
	}
	if t != nil {
		opts.Authorizer = t
	}

	res, err := r.req(opts)

	if err != nil {
		return nil, err
	}
	token := &AccessToken{}
	if err := request.ReadJSON(res.Body, token); err != nil {
		return nil, err
	}
	return token, nil
}

func parseError(res *http.Response, b []byte) error {
	var err Error
	if err := json.Unmarshal(b, &err); err != nil {
		return &request.Error{
			Status: http.StatusText(res.StatusCode),
			Title:  http.StatusText(res.StatusCode),
			Detail: string(b),
		}
	}
	// TODO: handle multi-error
	return &err
}

func readClient(r io.ReadCloser) (*Client, error) {
	client := &Client{}
	if err := request.ReadJSON(r, client); err != nil {
		return nil, err
	}
	return defaultClient(client), nil
}
<|endoftext|>