github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/authentication/interactions.go (about) 1 // Copyright 2016 Canonical Ltd. All rights reserved. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package authentication 5 6 import ( 7 "crypto/rand" 8 "fmt" 9 "sync" 10 "time" 11 12 "github.com/juju/errors" 13 "gopkg.in/juju/names.v2" 14 ) 15 16 // ErrWaitCanceled is returned by Interactions.Wait when the cancel 17 // channel is signalled. 18 var ErrWaitCanceled = errors.New("wait canceled") 19 20 // ErrExpired is returned by Interactions.Wait when interactions expire 21 // before they are done. 22 var ErrExpired = errors.New("interaction timed out") 23 24 // Interactions maintains a set of Interactions. 25 type Interactions struct { 26 mu sync.Mutex 27 items map[string]*item 28 } 29 30 type item struct { 31 c chan Interaction 32 caveatId []byte 33 expiry time.Time 34 done bool 35 } 36 37 // Interaction records details of an in-progress interactive 38 // macaroon-based login. 39 type Interaction struct { 40 CaveatId []byte 41 LoginUser names.UserTag 42 LoginError error 43 } 44 45 // NewInteractions returns a new Interactions. 46 func NewInteractions() *Interactions { 47 return &Interactions{ 48 items: make(map[string]*item), 49 } 50 } 51 52 func newId() (string, error) { 53 var id [12]byte 54 if _, err := rand.Read(id[:]); err != nil { 55 return "", fmt.Errorf("cannot read random id: %v", err) 56 } 57 return fmt.Sprintf("%x", id[:]), nil 58 } 59 60 // Start records the start of an interactive login, and returns a random ID 61 // that uniquely identifies it. A call to Wait with the same ID will return 62 // the Interaction once it is done. 63 func (m *Interactions) Start(caveatId []byte, expiry time.Time) (string, error) { 64 id, err := newId() 65 if err != nil { 66 return "", err 67 } 68 m.mu.Lock() 69 defer m.mu.Unlock() 70 m.items[id] = &item{ 71 c: make(chan Interaction, 1), 72 caveatId: caveatId, 73 expiry: expiry, 74 } 75 return id, nil 76 } 77 78 // Done signals that the user has either logged in, or attempted to and failed. 79 func (m *Interactions) Done(id string, loginUser names.UserTag, loginError error) error { 80 m.mu.Lock() 81 defer m.mu.Unlock() 82 item := m.items[id] 83 84 if item == nil { 85 return errors.NotFoundf("interaction %q", id) 86 } 87 if item.done { 88 return errors.Errorf("interaction %q already done", id) 89 } 90 item.done = true 91 item.c <- Interaction{ 92 CaveatId: item.caveatId, 93 LoginUser: loginUser, 94 LoginError: loginError, 95 } 96 return nil 97 } 98 99 // Wait waits until the identified interaction is done, and returns the 100 // corresponding Interaction. If the cancel channel is signalled before 101 // the interaction is done, then ErrWaitCanceled is returned. If the 102 // interaction expires before it is done, ErrExpired is returned. 103 func (m *Interactions) Wait(id string, cancel <-chan struct{}) (*Interaction, error) { 104 m.mu.Lock() 105 item := m.items[id] 106 m.mu.Unlock() 107 if item == nil { 108 return nil, errors.NotFoundf("interaction %q", id) 109 } 110 select { 111 case <-cancel: 112 return nil, ErrWaitCanceled 113 case interaction, ok := <-item.c: 114 if !ok { 115 return nil, ErrExpired 116 } 117 m.mu.Lock() 118 delete(m.items, id) 119 m.mu.Unlock() 120 return &interaction, nil 121 } 122 } 123 124 // Expire removes any interactions that were due to expire by the 125 // specified time. 126 func (m *Interactions) Expire(t time.Time) { 127 m.mu.Lock() 128 defer m.mu.Unlock() 129 for id, item := range m.items { 130 if item.done || item.expiry.After(t) { 131 continue 132 } 133 delete(m.items, id) 134 close(item.c) 135 } 136 }