-
Notifications
You must be signed in to change notification settings - Fork 0
/
authorization.go
202 lines (160 loc) · 4.47 KB
/
authorization.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
package auth
import (
"encoding/hex"
"net/http"
"strings"
"time"
"github.com/jslater89/graviton"
"github.com/labstack/echo"
"go.uber.org/zap"
"gopkg.in/mgo.v2/bson"
)
func extractBearer(c echo.Context) string {
authHeader := c.Request().Header.Get("Authorization")
splitHeader := strings.Split(authHeader, " ")
if len(splitHeader) == 2 {
return splitHeader[1]
}
cookie, err := c.Cookie("graviton_bearer")
if err == nil {
return cookie.Value
}
return ""
}
func checkSessionExpiration(session *Session) bool {
if time.Now().After(session.ExpiresAt) {
return false
}
return true
}
// IsAuthorized checks the session included in the request. If
// the user is not authorized for the given request, returns false and
// makes an appropriate response with the context. Otherwise, returns
// true and defers responses to the caller. If the user is authorized
// for a given endpoint, IsAuthorized extends the session's expiration.
func IsAuthorized(c echo.Context, path string) bool {
bearer := extractBearer(c)
if bearer == getOrCreateAPIKey(false) {
return true
}
sess, err := getSession(bearer)
if err != nil {
graviton.Logger.Info("Error getting session", zap.String("Bearer", bearer), zap.Error(err))
c.JSON(401, bson.M{"error": "not logged in"})
return false
}
if !checkSessionExpiration(sess) {
graviton.Logger.Info("Session expired for user", zap.String("Email", sess.User.Email))
deleteSession(bearer)
c.JSON(401, bson.M{"error": "login expired"})
return false
}
user := &sess.User
writeRequest := (c.Request().Method != http.MethodGet)
if !checkUserPermissions(user, writeRequest, path) {
graviton.Logger.Info("User not authorized for resource", zap.String("Email", user.Email), zap.String("Path", path))
c.JSON(403, bson.M{"error": "not authorized for resource"})
return false
}
graviton.Logger.Info("User authorized for path", zap.String("User", user.Email), zap.String("Path", path))
sess.ExpiresAt = time.Now().Add(1 * time.Hour)
saveSession(*sess)
return true
}
func GetAPIKey(c echo.Context) error {
if !IsAuthorized(c, "/auth/apikey") {
return nil
}
return c.JSON(200, bson.M{"key": getOrCreateAPIKey(false)})
}
func ResetAPIKey(c echo.Context) error {
if !IsAuthorized(c, "/auth/apikey") {
return nil
}
return c.JSON(200, bson.M{"key": getOrCreateAPIKey(true)})
}
type apiDoc struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Key string `bson:"key"`
}
func getOrCreateAPIKey(reset bool) string {
n, err := db.apiKeyCollection.Find(bson.M{}).Count()
if err != nil {
panic(err)
}
if n == 0 || reset {
db.apiKeyCollection.RemoveAll(bson.M{})
// 24 bytes == 48 hex characters
keyBytes := []byte(generateSessionToken() + generateSessionToken())
keyString := hex.EncodeToString(keyBytes)
key := apiDoc{
ID: bson.NewObjectId(),
Key: keyString,
}
db.apiKeyCollection.Insert(key)
}
key := &apiDoc{}
db.apiKeyCollection.Find(bson.M{}).One(key)
return key.Key
}
func checkUserPermissions(user *User, write bool, path string) bool {
roles, err := getUserRoles(user)
if err != nil {
graviton.Logger.Warn("User role lookup error", zap.String("Email", user.Email), zap.Error(err))
return false
}
permissions := []Permission{}
for _, role := range roles {
permissions = append(permissions, role.Permissions...)
}
bestMatchLength := 0
var bestPermission Permission
for _, permission := range permissions {
if strings.HasPrefix(path, permission.Path) && len(permission.Path) > bestMatchLength {
bestMatchLength = len(permission.Path)
bestPermission = permission
// Stop searching on exact match
if bestPermission.Path == path {
break
}
}
}
return write && bestPermission.CanWrite || !write && bestPermission.CanRead
}
func verifyBaseRoles() error {
n, err := db.roleCollection.Find(bson.M{"name": "Viewer"}).Count()
if err == nil && n > 0 {
return nil
}
graviton.Logger.Info("Making default roles")
viewerRole := Role{
ID: bson.NewObjectId(),
Name: "Viewer",
Permissions: []Permission{
Permission{
Path: "/",
CanRead: true,
CanWrite: false,
},
Permission{
Path: "/auth/apikey",
CanRead: false,
CanWrite: false,
},
},
}
db.roleCollection.UpsertId(viewerRole.ID, viewerRole)
editorRole := Role{
ID: bson.NewObjectId(),
Name: "Editor",
Permissions: []Permission{
Permission{
CanRead: true,
CanWrite: true,
Path: "/",
},
},
}
db.roleCollection.UpsertId(editorRole.ID, editorRole)
return nil
}