-
Notifications
You must be signed in to change notification settings - Fork 0
/
providers.go
157 lines (132 loc) · 4.25 KB
/
providers.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
package auth
import (
"context"
"net/http"
"github.com/readeck/readeck/internal/auth/users"
)
type (
ctxProviderKey struct{}
ctxAuthKey struct{}
)
// Info is the payload with the currently authenticated user
// and some information about the provider
type Info struct {
Provider *ProviderInfo
User *users.User
}
// ProviderInfo contains information about the provider.
type ProviderInfo struct {
Name string
Application string
}
// Provider is the interface that must implement any authentication
// provider.
type Provider interface {
// Must return true to enable the provider for the current request.
IsActive(r *http.Request) bool
// Must return a request with the Info provided when successful.
Authenticate(http.ResponseWriter, *http.Request) (*http.Request, error)
}
// FeatureCsrfProvider allows a provider to implement a method
// to bypass all CSRF protection.
type FeatureCsrfProvider interface {
// Must return true to disable CSRF protection for the request.
CsrfExempt(r *http.Request) bool
}
// NullProvider is the provider returned when no other provider
// could be activated.
type NullProvider struct{}
// Info return information about the provider.
func (p *NullProvider) Info(_ *http.Request) *ProviderInfo {
return &ProviderInfo{
Name: "null",
}
}
// IsActive is always false
func (p *NullProvider) IsActive(_ *http.Request) bool {
return false
}
// Authenticate doesn't do anything
func (p *NullProvider) Authenticate(_ http.ResponseWriter, r *http.Request) (*http.Request, error) {
return r, nil
}
// Init returns an http.Handler that will try to find a suitable
// authentication provider on each request. The first to return
// true with its IsActive() method becomes the request authentication
// provider.
//
// If no provider could be found, the NullProvider will then be used.
//
// The provider is then stored in the request's context and can be
// retrieved using GetRequestProvider().
func Init(providers ...Provider) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var provider Provider
for _, p := range providers {
if p.IsActive(r) {
provider = p
break
}
}
// Set a default provider
if provider == nil {
provider = &NullProvider{}
}
r = setRequestProvider(r, provider)
// Always set a anonymous user
r = SetRequestAuthInfo(r, &Info{User: &users.User{}})
next.ServeHTTP(w, r)
})
}
}
// Required returns an http.Handler that will enforce authentication
// on the request. It uses the request authentication provider to perform
// the authentication.
//
// A provider performing a successful authentication must store
// its authentication information using SetRequestAuthInfo.
//
// When the request has this attribute it will carry on.
// Otherwise it stops the response with a 403 error.
//
// The logged in user can be retrieved with GetRequestUser().
func Required(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
provider := GetRequestProvider(r)
r, err := provider.Authenticate(w, r)
if err != nil {
w.WriteHeader(http.StatusForbidden)
return
}
if GetRequestUser(r).IsAnonymous() {
w.WriteHeader(http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
// setRequestProvider stores the current provider for the request.
func setRequestProvider(r *http.Request, provider Provider) *http.Request {
ctx := context.WithValue(r.Context(), ctxProviderKey{}, provider)
return r.WithContext(ctx)
}
// GetRequestProvider returns the current request's authentication
// provider.
func GetRequestProvider(r *http.Request) Provider {
return r.Context().Value(ctxProviderKey{}).(Provider)
}
// SetRequestAuthInfo stores the request's user.
func SetRequestAuthInfo(r *http.Request, info *Info) *http.Request {
ctx := context.WithValue(r.Context(), ctxAuthKey{}, info)
return r.WithContext(ctx)
}
// GetRequestAuthInfo returns the current request's auth info
func GetRequestAuthInfo(r *http.Request) *Info {
return r.Context().Value(ctxAuthKey{}).(*Info)
}
// GetRequestUser returns the current request's user.
func GetRequestUser(r *http.Request) *users.User {
return GetRequestAuthInfo(r).User
}