-
Notifications
You must be signed in to change notification settings - Fork 0
/
group.go
208 lines (185 loc) · 6.36 KB
/
group.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
// Copyright (C) 2014 JT Olds
// See LICENSE for copying information
package whoauth2
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/spacemonkeygo/errors"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"gopkg.in/webhelp.v1/whcompat"
"gopkg.in/webhelp.v1/wherr"
"gopkg.in/webhelp.v1/whmux"
"gopkg.in/webhelp.v1/whredir"
"gopkg.in/webhelp.v1/whroute"
)
// ProviderGroup is an http.Handler that keeps track of authentication for
// multiple OAuth2 providers.
//
// Assuming OAuth2 providers have been configured for Facebook, Google,
// LinkedIn, and Github, ProviderGroup handles requests to the following paths:
// * /all/logout
// * /facebook/login
// * /facebook/logout
// * /facebook/_cb
// * /google/login
// * /google/logout
// * /google/_cb
// * /linkedin/login
// * /linkedin/logout
// * /linkedin/_cb
// * /github/login
// * /github/logout
// * /github/_cb
//
// ProviderGroup will also return associated state to you about each OAuth2
// provider's state, in addition to a LoginRequired middleware and a Login
// URL generator.
type ProviderGroup struct {
handlers map[string]*ProviderHandler
mux whmux.Dir
urls RedirectURLs
group_base_url string
}
// NewProviderGroup makes a provider group. Requires a session namespace (will
// be prepended to ":"+provider_name), the base URL of the ProviderGroup's
// http.Handler, a collection of URLs for redirecting, and a list of specific
// configured providers.
func NewProviderGroup(session_namespace string, group_base_url string,
urls RedirectURLs, providers ...*Provider) (*ProviderGroup, error) {
group_base_url = strings.TrimRight(group_base_url, "/")
g := &ProviderGroup{
handlers: make(map[string]*ProviderHandler, len(providers)),
urls: urls,
group_base_url: group_base_url}
g.mux = whmux.Dir{
"all": whmux.Dir{"logout": whmux.Exact(
http.HandlerFunc(g.logoutAll))},
}
for _, provider := range providers {
if provider.Name == "" {
return nil, fmt.Errorf("empty provider name")
}
_, exists := g.handlers[provider.Name]
if exists {
return nil, fmt.Errorf("two providers given with name %#v",
provider.Name)
}
handler := NewProviderHandler(provider,
fmt.Sprintf("%s-%s", session_namespace, provider.Name),
fmt.Sprintf("%s/%s", group_base_url, provider.Name), urls)
g.handlers[provider.Name] = handler
g.mux[provider.Name] = handler
}
return g, nil
}
func (g *ProviderGroup) ServeHTTP(w http.ResponseWriter, r *http.Request) {
g.mux.ServeHTTP(w, r)
}
// Routes implements whroute.Lister
func (g *ProviderGroup) Routes(
cb func(method, path string, annotations map[string]string)) {
whroute.Routes(g.mux, cb)
}
var _ http.Handler = (*ProviderGroup)(nil)
var _ whroute.Lister = (*ProviderGroup)(nil)
// Handler returns a specific ProviderHandler given the Provider name
func (g *ProviderGroup) Handler(provider_name string) (rv *ProviderHandler,
exists bool) {
rv, exists = g.handlers[provider_name]
return rv, exists
}
// LoginURL returns the login URL for a given provider.
// redirect_to is the URL to navigate to after logging in, and force_prompt
// tells OAuth2 whether or not the login prompt should always be shown
// regardless of if the user is already logged in.
func (g *ProviderGroup) LoginURL(provider_name, redirect_to string,
force_prompt bool) string {
return g.handlers[provider_name].LoginURL(redirect_to, force_prompt)
}
// LogoutURL returns the logout URL for a given provider.
// redirect_to is the URL to navigate to after logging out.
func (g *ProviderGroup) LogoutURL(provider_name, redirect_to string) string {
return g.handlers[provider_name].LogoutURL(redirect_to)
}
// LogoutAllURL returns the logout URL for all providers.
// redirect_to is the URL to navigate to after logging out.
func (g *ProviderGroup) LogoutAllURL(redirect_to string) string {
return g.group_base_url + "/all/logout?" + url.Values{
"redirect_to": {redirect_to}}.Encode()
}
// Tokens will return a map of all the currently valid OAuth2 tokens
func (g *ProviderGroup) Tokens(ctx context.Context) (map[string]*oauth2.Token,
error) {
rv := make(map[string]*oauth2.Token)
var errs errors.ErrorGroup
for name, handler := range g.handlers {
token, err := handler.Token(ctx)
errs.Add(err)
if err == nil && token != nil {
rv[name] = token
}
}
return rv, errs.Finalize()
}
// Providers will return a map of all the currently known providers.
func (g *ProviderGroup) Providers() map[string]*ProviderHandler {
copy := make(map[string]*ProviderHandler, len(g.handlers))
for name, handler := range g.handlers {
copy[name] = handler
}
return copy
}
// LoggedIn returns true if the user is logged in with any provider
func (g *ProviderGroup) LoggedIn(ctx context.Context) (bool, error) {
t, err := g.Tokens(ctx)
return len(t) > 0, err
}
// LogoutAll will not return any HTTP response, but will simply prepare a
// response for logging a user out completely from all providers. If a user
// should log out of just a specific OAuth2 provider, use the Logout method
// on the associated ProviderHandler.
func (g *ProviderGroup) LogoutAll(ctx context.Context,
w http.ResponseWriter) error {
var errs errors.ErrorGroup
for _, handler := range g.handlers {
errs.Add(handler.Logout(ctx, w))
}
return errs.Finalize()
}
func (g *ProviderGroup) logoutAll(w http.ResponseWriter, r *http.Request) {
err := g.LogoutAll(whcompat.Context(r), w)
if err != nil {
wherr.Handle(w, r, err)
return
}
redirect_to := r.FormValue("redirect_to")
if redirect_to == "" {
redirect_to = g.urls.DefaultLogoutURL
}
whredir.Redirect(w, r, redirect_to)
}
// LoginRequired is a middleware for redirecting users to a login page if
// they aren't logged in yet. login_redirect should take the URL to redirect
// to after logging in and return a URL that will actually do the logging in.
// If you already know which provider a user should use, consider using
// (*ProviderHandler).LoginRequired instead, which doesn't require a
// login_redirect URL.
func (g *ProviderGroup) LoginRequired(h http.Handler,
login_redirect func(redirect_to string) (url string)) http.Handler {
return whroute.HandlerFunc(h,
func(w http.ResponseWriter, r *http.Request) {
tokens, err := g.Tokens(whcompat.Context(r))
if err != nil {
wherr.Handle(w, r, err)
return
}
if len(tokens) <= 0 {
whredir.Redirect(w, r, login_redirect(r.RequestURI))
return
}
h.ServeHTTP(w, r)
})
}