github.com/wrgl/wrgl@v0.14.0/pkg/auth/fs/authz.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright © 2022 Wrangle Ltd 3 4 package authfs 5 6 import ( 7 _ "embed" 8 "net/http" 9 "os" 10 "path/filepath" 11 "sync" 12 13 "github.com/casbin/casbin/v2" 14 "github.com/casbin/casbin/v2/model" 15 fileadapter "github.com/casbin/casbin/v2/persist/file-adapter" 16 "github.com/fsnotify/fsnotify" 17 "github.com/wrgl/wrgl/pkg/local" 18 ) 19 20 //go:embed casbin_model.conf 21 var casbinModel string 22 23 type AuthzStore struct { 24 e *casbin.Enforcer 25 rootDir string 26 watcher *fsnotify.Watcher 27 mutex sync.Mutex 28 ErrChan chan error 29 done chan struct{} 30 wg sync.WaitGroup 31 } 32 33 func NewAuthzStore(rd *local.RepoDir) (s *AuthzStore, err error) { 34 s = &AuthzStore{ 35 rootDir: rd.FullPath, 36 ErrChan: make(chan error, 1), 37 done: make(chan struct{}, 1), 38 } 39 if err := s.read(); err != nil { 40 return nil, err 41 } 42 s.watcher, err = rd.Watcher() 43 if err != nil { 44 return nil, err 45 } 46 go s.watch() 47 return s, nil 48 } 49 50 func (s *AuthzStore) filepath() string { 51 return filepath.Join(s.rootDir, "authz.csv") 52 } 53 54 func (s *AuthzStore) read() error { 55 m, err := model.NewModelFromString(casbinModel) 56 if err != nil { 57 return err 58 } 59 fp := s.filepath() 60 if _, err := os.Stat(fp); os.IsNotExist(err) { 61 f, err := os.OpenFile(fp, os.O_CREATE, 0600) 62 if err != nil { 63 return err 64 } 65 if err := f.Close(); err != nil { 66 return err 67 } 68 } 69 e, err := casbin.NewEnforcer(m, fileadapter.NewAdapter(fp)) 70 if err != nil { 71 return err 72 } 73 s.e = e 74 return nil 75 } 76 77 func (s *AuthzStore) reload() error { 78 s.mutex.Lock() 79 defer s.mutex.Unlock() 80 return s.e.LoadPolicy() 81 } 82 83 func (s *AuthzStore) watch() { 84 s.wg.Add(1) 85 defer s.wg.Done() 86 for { 87 select { 88 case event, ok := <-s.watcher.Events: 89 if !ok { 90 return 91 } 92 if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { 93 if event.Name == s.filepath() { 94 if err := s.reload(); err != nil { 95 s.ErrChan <- err 96 } 97 } 98 } 99 case err, ok := <-s.watcher.Errors: 100 if !ok { 101 return 102 } 103 s.ErrChan <- err 104 case <-s.done: 105 return 106 } 107 } 108 } 109 110 func (s *AuthzStore) AddPolicy(email, act string) error { 111 _, err := s.e.AddPolicy(email, "-", act) 112 return err 113 } 114 115 func (s *AuthzStore) RemovePolicy(email, act string) error { 116 _, err := s.e.RemovePolicy(email, "-", act) 117 return err 118 } 119 120 func (s *AuthzStore) Authorized(r *http.Request, email, scope string) (bool, error) { 121 ok, err := s.e.Enforce(email, "-", scope) 122 if err != nil { 123 return false, err 124 } 125 return ok, nil 126 } 127 128 func (s *AuthzStore) Flush() error { 129 s.mutex.Lock() 130 defer s.mutex.Unlock() 131 return s.e.SavePolicy() 132 } 133 134 func (s *AuthzStore) ListPolicies(email string) (scopes []string, err error) { 135 policies := s.e.GetFilteredPolicy(0, email, "-") 136 scopes = make([]string, len(policies)) 137 for i, sl := range policies { 138 scopes[i] = sl[2] 139 } 140 return scopes, nil 141 } 142 143 func (s *AuthzStore) Close() { 144 close(s.done) 145 s.wg.Wait() 146 close(s.ErrChan) 147 }