oauth2_proxy/validator.go
Mike Bland 6a0f119fc2 Provide graceful shutdown of file watcher in tests
This test failure from #92 inspired this change:
https://travis-ci.org/bitly/google_auth_proxy/jobs/62425336

2015/05/13 16:27:33 using authenticated emails file /tmp/test_auth_emails_952353477
2015/05/13 16:27:33 watching /tmp/test_auth_emails_952353477 for updates
2015/05/13 16:27:33 validating: is xyzzy@example.com valid? true
2015/05/13 16:27:33 watching interrupted on event: "/tmp/test_auth_emails_952353477": CHMOD
2015/05/13 16:27:33 watching resumed for /tmp/test_auth_emails_952353477
2015/05/13 16:27:33 reloading after event: "/tmp/test_auth_emails_952353477": CHMOD
2015/05/13 16:27:33 watching interrupted on event: "/tmp/test_auth_emails_952353477": REMOVE
2015/05/13 16:27:33 validating: is xyzzy@example.com valid? false
2015/05/13 16:27:33 watching resumed for /tmp/test_auth_emails_952353477
2015/05/13 16:27:33 reloading after event: "/tmp/test_auth_emails_952353477": REMOVE
2015/05/13 16:27:33 failed opening authenticated-emails-file="/tmp/test_auth_emails_952353477", open /tmp/test_auth_emails_952353477: no such file or directory

I believe that what happened was that the call to reload the file after the
second "reloading after event" lost the race when the test shut down and the
file was removed. This change introduces a `done` channel that ensures
outstanding actions complete and the watcher exits before the test removes the
file.
2015-05-13 18:02:22 -04:00

90 lines
2.1 KiB
Go

package main
import (
"encoding/csv"
"fmt"
"log"
"os"
"strings"
"sync/atomic"
"unsafe"
)
type UserMap struct {
usersFile string
m unsafe.Pointer
}
func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
um := &UserMap{usersFile: usersFile}
m := make(map[string]bool)
atomic.StorePointer(&um.m, unsafe.Pointer(&m))
if usersFile != "" {
log.Printf("using authenticated emails file %s", usersFile)
started := WatchForUpdates(usersFile, done, func() {
um.LoadAuthenticatedEmailsFile()
onUpdate()
})
if started {
log.Printf("watching %s for updates", usersFile)
}
um.LoadAuthenticatedEmailsFile()
}
return um
}
func (um *UserMap) IsValid(email string) (result bool) {
m := *(*map[string]bool)(atomic.LoadPointer(&um.m))
_, result = m[email]
return
}
func (um *UserMap) LoadAuthenticatedEmailsFile() {
r, err := os.Open(um.usersFile)
if err != nil {
log.Fatalf("failed opening authenticated-emails-file=%q, %s", um.usersFile, err)
}
defer r.Close()
csv_reader := csv.NewReader(r)
csv_reader.Comma = ','
csv_reader.Comment = '#'
csv_reader.TrimLeadingSpace = true
records, err := csv_reader.ReadAll()
if err != nil {
log.Printf("error reading authenticated-emails-file=%q, %s", um.usersFile, err)
return
}
updated := make(map[string]bool)
for _, r := range records {
updated[strings.ToLower(r[0])] = true
}
atomic.StorePointer(&um.m, unsafe.Pointer(&updated))
}
func newValidatorImpl(domains []string, usersFile string,
done <-chan bool, onUpdate func()) func(string) bool {
validUsers := NewUserMap(usersFile, done, onUpdate)
for i, domain := range domains {
domains[i] = fmt.Sprintf("@%s", strings.ToLower(domain))
}
validator := func(email string) bool {
email = strings.ToLower(email)
valid := false
for _, domain := range domains {
valid = valid || strings.HasSuffix(email, domain)
}
if !valid {
valid = validUsers.IsValid(email)
}
log.Printf("validating: is %s valid? %v", email, valid)
return valid
}
return validator
}
func NewValidator(domains []string, usersFile string) func(string) bool {
return newValidatorImpl(domains, usersFile, nil, func() {})
}