-
Notifications
You must be signed in to change notification settings - Fork 0
/
permissions.go
114 lines (102 loc) · 2.91 KB
/
permissions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package main
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"os"
"path/filepath"
"github.com/fsnotify/fsnotify"
"gopkg.in/yaml.v2"
)
var (
permissionsPath string
secretKey []byte
)
var permissions struct {
Admin string
Addresses map[string][]string
Groups map[string]struct {
Members map[string]bool
Threads, Mails map[hashID]bool
}
}
// readPermissions reads the permissions.yaml file which resides in the
// mailDir. Note that we only log parsing errors here instead of treating them
// as fatal errors because it may be that the permissions.yaml is not yet fully
// written. FWIW, fsnotify fires four “write” notifications if I save the file
// with Emacs or Nano.
func readPermissions() {
data, err := os.ReadFile(permissionsPath)
check(err)
err = yaml.Unmarshal(data, &permissions)
if err != nil {
logger.Println("invalid permissions.yaml")
permissions.Addresses = nil
permissions.Groups = nil
} else {
logger.Println("re-read permissions.yaml")
}
}
// setUpWatcher starts a goroutine that watches for changes in permissions.yaml
// and re-reads it when necessary.
func setUpPermissionsWatcher() {
watcher, err := fsnotify.NewWatcher()
check(err)
go func() {
for {
select {
case event := <-watcher.Events:
if event.Name == permissionsPath && (event.Op&fsnotify.Create == fsnotify.Create ||
event.Op&fsnotify.Write == fsnotify.Write) {
readPermissions()
}
case err := <-watcher.Errors:
check(err)
}
}
}()
err = watcher.Add(permissionsPath)
check(err)
err = watcher.Add(filepath.Dir(permissionsPath))
check(err)
}
// getEmailAddress returns the email address of the given user. If it is not
// found in permissions.yaml, the result is empty.
func getEmailAddress(loginName string) string {
addresses := permissions.Addresses[loginName]
if len(addresses) == 0 {
return ""
} else {
return addresses[0]
}
}
// getEmailAddress returns all email addresses the given user can read.
func getEmailAddresses(loginName string) []string {
return permissions.Addresses[loginName]
}
// hashMessageID hashes the message ID with a pepper taken from
// SECRET_KEY_PATH. The salt can be used to add futher entropy, effectively
// selecting a hash namespace.
func hashMessageID(messageID messageID, salt string) hashID {
hasher := sha256.New()
hasher.Write(secretKey)
if salt != "" {
// “>” is guaranteed to never occur in message IDs.
hasher.Write([]byte(salt + ">"))
}
hasher.Write([]byte(messageID))
return hashID(base64.URLEncoding.EncodeToString(hasher.Sum(nil))[:10])
}
func init() {
permissionsPath = filepath.Join(mailDir, "permissions.yaml")
var err error
secretKeyPath := os.Getenv("SECRET_KEY_PATH")
if secretKeyPath == "" {
secretKeyPath = "/var/lib/mail2web_secrets/secret_key"
}
secretKey, err = os.ReadFile(secretKeyPath)
check(err)
secretKey = bytes.Trim(secretKey, "\t\n\r\f\v ")
readPermissions()
setUpPermissionsWatcher()
}