aboutsummaryrefslogtreecommitdiffstats
path: root/icalproxy.go
diff options
context:
space:
mode:
Diffstat (limited to 'icalproxy.go')
-rw-r--r--icalproxy.go117
1 files changed, 117 insertions, 0 deletions
diff --git a/icalproxy.go b/icalproxy.go
new file mode 100644
index 0000000..cf09e5d
--- /dev/null
+++ b/icalproxy.go
@@ -0,0 +1,117 @@
1package main
2
3import (
4 "bytes"
5 "log"
6 "net/http"
7 "net/url"
8 "time"
9
10 "github.com/emersion/go-ical"
11 "golang.org/x/crypto/argon2"
12)
13
14func main() {
15 cfg := loadConfig()
16 printConfig(&cfg)
17
18 handler := handler{ignore: cfg.Ignore, tokens: cfg.UserTokens}
19
20 mux := http.ServeMux{}
21 mux.HandleFunc("/", handler.handle)
22 server := http.Server{
23 Addr: ":" + cfg.Port,
24 Handler: &mux,
25 ReadTimeout: 5 * time.Second,
26 WriteTimeout: 30 * time.Second,
27 IdleTimeout: 30 * time.Second,
28 ReadHeaderTimeout: 2 * time.Second,
29 }
30
31 log.Println("Listening on", cfg.Port)
32 log.Fatal(server.ListenAndServe())
33}
34
35type handler struct {
36 ignore ignoreRules
37 tokens map[string]userToken
38 calURL url.URL
39}
40
41func (h handler) makeTokenURL(token string) string {
42 newURL := h.calURL
43 query := newURL.Query()
44 query.Set("token", token)
45 newURL.RawQuery = query.Encode()
46 return newURL.String()
47}
48
49func (h handler) handle(w http.ResponseWriter, r *http.Request) {
50 brightspaceToken := r.URL.Query().Get("brightspace_token")
51 proxyUserID := r.URL.Query().Get("proxy_user_id")
52 proxyToken := r.URL.Query().Get("proxy_token")
53 if !userOK(h.tokens, proxyUserID, []byte(proxyToken)) {
54 http.Error(w, "Bad proxy_user_id or proxy_token", http.StatusUnauthorized)
55 return
56 }
57
58 resp, err := http.Get(h.makeTokenURL(brightspaceToken))
59 if err != nil {
60 http.Error(w, "Error sending request to Brightspace", http.StatusInternalServerError)
61 return
62 }
63
64 cal, err := ical.NewDecoder(resp.Body).Decode()
65 if err != nil {
66 http.Error(w, "Could not decode iCal", http.StatusInternalServerError)
67 return
68 }
69
70 filter(h.ignore, cal.Component)
71
72 if err = ical.NewEncoder(w).Encode(cal); err != nil {
73 log.Println("Error writing calendar:", err)
74 }
75}
76
77func filter(ignore ignoreRules, c *ical.Component) {
78 j := 0
79 for _, child := range c.Children {
80 keep := true
81
82 if child.Name == ical.CompEvent {
83 if location := child.Props.Get(ical.PropLocation); location != nil {
84 for _, locationRule := range ignore.LocationRegexes {
85 if locationRule.MatchString(location.Value) {
86 keep = false
87 }
88 }
89 }
90 if summary := child.Props.Get(ical.PropSummary); summary != nil {
91 for _, summaryRule := range ignore.SummaryRegexes {
92 if summaryRule.MatchString(summary.Value) {
93 keep = false
94 }
95 }
96 }
97 }
98
99 if keep {
100 c.Children[j] = child
101 j++
102 }
103 }
104 c.Children = c.Children[:j]
105}
106
107func userOK(tokens map[string]userToken, id string, token []byte) bool {
108 info, ok := tokens[id]
109 if !ok {
110 return false
111 }
112 return bytes.Compare(hash(token, info.Salt), info.Hash) == 0
113}
114
115func hash(token []byte, salt []byte) []byte {
116 return argon2.IDKey(token, salt, 1, 64*1024, 4, 32)
117}