This repository has been archived by the owner on Apr 5, 2021. It is now read-only.
/
provider.go
259 lines (209 loc) · 6.8 KB
/
provider.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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Package oauthmw provides an OAuth2.0 login flow middleware for Goji v2.
package oauthmw
import (
"crypto/md5"
"errors"
"fmt"
"net/http"
"sort"
"time"
"goji.io"
"github.com/gorilla/securecookie"
"golang.org/x/oauth2"
)
const (
// DefaultSessionKey is the default key used for the oauthmw session store.
//
// Override with Provider.SessionKey
DefaultSessionKey = "oauthmw"
// DefaultPagePrefix is the default page prefix used for oauthmw pages.
//
// Override with Provider.PagePrefix
DefaultPagePrefix = "oauth-"
// DefaultRedirectPrefix is the default prefix used for redirects to
// OAuth2.0 pages.
//
// Override with Provider.
DefaultRedirectPrefix = "redirect-"
// DefaultReturnName is the default path name used for return (login).
//
// Override with Provider.ReturnName
DefaultReturnName = "login"
// DefaultLogoutName is the default path name used for logout.
//
// Please note this is not yet implemented.
//
// Override with Provider.LogoutName
DefaultLogoutName = "logout"
// DefaultStateLifetime is the default lifetime (ttl) for an oauth2
// transfer state.
//
// Override with Provider.StateLifetime
DefaultStateLifetime = 12 * time.Hour
// DefaultMaxStates is the maximum number of states allowed in the session
// storage before a cleanup is triggered.
//
// Override with Provider.MaxStates
DefaultMaxStates = 128
)
// Provider configuration.
type Provider struct {
// Secret for oauth2 transfer state (passed to gorilla/securecookie).
//
// Must not be empty.
Secret []byte
// BlockSecret for oauth2 transfer state (passed to gorilla/securecookie).
//
// Must not be empty.
BlockSecret []byte
// Path that is being secured.
//
// Used for redirects. Must not be empty.
Path string
// Configs for oauth2
Configs map[string]*oauth2.Config
// SessionKey is the key used to retrieve the oauthmw states from the
// session.
//
// Should be unique per path.
//
// If empty, then this is set as the DefaultSessionKey plus the first 6
// characters of the md5 hash of the Provider.Path.
SessionKey string
// StateLifetime is the lifetime (ttl) of an oauth2 transfer state.
StateLifetime time.Duration
// TokenLifetime is maximum allowed token lifetime (ttl) after redemption.
//
// This is useful if you want to force an expiration for redeemed oauth2
// tokens.
TokenLifetime time.Duration
// PagePrefix is the prefix used to check all page requests (default: "oauth-")
//
// All redirect/return/logout paths must start with this prefix.
PagePrefix string
// RedirectPrefix is the optional path prefix used for redirects (default: "redirect-").
RedirectPrefix string
// ReturnName is the path name used for returns (default: "login").
ReturnName string
// LogoutName is the path name used for logout (default: "logout").
//
// Please note that logout is not yet implemented.
LogoutName string
// ConfigsOrder is an optional for the configs processing on the protected
// page template.
//
// Optional to specify, but when provided then this is the order that
// providers are listed in the template to users.
ConfigsOrder []string // FIXME -- not implemented properly
// TemplateFn is the function used for generating template on protected
// page when there is no valid oauth2.Token in the session.
TemplateFn func(http.ResponseWriter, *http.Request, map[string]interface{})
// ErrorFn is the function called when an error is produced.
ErrorFn func(int, string, http.ResponseWriter, *http.Request)
// SubRouter toggles SubRouter path handling for goji subrouter middleware.
//SubRouter bool
// CleanupStates when true causes simple cleanup to happen on the oauth2
// transfer states stored in the session that are already expired.
CleanupStates bool
// MaxStates is the number of states allowed before cleanup is triggered.
//
// Set to -1 for unlimited states.
MaxStates int
}
// EncodeState returns an encoded (and secure) oauth2 transfer state for the
// provided session id, named provider, and specified resource.
func (p Provider) EncodeState(sessionID, provName, resource string) (string, error) {
sc := securecookie.New(p.Secret, p.BlockSecret)
sc.MaxAge(int(p.StateLifetime))
state := map[string]string{
"sid": sessionID,
"provider": provName,
"resource": resource,
}
return sc.Encode(p.SessionKey, state)
}
// DecodeState decodes the oauth2 transfer state encoded with EncodeState.
func (p Provider) DecodeState(data string) (map[string]string, error) {
sc := securecookie.New(p.Secret, p.BlockSecret)
sc.MaxAge(int(p.StateLifetime))
state := make(map[string]string)
err := sc.Decode(p.SessionKey, data, &state)
return state, err
}
// checkDefaults checks (and sets) defaults on Provider
func (p *Provider) checkDefaults() {
if len(p.Secret) < 1 {
panic(errors.New("oauthmw provider Secret cannot be empty"))
}
if len(p.BlockSecret) < 1 {
panic(errors.New("oauthmw provider BlockSecret cannot be empty"))
}
if p.Path == "" {
panic(errors.New("oauthmw provider Path cannot be empty string"))
}
if p.SessionKey == "" {
h := md5.Sum([]byte(p.Path))
p.SessionKey = fmt.Sprintf("%s%x", DefaultSessionKey, h[:3])
}
if p.PagePrefix == "" {
p.PagePrefix = "/" + DefaultPagePrefix
}
if p.RedirectPrefix == "" {
p.RedirectPrefix = p.PagePrefix + DefaultRedirectPrefix
}
if p.ReturnName == "" {
p.ReturnName = p.PagePrefix + DefaultReturnName
}
if p.LogoutName == "" {
p.LogoutName = p.PagePrefix + DefaultLogoutName
}
if p.StateLifetime == 0 {
p.StateLifetime = DefaultStateLifetime
}
if p.TemplateFn == nil {
p.TemplateFn = defaultTemplateFn
}
if p.ErrorFn == nil {
p.ErrorFn = defaultErrorFn
}
if p.MaxStates == 0 {
p.MaxStates = DefaultMaxStates
}
// fill ConfigsOrder with keys in alphabetical order if not provided
if len(p.ConfigsOrder) < 1 {
p.ConfigsOrder = make([]string, len(p.Configs))
i := 0
for k := range p.Configs {
p.ConfigsOrder[i] = k
i++
}
sort.Strings(p.ConfigsOrder)
}
}
// buildLogin creates the actual login provider.
func (p Provider) buildLogin(checkFn CheckFn, required bool) func(goji.Handler) goji.Handler {
prov := &p
prov.checkDefaults()
return func(h goji.Handler) goji.Handler {
return login{
provider: prov,
required: required,
checkFn: checkFn,
h: h,
}
}
}
// Login provides a goji.Handler that handles oauth2 login flows, but does
// not require there to be a login.
//
// NOTE: Any mux using this middleware WILL be visible to an unauthenticated
// user.
func (p Provider) Login(checkFn CheckFn) func(goji.Handler) goji.Handler {
return p.buildLogin(checkFn, false)
}
// RequireLogin provides goji.Handler that handles oauth2 login flows,
// requiring that there be a valid login prior to acessing a protected
// resource.
func (p Provider) RequireLogin(checkFn CheckFn) func(goji.Handler) goji.Handler {
return p.buildLogin(checkFn, true)
}