Skip to content

Commit c4b1248

Browse files
committed
Implement permission checks internally
Permissions are still not a thing yet, everything is subject to change. This is being implemented for use by forks to add a permission engine. There's, not a lot here... yet...
1 parent e3e0e13 commit c4b1248

File tree

3 files changed

+114
-41
lines changed

3 files changed

+114
-41
lines changed

endpoints_auth.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,19 @@ func accountsEndpoint(connector *Connector, w http.ResponseWriter, r *http.Reque
153153
"Perform user management on the primary node!", http.StatusForbidden)
154154
return
155155
}
156-
user := connector.ValidateAndReject(w, r)
157-
if user == "" {
156+
perm := ""
157+
switch r.Method {
158+
case "GET":
159+
perm = "accounts.view"
160+
case "POST":
161+
perm = "accounts.create"
162+
case "PATCH":
163+
perm = "accounts.update"
164+
case "DELETE":
165+
perm = "accounts.delete"
166+
}
167+
user, hasPerm := connector.ValidateWithPermAndReject(w, r, perm)
168+
if user == "" || !hasPerm {
158169
return
159170
}
160171
var users map[string]string

endpoints_files.go

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,23 @@ type serverFilesResponse struct {
4343
}
4444

4545
func filesEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
46+
id := r.PathValue("id")
4647
// Check with authenticator.
47-
user := connector.ValidateAndReject(w, r)
48-
if user == "" {
48+
var perm string
49+
switch r.Method {
50+
case "GET":
51+
perm = "server<" + id + ">.files.view"
52+
case "PATCH":
53+
perm = "server<" + id + ">.files.modify"
54+
default:
55+
httpError(w, "Only GET and PATCH are allowed!", http.StatusMethodNotAllowed)
56+
return
57+
}
58+
user, hasPerm := connector.ValidateWithPermAndReject(w, r, perm)
59+
if user == "" || !hasPerm {
4960
return
5061
}
5162
// Get the process being accessed.
52-
id := r.PathValue("id")
5363
process, err := connector.Processes.Load(id)
5464
// In case the process doesn't exist.
5565
if !err {
@@ -61,8 +71,6 @@ func filesEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request)
6171
filesEndpointGet(w, r, process)
6272
case "PATCH":
6373
filesEndpointPatch(connector, w, r, process, id, user)
64-
default:
65-
httpError(w, "Only GET and PATCH are allowed!", http.StatusMethodNotAllowed)
6674
}
6775
}
6876

@@ -340,15 +348,30 @@ func filesEndpointPatch(
340348
// DELETE /server/{id}/file?path=path
341349
// PATCH /server/{id}/file?path=path
342350
func fileEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
351+
id := r.PathValue("id")
352+
var perm string
353+
switch r.Method {
354+
case "GET":
355+
perm = "server<" + id + ">.files.download"
356+
case "DELETE":
357+
perm = "server<" + id + ">.files.modify"
358+
case "POST":
359+
perm = "server<" + id + ">.files.upload"
360+
case "PATCH":
361+
perm = "server<" + id + ">.files.modify"
362+
default:
363+
httpError(w, "Only GET, POST, PATCH and DELETE are allowed!", http.StatusMethodNotAllowed)
364+
return
365+
}
343366
ticket, ticketExists := connector.Tickets.LoadAndDelete(r.URL.Query().Get("ticket"))
344367
user := ""
368+
hasPerm := false
345369
if ticketExists && ticket.IPAddr == GetIP(r) && r.Method == "GET" {
346370
user = ticket.User
347-
} else if user = connector.ValidateAndReject(w, r); user == "" {
371+
} else if user, hasPerm = connector.ValidateWithPermAndReject(w, r, perm); user == "" || !hasPerm {
348372
return
349373
}
350374
// Get the process being accessed.
351-
id := r.PathValue("id")
352375
process, err := connector.Processes.Load(id)
353376
// In case the process doesn't exist.
354377
if !err {
@@ -373,8 +396,6 @@ func fileEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request)
373396
fileEndpointPost(connector, w, r, id, filePath, user)
374397
case "PATCH":
375398
fileEndpointPatch(connector, w, r, process, id, user)
376-
default:
377-
httpError(w, "Only GET, POST, PATCH and DELETE are allowed!", http.StatusMethodNotAllowed)
378399
}
379400
}
380401

@@ -557,13 +578,13 @@ func fileEndpointDelete(connector *Connector, w http.ResponseWriter, r *http.Req
557578

558579
// POST /server/{id}/folder?path=path
559580
func folderEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
581+
id := r.PathValue("id")
560582
// Check with authenticator.
561-
user := connector.ValidateAndReject(w, r)
562-
if user == "" {
583+
user, hasPerm := connector.ValidateWithPermAndReject(w, r, "server<"+id+">.files.createFolder")
584+
if user == "" || !hasPerm {
563585
return
564586
}
565587
// Get the process being accessed.
566-
id := r.PathValue("id")
567588
process, err := connector.Processes.Load(id)
568589
// In case the process doesn't exist.
569590
if !err {
@@ -606,13 +627,13 @@ func folderEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request
606627
var compressionProgressMap = xsync.NewMapOf[string, string]()
607628

608629
func compressionEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
630+
id := r.PathValue("id")
609631
// Check with authenticator.
610-
user := connector.ValidateAndReject(w, r)
611-
if user == "" {
632+
user, hasPerm := connector.ValidateWithPermAndReject(w, r, "server<"+id+">.files.compress")
633+
if user == "" || !hasPerm {
612634
return
613635
}
614636
// Get the process being accessed.
615-
id := r.PathValue("id")
616637
process, exists := connector.Processes.Load(id)
617638
// In case the process doesn't exist.
618639
if !exists {
@@ -785,13 +806,13 @@ func compressionEndpoint(connector *Connector, w http.ResponseWriter, r *http.Re
785806

786807
// POST /server/{id}/decompress?path=path
787808
func decompressionEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
809+
id := r.PathValue("id")
788810
// Check with authenticator.
789-
user := connector.ValidateAndReject(w, r)
790-
if user == "" {
811+
user, hasPerm := connector.ValidateWithPermAndReject(w, r, "server<"+id+">.files.decompress")
812+
if user == "" || !hasPerm {
791813
return
792814
}
793815
// Get the process being accessed.
794-
id := r.PathValue("id")
795816
process, err := connector.Processes.Load(id)
796817
// In case the process doesn't exist.
797818
if !err {

endpoints_misc.go

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ func rootEndpoint(w http.ResponseWriter, _ *http.Request) {
2525
// GET /config
2626
// PATCH /config
2727
func configEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
28-
user := connector.ValidateAndReject(w, r)
29-
if user == "" {
30-
return
31-
}
3228
switch r.Method {
3329
case "GET":
30+
user, hasPerm := connector.ValidateWithPermAndReject(w, r, "config.view")
31+
if user == "" || !hasPerm {
32+
return
33+
}
3434
contents, err := os.ReadFile(ConfigJsonPath)
3535
if err != nil {
3636
log.Println("Error reading "+ConfigJsonPath+" when user accessed /config!", err)
@@ -41,6 +41,10 @@ func configEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request
4141
w.Header().Set("content-type", "application/json")
4242
_, _ = w.Write(contents)
4343
case "PATCH":
44+
user, hasPerm := connector.ValidateWithPermAndReject(w, r, "config.edit")
45+
if user == "" || !hasPerm {
46+
return
47+
}
4448
var buffer bytes.Buffer
4549
_, err := buffer.ReadFrom(r.Body)
4650
if err != nil {
@@ -83,8 +87,8 @@ func configEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request
8387
// GET /config/reload
8488
func configReloadEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
8589
// Check with authenticator.
86-
user := connector.ValidateAndReject(w, r)
87-
if user == "" {
90+
user, hasPerm := connector.ValidateWithPermAndReject(w, r, "config.reload")
91+
if user == "" || !hasPerm {
8892
return
8993
}
9094
// Read the new config.
@@ -109,23 +113,36 @@ type serversResponse struct {
109113

110114
func serversEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
111115
// Check with authenticator.
112-
if connector.ValidateAndReject(w, r) == "" {
116+
user := connector.ValidateAndReject(w, r)
117+
if user == "" {
113118
return
114119
}
115120
// Get a map of processes and their online status.
116121
processes := make(map[string]interface{})
117-
connector.Processes.Range(func(_ string, v *ExposedProcess) bool {
118-
if r.URL.Query().Get("extrainfo") == "true" {
119-
processes[v.Name] = map[string]interface{}{
122+
errored := false
123+
connector.Processes.Range(func(name string, v *ExposedProcess) bool {
124+
hasPerm, err := connector.Authenticator.HasPerm(user, "server<"+name+">.view")
125+
if !hasPerm {
126+
return true
127+
} else if err != nil {
128+
log.Println("An error occurred while checking permissions for user \""+user+"\"!", err)
129+
httpError(w, "Internal Server Error!", http.StatusInternalServerError)
130+
errored = true
131+
return false
132+
} else if r.URL.Query().Get("extrainfo") == "true" {
133+
processes[name] = map[string]interface{}{
120134
"status": v.Online.Load(),
121135
"toDelete": v.ToDelete.Load(),
122136
}
123137
} else {
124-
processes[v.Name] = v.Online.Load()
138+
processes[name] = v.Online.Load()
125139
}
126140
return true
127141
})
128142
// Send the list.
143+
if errored {
144+
return
145+
}
129146
writeJsonStructRes(w, serversResponse{Servers: processes}) // skipcq GSC-G104
130147
}
131148

@@ -143,13 +160,23 @@ type serverResponse struct {
143160
var totalMemory = int64(system.GetTotalSystemMemory())
144161

145162
func serverEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
163+
id := r.PathValue("id")
146164
// Check with authenticator.
147-
user := connector.ValidateAndReject(w, r)
148-
if user == "" {
165+
var perm string
166+
switch r.Method {
167+
case "GET":
168+
perm = "server<" + id + ">.view"
169+
case "POST":
170+
perm = "server<" + id + ">.control"
171+
default:
172+
httpError(w, "Only GET and POST is allowed!", http.StatusMethodNotAllowed)
173+
return
174+
}
175+
user, hasPerm := connector.ValidateWithPermAndReject(w, r, perm)
176+
if user == "" || !hasPerm {
149177
return
150178
}
151179
// Get the process being accessed.
152-
id := r.PathValue("id")
153180
process, err := connector.Processes.Load(id)
154181
// In case the process doesn't exist.
155182
if !err {
@@ -161,8 +188,6 @@ func serverEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request
161188
serverEndpointGet(w, process)
162189
case "POST":
163190
serverEndpointPost(connector, w, r, process, id, user)
164-
default:
165-
httpError(w, "Only GET and POST is allowed!", http.StatusMethodNotAllowed)
166191
}
167192
}
168193

@@ -261,10 +286,12 @@ type consolePing struct {
261286
}
262287

263288
type consoleSettings struct {
264-
Type string `json:"type"`
289+
Type string `json:"type"`
290+
ReadOnly bool `json:"readOnly"`
265291
}
266292

267293
func consoleEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request) {
294+
id := r.PathValue("id")
268295
// Get console protocol version.
269296
v2 := slices.Contains(websocket.Subprotocols(r), "console-v2")
270297
// Check with authenticator.
@@ -276,21 +303,31 @@ func consoleEndpoint(connector *Connector, w http.ResponseWriter, r *http.Reques
276303
} else {
277304
user, userErr = connector.Authenticator.Validate(r)
278305
}
306+
hasPerm := false
307+
canWrite := false
308+
if user != "" && userErr == nil {
309+
hasPerm, userErr = connector.Authenticator.HasPerm(user, "server<"+id+">.console.view")
310+
if userErr == nil {
311+
canWrite, userErr = connector.Authenticator.HasPerm(user, "server<"+id+">.console.write")
312+
}
313+
}
279314
if !v2 && userErr != nil {
280315
log.Println("An error occurred while validating authorization for an HTTP request!", userErr)
281316
httpError(w, "Internal Server Error!", http.StatusInternalServerError)
282317
return
283318
} else if !v2 && user == "" {
284319
httpError(w, "You are not authenticated to access this resource!", http.StatusUnauthorized)
285320
return
321+
} else if !v2 && !hasPerm {
322+
httpError(w, "You are not allowed to access this resource!", http.StatusForbidden)
323+
return
286324
}
287325
// Retrieve the token.
288326
token := auth.GetTokenFromRequest(r)
289327
if ticketExists {
290328
token = ticket.Token
291329
}
292330
// Get the server being accessed.
293-
id := r.PathValue("id")
294331
process, exists := connector.Processes.Load(id)
295332
// In case the server doesn't exist.
296333
if !exists && !v2 {
@@ -311,6 +348,9 @@ func consoleEndpoint(connector *Connector, w http.ResponseWriter, r *http.Reques
311348
} else if user == "" {
312349
errStr = "You are not authenticated to access this resource!"
313350
errNo = 4000 + http.StatusUnauthorized
351+
} else if !hasPerm {
352+
errStr = "You are not allowed to access this resource!"
353+
errNo = 4000 + http.StatusForbidden
314354
}
315355
if errStr != "" {
316356
c.WriteJSON(consoleError{"error", errStr})
@@ -327,7 +367,7 @@ func consoleEndpoint(connector *Connector, w http.ResponseWriter, r *http.Reques
327367
// If v2, send settings and set read deadline.
328368
if v2 {
329369
c.SetReadDeadline(time.Now().Add(timeout))
330-
c.WriteJSON(consoleSettings{"settings"})
370+
c.WriteJSON(consoleSettings{"settings", !canWrite})
331371
}
332372
// Use a channel to synchronise all writes to the WebSocket.
333373
writeChannel := make(chan interface{}, 8)
@@ -398,7 +438,8 @@ func consoleEndpoint(connector *Connector, w http.ResponseWriter, r *http.Reques
398438
var data map[string]string
399439
err := json.Unmarshal(message, &data)
400440
if err == nil {
401-
if data["type"] == "input" && data["data"] != "" {
441+
if data["type"] == "input" && data["data"] != "" && canWrite {
442+
// Simply drop inputs if the user cannot write.
402443
connector.Info("server.console.input", "ip", GetIP(r), "user", user, "server", id,
403444
"input", data["data"])
404445
process.SendCommand(data["data"])
@@ -413,7 +454,7 @@ func consoleEndpoint(connector *Connector, w http.ResponseWriter, r *http.Reques
413454
json, _ := json.Marshal(consoleError{"error", "Invalid message format"})
414455
writeChannel <- json
415456
}
416-
} else {
457+
} else if canWrite {
417458
connector.Info("server.console.input", "ip", GetIP(r), "user", user, "server", id,
418459
"input", string(message))
419460
process.SendCommand(string(message))

0 commit comments

Comments
 (0)