blob: 4dae1a93aab3c43bbd2a618da0f31c3891b08f9e [file] [log] [blame]
DTabidze0d802592024-03-19 17:42:45 +04001package main
2
3import (
4 "database/sql"
5 "embed"
DTabidzed7744a62024-03-20 14:09:15 +04006 "encoding/json"
DTabidze0d802592024-03-19 17:42:45 +04007 "flag"
8 "fmt"
9 "html/template"
10 "log"
11 "net/http"
DTabidze0d802592024-03-19 17:42:45 +040012
13 "github.com/ncruces/go-sqlite3"
14 _ "github.com/ncruces/go-sqlite3/driver"
15 _ "github.com/ncruces/go-sqlite3/embed"
DTabidzed7744a62024-03-20 14:09:15 +040016
17 "github.com/gorilla/mux"
DTabidze0d802592024-03-19 17:42:45 +040018)
19
20var port = flag.Int("port", 8080, "ort to listen on")
21var dbPath = flag.String("db-path", "memberships.db", "Path to SQLite file")
22
23//go:embed index.html
24var indexHTML string
25
26//go:embed group.html
27var groupHTML string
28
29//go:embed static
30var staticResources embed.FS
31
32type Store interface {
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +040033 // Initializes store with admin user and their groups.
34 Init(owner string, groups []string) error
DTabidze0d802592024-03-19 17:42:45 +040035 CreateGroup(owner string, group Group) error
36 AddChildGroup(parent, child string) error
37 GetGroupsOwnedBy(user string) ([]Group, error)
DTabidzed7744a62024-03-20 14:09:15 +040038 GetGroupsUserBelongsTo(user string) ([]Group, error)
DTabidze0d802592024-03-19 17:42:45 +040039 IsGroupOwner(user, group string) (bool, error)
40 AddGroupMember(user, group string) error
41 AddGroupOwner(user, group string) error
42 GetGroupOwners(group string) ([]string, error)
43 GetGroupMembers(group string) ([]string, error)
44 GetGroupDescription(group string) (string, error)
45 GetAvailableGroupsAsChild(group string) ([]string, error)
DTabidzec0b4d8f2024-03-22 17:25:10 +040046 GetAllTransitiveGroupsForUser(user string) ([]Group, error)
47 GetGroupsGroupBelongsTo(group string) ([]Group, error)
48 GetDirectChildrenGroups(group string) ([]Group, error)
49 GetAllTransitiveGroupsForGroup(group string) ([]Group, error)
DTabidze0d802592024-03-19 17:42:45 +040050}
51
52type Server struct {
53 store Store
54}
55
56type Group struct {
57 Name string
58 Description string
59}
60
61type SQLiteStore struct {
62 db *sql.DB
63}
64
DTabidzec0b4d8f2024-03-22 17:25:10 +040065func NewSQLiteStore(db *sql.DB) (*SQLiteStore, error) {
66 _, err := db.Exec(`
DTabidze0d802592024-03-19 17:42:45 +040067 CREATE TABLE IF NOT EXISTS groups (
68 name TEXT PRIMARY KEY,
69 description TEXT
70 );
71
72 CREATE TABLE IF NOT EXISTS owners (
73 username TEXT,
74 group_name TEXT,
75 FOREIGN KEY(group_name) REFERENCES groups(name)
76 );
77
78 CREATE TABLE IF NOT EXISTS group_to_group (
79 parent_group TEXT,
80 child_group TEXT,
81 FOREIGN KEY(parent_group) REFERENCES groups(name),
82 FOREIGN KEY(child_group) REFERENCES groups(name)
83 );
84
85 CREATE TABLE IF NOT EXISTS user_to_group (
86 username TEXT,
87 group_name TEXT,
88 FOREIGN KEY(group_name) REFERENCES groups(name)
89 );`)
90 if err != nil {
91 return nil, err
92 }
93 return &SQLiteStore{db: db}, nil
94}
95
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +040096func (s *SQLiteStore) Init(owner string, groups []string) error {
97 tx, err := s.db.Begin()
98 if err != nil {
99 return err
100 }
101 defer tx.Rollback()
102 row := tx.QueryRow("SELECT COUNT(*) FROM groups")
103 var count int
104 if err := row.Scan(&count); err != nil {
105 return err
106 }
107 if count != 0 {
108 return fmt.Errorf("store already initialised")
109 }
110 for _, g := range groups {
111 query := `INSERT INTO groups (name, description) VALUES (?, '')`
112 if _, err := tx.Exec(query, g); err != nil {
113 return err
114 }
115 query = `INSERT INTO owners (username, group_name) VALUES (?, ?)`
116 if _, err := tx.Exec(query, owner, g); err != nil {
117 return err
118 }
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400119 query = `INSERT INTO user_to_group (username, group_name) VALUES (?, ?)`
120 if _, err := tx.Exec(query, owner, g); err != nil {
121 return err
122 }
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400123 }
124 return tx.Commit()
125}
126
DTabidze0d802592024-03-19 17:42:45 +0400127func (s *SQLiteStore) queryGroups(query string, args ...interface{}) ([]Group, error) {
128 groups := make([]Group, 0)
129 rows, err := s.db.Query(query, args...)
130 if err != nil {
131 return nil, err
132 }
133 defer rows.Close()
134 for rows.Next() {
135 var group Group
136 if err := rows.Scan(&group.Name, &group.Description); err != nil {
137 return nil, err
138 }
139 groups = append(groups, group)
140 }
141 if err := rows.Err(); err != nil {
142 return nil, err
143 }
144 return groups, nil
145}
146
147func (s *SQLiteStore) GetGroupsOwnedBy(user string) ([]Group, error) {
148 query := `
149 SELECT groups.name, groups.description
150 FROM groups
151 JOIN owners ON groups.name = owners.group_name
152 WHERE owners.username = ?`
153 return s.queryGroups(query, user)
154}
155
DTabidzed7744a62024-03-20 14:09:15 +0400156func (s *SQLiteStore) GetGroupsUserBelongsTo(user string) ([]Group, error) {
DTabidze0d802592024-03-19 17:42:45 +0400157 query := `
158 SELECT groups.name, groups.description
159 FROM groups
160 JOIN user_to_group ON groups.name = user_to_group.group_name
161 WHERE user_to_group.username = ?`
162 return s.queryGroups(query, user)
163}
164
165func (s *SQLiteStore) CreateGroup(owner string, group Group) error {
166 tx, err := s.db.Begin()
167 if err != nil {
168 return err
169 }
170 defer tx.Rollback()
171 query := `INSERT INTO groups (name, description) VALUES (?, ?)`
172 if _, err := tx.Exec(query, group.Name, group.Description); err != nil {
173 sqliteErr, ok := err.(*sqlite3.Error)
174 if ok && sqliteErr.ExtendedCode() == 1555 {
175 return fmt.Errorf("Group with the name %s already exists", group.Name)
176 }
177 return err
178 }
179 query = `INSERT INTO owners (username, group_name) VALUES (?, ?)`
180 if _, err := tx.Exec(query, owner, group.Name); err != nil {
181 return err
182 }
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400183 return tx.Commit()
DTabidze0d802592024-03-19 17:42:45 +0400184}
185
186func (s *SQLiteStore) IsGroupOwner(user, group string) (bool, error) {
187 query := `
188 SELECT EXISTS (
189 SELECT 1
190 FROM owners
191 WHERE username = ? AND group_name = ?
192 )`
193 var exists bool
194 if err := s.db.QueryRow(query, user, group).Scan(&exists); err != nil {
195 return false, err
196 }
197 return exists, nil
198}
199
200func (s *SQLiteStore) userGroupPairExists(tx *sql.Tx, table, user, group string) (bool, error) {
201 query := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM %s WHERE username = ? AND group_name = ?)", table)
202 var exists bool
203 if err := tx.QueryRow(query, user, group).Scan(&exists); err != nil {
204 return false, err
205 }
206 return exists, nil
207}
208
209func (s *SQLiteStore) AddGroupMember(user, group string) error {
210 tx, err := s.db.Begin()
211 if err != nil {
212 return err
213 }
214 defer tx.Rollback()
215 existsInUserToGroup, err := s.userGroupPairExists(tx, "user_to_group", user, group)
216 if err != nil {
217 return err
218 }
219 if existsInUserToGroup {
220 return fmt.Errorf("%s is already a member of group %s", user, group)
221 }
222 if _, err := tx.Exec(`INSERT INTO user_to_group (username, group_name) VALUES (?, ?)`, user, group); err != nil {
223 return err
224 }
225 if err := tx.Commit(); err != nil {
226 return err
227 }
228 return nil
229}
230
231func (s *SQLiteStore) AddGroupOwner(user, group string) error {
232 tx, err := s.db.Begin()
233 if err != nil {
234 return err
235 }
236 defer tx.Rollback()
237 existsInOwners, err := s.userGroupPairExists(tx, "owners", user, group)
238 if err != nil {
239 return err
240 }
241 if existsInOwners {
242 return fmt.Errorf("%s is already an owner of group %s", user, group)
243 }
244 if _, err = tx.Exec(`INSERT INTO owners (username, group_name) VALUES (?, ?)`, user, group); err != nil {
245 return err
246 }
247 if err := tx.Commit(); err != nil {
248 return err
249 }
250 return nil
251}
252
253func (s *SQLiteStore) getUsersByGroup(table, group string) ([]string, error) {
254 query := fmt.Sprintf("SELECT username FROM %s WHERE group_name = ?", table)
255 rows, err := s.db.Query(query, group)
256 if err != nil {
257 return nil, err
258 }
259 defer rows.Close()
260 var users []string
261 for rows.Next() {
262 var username string
263 if err := rows.Scan(&username); err != nil {
264 return nil, err
265 }
266 users = append(users, username)
267 }
268 if err := rows.Err(); err != nil {
269 return nil, err
270 }
271 return users, nil
272}
273
274func (s *SQLiteStore) GetGroupOwners(group string) ([]string, error) {
275 return s.getUsersByGroup("owners", group)
276}
277
278func (s *SQLiteStore) GetGroupMembers(group string) ([]string, error) {
279 return s.getUsersByGroup("user_to_group", group)
280}
281
282func (s *SQLiteStore) GetGroupDescription(group string) (string, error) {
283 var description string
284 query := `SELECT description FROM groups WHERE name = ?`
285 if err := s.db.QueryRow(query, group).Scan(&description); err != nil {
286 return "", err
287 }
288 return description, nil
289}
290
291func (s *SQLiteStore) parentChildGroupPairExists(tx *sql.Tx, parent, child string) (bool, error) {
292 query := `SELECT EXISTS (SELECT 1 FROM group_to_group WHERE parent_group = ? AND child_group = ?)`
293 var exists bool
294 if err := tx.QueryRow(query, parent, child).Scan(&exists); err != nil {
295 return false, err
296 }
297 return exists, nil
298}
299
300func (s *SQLiteStore) AddChildGroup(parent, child string) error {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400301 parentGroups, err := s.GetAllTransitiveGroupsForGroup(parent)
302 if err != nil {
303 return err
304 }
305 for _, group := range parentGroups {
306 if group.Name == child {
307 return fmt.Errorf("circular reference detected: group %s is already a parent of group %s", child, parent)
308 }
309 }
DTabidze0d802592024-03-19 17:42:45 +0400310 tx, err := s.db.Begin()
311 if err != nil {
312 return err
313 }
314 defer tx.Rollback()
315 existsInGroupToGroup, err := s.parentChildGroupPairExists(tx, parent, child)
316 if err != nil {
317 return err
318 }
319 if existsInGroupToGroup {
320 return fmt.Errorf("child group name %s already exists in group %s", child, parent)
321 }
322 if _, err := tx.Exec(`INSERT INTO group_to_group (parent_group, child_group) VALUES (?, ?)`, parent, child); err != nil {
323 return err
324 }
325 if err := tx.Commit(); err != nil {
326 return err
327 }
328 return nil
329}
330
331func (s *SQLiteStore) GetAvailableGroupsAsChild(group string) ([]string, error) {
332 // TODO(dtabidze): Might have to add further logic to filter available groups as children.
333 query := `
334 SELECT name FROM groups
335 WHERE name != ? AND name NOT IN (
336 SELECT child_group FROM group_to_group WHERE parent_group = ?
337 )
338 `
339 rows, err := s.db.Query(query, group, group)
340 if err != nil {
341 return nil, err
342 }
343 defer rows.Close()
344 var availableGroups []string
345 for rows.Next() {
346 var groupName string
347 if err := rows.Scan(&groupName); err != nil {
348 return nil, err
349 }
350 availableGroups = append(availableGroups, groupName)
351 }
352 return availableGroups, nil
353}
354
DTabidzec0b4d8f2024-03-22 17:25:10 +0400355func (s *SQLiteStore) GetAllTransitiveGroupsForUser(user string) ([]Group, error) {
356 if groups, err := s.GetGroupsUserBelongsTo(user); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400357 return nil, err
DTabidzec0b4d8f2024-03-22 17:25:10 +0400358 } else {
359 visited := map[string]struct{}{}
360 return s.getAllParentGroupsRecursive(groups, visited)
DTabidzed7744a62024-03-20 14:09:15 +0400361 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400362}
363
364func (s *SQLiteStore) GetAllTransitiveGroupsForGroup(group string) ([]Group, error) {
365 if p, err := s.GetGroupsGroupBelongsTo(group); err != nil {
366 return nil, err
367 } else {
368 // Mark initial group as visited
369 visited := map[string]struct{}{
370 group: struct{}{},
371 }
372 return s.getAllParentGroupsRecursive(p, visited)
373 }
374}
375
376func (s *SQLiteStore) getAllParentGroupsRecursive(groups []Group, visited map[string]struct{}) ([]Group, error) {
377 var ret []Group
378 for _, g := range groups {
379 if _, ok := visited[g.Name]; ok {
380 continue
381 }
382 visited[g.Name] = struct{}{}
383 ret = append(ret, g)
384 if p, err := s.GetGroupsGroupBelongsTo(g.Name); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400385 return nil, err
DTabidzec0b4d8f2024-03-22 17:25:10 +0400386 } else if res, err := s.getAllParentGroupsRecursive(p, visited); err != nil {
387 return nil, err
388 } else {
389 ret = append(ret, res...)
DTabidzed7744a62024-03-20 14:09:15 +0400390 }
391 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400392 return ret, nil
DTabidzed7744a62024-03-20 14:09:15 +0400393}
394
DTabidzec0b4d8f2024-03-22 17:25:10 +0400395func (s *SQLiteStore) GetGroupsGroupBelongsTo(group string) ([]Group, error) {
396 query := `
397 SELECT groups.name, groups.description
398 FROM groups
399 JOIN group_to_group ON groups.name = group_to_group.parent_group
400 WHERE group_to_group.child_group = ?`
DTabidzed7744a62024-03-20 14:09:15 +0400401 rows, err := s.db.Query(query, group)
402 if err != nil {
403 return nil, err
404 }
405 defer rows.Close()
DTabidzec0b4d8f2024-03-22 17:25:10 +0400406 var parentGroups []Group
DTabidzed7744a62024-03-20 14:09:15 +0400407 for rows.Next() {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400408 var parentGroup Group
409 if err := rows.Scan(&parentGroup.Name, &parentGroup.Description); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400410 return nil, err
411 }
412 parentGroups = append(parentGroups, parentGroup)
413 }
414 if err := rows.Err(); err != nil {
415 return nil, err
416 }
417 return parentGroups, nil
418}
419
DTabidzec0b4d8f2024-03-22 17:25:10 +0400420func (s *SQLiteStore) GetDirectChildrenGroups(group string) ([]Group, error) {
421 query := `
422 SELECT groups.name, groups.description
423 FROM groups
424 JOIN group_to_group ON groups.name = group_to_group.child_group
425 WHERE group_to_group.parent_group = ?`
426 rows, err := s.db.Query(query, group)
427 if err != nil {
428 return nil, err
429 }
430 defer rows.Close()
431 var childrenGroups []Group
432 for rows.Next() {
433 var childGroup Group
434 if err := rows.Scan(&childGroup.Name, &childGroup.Description); err != nil {
435 return nil, err
436 }
437 childrenGroups = append(childrenGroups, childGroup)
438 }
439 if err := rows.Err(); err != nil {
440 return nil, err
441 }
442 return childrenGroups, nil
443}
444
DTabidze0d802592024-03-19 17:42:45 +0400445func getLoggedInUser(r *http.Request) (string, error) {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400446 if user := r.Header.Get("X-User"); user != "" {
447 return user, nil
448 } else {
449 return "", fmt.Errorf("unauthenticated")
450 }
DTabidze0d802592024-03-19 17:42:45 +0400451}
452
453type Status int
454
455const (
456 Owner Status = iota
457 Member
458)
459
460func convertStatus(status string) (Status, error) {
461 switch status {
462 case "Owner":
463 return Owner, nil
464 case "Member":
465 return Member, nil
466 default:
467 return Owner, fmt.Errorf("invalid status: %s", status)
468 }
469}
470
471func (s *Server) Start() {
DTabidzed7744a62024-03-20 14:09:15 +0400472 router := mux.NewRouter()
473 router.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticResources)))
474 router.HandleFunc("/group/{group-name}", s.groupHandler)
475 router.HandleFunc("/create-group", s.createGroupHandler)
476 router.HandleFunc("/add-user", s.addUserHandler)
477 router.HandleFunc("/add-child-group", s.addChildGroupHandler)
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400478 router.HandleFunc("/api/init", s.apiInitHandler)
DTabidzed7744a62024-03-20 14:09:15 +0400479 router.HandleFunc("/api/user/{username}", s.apiMemberOfHandler)
480 router.HandleFunc("/", s.homePageHandler)
481 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), router))
DTabidze0d802592024-03-19 17:42:45 +0400482}
483
484type GroupData struct {
485 Group Group
486 Membership string
487}
488
489func (s *Server) checkIsOwner(w http.ResponseWriter, user, group string) (bool, error) {
490 isOwner, err := s.store.IsGroupOwner(user, group)
491 if err != nil {
492 http.Error(w, err.Error(), http.StatusInternalServerError)
493 return false, err
494 }
495 if !isOwner {
496 http.Error(w, fmt.Sprintf("You are not the owner of the group %s", group), http.StatusUnauthorized)
497 return false, nil
498 }
499 return true, nil
500}
501
502func (s *Server) homePageHandler(w http.ResponseWriter, r *http.Request) {
503 loggedInUser, err := getLoggedInUser(r)
504 if err != nil {
505 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
506 return
507 }
508 ownerGroups, err := s.store.GetGroupsOwnedBy(loggedInUser)
509 if err != nil {
510 http.Error(w, err.Error(), http.StatusInternalServerError)
511 return
512 }
DTabidzed7744a62024-03-20 14:09:15 +0400513 membershipGroups, err := s.store.GetGroupsUserBelongsTo(loggedInUser)
DTabidze0d802592024-03-19 17:42:45 +0400514 if err != nil {
515 http.Error(w, err.Error(), http.StatusInternalServerError)
516 return
517 }
518 tmpl, err := template.New("index").Parse(indexHTML)
519 if err != nil {
520 http.Error(w, err.Error(), http.StatusInternalServerError)
521 return
522 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400523 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(loggedInUser)
524 if err != nil {
525 http.Error(w, err.Error(), http.StatusInternalServerError)
526 return
527 }
DTabidze0d802592024-03-19 17:42:45 +0400528 data := struct {
529 OwnerGroups []Group
530 MembershipGroups []Group
DTabidzec0b4d8f2024-03-22 17:25:10 +0400531 TransitiveGroups []Group
DTabidze0d802592024-03-19 17:42:45 +0400532 }{
533 OwnerGroups: ownerGroups,
534 MembershipGroups: membershipGroups,
DTabidzec0b4d8f2024-03-22 17:25:10 +0400535 TransitiveGroups: transitiveGroups,
DTabidze0d802592024-03-19 17:42:45 +0400536 }
537 w.Header().Set("Content-Type", "text/html")
538 if err := tmpl.Execute(w, data); err != nil {
539 http.Error(w, err.Error(), http.StatusInternalServerError)
540 return
541 }
542}
543
544func (s *Server) createGroupHandler(w http.ResponseWriter, r *http.Request) {
545 loggedInUser, err := getLoggedInUser(r)
546 if err != nil {
547 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
548 return
549 }
550 if r.Method != http.MethodPost {
551 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
552 return
553 }
554 if err := r.ParseForm(); err != nil {
555 http.Error(w, err.Error(), http.StatusInternalServerError)
556 return
557 }
558 var group Group
559 group.Name = r.PostFormValue("group-name")
560 group.Description = r.PostFormValue("description")
561 if err := s.store.CreateGroup(loggedInUser, group); err != nil {
562 http.Error(w, err.Error(), http.StatusInternalServerError)
563 return
564 }
565 http.Redirect(w, r, "/", http.StatusSeeOther)
566}
567
568func (s *Server) groupHandler(w http.ResponseWriter, r *http.Request) {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400569 _, err := getLoggedInUser(r)
570 if err != nil {
571 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
572 return
573 }
DTabidzed7744a62024-03-20 14:09:15 +0400574 vars := mux.Vars(r)
575 groupName := vars["group-name"]
DTabidze0d802592024-03-19 17:42:45 +0400576 tmpl, err := template.New("group").Parse(groupHTML)
577 if err != nil {
578 http.Error(w, err.Error(), http.StatusInternalServerError)
579 return
580 }
581 owners, err := s.store.GetGroupOwners(groupName)
582 if err != nil {
583 http.Error(w, err.Error(), http.StatusInternalServerError)
584 return
585 }
586 members, err := s.store.GetGroupMembers(groupName)
587 if err != nil {
588 http.Error(w, err.Error(), http.StatusInternalServerError)
589 return
590 }
591 description, err := s.store.GetGroupDescription(groupName)
592 if err != nil {
593 http.Error(w, err.Error(), http.StatusInternalServerError)
594 return
595 }
596 availableGroups, err := s.store.GetAvailableGroupsAsChild(groupName)
597 if err != nil {
598 http.Error(w, err.Error(), http.StatusInternalServerError)
599 return
600 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400601 transitiveGroups, err := s.store.GetAllTransitiveGroupsForGroup(groupName)
602 if err != nil {
603 http.Error(w, err.Error(), http.StatusInternalServerError)
604 return
605 }
606 childGroups, err := s.store.GetDirectChildrenGroups(groupName)
607 if err != nil {
608 http.Error(w, err.Error(), http.StatusInternalServerError)
609 return
610 }
DTabidze0d802592024-03-19 17:42:45 +0400611 data := struct {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400612 GroupName string
613 Description string
614 Owners []string
615 Members []string
616 AvailableGroups []string
617 TransitiveGroups []Group
618 ChildGroups []Group
DTabidze0d802592024-03-19 17:42:45 +0400619 }{
DTabidzec0b4d8f2024-03-22 17:25:10 +0400620 GroupName: groupName,
621 Description: description,
622 Owners: owners,
623 Members: members,
624 AvailableGroups: availableGroups,
625 TransitiveGroups: transitiveGroups,
626 ChildGroups: childGroups,
DTabidze0d802592024-03-19 17:42:45 +0400627 }
628 if err := tmpl.Execute(w, data); err != nil {
629 http.Error(w, err.Error(), http.StatusInternalServerError)
630 return
631 }
632}
633
634func (s *Server) addUserHandler(w http.ResponseWriter, r *http.Request) {
635 if r.Method != http.MethodPost {
636 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
637 return
638 }
639 loggedInUser, err := getLoggedInUser(r)
640 if err != nil {
641 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
642 return
643 }
644 groupName := r.FormValue("group")
645 username := r.FormValue("username")
646 status, err := convertStatus(r.FormValue("status"))
647 if err != nil {
648 http.Error(w, err.Error(), http.StatusBadRequest)
649 return
650 }
651 if _, err := s.checkIsOwner(w, loggedInUser, groupName); err != nil {
652 return
653 }
654 switch status {
655 case Owner:
656 err = s.store.AddGroupOwner(username, groupName)
657 case Member:
658 err = s.store.AddGroupMember(username, groupName)
659 default:
660 http.Error(w, "Invalid status", http.StatusBadRequest)
661 return
662 }
663 if err != nil {
664 http.Error(w, err.Error(), http.StatusInternalServerError)
665 return
666 }
667 http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
668}
669
670func (s *Server) addChildGroupHandler(w http.ResponseWriter, r *http.Request) {
671 // TODO(dtabidze): In future we might need to make one group OWNER of another and not just a member.
672 if r.Method != http.MethodPost {
673 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
674 return
675 }
676 loggedInUser, err := getLoggedInUser(r)
677 if err != nil {
678 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
679 return
680 }
681 parentGroup := r.FormValue("parent-group")
682 childGroup := r.FormValue("child-group")
683 if _, err := s.checkIsOwner(w, loggedInUser, parentGroup); err != nil {
684 return
685 }
686 if err := s.store.AddChildGroup(parentGroup, childGroup); err != nil {
687 http.Error(w, err.Error(), http.StatusInternalServerError)
688 return
689 }
690 http.Redirect(w, r, "/group/"+parentGroup, http.StatusSeeOther)
691}
692
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400693type initRequest struct {
694 Owner string `json:"owner"`
695 Groups []string `json:"groups"`
696}
697
698func (s *Server) apiInitHandler(w http.ResponseWriter, r *http.Request) {
699 var req initRequest
700 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
701 http.Error(w, err.Error(), http.StatusBadRequest)
702 return
703 }
704 if err := s.store.Init(req.Owner, req.Groups); err != nil {
705 http.Error(w, err.Error(), http.StatusInternalServerError)
706 return
707 }
708}
709
710type userInfo struct {
DTabidzed7744a62024-03-20 14:09:15 +0400711 MemberOf []string `json:"memberOf"`
712}
713
714func (s *Server) apiMemberOfHandler(w http.ResponseWriter, r *http.Request) {
715 vars := mux.Vars(r)
716 user, ok := vars["username"]
717 if !ok {
718 http.Error(w, "Username parameter is required", http.StatusBadRequest)
719 return
720 }
721 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(user)
722 if err != nil {
723 http.Error(w, err.Error(), http.StatusInternalServerError)
724 return
725 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400726 var groupNames []string
727 for _, group := range transitiveGroups {
728 groupNames = append(groupNames, group.Name)
729 }
DTabidzed7744a62024-03-20 14:09:15 +0400730 w.Header().Set("Content-Type", "application/json")
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400731 if err := json.NewEncoder(w).Encode(userInfo{groupNames}); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400732 http.Error(w, err.Error(), http.StatusInternalServerError)
733 return
734 }
735}
736
DTabidze0d802592024-03-19 17:42:45 +0400737func main() {
738 flag.Parse()
DTabidzec0b4d8f2024-03-22 17:25:10 +0400739 db, err := sql.Open("sqlite3", *dbPath)
DTabidze0d802592024-03-19 17:42:45 +0400740 if err != nil {
741 panic(err)
742 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400743 store, err := NewSQLiteStore(db)
744 if err != nil {
745 panic(err)
746 }
747 s := Server{store}
DTabidze0d802592024-03-19 17:42:45 +0400748 s.Start()
749}