auth

Paddy 2015-07-15 Parent:73e12d5a1124

178:0a2c3d677161 Go to Latest

auth/scope.go

Update to use a generic event emitter. Rather can creating a purpose-built event emitter for each and every event we need to emit (I'm looking at you, login verification event) which is _downright silly_, we're now using a generic event publisher that's based on saying "HEY A MODEL UPDATED". This means we need to change all our setup code in authd to use events.NewNSQPublisher or events.NewStdoutPublisher instead of our homegrown solutions. Which also means updating our config to take an events.Publisher instead of our LoginVerificationNotifier (blergh). Our Context also now uses an events.Publisher instead of a LoginVerificationNotifier. Party all around! We also replaced our SendLoginVerification helper method on Context with a SendModelEvent helper method on Context, which is just a light wrapper around events.PublishModelEvent. Of course, all this means we need to update our email_verification listener to listen to the correct channel (based on the model we want updates about) and filter down to a Created action or our new custom action for "the customer wants their verification resent", which I'm OK making a special case and not generic, because c'mon. But we had a subtle change to all our constants, some of which are unofficial constants now. I'm unsure how I feel about this. We also updated our email_verification listener so that we're unmarshalling to a custom loginEvent, which is just an events.Event that overwrites the Data property to be an auth.Login instance. This is to make sure we don't need to wrangle a map[string]interface{}, which is no fun. I'm also OK with special-casing like this, because it's 1) a tiny amount of code, 2) properly utilising composition, and 3) the only way I can think of to cleanly accomplish what I want. I also added a note about GetLogin's deficient handling of logins, namely that it doesn't recognise admins and return Verification codes to them, which would be a useful property for internal tools to take advantage of. Ah well. I updated the Profile and Login implementations so they're now event.Model instances, mainly by just exporting some strings from them through getters that will let us automatically build an Event from them. This lets us use the PublishModelEvent helper. I updated our CreateProfileHandler to properly mangle the login Verification property, and to fire off the ActionCreated events for the new Login and the new Profile. I updated our GetLoginHandler and UpdateLoginHandler to properly mangle the loginVerification property. God that's annoying. :-/ You'll note I didn't start publishing the events.ActionUpdated or events.ActionDeleted events for Profiles or Logins yet, and didn't bother publishing any events for literally any other type. That's because I'm a lazy piece of crap and will end up publishing them when I absolutely have to. Part of that is because if a channel isn't created/being read for a topic, the messages will just stack up in NSQ, and I don't want that. But mostly I'm lazy. Finally, I got to delete the entire profile_verification.go file, because we're no longer special-casing that. Hooray!

History
paddy@134 1 package auth
paddy@134 2
paddy@134 3 import (
paddy@134 4 "errors"
paddy@134 5 "sort"
paddy@134 6 )
paddy@134 7
paddy@134 8 var (
paddy@153 9 ErrNoScopeStore = errors.New("scopeStore not set in Context")
paddy@153 10 ErrScopeNotFound = errors.New("scope not found")
paddy@153 11 ErrScopeAlreadyExists = errors.New("scope already exists")
paddy@134 12 )
paddy@134 13
paddy@134 14 // Scope represents a limit on the access that a grant provides.
paddy@134 15 type Scope struct {
paddy@134 16 ID string
paddy@134 17 Name string
paddy@134 18 Description string
paddy@134 19 }
paddy@134 20
paddy@134 21 func (s *Scope) ApplyChange(change ScopeChange) {
paddy@134 22 if change.Name != nil {
paddy@134 23 s.Name = *change.Name
paddy@134 24 }
paddy@134 25 if change.Description != nil {
paddy@134 26 s.Description = *change.Description
paddy@134 27 }
paddy@134 28 }
paddy@134 29
paddy@163 30 type Scopes []Scope
paddy@134 31
paddy@163 32 func (s Scopes) Len() int {
paddy@134 33 return len(s)
paddy@134 34 }
paddy@134 35
paddy@163 36 func (s Scopes) Swap(i, j int) {
paddy@134 37 s[i], s[j] = s[j], s[i]
paddy@134 38 }
paddy@134 39
paddy@163 40 func (s Scopes) Less(i, j int) bool {
paddy@134 41 return s[i].ID < s[j].ID
paddy@134 42 }
paddy@134 43
paddy@163 44 func (s Scopes) Strings() []string {
paddy@163 45 res := make([]string, len(s))
paddy@163 46 for pos, scope := range s {
paddy@163 47 res[pos] = scope.ID
paddy@163 48 }
paddy@163 49 return res
paddy@163 50 }
paddy@163 51
paddy@163 52 func stringsToScopes(s []string) Scopes {
paddy@163 53 res := make(Scopes, len(s))
paddy@163 54 for pos, scope := range s {
paddy@163 55 res[pos] = Scope{ID: scope}
paddy@163 56 }
paddy@163 57 return res
paddy@163 58 }
paddy@163 59
paddy@134 60 // ScopeChange represents a change to a Scope.
paddy@134 61 type ScopeChange struct {
paddy@134 62 Name *string
paddy@134 63 Description *string
paddy@134 64 }
paddy@134 65
paddy@152 66 func (s ScopeChange) Empty() bool {
paddy@152 67 return s.Name == nil && s.Description == nil
paddy@152 68 }
paddy@152 69
paddy@134 70 type scopeStore interface {
paddy@134 71 createScopes(scopes []Scope) error
paddy@134 72 getScopes(ids []string) ([]Scope, error)
paddy@152 73 updateScope(id string, change ScopeChange) error
paddy@134 74 removeScopes(ids []string) error
paddy@134 75 listScopes() ([]Scope, error)
paddy@134 76 }
paddy@134 77
paddy@134 78 func (m *memstore) createScopes(scopes []Scope) error {
paddy@134 79 m.scopeLock.Lock()
paddy@134 80 defer m.scopeLock.Unlock()
paddy@134 81
paddy@153 82 for _, scope := range scopes {
paddy@134 83 if _, ok := m.scopes[scope.ID]; ok {
paddy@153 84 return ErrScopeAlreadyExists
paddy@134 85 }
paddy@134 86 }
paddy@134 87 for _, scope := range scopes {
paddy@134 88 m.scopes[scope.ID] = scope
paddy@134 89 }
paddy@134 90 return nil
paddy@134 91 }
paddy@134 92
paddy@134 93 func (m *memstore) getScopes(ids []string) ([]Scope, error) {
paddy@134 94 m.scopeLock.RLock()
paddy@134 95 defer m.scopeLock.RUnlock()
paddy@134 96
paddy@134 97 scopes := []Scope{}
paddy@153 98 for _, id := range ids {
paddy@134 99 scope, ok := m.scopes[id]
paddy@134 100 if !ok {
paddy@153 101 continue
paddy@134 102 }
paddy@134 103 scopes = append(scopes, scope)
paddy@134 104 }
paddy@163 105 sorted := Scopes(scopes)
paddy@134 106 sort.Sort(sorted)
paddy@134 107 scopes = sorted
paddy@134 108 return scopes, nil
paddy@134 109 }
paddy@134 110
paddy@152 111 func (m *memstore) updateScope(id string, change ScopeChange) error {
paddy@134 112 m.scopeLock.Lock()
paddy@134 113 defer m.scopeLock.Unlock()
paddy@134 114
paddy@152 115 scope, ok := m.scopes[id]
paddy@152 116 if !ok {
paddy@153 117 return ErrScopeNotFound
paddy@134 118 }
paddy@152 119 scope.ApplyChange(change)
paddy@152 120 m.scopes[id] = scope
paddy@152 121 return nil
paddy@134 122 }
paddy@134 123
paddy@134 124 func (m *memstore) removeScopes(ids []string) error {
paddy@134 125 m.scopeLock.Lock()
paddy@134 126 defer m.scopeLock.Unlock()
paddy@134 127
paddy@153 128 for _, id := range ids {
paddy@134 129 if _, ok := m.scopes[id]; !ok {
paddy@153 130 return ErrScopeNotFound
paddy@134 131 }
paddy@134 132 }
paddy@134 133 for _, id := range ids {
paddy@134 134 delete(m.scopes, id)
paddy@134 135 }
paddy@134 136 return nil
paddy@134 137 }
paddy@134 138
paddy@134 139 func (m *memstore) listScopes() ([]Scope, error) {
paddy@134 140 m.scopeLock.RLock()
paddy@134 141 defer m.scopeLock.RUnlock()
paddy@134 142
paddy@134 143 scopes := []Scope{}
paddy@134 144 for _, scope := range m.scopes {
paddy@134 145 scopes = append(scopes, scope)
paddy@134 146 }
paddy@163 147 sorted := Scopes(scopes)
paddy@134 148 sort.Sort(sorted)
paddy@134 149 scopes = sorted
paddy@134 150 return scopes, nil
paddy@134 151 }