<commit_before>package firewaller

import (
	"fmt"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/log"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/state/watcher"
	"launchpad.net/tomb"
)

// Firewaller watches the state for ports opened or closed
// and reflects those changes onto the backing environment.
type Firewaller struct {
	environ             environs.Environ
	st                  *state.State
	tomb                tomb.Tomb
	machinesWatcher     *state.MachinesWatcher
	machines            map[int]*machineTracker
	machineUnitsChanges chan *machineUnitsChange
	units               map[string]*unitTracker
	unitPortsChanges    chan *unitPortsChange
	services            map[string]*serviceTracker
}

// NewFirewaller returns a new Firewaller.
func NewFirewaller(environ environs.Environ, st *state.State) (*Firewaller, error) {
	fw := &Firewaller{
		environ:             environ,
		st:                  st,
		machinesWatcher:     st.WatchMachines(),
		machines:            make(map[int]*machineTracker),
		machineUnitsChanges: make(chan *machineUnitsChange),
		units:               make(map[string]*unitTracker),
		unitPortsChanges:    make(chan *unitPortsChange),
		services:            make(map[string]*serviceTracker),
	}
	go fw.loop()
	return fw, nil
}

func (fw *Firewaller) loop() {
	defer fw.finish()
	for {
		select {
		case <-fw.tomb.Dying():
			return
		case change, ok := <-fw.machinesWatcher.Changes():
			if !ok {
				return
			}
			for _, removedMachine := range change.Removed {
				mt, ok := fw.machines[removedMachine.Id()]
				if !ok {
					panic("trying to remove machine that wasn't added")
				}
				delete(fw.machines, removedMachine.Id())
				if err := mt.stop(); err != nil {
					log.Printf("machine tracker %d returned error when stopping: %v", removedMachine.Id(), err)
				}
				log.Debugf("firewaller: stopped tracking machine %d", removedMachine.Id())
			}
			for _, addedMachine := range change.Added {
				mt := newMachineTracker(addedMachine, fw)
				fw.machines[addedMachine.Id()] = mt
				log.Debugf("firewaller: started tracking machine %d", mt.id)
			}
		case change, ok := <-fw.machineUnitsChanges:
			if !ok {
				panic("aggregation of machine units changes failed")
			}
			if change.change == nil {
				log.Printf("tracker of machine %d terminated prematurely", change.machine.id)
				delete(fw.machines, change.machine.id)
				continue
			}
			for _, removedUnit := range change.change.Removed {
				ut, ok := fw.units[removedUnit.Name()]
				if !ok {
					panic("trying to remove unit that wasn't added")
				}
				delete(fw.units, removedUnit.Name())
				if err := ut.stop(); err != nil {
					log.Printf("unit tracker %s returned error when stopping: %v", removedUnit.Name(), err)
				}
				log.Debugf("firewaller: stopped tracking unit %s", removedUnit.Name())
			}
			for _, addedUnit := range change.change.Added {
				ut := newUnitTracker(addedUnit, change.machine.id, fw)
				fw.units[addedUnit.Name()] = ut
				if fw.services[addedUnit.ServiceName()] == nil {
					service, err := fw.st.Service(addedUnit.ServiceName())
					if err != nil {
						// TODO(mue) Check if panic is too hard.
						panic(fmt.Sprintf("service state %q can't be retrieved: %v", addedUnit.ServiceName(), err))
					}
					st := newServiceTracker(service, fw)
					ut.service = st
					fw.services[addedUnit.ServiceName()] = st
				}
				log.Debugf("firewaller: started tracking unit %s", ut.name)
			}
		case change := <-fw.unitPortsChanges:
			mt, ok := fw.machines[change.unitTracker.machineId]
			if !ok {
				panic("machine for unit ports change isn't tracked")
			}
			for _, port := range change.change {
				if mt.ports[port] == nil && change.unitTracker.service.exposed {
					mt.ports[port] = change.unitTracker
					if err := fw.openPortOnAllInstances(mt.id, port); err != nil {
						log.Printf("can't open port %v on machine %d: %v", port, mt.id, err)
					}
					log.Debugf("firewaller: opened port %v on machine %d", port, mt.id)
				}
			}
			for _, port := range change.unitTracker.ports {
				if mt.ports[port] == change.unitTracker {
					delete(mt.ports, port)
					if change.unitTracker.service.exposed {
						if err := fw.closePortOnAllInstances(mt.id, port); err != nil {
							log.Printf("can't close port %v on machine %d: %v", port, mt.id, err)
						}
						log.Debugf("firewaller: closed port %v on machine %d", port, mt.id)
					}
				}
			}
			change.unitTracker.ports = change.change
		}
	}
}

// openPortOnAllInstances opens the passed port on all instances in the environment.
func (fw *Firewaller) openPortOnAllInstances(machineId int, port state.Port) error {
	instances, err := fw.environ.AllInstances()
	if err != nil {
		return err
	}
	for _, instance := range instances {
		err = instance.OpenPorts(machineId, []state.Port{port})
		if err != nil {
			// TODO(mue) Add a retry logic later.
			return err
		}
	}
	return nil
}

// closePortOnAllInstances closes the passed port on all instances in the environment.
func (fw *Firewaller) closePortOnAllInstances(machineId int, port state.Port) error {
	instances, err := fw.environ.AllInstances()
	if err != nil {
		return err
	}
	for _, instance := range instances {
		err = instance.ClosePorts(machineId, []state.Port{port})
		if err != nil {
			// TODO(mue) Add a retry logic later.
			return err
		}
	}
	return nil
}

// finishes cleans up when the firewaller is stopping.
func (fw *Firewaller) finish() {
	watcher.Stop(fw.machinesWatcher, &fw.tomb)
	for _, ut := range fw.units {
		fw.tomb.Kill(ut.stop())
	}
	for _, mt := range fw.machines {
		fw.tomb.Kill(mt.stop())
	}
	fw.tomb.Done()
}

// Wait waits for the Firewaller to exit.
func (fw *Firewaller) Wait() error {
	return fw.tomb.Wait()
}

// Stop stops the Firewaller and returns any error encountered while stopping.
func (fw *Firewaller) Stop() error {
	fw.tomb.Kill(nil)
	return fw.tomb.Wait()
}

// machineUnitsChange contains the changed units for one specific machine.
type machineUnitsChange struct {
	machine *machineTracker
	change  *state.MachineUnitsChange
}

// machineTracker keeps track of the unit changes of a machine.
type machineTracker struct {
	tomb       tomb.Tomb
	firewaller *Firewaller
	id         int
	watcher    *state.MachineUnitsWatcher
	ports      map[state.Port]*unitTracker
}

// newMachineTracker creates a new machine tracker keeping track of
// unit changes of the passed machine.
func newMachineTracker(mst *state.Machine, fw *Firewaller) *machineTracker {
	mt := &machineTracker{
		firewaller: fw,
		id:         mst.Id(),
		watcher:    mst.WatchUnits(),
		ports:      make(map[state.Port]*unitTracker),
	}
	go mt.loop()
	return mt
}

// loop is the backend watching for machine units changes.
func (mt *machineTracker) loop() {
	defer mt.tomb.Done()
	defer mt.watcher.Stop()
	for {
		select {
		case <-mt.tomb.Dying():
			return
		case change, ok := <-mt.watcher.Changes():
			// Send change or nil in case of an error.
			select {
			case mt.firewaller.machineUnitsChanges <- &machineUnitsChange{mt, change}:
			case <-mt.tomb.Dying():
				return
			}
			// The watcher terminated prematurely, so end the loop.
			if !ok {
				mt.firewaller.tomb.Kill(watcher.MustErr(mt.watcher))
				return
			}
		}
	}
}

// stop stops the machine tracker.
func (mt *machineTracker) stop() error {
	mt.tomb.Kill(nil)
	return mt.tomb.Wait()
}

// unitPortsChange contains the changed ports for one specific unit.
type unitPortsChange struct {
	unitTracker *unitTracker
	change      []state.Port
}

// unitTracker keeps track of the port changes of a unit.
type unitTracker struct {
	tomb       tomb.Tomb
	firewaller *Firewaller
	machineId  int
	name       string
	watcher    *state.PortsWatcher
	service    *serviceTracker
	ports      []state.Port
}

// newUnitTracker creates a new machine tracker keeping track of
// unit changes of the passed machine.
func newUnitTracker(ust *state.Unit, machineId int, fw *Firewaller) *unitTracker {
	ut := &unitTracker{
		firewaller: fw,
		machineId:  machineId,
		name:       ust.Name(),
		watcher:    ust.WatchPorts(),
		ports:      make([]state.Port, 0),
	}
	go ut.loop()
	return ut
}

// loop is the backend watching for unit ports changes.
func (ut *unitTracker) loop() {
	defer ut.tomb.Done()
	defer ut.watcher.Stop()
	for {
		select {
		case <-ut.tomb.Dying():
			return
		case change, ok := <-ut.watcher.Changes():
			// Send change or nil in case of an error.
			select {
			case ut.firewaller.unitPortsChanges <- &unitPortsChange{ut, change}:
			case <-ut.tomb.Dying():
				return
			}
			// The watcher terminated prematurely, so end the loop.
			if !ok {
				ut.firewaller.tomb.Kill(watcher.MustErr(ut.watcher))
				return
			}
		}
	}
}

// stop stops the unit tracker.
func (ut *unitTracker) stop() error {
	ut.tomb.Kill(nil)
	return ut.tomb.Wait()
}

// serviceTracker  keeps track of the changes of a service.
type serviceTracker struct {
	tomb       tomb.Tomb
	firewaller *Firewaller
	name       string
	exposed    bool
}

// newUnitTracker creates a new service tracker keeping track of
// exposed flag changes of the passed service.
func newServiceTracker(sst *state.Service, fw *Firewaller) *serviceTracker {
	st := &serviceTracker{
		firewaller: fw,
		name:       sst.Name(),
	}
	isExposed, err := sst.IsExposed()
	if err != nil {
		panic(fmt.Sprintf("can't retrieve exposed state of service %q: %v", sst.Name(), err))
	}
	st.exposed = isExposed
	// TODO(mue) Start backend loop.
	return st
}
<commit_msg>firewaller: changed behavior of opening and closing ports after wrong approach<commit_after>package firewaller

import (
	"fmt"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/log"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/state/watcher"
	"launchpad.net/tomb"
)

// Firewaller watches the state for ports opened or closed
// and reflects those changes onto the backing environment.
type Firewaller struct {
	environ             environs.Environ
	st                  *state.State
	tomb                tomb.Tomb
	machinesWatcher     *state.MachinesWatcher
	machines            map[int]*machineTracker
	machineUnitsChanges chan *machineUnitsChange
	units               map[string]*unitTracker
	unitPortsChanges    chan *unitPortsChange
	services            map[string]*serviceTracker
}

// NewFirewaller returns a new Firewaller.
func NewFirewaller(environ environs.Environ, st *state.State) (*Firewaller, error) {
	fw := &Firewaller{
		environ:             environ,
		st:                  st,
		machinesWatcher:     st.WatchMachines(),
		machines:            make(map[int]*machineTracker),
		machineUnitsChanges: make(chan *machineUnitsChange),
		units:               make(map[string]*unitTracker),
		unitPortsChanges:    make(chan *unitPortsChange),
		services:            make(map[string]*serviceTracker),
	}
	go fw.loop()
	return fw, nil
}

func (fw *Firewaller) loop() {
	defer fw.finish()
	for {
		select {
		case <-fw.tomb.Dying():
			return
		case change, ok := <-fw.machinesWatcher.Changes():
			if !ok {
				return
			}
			for _, removedMachine := range change.Removed {
				mt, ok := fw.machines[removedMachine.Id()]
				if !ok {
					panic("trying to remove machine that wasn't added")
				}
				delete(fw.machines, removedMachine.Id())
				if err := mt.stop(); err != nil {
					log.Printf("machine tracker %d returned error when stopping: %v", removedMachine.Id(), err)
				}
				log.Debugf("firewaller: stopped tracking machine %d", removedMachine.Id())
			}
			for _, addedMachine := range change.Added {
				mt := newMachineTracker(addedMachine, fw)
				fw.machines[addedMachine.Id()] = mt
				log.Debugf("firewaller: started tracking machine %d", addedMachine.Id())
			}
		case change, ok := <-fw.machineUnitsChanges:
			if !ok {
				panic("aggregation of machine units changes failed")
			}
			if change.change == nil {
				log.Printf("tracker of machine %d terminated prematurely", change.machineTracker.machine.Id())
				delete(fw.machines, change.machineTracker.machine.Id())
				continue
			}
			for _, removedUnit := range change.change.Removed {
				ut, ok := fw.units[removedUnit.Name()]
				if !ok {
					panic("trying to remove unit that wasn't added")
				}
				delete(fw.units, removedUnit.Name())
				if err := ut.stop(); err != nil {
					log.Printf("unit tracker %s returned error when stopping: %v", removedUnit.Name(), err)
				}
				log.Debugf("firewaller: stopped tracking unit %s", removedUnit.Name())
			}
			for _, addedUnit := range change.change.Added {
				ut := newUnitTracker(addedUnit, change.machineTracker.machine.Id(), fw)
				fw.units[addedUnit.Name()] = ut
				if fw.services[addedUnit.ServiceName()] == nil {
					service, err := fw.st.Service(addedUnit.ServiceName())
					if err != nil {
						// TODO(mue) Check if panic is too hard.
						panic(fmt.Sprintf("service state %q can't be retrieved: %v", addedUnit.ServiceName(), err))
					}
					st := newServiceTracker(service, fw)
					ut.service = st
					fw.services[addedUnit.ServiceName()] = st
				}
				log.Debugf("firewaller: started tracking unit %s", ut.name)
			}
		case change := <-fw.unitPortsChanges:
			mt, ok := fw.machines[change.unitTracker.machineId]
			if !ok {
				panic("machine for unit ports change isn't tracked")
			}
			for _, port := range change.change {
				if mt.ports[port] == nil && change.unitTracker.service.exposed {
					mt.ports[port] = change.unitTracker
					if err := fw.openPort(mt, port); err != nil {
						log.Printf("can't open port %v on machine %d: %v", port, mt.machine.Id(), err)
					}
					log.Debugf("firewaller: opened port %v on machine %d", port, mt.machine.Id())
				}
			}
			for _, port := range change.unitTracker.ports {
				if mt.ports[port] == change.unitTracker {
					delete(mt.ports, port)
					if change.unitTracker.service.exposed {
						if err := fw.closePort(mt, port); err != nil {
							log.Printf("can't close port %v on machine %d: %v", port, mt.machine.Id(), err)
						}
						log.Debugf("firewaller: closed port %v on machine %d", port, mt.machine.Id())
					}
				}
			}
			change.unitTracker.ports = change.change
		}
	}
}

// openPort opens the passed port on the instances for the passed machine.
func (fw *Firewaller) openPort(mt *machineTracker, port state.Port) error {
	instanceId, err := mt.machine.InstanceId()
	if err != nil {
		return err
	}
	instances, err := fw.environ.Instances([]string{instanceId})
	if err != nil {
		return err
	}
	err = instances[0].OpenPorts(mt.machine.Id(), []state.Port{port})
	if err != nil {
		// TODO(mue) Add a retry logic later.
		return err
	}
	return nil
}

// openPort closes the passed port on the instances for the passed machine.
func (fw *Firewaller) closePort(mt *machineTracker, port state.Port) error {
	instanceId, err := mt.machine.InstanceId()
	if err != nil {
		return err
	}
	instances, err := fw.environ.Instances([]string{instanceId})
	if err != nil {
		return err
	}
	err = instances[0].ClosePorts(mt.machine.Id(), []state.Port{port})
	if err != nil {
		// TODO(mue) Add a retry logic later.
		return err
	}
	return nil
}

// finishes cleans up when the firewaller is stopping.
func (fw *Firewaller) finish() {
	watcher.Stop(fw.machinesWatcher, &fw.tomb)
	for _, ut := range fw.units {
		fw.tomb.Kill(ut.stop())
	}
	for _, mt := range fw.machines {
		fw.tomb.Kill(mt.stop())
	}
	fw.tomb.Done()
}

// Wait waits for the Firewaller to exit.
func (fw *Firewaller) Wait() error {
	return fw.tomb.Wait()
}

// Stop stops the Firewaller and returns any error encountered while stopping.
func (fw *Firewaller) Stop() error {
	fw.tomb.Kill(nil)
	return fw.tomb.Wait()
}

// machineUnitsChange contains the changed units for one specific machine.
type machineUnitsChange struct {
	machineTracker *machineTracker
	change         *state.MachineUnitsChange
}

// machineTracker keeps track of the unit changes of a machine.
type machineTracker struct {
	tomb       tomb.Tomb
	firewaller *Firewaller
	machine    *state.Machine
	watcher    *state.MachineUnitsWatcher
	ports      map[state.Port]*unitTracker
}

// newMachineTracker creates a new machine tracker keeping track of
// unit changes of the passed machine.
func newMachineTracker(mst *state.Machine, fw *Firewaller) *machineTracker {
	mt := &machineTracker{
		firewaller: fw,
		machine:    mst,
		watcher:    mst.WatchUnits(),
		ports:      make(map[state.Port]*unitTracker),
	}
	go mt.loop()
	return mt
}

// loop is the backend watching for machine units changes.
func (mt *machineTracker) loop() {
	defer mt.tomb.Done()
	defer mt.watcher.Stop()
	for {
		select {
		case <-mt.tomb.Dying():
			return
		case change, ok := <-mt.watcher.Changes():
			// Send change or nil in case of an error.
			select {
			case mt.firewaller.machineUnitsChanges <- &machineUnitsChange{mt, change}:
			case <-mt.tomb.Dying():
				return
			}
			// The watcher terminated prematurely, so end the loop.
			if !ok {
				mt.firewaller.tomb.Kill(watcher.MustErr(mt.watcher))
				return
			}
		}
	}
}

// stop stops the machine tracker.
func (mt *machineTracker) stop() error {
	mt.tomb.Kill(nil)
	return mt.tomb.Wait()
}

// unitPortsChange contains the changed ports for one specific unit.
type unitPortsChange struct {
	unitTracker *unitTracker
	change      []state.Port
}

// unitTracker keeps track of the port changes of a unit.
type unitTracker struct {
	tomb       tomb.Tomb
	firewaller *Firewaller
	machineId  int
	name       string
	watcher    *state.PortsWatcher
	service    *serviceTracker
	ports      []state.Port
}

// newUnitTracker creates a new machine tracker keeping track of
// unit changes of the passed machine.
func newUnitTracker(ust *state.Unit, machineId int, fw *Firewaller) *unitTracker {
	ut := &unitTracker{
		firewaller: fw,
		machineId:  machineId,
		name:       ust.Name(),
		watcher:    ust.WatchPorts(),
		ports:      make([]state.Port, 0),
	}
	go ut.loop()
	return ut
}

// loop is the backend watching for unit ports changes.
func (ut *unitTracker) loop() {
	defer ut.tomb.Done()
	defer ut.watcher.Stop()
	for {
		select {
		case <-ut.tomb.Dying():
			return
		case change, ok := <-ut.watcher.Changes():
			// Send change or nil in case of an error.
			select {
			case ut.firewaller.unitPortsChanges <- &unitPortsChange{ut, change}:
			case <-ut.tomb.Dying():
				return
			}
			// The watcher terminated prematurely, so end the loop.
			if !ok {
				ut.firewaller.tomb.Kill(watcher.MustErr(ut.watcher))
				return
			}
		}
	}
}

// stop stops the unit tracker.
func (ut *unitTracker) stop() error {
	ut.tomb.Kill(nil)
	return ut.tomb.Wait()
}

// serviceTracker  keeps track of the changes of a service.
type serviceTracker struct {
	tomb       tomb.Tomb
	firewaller *Firewaller
	name       string
	exposed    bool
}

// newUnitTracker creates a new service tracker keeping track of
// exposed flag changes of the passed service.
func newServiceTracker(sst *state.Service, fw *Firewaller) *serviceTracker {
	st := &serviceTracker{
		firewaller: fw,
		name:       sst.Name(),
	}
	isExposed, err := sst.IsExposed()
	if err != nil {
		panic(fmt.Sprintf("can't retrieve exposed state of service %q: %v", sst.Name(), err))
	}
	st.exposed = isExposed
	// TODO(mue) Start backend loop.
	return st
}
<|endoftext|>


<commit_before>package service

import (
	"bytes"
	"time"

	"github.com/builderscon/octav/octav/db"
	"github.com/builderscon/octav/octav/gettext"
	"github.com/builderscon/octav/octav/internal/errors"
	"github.com/builderscon/octav/octav/model"
	"github.com/builderscon/octav/octav/tools"
	pdebug "github.com/lestrrat/go-pdebug"
)

func (v *UserSvc) Init() {}

func (v *UserSvc) populateRowForCreate(vdb *db.User, payload model.CreateUserRequest) error {
	vdb.EID = tools.UUID()

	vdb.Nickname = payload.Nickname
	vdb.AuthVia = payload.AuthVia
	vdb.AuthUserID = payload.AuthUserID

	if payload.AvatarURL.Valid() {
		vdb.AvatarURL.Valid = true
		vdb.AvatarURL.String = payload.AvatarURL.String
	}

	if payload.FirstName.Valid() {
		vdb.FirstName.Valid = true
		vdb.FirstName.String = payload.FirstName.String
	}

	if payload.LastName.Valid() {
		vdb.LastName.Valid = true
		vdb.LastName.String = payload.LastName.String
	}

	if payload.Email.Valid() {
		vdb.Email.Valid = true
		vdb.Email.String = payload.Email.String
	}

	if payload.TshirtSize.Valid() {
		vdb.TshirtSize.Valid = true
		vdb.TshirtSize.String = payload.TshirtSize.String
	}

	return nil
}

func (v *UserSvc) populateRowForUpdate(vdb *db.User, payload model.UpdateUserRequest) error {
	if payload.Nickname.Valid() {
		vdb.Nickname = payload.Nickname.String
	}

	if payload.AuthVia.Valid() {
		vdb.AuthVia = payload.AuthVia.String
	}

	if payload.AuthUserID.Valid() {
		vdb.AuthUserID = payload.AuthUserID.String
	}

	if payload.AvatarURL.Valid() {
		vdb.AvatarURL.Valid = true
		vdb.AvatarURL.String = payload.AvatarURL.String
	}

	if payload.FirstName.Valid() {
		vdb.FirstName.Valid = true
		vdb.FirstName.String = payload.FirstName.String
	}

	if payload.LastName.Valid() {
		vdb.LastName.Valid = true
		vdb.LastName.String = payload.LastName.String
	}

	if payload.Email.Valid() {
		vdb.Email.Valid = true
		vdb.Email.String = payload.Email.String
	}

	if payload.TshirtSize.Valid() {
		vdb.TshirtSize.Valid = true
		vdb.TshirtSize.String = payload.TshirtSize.String
	}

	return nil
}

func (v *UserSvc) IsAdministrator(tx *db.Tx, id string) error {
	// TODO: cache
	return db.IsAdministrator(tx, id)
}

func (v *UserSvc) IsSystemAdmin(tx *db.Tx, id string) error {
	// TODO: cache
	var u model.User
	if err := v.Lookup(tx, &u, id); err != nil {
		return errors.Wrap(err, "failed to load user from database")
	}

	if !u.IsAdmin {
		return errors.Errorf("user %s lacks system administrator privileges", id)
	}
	return nil
}

func (v *UserSvc) IsConferenceSeriesAdministrator(tx *db.Tx, seriesID, userID string) error {
	// TODO: cache
	if err := db.IsConferenceSeriesAdministrator(tx, seriesID, userID); err == nil {
		return nil
	}

	if err := v.IsSystemAdmin(tx, userID); err == nil {
		return nil
	}
	return errors.Errorf("user %s lacks conference series administrator privileges for %s", userID, seriesID)
}

func (v *UserSvc) IsConferenceAdministrator(tx *db.Tx, confID, userID string) error {
	// TODO: cache
	if err := db.IsConferenceAdministrator(tx, confID, userID); err == nil {
		return nil
	}

	var c model.Conference
	sc := Conference()
	if err := sc.Lookup(tx, &c, confID); err != nil {
		return errors.Wrap(err, "failed to load conference from database")
	}

	if err := v.IsConferenceSeriesAdministrator(tx, c.SeriesID, userID); err == nil {
		return nil
	}

	return errors.Errorf("user %s lacks conference administrator privileges for %s", userID, confID)
}

func (v *UserSvc) IsOwnerUser(tx *db.Tx, targetID, userID string) error {
	if targetID == userID {
		return nil
	}

	return v.IsSystemAdmin(tx, userID)
}

func (v *UserSvc) ListFromPayload(tx *db.Tx, result *model.UserList, payload model.ListUserRequest) error {
	var vdbl db.UserList
	if err := vdbl.LoadSinceEID(tx, payload.Since.String, int(payload.Limit.Int)); err != nil {
		return errors.Wrap(err, "failed to load from database")
	}

	l := make(model.UserList, len(vdbl))
	for i, vdb := range vdbl {
		if err := l[i].FromRow(vdb); err != nil {
			return errors.Wrap(err, "failed to populate model from database")
		}

		if err := v.Decorate(tx, &l[i], payload.TrustedCall, payload.Lang.String); err != nil {
			return errors.Wrap(err, "failed to decorate user with associated data")
		}
	}

	*result = l
	return nil
}

func (v *UserSvc) CreateFromPayload(tx *db.Tx, result *model.User, payload model.CreateUserRequest) error {
	// Normally we would like to limit who can create users, but this
	// is done via OAuth login, so anybody must be able to do it.
	var vdb db.User
	if err := v.Create(tx, &vdb, payload); err != nil {
		return errors.Wrap(err, "failed to create new user in database")
	}

	var m model.User
	if err := m.FromRow(vdb); err != nil {
		return errors.Wrap(err, "failed to populate model from database")
	}

	*result = m
	return nil
}

func (v *UserSvc) DeleteFromPayload(tx *db.Tx, payload model.DeleteUserRequest) error {
	if err := v.IsOwnerUser(tx, payload.ID, payload.UserID); err != nil {
		return errors.Wrap(err, "deleting a user requires to be the user themselves, or a system administrator")
	}

	return errors.Wrap(v.Delete(tx, payload.ID), "failed to delete user")
}

func (v *UserSvc) UpdateFromPayload(tx *db.Tx, payload model.UpdateUserRequest) error {
	if err := v.IsOwnerUser(tx, payload.ID, payload.UserID); err != nil {
		return errors.Wrap(err, "updating a user requires to be the user themselves, or a system administrator")
	}

	var vdb db.User
	if err := vdb.LoadByEID(tx, payload.ID); err != nil {
		return errors.Wrap(err, "failed to load from database")
	}

	return errors.Wrap(v.Update(tx, &vdb, payload), "failed to update database")
}

func (v *UserSvc) IsSessionOwner(tx *db.Tx, sessionID, userID string) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("session.User.IsSessionOwner session ID = %s, user ID = %s", sessionID, userID).BindError(&err)
		defer g.End()
	}

	if err := db.IsSessionOwner(tx, sessionID, userID); err == nil {
		return nil
	}

	ss := Session()
	var m model.Session
	if err := ss.Lookup(tx, &m, sessionID); err != nil {
		return errors.Wrap(err, "failed to load session")
	}

	if err := v.IsConferenceAdministrator(tx, m.ConferenceID, userID); err == nil {
		return nil
	}

	return errors.Errorf("user %s lacks session owner privileges for %s", userID, sessionID)
}

func (v *UserSvc) Decorate(tx *db.Tx, user *model.User, trustedCall bool, lang string) error {
	if !trustedCall {
		user.Email = ""
		user.TshirtSize = ""
		user.AuthVia = ""
		user.AuthUserID = ""
	}

	if lang != "" {
		if err := v.ReplaceL10NStrings(tx, user, lang); err != nil {
			return errors.Wrap(err, "failed to replace L10N strings")
		}
	}
	return nil
}

func (v *UserSvc) LookupUserByAuthUserIDFromPayload(tx *db.Tx, result *model.User, payload model.LookupUserByAuthUserIDRequest) error {
	var vdb db.User
	if err := vdb.LoadByAuthUserID(tx, payload.AuthVia, payload.AuthUserID); err != nil {
		return errors.Wrap(err, "failed to load from database")
	}

	var r model.User
	if err := r.FromRow(vdb); err != nil {
		return errors.Wrap(err, "failed to populate mode from database")
	}

	if err := v.Decorate(tx, &r, payload.TrustedCall, payload.Lang.String); err != nil {
		return errors.Wrap(err, "failed to decorate with assocaited data")
	}

	*result = r
	return nil
}

func (v *UserSvc) CreateTemporaryEmailFromPayload(tx *db.Tx, key *string, payload model.CreateTemporaryEmailRequest) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("service.User.CreateTemporaryEmailFromPayload").BindError(&err)
		defer g.End()
	}

	var row db.TemporaryEmail

	row.UserID = payload.TargetID
	row.Email = payload.Email
	row.ConfirmationKey = tools.UUID()
	row.ExpiresOn = time.Now().Add(time.Duration(24 * time.Hour))

	if err := row.Upsert(tx); err != nil {
		return errors.Wrap(err, "failed to create temporary email")
	}

	*key = row.ConfirmationKey
	gettext.SetLocale(payload.Lang.String)

	var txt bytes.Buffer
	if err := Template().Execute(&txt, "templates/eml/confirm_registration.eml", row); err != nil {
		return errors.Wrap(err, "failed to execute template")
	}

	mg := Mailgun()
	if pdebug.Enabled {
		pdebug.Printf("Got mailgun %v", mg)
	}
	mm := MailMessage{
		Recipients: []string{payload.Email},
		Subject:    gettext.Get("Confirm Your Email Registration"),
		Text:       txt.String(),
	}

	if pdebug.Enabled {
		pdebug.Printf("Sending via mailgun: %#v", mm)
	}

	if err := mg.Send(&mm); err != nil {
		return errors.Wrap(err, "failed to send message")
	}
	return nil
}

func (v *UserSvc) ConfirmTemporaryEmailFromPayload(tx *db.Tx, payload model.ConfirmTemporaryEmailRequest) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("service.User.ConfirmTemporaryEmailFromPayload").BindError(&err)
		defer g.End()
	}

	var row db.TemporaryEmail
	if err := row.LoadByUserIDAndConfirmationKey(tx, payload.TargetID, payload.ConfirmationKey); err != nil {
		return errors.Wrap(err, "failed to load temporary email")
	}

	var u db.User
	if err := u.LoadByEID(tx, payload.TargetID); err != nil {
		return errors.Wrap(err, "failed to load user")
	}

	u.Email.String = row.Email
	if err := u.Update(tx); err != nil {
		return errors.Wrap(err, "failed to update user")
	}

	if err := row.Delete(tx); err != nil {
		return errors.Wrap(err, "failed to delete temporary email")
	}

	return nil
}
<commit_msg>Set valid<commit_after>package service

import (
	"bytes"
	"time"

	"github.com/builderscon/octav/octav/db"
	"github.com/builderscon/octav/octav/gettext"
	"github.com/builderscon/octav/octav/internal/errors"
	"github.com/builderscon/octav/octav/model"
	"github.com/builderscon/octav/octav/tools"
	pdebug "github.com/lestrrat/go-pdebug"
)

func (v *UserSvc) Init() {}

func (v *UserSvc) populateRowForCreate(vdb *db.User, payload model.CreateUserRequest) error {
	vdb.EID = tools.UUID()

	vdb.Nickname = payload.Nickname
	vdb.AuthVia = payload.AuthVia
	vdb.AuthUserID = payload.AuthUserID

	if payload.AvatarURL.Valid() {
		vdb.AvatarURL.Valid = true
		vdb.AvatarURL.String = payload.AvatarURL.String
	}

	if payload.FirstName.Valid() {
		vdb.FirstName.Valid = true
		vdb.FirstName.String = payload.FirstName.String
	}

	if payload.LastName.Valid() {
		vdb.LastName.Valid = true
		vdb.LastName.String = payload.LastName.String
	}

	if payload.Email.Valid() {
		vdb.Email.Valid = true
		vdb.Email.String = payload.Email.String
	}

	if payload.TshirtSize.Valid() {
		vdb.TshirtSize.Valid = true
		vdb.TshirtSize.String = payload.TshirtSize.String
	}

	return nil
}

func (v *UserSvc) populateRowForUpdate(vdb *db.User, payload model.UpdateUserRequest) error {
	if payload.Nickname.Valid() {
		vdb.Nickname = payload.Nickname.String
	}

	if payload.AuthVia.Valid() {
		vdb.AuthVia = payload.AuthVia.String
	}

	if payload.AuthUserID.Valid() {
		vdb.AuthUserID = payload.AuthUserID.String
	}

	if payload.AvatarURL.Valid() {
		vdb.AvatarURL.Valid = true
		vdb.AvatarURL.String = payload.AvatarURL.String
	}

	if payload.FirstName.Valid() {
		vdb.FirstName.Valid = true
		vdb.FirstName.String = payload.FirstName.String
	}

	if payload.LastName.Valid() {
		vdb.LastName.Valid = true
		vdb.LastName.String = payload.LastName.String
	}

	if payload.Email.Valid() {
		vdb.Email.Valid = true
		vdb.Email.String = payload.Email.String
	}

	if payload.TshirtSize.Valid() {
		vdb.TshirtSize.Valid = true
		vdb.TshirtSize.String = payload.TshirtSize.String
	}

	return nil
}

func (v *UserSvc) IsAdministrator(tx *db.Tx, id string) error {
	// TODO: cache
	return db.IsAdministrator(tx, id)
}

func (v *UserSvc) IsSystemAdmin(tx *db.Tx, id string) error {
	// TODO: cache
	var u model.User
	if err := v.Lookup(tx, &u, id); err != nil {
		return errors.Wrap(err, "failed to load user from database")
	}

	if !u.IsAdmin {
		return errors.Errorf("user %s lacks system administrator privileges", id)
	}
	return nil
}

func (v *UserSvc) IsConferenceSeriesAdministrator(tx *db.Tx, seriesID, userID string) error {
	// TODO: cache
	if err := db.IsConferenceSeriesAdministrator(tx, seriesID, userID); err == nil {
		return nil
	}

	if err := v.IsSystemAdmin(tx, userID); err == nil {
		return nil
	}
	return errors.Errorf("user %s lacks conference series administrator privileges for %s", userID, seriesID)
}

func (v *UserSvc) IsConferenceAdministrator(tx *db.Tx, confID, userID string) error {
	// TODO: cache
	if err := db.IsConferenceAdministrator(tx, confID, userID); err == nil {
		return nil
	}

	var c model.Conference
	sc := Conference()
	if err := sc.Lookup(tx, &c, confID); err != nil {
		return errors.Wrap(err, "failed to load conference from database")
	}

	if err := v.IsConferenceSeriesAdministrator(tx, c.SeriesID, userID); err == nil {
		return nil
	}

	return errors.Errorf("user %s lacks conference administrator privileges for %s", userID, confID)
}

func (v *UserSvc) IsOwnerUser(tx *db.Tx, targetID, userID string) error {
	if targetID == userID {
		return nil
	}

	return v.IsSystemAdmin(tx, userID)
}

func (v *UserSvc) ListFromPayload(tx *db.Tx, result *model.UserList, payload model.ListUserRequest) error {
	var vdbl db.UserList
	if err := vdbl.LoadSinceEID(tx, payload.Since.String, int(payload.Limit.Int)); err != nil {
		return errors.Wrap(err, "failed to load from database")
	}

	l := make(model.UserList, len(vdbl))
	for i, vdb := range vdbl {
		if err := l[i].FromRow(vdb); err != nil {
			return errors.Wrap(err, "failed to populate model from database")
		}

		if err := v.Decorate(tx, &l[i], payload.TrustedCall, payload.Lang.String); err != nil {
			return errors.Wrap(err, "failed to decorate user with associated data")
		}
	}

	*result = l
	return nil
}

func (v *UserSvc) CreateFromPayload(tx *db.Tx, result *model.User, payload model.CreateUserRequest) error {
	// Normally we would like to limit who can create users, but this
	// is done via OAuth login, so anybody must be able to do it.
	var vdb db.User
	if err := v.Create(tx, &vdb, payload); err != nil {
		return errors.Wrap(err, "failed to create new user in database")
	}

	var m model.User
	if err := m.FromRow(vdb); err != nil {
		return errors.Wrap(err, "failed to populate model from database")
	}

	*result = m
	return nil
}

func (v *UserSvc) DeleteFromPayload(tx *db.Tx, payload model.DeleteUserRequest) error {
	if err := v.IsOwnerUser(tx, payload.ID, payload.UserID); err != nil {
		return errors.Wrap(err, "deleting a user requires to be the user themselves, or a system administrator")
	}

	return errors.Wrap(v.Delete(tx, payload.ID), "failed to delete user")
}

func (v *UserSvc) UpdateFromPayload(tx *db.Tx, payload model.UpdateUserRequest) error {
	if err := v.IsOwnerUser(tx, payload.ID, payload.UserID); err != nil {
		return errors.Wrap(err, "updating a user requires to be the user themselves, or a system administrator")
	}

	var vdb db.User
	if err := vdb.LoadByEID(tx, payload.ID); err != nil {
		return errors.Wrap(err, "failed to load from database")
	}

	return errors.Wrap(v.Update(tx, &vdb, payload), "failed to update database")
}

func (v *UserSvc) IsSessionOwner(tx *db.Tx, sessionID, userID string) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("session.User.IsSessionOwner session ID = %s, user ID = %s", sessionID, userID).BindError(&err)
		defer g.End()
	}

	if err := db.IsSessionOwner(tx, sessionID, userID); err == nil {
		return nil
	}

	ss := Session()
	var m model.Session
	if err := ss.Lookup(tx, &m, sessionID); err != nil {
		return errors.Wrap(err, "failed to load session")
	}

	if err := v.IsConferenceAdministrator(tx, m.ConferenceID, userID); err == nil {
		return nil
	}

	return errors.Errorf("user %s lacks session owner privileges for %s", userID, sessionID)
}

func (v *UserSvc) Decorate(tx *db.Tx, user *model.User, trustedCall bool, lang string) error {
	if !trustedCall {
		user.Email = ""
		user.TshirtSize = ""
		user.AuthVia = ""
		user.AuthUserID = ""
	}

	if lang != "" {
		if err := v.ReplaceL10NStrings(tx, user, lang); err != nil {
			return errors.Wrap(err, "failed to replace L10N strings")
		}
	}
	return nil
}

func (v *UserSvc) LookupUserByAuthUserIDFromPayload(tx *db.Tx, result *model.User, payload model.LookupUserByAuthUserIDRequest) error {
	var vdb db.User
	if err := vdb.LoadByAuthUserID(tx, payload.AuthVia, payload.AuthUserID); err != nil {
		return errors.Wrap(err, "failed to load from database")
	}

	var r model.User
	if err := r.FromRow(vdb); err != nil {
		return errors.Wrap(err, "failed to populate mode from database")
	}

	if err := v.Decorate(tx, &r, payload.TrustedCall, payload.Lang.String); err != nil {
		return errors.Wrap(err, "failed to decorate with assocaited data")
	}

	*result = r
	return nil
}

func (v *UserSvc) CreateTemporaryEmailFromPayload(tx *db.Tx, key *string, payload model.CreateTemporaryEmailRequest) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("service.User.CreateTemporaryEmailFromPayload").BindError(&err)
		defer g.End()
	}

	var row db.TemporaryEmail

	row.UserID = payload.TargetID
	row.Email = payload.Email
	row.ConfirmationKey = tools.UUID()
	row.ExpiresOn = time.Now().Add(time.Duration(24 * time.Hour))

	if err := row.Upsert(tx); err != nil {
		return errors.Wrap(err, "failed to create temporary email")
	}

	*key = row.ConfirmationKey
	gettext.SetLocale(payload.Lang.String)

	var txt bytes.Buffer
	if err := Template().Execute(&txt, "templates/eml/confirm_registration.eml", row); err != nil {
		return errors.Wrap(err, "failed to execute template")
	}

	mg := Mailgun()
	if pdebug.Enabled {
		pdebug.Printf("Got mailgun %v", mg)
	}
	mm := MailMessage{
		Recipients: []string{payload.Email},
		Subject:    gettext.Get("Confirm Your Email Registration"),
		Text:       txt.String(),
	}

	if pdebug.Enabled {
		pdebug.Printf("Sending via mailgun: %#v", mm)
	}

	if err := mg.Send(&mm); err != nil {
		return errors.Wrap(err, "failed to send message")
	}
	return nil
}

func (v *UserSvc) ConfirmTemporaryEmailFromPayload(tx *db.Tx, payload model.ConfirmTemporaryEmailRequest) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("service.User.ConfirmTemporaryEmailFromPayload").BindError(&err)
		defer g.End()
	}

	var row db.TemporaryEmail
	if err := row.LoadByUserIDAndConfirmationKey(tx, payload.TargetID, payload.ConfirmationKey); err != nil {
		return errors.Wrap(err, "failed to load temporary email")
	}

	var u db.User
	if err := u.LoadByEID(tx, payload.TargetID); err != nil {
		return errors.Wrap(err, "failed to load user")
	}

	u.Email.String = row.Email
	u.Email.Valid = true
	if err := u.Update(tx); err != nil {
		return errors.Wrap(err, "failed to update user")
	}

	if err := row.Delete(tx); err != nil {
		return errors.Wrap(err, "failed to delete temporary email")
	}

	return nil
}
<|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.

// certcheck is a utility to show and check the contents of certificates.
package main

import (
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"net/url"
	"os"
	"strings"

	"github.com/golang/glog"
	"github.com/google/certificate-transparency-go/x509"
	"github.com/google/certificate-transparency-go/x509util"
)

var (
	root                      = flag.String("root", "", "Root CA certificate file")
	intermediate              = flag.String("intermediate", "", "Intermediate CA certificate file")
	useSystemRoots            = flag.Bool("system_roots", false, "Use system roots")
	verbose                   = flag.Bool("verbose", false, "Verbose output")
	validate                  = flag.Bool("validate", false, "Validate certificate signatures")
	timecheck                 = flag.Bool("timecheck", false, "Check current validity of certificate")
	revokecheck               = flag.Bool("check_revocation", false, "Check revocation status of certificate")
	ignoreUnknownCriticalExts = flag.Bool("ignore_unknown_critical_exts", false, "Ignore unknown-critical-extension errors")
)

func addCerts(filename string, pool *x509.CertPool) {
	if filename != "" {
		dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
		if err != nil {
			glog.Exitf("Failed to read certificate file: %v", err)
		}
		for _, data := range dataList {
			certs, err := x509.ParseCertificates(data)
			if err != nil {
				glog.Exitf("Failed to parse certificate from %s: %v", filename, err)
			}
			for _, cert := range certs {
				pool.AddCert(cert)
			}
		}
	}
}

func main() {
	flag.Parse()

	failed := false
	for _, target := range flag.Args() {
		var err error
		var chain []*x509.Certificate
		if strings.HasPrefix(target, "https://") {
			chain, err = chainFromSite(target)
		} else {
			chain, err = chainFromFile(target)
		}
		if err != nil {
			glog.Errorf("%v", err)
			failed = true
			continue
		}
		for _, cert := range chain {
			if *verbose {
				fmt.Print(x509util.CertificateToString(cert))
			}
			if *revokecheck {
				if err := checkRevocation(cert, *verbose); err != nil {
					glog.Errorf("%s: certificate is revoked: %v", target, err)
					failed = true
				}
			}
		}
		if *validate && len(chain) > 0 {
			if *ignoreUnknownCriticalExts {
				// We don't want failures from Verify due to unknown critical extensions,
				// so clear them out.
				for _, cert := range chain {
					cert.UnhandledCriticalExtensions = nil
				}
			}
			if err := validateChain(chain, *timecheck, *root, *intermediate, *useSystemRoots); err != nil {
				glog.Errorf("%s: verification error: %v", target, err)
				failed = true
			}
		}
	}
	if failed {
		os.Exit(1)
	}
}

func chainFromSite(target string) ([]*x509.Certificate, error) {
	u, err := url.Parse(target)
	if err != nil {
		return nil, fmt.Errorf("%s: failed to parse URL: %v", target, err)
	}
	if u.Scheme != "https" {
		return nil, fmt.Errorf("%s: non-https URL provided", target)
	}
	host := u.Host
	if !strings.Contains(host, ":") {
		host += ":443"
	}

	conn, err := tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true})
	if err != nil {
		return nil, fmt.Errorf("%s: failed to dial %q: %v", target, host, err)
	}
	defer conn.Close()

	// Convert base crypto/x509.Certificates to our forked x509.Certificate type.
	goChain := conn.ConnectionState().PeerCertificates
	chain := make([]*x509.Certificate, len(goChain))
	for i, goCert := range goChain {
		cert, err := x509.ParseCertificate(goCert.Raw)
		if err != nil {
			return nil, fmt.Errorf("%s: failed to convert Go Certificate [%d]: %v", target, i, err)
		}
		chain[i] = cert
	}

	return chain, nil
}

func chainFromFile(filename string) ([]*x509.Certificate, error) {
	dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
	if err != nil {
		return nil, fmt.Errorf("%s: failed to read data: %v", filename, err)
	}
	var chain []*x509.Certificate
	for _, data := range dataList {
		certs, err := x509.ParseCertificates(data)
		if err != nil {
			return nil, fmt.Errorf("%s: failed to parse: %v", filename, err)
		}
		chain = append(chain, certs...)
	}
	return chain, nil
}

func validateChain(chain []*x509.Certificate, timecheck bool, rootsFile, intermediatesFile string, useSystemRoots bool) error {
	roots := x509.NewCertPool()
	if useSystemRoots {
		systemRoots, err := x509.SystemCertPool()
		if err != nil {
			glog.Errorf("Failed to get system roots: %v", err)
		}
		roots = systemRoots
	}
	opts := x509.VerifyOptions{
		KeyUsages:         []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
		Roots:             roots,
		Intermediates:     x509.NewCertPool(),
		DisableTimeChecks: !timecheck,
	}
	addCerts(rootsFile, opts.Roots)
	addCerts(intermediatesFile, opts.Intermediates)

	if !useSystemRoots && len(rootsFile) == 0 {
		// No root CA certs provided, so assume the chain is self-contained.
		count := len(chain)
		if len(chain) > 1 {
			last := chain[len(chain)-1]
			if bytes.Equal(last.RawSubject, last.RawIssuer) {
				opts.Roots.AddCert(last)
				count--
			}
		}
	}
	if len(intermediatesFile) == 0 {
		// No intermediate CA certs provided, so assume later entries in the chain are intermediates.
		for i := 1; i < len(chain); i++ {
			opts.Intermediates.AddCert(chain[i])
		}
	}
	_, err := chain[0].Verify(opts)
	return err
}

func checkRevocation(cert *x509.Certificate, verbose bool) error {
	for _, crldp := range cert.CRLDistributionPoints {
		crlDataList, err := x509util.ReadPossiblePEMURL(crldp, "X509 CRL")
		if err != nil {
			glog.Errorf("failed to retrieve CRL from %q: %v", crldp, err)
			continue
		}
		for _, crlData := range crlDataList {
			crl, err := x509.ParseCertificateList(crlData)
			if err != nil {
				glog.Errorf("failed to parse CRL from %q: %v", crldp, err)
				continue
			}
			if verbose {
				fmt.Printf("\nRevocation data from %s:\n", crldp)
				fmt.Print(x509util.CRLToString(crl))
			}
			for _, c := range crl.TBSCertList.RevokedCertificates {
				if c.SerialNumber.Cmp(cert.SerialNumber) == 0 {
					return fmt.Errorf("certificate is revoked since %v", c.RevocationTime)
				}
			}
		}
	}
	return nil
}
<commit_msg>x509util/certcheck: allow non-fatal errors through<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.

// certcheck is a utility to show and check the contents of certificates.
package main

import (
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"net/url"
	"os"
	"strings"

	"github.com/golang/glog"
	"github.com/google/certificate-transparency-go/x509"
	"github.com/google/certificate-transparency-go/x509util"
)

var (
	root                      = flag.String("root", "", "Root CA certificate file")
	intermediate              = flag.String("intermediate", "", "Intermediate CA certificate file")
	useSystemRoots            = flag.Bool("system_roots", false, "Use system roots")
	verbose                   = flag.Bool("verbose", false, "Verbose output")
	validate                  = flag.Bool("validate", false, "Validate certificate signatures")
	timecheck                 = flag.Bool("timecheck", false, "Check current validity of certificate")
	revokecheck               = flag.Bool("check_revocation", false, "Check revocation status of certificate")
	ignoreUnknownCriticalExts = flag.Bool("ignore_unknown_critical_exts", false, "Ignore unknown-critical-extension errors")
)

func addCerts(filename string, pool *x509.CertPool) {
	if filename != "" {
		dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
		if err != nil {
			glog.Exitf("Failed to read certificate file: %v", err)
		}
		for _, data := range dataList {
			certs, err := x509.ParseCertificates(data)
			if err != nil {
				glog.Exitf("Failed to parse certificate from %s: %v", filename, err)
			}
			for _, cert := range certs {
				pool.AddCert(cert)
			}
		}
	}
}

func main() {
	flag.Parse()

	failed := false
	for _, target := range flag.Args() {
		var err error
		var chain []*x509.Certificate
		if strings.HasPrefix(target, "https://") {
			chain, err = chainFromSite(target)
		} else {
			chain, err = chainFromFile(target)
		}
		if err != nil {
			glog.Errorf("%v", err)
			failed = true
			continue
		}
		for _, cert := range chain {
			if *verbose {
				fmt.Print(x509util.CertificateToString(cert))
			}
			if *revokecheck {
				if err := checkRevocation(cert, *verbose); err != nil {
					glog.Errorf("%s: certificate is revoked: %v", target, err)
					failed = true
				}
			}
		}
		if *validate && len(chain) > 0 {
			if *ignoreUnknownCriticalExts {
				// We don't want failures from Verify due to unknown critical extensions,
				// so clear them out.
				for _, cert := range chain {
					cert.UnhandledCriticalExtensions = nil
				}
			}
			if err := validateChain(chain, *timecheck, *root, *intermediate, *useSystemRoots); err != nil {
				glog.Errorf("%s: verification error: %v", target, err)
				failed = true
			}
		}
	}
	if failed {
		os.Exit(1)
	}
}

func chainFromSite(target string) ([]*x509.Certificate, error) {
	u, err := url.Parse(target)
	if err != nil {
		return nil, fmt.Errorf("%s: failed to parse URL: %v", target, err)
	}
	if u.Scheme != "https" {
		return nil, fmt.Errorf("%s: non-https URL provided", target)
	}
	host := u.Host
	if !strings.Contains(host, ":") {
		host += ":443"
	}

	conn, err := tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true})
	if err != nil {
		return nil, fmt.Errorf("%s: failed to dial %q: %v", target, host, err)
	}
	defer conn.Close()

	// Convert base crypto/x509.Certificates to our forked x509.Certificate type.
	goChain := conn.ConnectionState().PeerCertificates
	chain := make([]*x509.Certificate, len(goChain))
	for i, goCert := range goChain {
		cert, err := x509.ParseCertificate(goCert.Raw)
		if err != nil {
			return nil, fmt.Errorf("%s: failed to convert Go Certificate [%d]: %v", target, i, err)
		}
		chain[i] = cert
	}

	return chain, nil
}

func chainFromFile(filename string) ([]*x509.Certificate, error) {
	dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
	if err != nil {
		return nil, fmt.Errorf("%s: failed to read data: %v", filename, err)
	}
	var chain []*x509.Certificate
	for _, data := range dataList {
		certs, err := x509.ParseCertificates(data)
		if x509.IsFatal(err) {
			return nil, fmt.Errorf("%s: failed to parse: %v", filename, err)
		}
		if err != nil {
			glog.Warningf("%s: non-fatal error parsing: %v", filename, err)
		}
		chain = append(chain, certs...)
	}
	return chain, nil
}

func validateChain(chain []*x509.Certificate, timecheck bool, rootsFile, intermediatesFile string, useSystemRoots bool) error {
	roots := x509.NewCertPool()
	if useSystemRoots {
		systemRoots, err := x509.SystemCertPool()
		if err != nil {
			glog.Errorf("Failed to get system roots: %v", err)
		}
		roots = systemRoots
	}
	opts := x509.VerifyOptions{
		KeyUsages:         []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
		Roots:             roots,
		Intermediates:     x509.NewCertPool(),
		DisableTimeChecks: !timecheck,
	}
	addCerts(rootsFile, opts.Roots)
	addCerts(intermediatesFile, opts.Intermediates)

	if !useSystemRoots && len(rootsFile) == 0 {
		// No root CA certs provided, so assume the chain is self-contained.
		count := len(chain)
		if len(chain) > 1 {
			last := chain[len(chain)-1]
			if bytes.Equal(last.RawSubject, last.RawIssuer) {
				opts.Roots.AddCert(last)
				count--
			}
		}
	}
	if len(intermediatesFile) == 0 {
		// No intermediate CA certs provided, so assume later entries in the chain are intermediates.
		for i := 1; i < len(chain); i++ {
			opts.Intermediates.AddCert(chain[i])
		}
	}
	_, err := chain[0].Verify(opts)
	return err
}

func checkRevocation(cert *x509.Certificate, verbose bool) error {
	for _, crldp := range cert.CRLDistributionPoints {
		crlDataList, err := x509util.ReadPossiblePEMURL(crldp, "X509 CRL")
		if err != nil {
			glog.Errorf("failed to retrieve CRL from %q: %v", crldp, err)
			continue
		}
		for _, crlData := range crlDataList {
			crl, err := x509.ParseCertificateList(crlData)
			if x509.IsFatal(err) {
				glog.Errorf("failed to parse CRL from %q: %v", crldp, err)
				continue
			}
			if err != nil {
				glog.Warningf("non-fatal error parsing CRL from %q: %v", crldp, err)
			}
			if verbose {
				fmt.Printf("\nRevocation data from %s:\n", crldp)
				fmt.Print(x509util.CRLToString(crl))
			}
			for _, c := range crl.TBSCertList.RevokedCertificates {
				if c.SerialNumber.Cmp(cert.SerialNumber) == 0 {
					return fmt.Errorf("certificate is revoked since %v", c.RevocationTime)
				}
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package dirble

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// Version is the library version
const Version = "0.1"

// APIBase is the root URL of the API
const APIBase = "http://api.dirble.com/v2/"

type Dirble struct {
	client *http.Client
	token  string
}

type Timestamped struct {
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

type Thumb struct {
	URL *string
}

type Image struct {
	URL   *string
	Thumb Thumb
}

type StationItem struct {
	ID         int
	Name       string
	Country    string
	Image      Image
	Slug       string
	Website    string
	Categories Categories
	Streams    []Stream
	Timestamped
}

type Stations []StationItem

type Stream struct {
	ID           int
	Stream       string
	Bitrate      int
	ContenType   string
	Status       int
	StationId    int `json:"Station_id"`
	Timedout     bool
	EmptyCounter int
	Timestamped
}

type Category struct {
	ID          int
	Title       string
	Description string
	Slug        string
	Ancestry    string
	Timestamped
}

type StationSong struct {
	ID        string
	Name      string
	Title     string
	Week      int
	Year      int
	StationID int `json:"station_id"`
	Info      *string
	Date      time.Time
}

type StationSongs []StationSong
type Categories []Category

type Station struct {
	ID           int
	Name         string
	Accepted     int
	Added        time.Time
	Country      string
	Description  string
	Image        Image
	Slug         string
	Website      string
	Streams      []Stream
	Categories   Categories
	StationSongs StationSongs `json:"station_songs"`
}

type CategoryTree struct {
	Children Categories
	Category
}

type CategoryStations []Station

type Country struct {
	ID          int
	CountryCode string `json:"country_code"`
	ContinentID int    `json:"Continent_id"`
	Timestamped
}

type Countries []Country

type Continent struct {
	ID       int
	Name     string
	Slug     string
	LegacyID int `json:"legacy_id"`
	Timestamped
}

type Continents []Continent

type SearchResults []Station

func New(rt http.RoundTripper, token string) *Dirble {
	return &Dirble{
		client: &http.Client{
			Transport: rt,
		},
		token: token,
	}
}

func (d *Dirble) fetchURL(url string) ([]byte, error) {
	var err error
	var resp *http.Response
	if resp, err = d.client.Get(url); err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP Error: %d", resp.StatusCode)
	}
	return ioutil.ReadAll(resp.Body)
}

func (d *Dirble) getStations(url string) (*Stations, error) {
	var err error
	var content []byte
	if content, err = d.fetchURL(url); err != nil {
		return nil, err
	}
	var s Stations
	if err = json.Unmarshal(content, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

func (d *Dirble) Stations(page, perPage, offset *int) (*Stations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations"
	q := u.Query()
	q.Set("token", d.token)
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	return d.getStations(u.String())
}

func (d *Dirble) Station(id int) (*Station, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "station/" + strconv.Itoa(id)
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var s Station
	if err = json.Unmarshal(content, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

func (d *Dirble) StationSongHistory(id int) (*StationSongs, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations/" + strconv.Itoa(id) + "/song_history"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var ss StationSongs
	if err = json.Unmarshal(content, &ss); err != nil {
		return nil, err
	}
	return &ss, nil
}

func (d *Dirble) StationSimilar(id int) (*Stations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations/" + strconv.Itoa(id) + "/similar"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	return d.getStations(u.String())
}

func (d *Dirble) Categories() (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CategoriesPrimary() (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories/primary"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CategoriesTree() (*CategoryTree, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories/tree"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var ct CategoryTree
	if err = json.Unmarshal(content, &ct); err != nil {
		return nil, err
	}
	return &ct, nil
}

func (d *Dirble) CategoryStations(id int, all bool, page, perPage, offset *int) (*CategoryStations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "category/" + strconv.Itoa(id) + "/stations"
	q := u.Query()
	q.Set("token", d.token)
	if all {
		q.Set("all", "1")
	}
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var cs CategoryStations
	if err = json.Unmarshal(content, &cs); err != nil {
		return nil, err
	}
	return &cs, nil
}

func (d *Dirble) CategoryChilds(id int) (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "category/" + strconv.Itoa(id) + "/childs"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) Countries() (*Countries, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "countries"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Countries
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CountriesStations(code string, all bool, page, perPage, offset *int) (*CategoryStations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "countries/" + code + "/stations"
	q := u.Query()
	q.Set("token", d.token)
	if all {
		q.Set("all", "1")
	}
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var cs CategoryStations
	if err = json.Unmarshal(content, &cs); err != nil {
		return nil, err
	}
	return &cs, nil
}

func (d *Dirble) Continents() (*Continents, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "continents"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Continents
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) ContinentsCountries(continentId int) (*Countries, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "continents/" + strconv.Itoa(continentId) + "/countries"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Countries
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) Search(query string, page *int) (*SearchResults, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "search/" + query
	q := u.Query()
	q.Set("token", d.token)

	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var sr SearchResults
	if err = json.Unmarshal(content, &sr); err != nil {
		return nil, err
	}
	return &sr, nil
}
<commit_msg>Fixed path<commit_after>package dirble

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// Version is the library version
const Version = "0.1"

// APIBase is the root URL of the API
const APIBase = "http://api.dirble.com/v2/"

type Dirble struct {
	client *http.Client
	token  string
}

type Timestamped struct {
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

type Thumb struct {
	URL *string
}

type Image struct {
	URL   *string
	Thumb Thumb
}

type StationItem struct {
	ID         int
	Name       string
	Country    string
	Image      Image
	Slug       string
	Website    string
	Categories Categories
	Streams    []Stream
	Timestamped
}

type Stations []StationItem

type Stream struct {
	ID           int
	Stream       string
	Bitrate      int
	ContenType   string
	Status       int
	StationId    int `json:"Station_id"`
	Timedout     bool
	EmptyCounter int
	Timestamped
}

type Category struct {
	ID          int
	Title       string
	Description string
	Slug        string
	Ancestry    string
	Timestamped
}

type StationSong struct {
	ID        string
	Name      string
	Title     string
	Week      int
	Year      int
	StationID int `json:"station_id"`
	Info      *string
	Date      time.Time
}

type StationSongs []StationSong
type Categories []Category

type Station struct {
	ID           int
	Name         string
	Accepted     int
	Added        time.Time
	Country      string
	Description  string
	Image        Image
	Slug         string
	Website      string
	Streams      []Stream
	Categories   Categories
	StationSongs StationSongs `json:"station_songs"`
}

type CategoryTree struct {
	Children Categories
	Category
}

type CategoryStations []Station

type Country struct {
	ID          int
	CountryCode string `json:"country_code"`
	ContinentID int    `json:"Continent_id"`
	Timestamped
}

type Countries []Country

type Continent struct {
	ID       int
	Name     string
	Slug     string
	LegacyID int `json:"legacy_id"`
	Timestamped
}

type Continents []Continent

type SearchResults []Station

func New(rt http.RoundTripper, token string) *Dirble {
	return &Dirble{
		client: &http.Client{
			Transport: rt,
		},
		token: token,
	}
}

func (d *Dirble) fetchURL(url string) ([]byte, error) {
	var err error
	var resp *http.Response
	if resp, err = d.client.Get(url); err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP Error: %d", resp.StatusCode)
	}
	return ioutil.ReadAll(resp.Body)
}

func (d *Dirble) getStations(url string) (*Stations, error) {
	var err error
	var content []byte
	if content, err = d.fetchURL(url); err != nil {
		return nil, err
	}
	var s Stations
	if err = json.Unmarshal(content, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

func (d *Dirble) Stations(page, perPage, offset *int) (*Stations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations"
	q := u.Query()
	q.Set("token", d.token)
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	return d.getStations(u.String())
}

func (d *Dirble) Station(id int) (*Station, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "station/" + strconv.Itoa(id)
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var s Station
	if err = json.Unmarshal(content, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

func (d *Dirble) StationSongHistory(id int) (*StationSongs, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "station/" + strconv.Itoa(id) + "/song_history"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var ss StationSongs
	if err = json.Unmarshal(content, &ss); err != nil {
		return nil, err
	}
	return &ss, nil
}

func (d *Dirble) StationSimilar(id int) (*Stations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "station/" + strconv.Itoa(id) + "/similar"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	return d.getStations(u.String())
}

func (d *Dirble) Categories() (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CategoriesPrimary() (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories/primary"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CategoriesTree() (*CategoryTree, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories/tree"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var ct CategoryTree
	if err = json.Unmarshal(content, &ct); err != nil {
		return nil, err
	}
	return &ct, nil
}

func (d *Dirble) CategoryStations(id int, all bool, page, perPage, offset *int) (*CategoryStations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "category/" + strconv.Itoa(id) + "/stations"
	q := u.Query()
	q.Set("token", d.token)
	if all {
		q.Set("all", "1")
	}
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var cs CategoryStations
	if err = json.Unmarshal(content, &cs); err != nil {
		return nil, err
	}
	return &cs, nil
}

func (d *Dirble) CategoryChilds(id int) (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "category/" + strconv.Itoa(id) + "/childs"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) Countries() (*Countries, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "countries"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Countries
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CountriesStations(code string, all bool, page, perPage, offset *int) (*CategoryStations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "countries/" + code + "/stations"
	q := u.Query()
	q.Set("token", d.token)
	if all {
		q.Set("all", "1")
	}
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var cs CategoryStations
	if err = json.Unmarshal(content, &cs); err != nil {
		return nil, err
	}
	return &cs, nil
}

func (d *Dirble) Continents() (*Continents, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "continents"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Continents
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) ContinentsCountries(continentId int) (*Countries, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "continents/" + strconv.Itoa(continentId) + "/countries"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Countries
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) Search(query string, page *int) (*SearchResults, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "search/" + query
	q := u.Query()
	q.Set("token", d.token)

	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var sr SearchResults
	if err = json.Unmarshal(content, &sr); err != nil {
		return nil, err
	}
	return &sr, nil
}
<|endoftext|>


<commit_before>package main

import (
	"archive/tar"
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/fsouza/go-dockerclient"

	c "github.com/wantedly/risu/cache"
	"github.com/wantedly/risu/schema"
)

const (
	DefaultDockerEndpoint = "unix:///var/run/docker.sock"
)

var dockerClient *docker.Client

func dockerBuild(build schema.Build) error {
	clonePath := DefaultSourceBaseDir + build.SourceRepo

	if err := addCacheToSrcRepo(build, clonePath); err != nil {
		return err
	}

	client, err := getDockerClient()

	if err != nil {
		return err
	}

	outputbuf := bytes.NewBuffer(nil)
	opts := docker.BuildImageOptions{
		Name:                build.ImageName,
		NoCache:             false,
		SuppressOutput:      true,
		RmTmpContainer:      true,
		ForceRmTmpContainer: true,
		Dockerfile:          build.Dockerfile,
		OutputStream:        outputbuf,
		ContextDir:          clonePath,
	}

	if err = client.BuildImage(opts); err != nil {
		return err
	}

	return nil
}

func extractCache(build schema.Build) (string, error) {
	client, err := getDockerClient()

	if err != nil {
		return "", err
	}

	container, err := runContainer(client, build)

	if err != nil {
		return "", err
	}

	defer disposeContainer(client, container)

	saveBaseDir := c.DefaultInflatedCacheDir + getCacheKey(build.SourceRepo) + "/"

	for _, cacheDirectory := range build.CacheDirectories {
		outputbuf := bytes.NewBuffer(nil)

		if err = client.CopyFromContainer(
			docker.CopyFromContainerOptions{
				Container:    container.ID,
				OutputStream: outputbuf,
				Resource:     cacheDirectory["container"],
			}); err != nil {
			return "", err
		}

		reader := tar.NewReader(outputbuf)

		for {
			header, err := reader.Next()

			if err == io.EOF {
				break
			}

			if err != nil {
				return "", err
			}

			buffer := new(bytes.Buffer)
			outPath := filepath.Join(saveBaseDir, filepath.Dir(cacheDirectory["source"]), header.Name)

			switch header.Typeflag {
			case tar.TypeDir:
				if _, err = os.Stat(outPath); err != nil {
					os.MkdirAll(outPath, 0755)
				}

			case tar.TypeReg, tar.TypeRegA:
				if _, err = io.Copy(buffer, reader); err != nil {
					return "", err
				}

				if err = ioutil.WriteFile(outPath, buffer.Bytes(), 0644); err != nil {
					return "", err
				}
			}
		}
	}

	return saveBaseDir, nil
}

func dockerPush(build schema.Build) error {
	client, err := getDockerClient()

	if err != nil {
		return err
	}

	dockerImageName := strings.Split(build.ImageName, ":")[0]
	dockerImageTag := strings.Split(build.ImageName, ":")[1]
	dockerRegistry := strings.Split(build.ImageName, "/")[0]

	outputbuf := bytes.NewBuffer(nil)
	pushOpts := docker.PushImageOptions{
		Name:         dockerImageName,
		Tag:          dockerImageTag,
		Registry:     dockerRegistry,
		OutputStream: outputbuf,
	}
	authConfig := docker.AuthConfiguration{
		Username:      os.Getenv("DOCKER_AUTH_USER_NAME"),
		Password:      os.Getenv("DOCKER_AUTH_USER_PASSWORD"),
		Email:         os.Getenv("DOCKER_AUTH_USER_EMAIL"),
		ServerAddress: dockerRegistry,
	}
	if err := client.PushImage(pushOpts, authConfig); err != nil {
		return err
	}
	return nil
}

func addCacheToSrcRepo(build schema.Build, clonePath string) error {
	cache := c.NewCache(os.Getenv("CACHE_BACKEND"))
	inflatedCachePath, err := cache.Get(getCacheKey(build.SourceRepo))

	if err != nil {
		return err
	}

	for _, cacheDirectory := range build.CacheDirectories {
		cachePath := inflatedCachePath + string(filepath.Separator) + cacheDirectory["source"]
		sourcePath := clonePath + string(filepath.Separator) + cacheDirectory["source"]

		if inflatedCachePath != "" {
			if _, err := os.Stat(sourcePath); err == nil {
				if e := os.RemoveAll(sourcePath); e != nil {
					return e
				}
			}

			if err := os.Rename(cachePath, sourcePath); err != nil {
				return err
			}
		} else {
			if _, err := os.Stat(sourcePath); err != nil {
				os.MkdirAll(sourcePath, 0755)
			}
		}
	}

	return nil
}

func putCache(build schema.Build, cacheSavedDirectory string) error {
	cache := c.NewCache(os.Getenv("CACHE_BACKEND"))

	if err := cache.Put(getCacheKey(build.SourceRepo), cacheSavedDirectory); err != nil {
		return err
	}

	return nil
}

func getCacheKey(text string) string {
	hasher := md5.New()
	hasher.Write([]byte(text))

	return hex.EncodeToString(hasher.Sum(nil))[0:12]
}

func runContainer(client *docker.Client, build schema.Build) (*docker.Container, error) {
	container, err := client.CreateContainer(
		docker.CreateContainerOptions{
			Config: &docker.Config{
				Hostname:        "",
				Domainname:      "",
				User:            "",
				AttachStdin:     false,
				AttachStdout:    false,
				AttachStderr:    false,
				Tty:             false,
				OpenStdin:       false,
				StdinOnce:       false,
				Env:             nil,
				Cmd:             []string{"sleep", "3600"},
				Entrypoint:      []string{"/bin/sh"},
				Image:           build.ImageName,
				Labels:          nil,
				Volumes:         nil,
				WorkingDir:      "",
				NetworkDisabled: false,
				MacAddress:      "",
				ExposedPorts:    nil,
			},
			HostConfig: &docker.HostConfig{},
		})

	if err != nil {
		return nil, err
	}

	if err = client.StartContainer(container.ID, &docker.HostConfig{}); err != nil {
		return nil, err
	}

	return container, nil
}

func disposeContainer(client *docker.Client, container *docker.Container) error {
	if err := client.StopContainer(container.ID, 1); err != nil {
		return err
	}

	return client.RemoveContainer(
		docker.RemoveContainerOptions{
			ID:            container.ID,
			RemoveVolumes: false,
			Force:         true,
		})
}

func getDockerClient() (*docker.Client, error) {
	if dockerClient != nil {
		return dockerClient, nil
	}

	dockerEndpoint := os.Getenv("DOCKER_HOST")

	if dockerEndpoint == "" {
		dockerEndpoint = DefaultDockerEndpoint
	}

	dockerClient, err := docker.NewClient(dockerEndpoint)
	if err != nil {
		return nil, err
	}

	return dockerClient, nil
}
<commit_msg>Print build log<commit_after>package main

import (
	"archive/tar"
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/fsouza/go-dockerclient"

	c "github.com/wantedly/risu/cache"
	"github.com/wantedly/risu/schema"
)

const (
	DefaultDockerEndpoint = "unix:///var/run/docker.sock"
)

var dockerClient *docker.Client

func dockerBuild(build schema.Build) error {
	clonePath := DefaultSourceBaseDir + build.SourceRepo

	if err := addCacheToSrcRepo(build, clonePath); err != nil {
		return err
	}

	client, err := getDockerClient()

	if err != nil {
		return err
	}

	logsReader, outputbuf := io.Pipe()

	go func(reader io.Reader, b schema.Build) {
		scanner := bufio.NewScanner(reader)
		for scanner.Scan() {
			printLog(b, scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			printLog(b, "There was an error with the scanner in attached container")
		}
	}(logsReader, build)

	opts := docker.BuildImageOptions{
		Name:                build.ImageName,
		NoCache:             false,
		SuppressOutput:      true,
		RmTmpContainer:      true,
		ForceRmTmpContainer: true,
		Dockerfile:          build.Dockerfile,
		OutputStream:        outputbuf,
		ContextDir:          clonePath,
	}

	if err = client.BuildImage(opts); err != nil {
		return err
	}

	return nil
}

func extractCache(build schema.Build) (string, error) {
	client, err := getDockerClient()

	if err != nil {
		return "", err
	}

	container, err := runContainer(client, build)

	if err != nil {
		return "", err
	}

	defer disposeContainer(client, container)

	saveBaseDir := c.DefaultInflatedCacheDir + getCacheKey(build.SourceRepo) + "/"

	for _, cacheDirectory := range build.CacheDirectories {
		outputbuf := bytes.NewBuffer(nil)

		if err = client.CopyFromContainer(
			docker.CopyFromContainerOptions{
				Container:    container.ID,
				OutputStream: outputbuf,
				Resource:     cacheDirectory["container"],
			}); err != nil {
			return "", err
		}

		reader := tar.NewReader(outputbuf)

		for {
			header, err := reader.Next()

			if err == io.EOF {
				break
			}

			if err != nil {
				return "", err
			}

			buffer := new(bytes.Buffer)
			outPath := filepath.Join(saveBaseDir, filepath.Dir(cacheDirectory["source"]), header.Name)

			switch header.Typeflag {
			case tar.TypeDir:
				if _, err = os.Stat(outPath); err != nil {
					os.MkdirAll(outPath, 0755)
				}

			case tar.TypeReg, tar.TypeRegA:
				if _, err = io.Copy(buffer, reader); err != nil {
					return "", err
				}

				if err = ioutil.WriteFile(outPath, buffer.Bytes(), 0644); err != nil {
					return "", err
				}
			}
		}
	}

	return saveBaseDir, nil
}

func dockerPush(build schema.Build) error {
	client, err := getDockerClient()

	if err != nil {
		return err
	}

	dockerImageName := strings.Split(build.ImageName, ":")[0]
	dockerImageTag := strings.Split(build.ImageName, ":")[1]
	dockerRegistry := strings.Split(build.ImageName, "/")[0]

	outputbuf := bytes.NewBuffer(nil)
	pushOpts := docker.PushImageOptions{
		Name:         dockerImageName,
		Tag:          dockerImageTag,
		Registry:     dockerRegistry,
		OutputStream: outputbuf,
	}
	authConfig := docker.AuthConfiguration{
		Username:      os.Getenv("DOCKER_AUTH_USER_NAME"),
		Password:      os.Getenv("DOCKER_AUTH_USER_PASSWORD"),
		Email:         os.Getenv("DOCKER_AUTH_USER_EMAIL"),
		ServerAddress: dockerRegistry,
	}
	if err := client.PushImage(pushOpts, authConfig); err != nil {
		return err
	}
	return nil
}

func addCacheToSrcRepo(build schema.Build, clonePath string) error {
	cache := c.NewCache(os.Getenv("CACHE_BACKEND"))
	inflatedCachePath, err := cache.Get(getCacheKey(build.SourceRepo))

	if err != nil {
		return err
	}

	for _, cacheDirectory := range build.CacheDirectories {
		cachePath := inflatedCachePath + string(filepath.Separator) + cacheDirectory["source"]
		sourcePath := clonePath + string(filepath.Separator) + cacheDirectory["source"]

		if inflatedCachePath != "" {
			if _, err := os.Stat(sourcePath); err == nil {
				if e := os.RemoveAll(sourcePath); e != nil {
					return e
				}
			}

			if err := os.Rename(cachePath, sourcePath); err != nil {
				return err
			}
		} else {
			if _, err := os.Stat(sourcePath); err != nil {
				os.MkdirAll(sourcePath, 0755)
			}
		}
	}

	return nil
}

func putCache(build schema.Build, cacheSavedDirectory string) error {
	cache := c.NewCache(os.Getenv("CACHE_BACKEND"))

	if err := cache.Put(getCacheKey(build.SourceRepo), cacheSavedDirectory); err != nil {
		return err
	}

	return nil
}

func getCacheKey(text string) string {
	hasher := md5.New()
	hasher.Write([]byte(text))

	return hex.EncodeToString(hasher.Sum(nil))[0:12]
}

func runContainer(client *docker.Client, build schema.Build) (*docker.Container, error) {
	container, err := client.CreateContainer(
		docker.CreateContainerOptions{
			Config: &docker.Config{
				Hostname:        "",
				Domainname:      "",
				User:            "",
				AttachStdin:     false,
				AttachStdout:    false,
				AttachStderr:    false,
				Tty:             false,
				OpenStdin:       false,
				StdinOnce:       false,
				Env:             nil,
				Cmd:             []string{"sleep", "3600"},
				Entrypoint:      []string{"/bin/sh"},
				Image:           build.ImageName,
				Labels:          nil,
				Volumes:         nil,
				WorkingDir:      "",
				NetworkDisabled: false,
				MacAddress:      "",
				ExposedPorts:    nil,
			},
			HostConfig: &docker.HostConfig{},
		})

	if err != nil {
		return nil, err
	}

	if err = client.StartContainer(container.ID, &docker.HostConfig{}); err != nil {
		return nil, err
	}

	return container, nil
}

func disposeContainer(client *docker.Client, container *docker.Container) error {
	if err := client.StopContainer(container.ID, 1); err != nil {
		return err
	}

	return client.RemoveContainer(
		docker.RemoveContainerOptions{
			ID:            container.ID,
			RemoveVolumes: false,
			Force:         true,
		})
}

func getDockerClient() (*docker.Client, error) {
	if dockerClient != nil {
		return dockerClient, nil
	}

	dockerEndpoint := os.Getenv("DOCKER_HOST")

	if dockerEndpoint == "" {
		dockerEndpoint = DefaultDockerEndpoint
	}

	dockerClient, err := docker.NewClient(dockerEndpoint)
	if err != nil {
		return nil, err
	}

	return dockerClient, nil
}
<|endoftext|>