forked from zeebo/admin
/
admin.go
196 lines (168 loc) · 5.1 KB
/
admin.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
package admin
import (
"crypto/rand"
"github.com/zeebo/sign"
"io"
"launchpad.net/mgo"
"log"
"net/http"
"os"
"reflect"
"strings"
"sync"
"time"
)
//useful type because these get made so often
type d map[string]interface{}
//adminHandler is a type representing a handler function on an *Admin.
type adminHandler func(*Admin, http.ResponseWriter, *http.Request)
//Admin is an http.Handler for serving up the admin pages
type Admin struct {
Auth Authorizer //If not nil, admin is auth protected.
Session *mgo.Session //The mongo session for managing.
Renderer Renderer //If nil, a default renderer is used to render the admin pages.
Routes map[string]string //Routes lets you change the url paths. If nil, uses DefaultRoutes.
Prefix string //The path the admin is mounted to in the handler.
Key []byte //Key for cryptographically signing cookies. Generated if nil.
Logger io.Writer //If nil, os.Stdout is used for logging information.
//created on demand
initd sync.Once
server *http.ServeMux
types map[string]collectionInfo
index_cache map[string][]string
object_id map[reflect.Type]int
object_coll map[reflect.Type]string
auth_cache map[*http.Request]AuthSession
logger *log.Logger
}
//DefaultRoutes is the mapping of actions to url paths.
var DefaultRoutes = map[string]string{
"index": "/",
"list": "/list/",
"update": "/update/",
"create": "/create/",
"detail": "/detail/",
"delete": "/delete/",
"auth": "/auth/",
}
//routes defines the mapping of type to function for the admin
var routes = map[string]adminHandler{
"index": (*Admin).index,
"list": (*Admin).list,
"update": (*Admin).update,
"create": (*Admin).create,
"detail": (*Admin).detail,
"delete": (*Admin).delete,
"auth": (*Admin).auth,
}
//init sets up the admin's caches and routes.
func (a *Admin) init() {
a.initd.Do(func() {
//ensure a valid database
if a.Session == nil {
panic("Mongo session not configured")
}
//make defaults
if a.Routes == nil {
a.Routes = DefaultRoutes
}
if a.Key == nil {
a.generateKey(128) //128 byte key
}
if a.Logger == nil {
a.Logger = os.Stdout
}
a.logger = log.New(a.Logger, "ADMIN", log.LstdFlags)
if a.Renderer == nil {
a.Renderer = newDefaultRenderer(a.logger)
}
required := []string{"index", "list", "update", "create", "detail", "delete", "auth"}
for _, r := range required {
if _, ex := a.Routes[r]; !ex {
panic("Route missing: " + r)
}
}
a.generateMux()
a.generateIndexCache()
a.auth_cache = make(map[*http.Request]AuthSession)
})
}
//generateMux creates the internal http.ServeMux to dispatch reqeusts to the
//appropriate handler.
func (a *Admin) generateMux() {
a.server = http.NewServeMux()
for key, path := range a.Routes {
r, fn := path, routes[key]
a.server.Handle(r, http.StripPrefix(r, a.bind(fn)))
}
}
//bind turns an adminHandler into an http.HandlerFunc by closing on the admin
//value on the adminHandler.
func (a *Admin) bind(fn adminHandler) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
fn(a, w, req)
}
}
//generateIndexCache generates the values needed for IndexContext and stores
//them for efficient lookup.
func (a *Admin) generateIndexCache() {
a.index_cache = make(map[string][]string)
for key := range a.types {
pieces := strings.Split(key, ".")
a.index_cache[pieces[0]] = append(a.index_cache[pieces[0]], pieces[1])
}
}
//generateKey generates a key for cryptographically signing cookie values.
func (a *Admin) generateKey(size int) {
a.Key = make([]byte, size)
for p := 0; p < len(a.Key); {
n, err := rand.Read(a.Key[p:])
if err != nil {
panic("Error while generating key: " + err.Error())
}
p += n
}
}
//collFor returns the mgo.Collection for the specified database.collection.
func (a *Admin) collFor(dbcoll string) *mgo.Collection {
pieces := strings.Split(dbcoll, ".")
return a.Session.DB(pieces[0]).C(pieces[1])
}
//ServeHTTP lets *Admin conform to the http.Handler interface for use in web servers.
func (a *Admin) ServeHTTP(w http.ResponseWriter, req *http.Request) {
a.init()
//strip off the prefix
req.URL.Path = req.URL.Path[len(a.Prefix):]
//if they're going to the auth handler, let them through
if a.Auth == nil || strings.HasPrefix(req.URL.Path, a.Routes["auth"]) {
a.server.ServeHTTP(w, req)
return
}
//set up a redirect function to handle adding the redirect cookie
//and sending them to the login handler
redirect := func() {
reverser := Reverser{a}
http.SetCookie(w, &http.Cookie{
Name: "redirect",
Value: a.Prefix + req.URL.Path, //gotta put the prefix back in
Path: "/",
Expires: time.Now().AddDate(1, 0, 0),
})
http.Redirect(w, req, reverser.Login(), http.StatusTemporaryRedirect)
}
signer := sign.Signer{a.Key}
var session AuthSession
cook, err := req.Cookie("auth")
if err != nil {
redirect()
return
}
if err := signer.Unsign(cook.Value, &session, 0); err != nil {
redirect()
return
}
//store the auth session into our cache
a.auth_cache[req] = session
defer delete(a.auth_cache, req)
a.server.ServeHTTP(w, req)
}