git.iko.soy/typeable/oauth2-proxy
on 4ee3f13 help sign in
typeable/oauth2-proxy/validator.go
126 lines 3.4 kB history raw
1package main
3import (
4 "encoding/csv"
5 "io"
6 "os"
7 "strings"
8 "sync/atomic"
9 "unsafe"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
14// UserMap holds information from the authenticated emails file
15type UserMap struct {
16 usersFile string
17 m unsafe.Pointer
20// NewUserMap parses the authenticated emails file into a new UserMap
21//
22// TODO (@NickMeves): Audit usage of `unsafe.Pointer` and potentially refactor
23func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
24 um := &UserMap{usersFile: usersFile}
25 m := make(map[string]bool)
26 atomic.StorePointer(&um.m, unsafe.Pointer(&m)) // #nosec G103
27 if usersFile != "" {
28 logger.Printf("using authenticated emails file %s", usersFile)
29 WatchForUpdates(usersFile, done, func() {
30 um.LoadAuthenticatedEmailsFile()
31 onUpdate()
32 })
33 um.LoadAuthenticatedEmailsFile()
34 }
35 return um
38// IsValid checks if an email is allowed
39func (um *UserMap) IsValid(email string) (result bool) {
40 m := *(*map[string]bool)(atomic.LoadPointer(&um.m))
41 _, result = m[email]
42 return
45// LoadAuthenticatedEmailsFile loads the authenticated emails file from disk
46// and parses the contents as CSV
47func (um *UserMap) LoadAuthenticatedEmailsFile() {
48 r, err := os.Open(um.usersFile)
49 if err != nil {
50 logger.Fatalf("failed opening authenticated-emails-file=%q, %s", um.usersFile, err)
51 }
52 defer func(c io.Closer) {
53 cerr := c.Close()
54 if cerr != nil {
55 logger.Fatalf("Error closing authenticated emails file: %s", cerr)
56 }
57 }(r)
58 csvReader := csv.NewReader(r)
59 csvReader.Comma = ','
60 csvReader.Comment = '#'
61 csvReader.TrimLeadingSpace = true
62 records, err := csvReader.ReadAll()
63 if err != nil {
64 logger.Errorf("error reading authenticated-emails-file=%q, %s", um.usersFile, err)
65 return
66 }
67 updated := make(map[string]bool)
68 for _, r := range records {
69 address := strings.ToLower(strings.TrimSpace(r[0]))
70 updated[address] = true
71 }
72 atomic.StorePointer(&um.m, unsafe.Pointer(&updated)) // #nosec G103
75func newValidatorImpl(domains []string, usersFile string,
76 done <-chan bool, onUpdate func()) func(string) bool {
77 validUsers := NewUserMap(usersFile, done, onUpdate)
79 var allowAll bool
80 for i, domain := range domains {
81 if domain == "*" {
82 allowAll = true
83 continue
84 }
85 domains[i] = strings.ToLower(domain)
86 }
88 validator := func(email string) (valid bool) {
89 if email == "" {
90 return
91 }
92 email = strings.ToLower(email)
93 valid = isEmailValidWithDomains(email, domains)
94 if !valid {
95 valid = validUsers.IsValid(email)
96 }
97 if allowAll {
98 valid = true
99 }
100 return valid
101 }
102 return validator
105// NewValidator constructs a function to validate email addresses
106func NewValidator(domains []string, usersFile string) func(string) bool {
107 return newValidatorImpl(domains, usersFile, nil, func() {})
110// isEmailValidWithDomains checks if the authenticated email is validated against the provided domain
111func isEmailValidWithDomains(email string, allowedDomains []string) bool {
112 for _, domain := range allowedDomains {
113 // allow if the domain is perfect suffix match with the email
114 if strings.HasSuffix(email, "@"+domain) {
115 return true
116 }
118 // allow if the domain is prefixed with . and
119 // the last element (split on @) has the suffix as the domain
120 atoms := strings.Split(email, "@")
121 if strings.HasPrefix(domain, ".") && strings.HasSuffix(atoms[len(atoms)-1], domain) {
122 return true
123 }
124 }
125 return false