/
cookiemask.go
218 lines (190 loc) · 5.27 KB
/
cookiemask.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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package authentication
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/proidiot/gone/log"
"github.com/stuphlabs/pullcord/config"
"github.com/stuphlabs/pullcord/util"
)
// CookiemaskFilter is a falcore.RequestFilter that will apply a "cookie mask"
// function before forwarding the (possibly modified) request to the next
// RequestFilter in the chain (which may ultimately lead to a proxy).
//
// As HTTP clients typically have internal cookie managers sophisticated enough
// to send multiple cookies (with possibly different scopes or properties) with
// each request, we should be able to use our own cookies to keep track of
// state without exposing those cookies to any external application to which we
// may be proxying (since we have no guarantee that the presence of such
// cookies would not effect the behavior of such an external application). To
// that end, this function is given a cookie masking function which checks for
// any cookies intended to be received by the particular function, and
// potentially retrieves session information from some session handler to be
// forwarded down the RequestFilter chain as part of the context map. The
// cookie filter function returns the array of cookies which are not masked
// (and are to be allowed down the RequestFilter chain along with the rest of
// the original request), plus an array of any new cookies associated with this
// particular filter which are to be sent to the browser for storage (which
// should happen seamlessly as part of the next response), plus any session data
// which is to be forwarded down the RequestFilter chain as part of the context
// map, and of course a possible error (which will cause the onError
// RequestFilter chain to receive the context instead, with any new cookies
// still being added to the response, even though the onError chain will
// receive no cookies as part of the request).
type CookiemaskFilter struct {
Handler SessionHandler
Masked http.Handler
}
func init() {
config.MustRegisterResourceType(
"cookiemaskfilter",
func() json.Unmarshaler {
return new(CookiemaskFilter)
},
)
}
// UnmarshalJSON implements encoding/json.Unmarshaler.
func (f *CookiemaskFilter) UnmarshalJSON(input []byte) error {
var t struct {
Handler config.Resource
Masked config.Resource
}
dec := json.NewDecoder(bytes.NewReader(input))
if e := dec.Decode(&t); e != nil {
return e
}
h := t.Handler.Unmarshalled
switch h := h.(type) {
case SessionHandler:
f.Handler = h
default:
_ = log.Err(
"Resource described by \"Handler\" is not a" +
" SessionHandler",
)
return config.UnexpectedResourceType
}
m := t.Masked.Unmarshalled
switch m := m.(type) {
case http.Handler:
f.Masked = m
default:
_ = log.Err(
"Resource described by \"Masked\" is not a" +
" net/http.Handler",
)
return config.UnexpectedResourceType
}
return nil
}
type cookieAppender struct {
ckes []*http.Cookie
w http.ResponseWriter
hdrs http.Header
started bool
}
func (ca cookieAppender) writeHeaders() {
for key, vals := range ca.hdrs {
for _, val := range vals {
ca.w.Header().Add(key, val)
}
}
for _, cke := range ca.ckes {
ca.w.Header().Add("Set-Cookie", cke.String())
}
}
func (ca cookieAppender) writeTrailers() {
for key, vals := range ca.hdrs {
if strings.HasPrefix(key, http.TrailerPrefix) {
for _, val := range vals {
ca.w.Header().Add(key, val)
}
}
}
if keys, present := ca.hdrs["Trailer"]; present {
for _, key := range keys {
for _, val := range ca.hdrs[key] {
ca.w.Header().Add(key, val)
}
}
}
}
func (ca cookieAppender) Header() http.Header {
return ca.hdrs
}
func (ca cookieAppender) Write(d []byte) (int, error) {
if !ca.started {
ca.started = true
ca.writeHeaders()
}
return ca.w.Write(d)
}
func (ca cookieAppender) WriteHeader(statusCode int) {
if !ca.started {
ca.started = true
ca.writeHeaders()
}
ca.w.WriteHeader(statusCode)
}
// FilterRequest implements the required function to allow CookiemaskFilter to
// be a falcore.RequestFilter.
func (f *CookiemaskFilter) ServeHTTP(
w http.ResponseWriter,
req *http.Request,
) {
_ = log.Debug("running cookiemask filter")
//TODO remove
_ = log.Debug(fmt.Sprintf("handler is: %v", f))
sesh, err := f.Handler.GetSession()
if err != nil {
_ = log.Err(
fmt.Sprintf(
"cookiemask filter was unable to get"+
" a new session from the session"+
" handler: %v",
err,
),
)
util.InternalServerError.ServeHTTP(w, req)
return
}
// TODO remove
_ = log.Debug(fmt.Sprintf("sesh is: %v", sesh))
req = req.WithContext(
context.WithValue(req.Context(), ctxKeySession, sesh),
)
fwdCkes, setCkes, err := sesh.CookieMask(
req.Cookies(),
)
req.Header.Del("Cookie")
for _, cke := range fwdCkes {
req.AddCookie(cke)
}
ca := cookieAppender{
ckes: setCkes,
w: w,
hdrs: make(map[string][]string),
started: false,
}
defer ca.writeTrailers()
if err != nil {
_ = log.Err(
fmt.Sprintf(
"cookiemask filter's call to the"+
" session handler's CookieMask"+
" function returned an error: %v",
err,
),
)
util.InternalServerError.ServeHTTP(ca, req)
return
}
_ = log.Info(
"request has run through cookiemask, now forwarding to next" +
" filter",
)
f.Masked.ServeHTTP(ca, req)
}