1
0
mirror of https://github.com/volatiletech/authboss.git synced 2024-11-28 08:58:38 +02:00

Redo events again

- The purpose of this change is to make it so that a different module
  can hijack the response to the client.
This commit is contained in:
Aaron L 2018-02-16 09:49:07 -08:00
parent 2137c827d3
commit 9eb5731a3d
3 changed files with 113 additions and 182 deletions

123
events.go
View File

@ -1,13 +1,15 @@
package authboss
import "context"
import (
"net/http"
)
//go:generate stringer -output stringers.go -type "Event,Interrupt"
//go:generate stringer -output stringers.go -type "Event"
// Event is used for callback registration.
// Event type is for describing events
type Event int
// Event values
// Event kinds
const (
EventRegister Event = iota
EventAuth
@ -21,93 +23,74 @@ const (
EventPasswordReset
)
// Interrupt is used to signal to callback mechanisms
// that the current process should not continue.
type Interrupt int
// EventHandler reacts to events that are fired by Authboss controllers.
// These controllers will normally process a request by themselves, but if
// there is special consideration for example a successful login, but the
// user is locked, the lock module's controller may seize control over the
// request.
//
// Very much a controller level middleware.
type EventHandler func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error)
// Interrupt values
const (
// InterruptNone means there was no interrupt present and the process should continue.
InterruptNone Interrupt = iota
// InterruptAccountLocked occurs if a user's account has been locked
// by the lock module.
InterruptAccountLocked
// InterruptAccountNotConfirmed occurs if a user's account is not confirmed
// and therefore cannot be used yet.
InterruptAccountNotConfirmed
// InterruptSessionExpired occurs when the user's account has had no activity for the
// configured duration.
InterruptSessionExpired
)
// Before Events can interrupt the flow by returning an interrupt value.
// This is used to stop the callback chain and the original handler from
// continuing execution. The execution should also stopped if there is an error.
type Before func(context.Context) (Interrupt, error)
// After is a request callback that happens after the event.
type After func(context.Context) error
// Events is a collection of Events that fire before and after certain
// methods.
// Events is a collection of Events that fire before and after certain methods.
type Events struct {
before map[Event][]Before
after map[Event][]After
before map[Event][]EventHandler
after map[Event][]EventHandler
}
// NewEvents creates a new set of before and after Events.
// Called only by authboss internals and for testing.
func NewEvents() *Events {
return &Events{
before: make(map[Event][]Before),
after: make(map[Event][]After),
before: make(map[Event][]EventHandler),
after: make(map[Event][]EventHandler),
}
}
// Before event, call f.
func (c *Events) Before(e Event, f Before) {
Events := c.before[e]
Events = append(Events, f)
c.before[e] = Events
func (c *Events) Before(e Event, f EventHandler) {
events := c.before[e]
events = append(events, f)
c.before[e] = events
}
// After event, call f.
func (c *Events) After(e Event, f After) {
Events := c.after[e]
Events = append(Events, f)
c.after[e] = Events
func (c *Events) After(e Event, f EventHandler) {
events := c.after[e]
events = append(events, f)
c.after[e] = events
}
// FireBefore event to all the Events with a context. The error
// should be passed up despite being logged once here already so it
// can write an error out to the HTTP Client. If err is nil then
// check the value of interrupted. If error is nil then the interrupt
// value should be checked. If it is not InterruptNone then there is a reason
// the current process should stop it's course of action.
func (c *Events) FireBefore(ctx context.Context, e Event) (interrupt Interrupt, err error) {
Events := c.before[e]
for _, fn := range Events {
interrupt, err = fn(ctx)
if err != nil {
return InterruptNone, err
}
if interrupt != InterruptNone {
return interrupt, nil
}
}
return InterruptNone, nil
// FireBefore executes the handlers that were registered to fire before
// the event passed in.
//
// If it encounters an error it will stop immediately without calling
// other handlers.
//
// If a handler handles the request, it will pass this information both
// to handlers further down the chain (to let them know that w has been used)
// as well as set w to nil as a precaution.
func (c *Events) FireBefore(e Event, w http.ResponseWriter, r *http.Request) (bool, error) {
return c.call(c.before[e], w, r)
}
// FireAfter event to all the Events with a context. The error can safely be
// ignored as it is logged.
func (c *Events) FireAfter(ctx context.Context, e Event) (err error) {
Events := c.after[e]
for _, fn := range Events {
if err = fn(ctx); err != nil {
return err
func (c *Events) FireAfter(e Event, w http.ResponseWriter, r *http.Request) (bool, error) {
return c.call(c.after[e], w, r)
}
func (c *Events) call(evs []EventHandler, w http.ResponseWriter, r *http.Request) (bool, error) {
handled := false
for _, fn := range evs {
interrupt, err := fn(w, r, handled)
if err != nil {
return false, err
}
if interrupt {
handled = true
}
}
return nil
return handled, nil
}

View File

@ -1,9 +1,10 @@
package authboss
import (
"context"
"errors"
"net/http"
"testing"
"github.com/pkg/errors"
)
func TestEvents(t *testing.T) {
@ -13,25 +14,25 @@ func TestEvents(t *testing.T) {
afterCalled := false
beforeCalled := false
ab.Events.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
ab.Events.Before(EventRegister, func(http.ResponseWriter, *http.Request, bool) (bool, error) {
beforeCalled = true
return InterruptNone, nil
return false, nil
})
ab.Events.After(EventRegister, func(ctx context.Context) error {
ab.Events.After(EventRegister, func(http.ResponseWriter, *http.Request, bool) (bool, error) {
afterCalled = true
return nil
return false, nil
})
if beforeCalled || afterCalled {
t.Error("Neither should be called.")
}
interrupt, err := ab.Events.FireBefore(context.Background(), EventRegister)
handled, err := ab.Events.FireBefore(EventRegister, nil, nil)
if err != nil {
t.Error("Unexpected error:", err)
}
if interrupt != InterruptNone {
t.Error("It should not have been stopped.")
if handled {
t.Error("It should not have been handled.")
}
if !beforeCalled {
@ -41,106 +42,84 @@ func TestEvents(t *testing.T) {
t.Error("Expected after not to be called.")
}
ab.Events.FireAfter(context.Background(), EventRegister)
ab.Events.FireAfter(EventRegister, nil, nil)
if !afterCalled {
t.Error("Expected after to be called.")
}
}
func TestCallbacksInterrupt(t *testing.T) {
func TestEventsHandled(t *testing.T) {
t.Parallel()
ev := NewEvents()
before1 := false
before2 := false
ab := New()
firstCalled := false
secondCalled := false
ev.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
before1 = true
return InterruptAccountLocked, nil
firstHandled := false
secondHandled := false
ab.Events.Before(EventRegister, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
firstCalled = true
firstHandled = handled
return true, nil
})
ev.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
before2 = true
return InterruptNone, nil
ab.Events.Before(EventRegister, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
secondCalled = true
secondHandled = handled
return false, nil
})
interrupt, err := ev.FireBefore(context.Background(), EventRegister)
handled, err := ab.Events.FireBefore(EventRegister, nil, nil)
if err != nil {
t.Error(err)
t.Error("Unexpected error:", err)
}
if interrupt != InterruptAccountLocked {
t.Error("The interrupt signal was not account locked:", interrupt)
if !handled {
t.Error("it should have been handled")
}
if !before1 {
t.Error("Before1 should have been called.")
if !firstCalled {
t.Error("expected first to have been called")
}
if before2 {
t.Error("Before2 should not have been called.")
if !secondCalled {
t.Error("expected second to have been called")
}
if firstHandled {
t.Error("first should not see the event as being handled")
}
if !secondHandled {
t.Error("second should see the event as being handled")
}
}
func TestCallbacksBeforeErrors(t *testing.T) {
func TestEventsErrors(t *testing.T) {
t.Parallel()
ev := NewEvents()
before1 := false
before2 := false
ab := New()
firstCalled := false
secondCalled := false
errValue := errors.New("Problem occured")
expect := errors.New("error")
ev.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
before1 = true
return InterruptNone, errValue
ab.Events.Before(EventRegister, func(http.ResponseWriter, *http.Request, bool) (bool, error) {
firstCalled = true
return false, expect
})
ev.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
before2 = true
return InterruptNone, nil
ab.Events.Before(EventRegister, func(http.ResponseWriter, *http.Request, bool) (bool, error) {
secondCalled = true
return false, nil
})
interrupt, err := ev.FireBefore(context.Background(), EventRegister)
if err != errValue {
t.Error("Expected an error to come back.")
}
if interrupt != InterruptNone {
t.Error("It should not have been stopped.")
_, err := ab.Events.FireBefore(EventRegister, nil, nil)
if err != expect {
t.Error("got the wrong error back:", err)
}
if !before1 {
t.Error("Before1 should have been called.")
if !firstCalled {
t.Error("expected first to have been called")
}
if before2 {
t.Error("Before2 should not have been called.")
}
}
func TestCallbacksAfterErrors(t *testing.T) {
t.Parallel()
ev := NewEvents()
after1 := false
after2 := false
errValue := errors.New("Problem occured")
ev.After(EventRegister, func(ctx context.Context) error {
after1 = true
return errValue
})
ev.After(EventRegister, func(ctx context.Context) error {
after2 = true
return nil
})
err := ev.FireAfter(context.Background(), EventRegister)
if err != errValue {
t.Error("Expected an error to come back.")
}
if !after1 {
t.Error("After1 should have been called.")
}
if after2 {
t.Error("After2 should not have been called.")
if secondCalled {
t.Error("expected second to not have been called")
}
}
@ -169,23 +148,3 @@ func TestEventString(t *testing.T) {
}
}
}
func TestInterruptString(t *testing.T) {
t.Parallel()
tests := []struct {
in Interrupt
str string
}{
{InterruptNone, "InterruptNone"},
{InterruptAccountLocked, "InterruptAccountLocked"},
{InterruptAccountNotConfirmed, "InterruptAccountNotConfirmed"},
{InterruptSessionExpired, "InterruptSessionExpired"},
}
for i, test := range tests {
if got := test.in.String(); got != test.str {
t.Errorf("%d) Wrong string for Event(%d) expected: %v got: %s", i, test.in, test.str, got)
}
}
}

View File

@ -1,4 +1,4 @@
// Code generated by "stringer -output stringers.go -type Event,Interrupt"; DO NOT EDIT.
// Code generated by "stringer -output stringers.go -type Event"; DO NOT EDIT.
package authboss
@ -14,14 +14,3 @@ func (i Event) String() string {
}
return _Event_name[_Event_index[i]:_Event_index[i+1]]
}
const _Interrupt_name = "InterruptNoneInterruptAccountLockedInterruptAccountNotConfirmedInterruptSessionExpired"
var _Interrupt_index = [...]uint8{0, 13, 35, 63, 86}
func (i Interrupt) String() string {
if i < 0 || i >= Interrupt(len(_Interrupt_index)-1) {
return "Interrupt(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Interrupt_name[_Interrupt_index[i]:_Interrupt_index[i+1]]
}