blob: 20f08ef592c0f6dad84e03bbda7ed8973061db24 [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"
DTabidze4b44ff42024-04-02 03:16:26 +040012 "net/url"
DTabidze908bb852024-03-25 20:07:57 +040013 "regexp"
14 "strings"
DTabidze0d802592024-03-19 17:42:45 +040015
16 "github.com/ncruces/go-sqlite3"
17 _ "github.com/ncruces/go-sqlite3/driver"
18 _ "github.com/ncruces/go-sqlite3/embed"
DTabidzed7744a62024-03-20 14:09:15 +040019
20 "github.com/gorilla/mux"
DTabidze0d802592024-03-19 17:42:45 +040021)
22
Giorgi Lekveishvili329af572024-03-25 20:14:41 +040023var port = flag.Int("port", 8080, "Port to listen on")
24var apiPort = flag.Int("api-port", 8081, "Port to listen on for API requests")
DTabidze0d802592024-03-19 17:42:45 +040025var dbPath = flag.String("db-path", "memberships.db", "Path to SQLite file")
26
DTabidze4b44ff42024-04-02 03:16:26 +040027//go:embed memberships-tmpl/*
28var tmpls embed.FS
DTabidze0d802592024-03-19 17:42:45 +040029
30//go:embed static
31var staticResources embed.FS
32
33type Store interface {
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +040034 // Initializes store with admin user and their groups.
35 Init(owner string, groups []string) error
DTabidze0d802592024-03-19 17:42:45 +040036 CreateGroup(owner string, group Group) error
37 AddChildGroup(parent, child string) error
Davit Tabidzec0d2bf52024-04-03 15:39:33 +040038 AddOwnerGroup(owned_group, owner_group string) error
DTabidze908bb852024-03-25 20:07:57 +040039 DoesGroupExist(group string) (bool, error)
DTabidze0d802592024-03-19 17:42:45 +040040 GetGroupsOwnedBy(user string) ([]Group, error)
DTabidzed7744a62024-03-20 14:09:15 +040041 GetGroupsUserBelongsTo(user string) ([]Group, error)
DTabidze0d802592024-03-19 17:42:45 +040042 IsGroupOwner(user, group string) (bool, error)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +040043 IsMemberOfOwnerGroup(user, group string) (bool, error)
DTabidze0d802592024-03-19 17:42:45 +040044 AddGroupMember(user, group string) error
45 AddGroupOwner(user, group string) error
46 GetGroupOwners(group string) ([]string, error)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +040047 GetGroupOwnerGroups(group string) ([]Group, error)
DTabidze0d802592024-03-19 17:42:45 +040048 GetGroupMembers(group string) ([]string, error)
49 GetGroupDescription(group string) (string, error)
DTabidzec0b4d8f2024-03-22 17:25:10 +040050 GetAllTransitiveGroupsForUser(user string) ([]Group, error)
51 GetGroupsGroupBelongsTo(group string) ([]Group, error)
52 GetDirectChildrenGroups(group string) ([]Group, error)
53 GetAllTransitiveGroupsForGroup(group string) ([]Group, error)
DTabidze2b224bf2024-03-27 13:25:49 +040054 RemoveFromGroupToGroup(parent, child string) error
55 RemoveUserFromTable(username, groupName, tableName string) error
Davit Tabidzec0d2bf52024-04-03 15:39:33 +040056 GetAllGroups() ([]Group, error)
DTabidze0d802592024-03-19 17:42:45 +040057}
58
59type Server struct {
60 store Store
61}
62
63type Group struct {
64 Name string
65 Description string
66}
67
68type SQLiteStore struct {
69 db *sql.DB
70}
71
Davit Tabidzec0d2bf52024-04-03 15:39:33 +040072const (
73 ErrorUniqueConstraintViolation = 2067
74 ErrorConstraintPrimaryKeyViolation = 1555
75)
76
DTabidzec0b4d8f2024-03-22 17:25:10 +040077func NewSQLiteStore(db *sql.DB) (*SQLiteStore, error) {
78 _, err := db.Exec(`
Davit Tabidzec0d2bf52024-04-03 15:39:33 +040079 CREATE TABLE IF NOT EXISTS groups (
80 name TEXT PRIMARY KEY,
81 description TEXT
82 );
83 CREATE TABLE IF NOT EXISTS owners (
84 username TEXT,
85 group_name TEXT,
86 FOREIGN KEY(group_name) REFERENCES groups(name),
87 UNIQUE (username, group_name)
88 );
89 CREATE TABLE IF NOT EXISTS owner_groups (
90 owner_group TEXT,
91 owned_group TEXT,
92 FOREIGN KEY(owner_group) REFERENCES groups(name),
93 FOREIGN KEY(owned_group) REFERENCES groups(name),
94 UNIQUE (owner_group, owned_group)
95 );
96 CREATE TABLE IF NOT EXISTS group_to_group (
97 parent_group TEXT,
98 child_group TEXT,
99 FOREIGN KEY(parent_group) REFERENCES groups(name),
100 FOREIGN KEY(child_group) REFERENCES groups(name),
101 UNIQUE (parent_group, child_group)
102 );
103 CREATE TABLE IF NOT EXISTS user_to_group (
104 username TEXT,
105 group_name TEXT,
106 FOREIGN KEY(group_name) REFERENCES groups(name),
107 UNIQUE (username, group_name)
108 );`)
DTabidze0d802592024-03-19 17:42:45 +0400109 if err != nil {
110 return nil, err
111 }
112 return &SQLiteStore{db: db}, nil
113}
114
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400115func (s *SQLiteStore) Init(owner string, groups []string) error {
116 tx, err := s.db.Begin()
117 if err != nil {
118 return err
119 }
120 defer tx.Rollback()
121 row := tx.QueryRow("SELECT COUNT(*) FROM groups")
122 var count int
123 if err := row.Scan(&count); err != nil {
124 return err
125 }
126 if count != 0 {
127 return fmt.Errorf("store already initialised")
128 }
129 for _, g := range groups {
130 query := `INSERT INTO groups (name, description) VALUES (?, '')`
131 if _, err := tx.Exec(query, g); err != nil {
132 return err
133 }
134 query = `INSERT INTO owners (username, group_name) VALUES (?, ?)`
135 if _, err := tx.Exec(query, owner, g); err != nil {
136 return err
137 }
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400138 query = `INSERT INTO user_to_group (username, group_name) VALUES (?, ?)`
139 if _, err := tx.Exec(query, owner, g); err != nil {
140 return err
141 }
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400142 }
143 return tx.Commit()
144}
145
DTabidze0d802592024-03-19 17:42:45 +0400146func (s *SQLiteStore) queryGroups(query string, args ...interface{}) ([]Group, error) {
147 groups := make([]Group, 0)
148 rows, err := s.db.Query(query, args...)
149 if err != nil {
150 return nil, err
151 }
152 defer rows.Close()
153 for rows.Next() {
154 var group Group
155 if err := rows.Scan(&group.Name, &group.Description); err != nil {
156 return nil, err
157 }
158 groups = append(groups, group)
159 }
160 if err := rows.Err(); err != nil {
161 return nil, err
162 }
163 return groups, nil
164}
165
166func (s *SQLiteStore) GetGroupsOwnedBy(user string) ([]Group, error) {
167 query := `
168 SELECT groups.name, groups.description
169 FROM groups
170 JOIN owners ON groups.name = owners.group_name
171 WHERE owners.username = ?`
172 return s.queryGroups(query, user)
173}
174
DTabidzed7744a62024-03-20 14:09:15 +0400175func (s *SQLiteStore) GetGroupsUserBelongsTo(user string) ([]Group, error) {
DTabidze0d802592024-03-19 17:42:45 +0400176 query := `
177 SELECT groups.name, groups.description
178 FROM groups
179 JOIN user_to_group ON groups.name = user_to_group.group_name
180 WHERE user_to_group.username = ?`
181 return s.queryGroups(query, user)
182}
183
184func (s *SQLiteStore) CreateGroup(owner string, group Group) error {
185 tx, err := s.db.Begin()
186 if err != nil {
187 return err
188 }
189 defer tx.Rollback()
190 query := `INSERT INTO groups (name, description) VALUES (?, ?)`
191 if _, err := tx.Exec(query, group.Name, group.Description); err != nil {
192 sqliteErr, ok := err.(*sqlite3.Error)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400193 if ok && sqliteErr.ExtendedCode() == ErrorConstraintPrimaryKeyViolation {
DTabidze0d802592024-03-19 17:42:45 +0400194 return fmt.Errorf("Group with the name %s already exists", group.Name)
195 }
196 return err
197 }
198 query = `INSERT INTO owners (username, group_name) VALUES (?, ?)`
199 if _, err := tx.Exec(query, owner, group.Name); err != nil {
200 return err
201 }
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400202 return tx.Commit()
DTabidze0d802592024-03-19 17:42:45 +0400203}
204
205func (s *SQLiteStore) IsGroupOwner(user, group string) (bool, error) {
206 query := `
207 SELECT EXISTS (
208 SELECT 1
209 FROM owners
210 WHERE username = ? AND group_name = ?
211 )`
212 var exists bool
213 if err := s.db.QueryRow(query, user, group).Scan(&exists); err != nil {
214 return false, err
215 }
216 return exists, nil
217}
218
DTabidze0d802592024-03-19 17:42:45 +0400219func (s *SQLiteStore) AddGroupMember(user, group string) error {
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400220 _, err := s.db.Exec(`INSERT INTO user_to_group (username, group_name) VALUES (?, ?)`, user, group)
DTabidze0d802592024-03-19 17:42:45 +0400221 if err != nil {
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400222 sqliteErr, ok := err.(*sqlite3.Error)
223 if ok && sqliteErr.ExtendedCode() == ErrorUniqueConstraintViolation {
224 return fmt.Errorf("%s is already a member of group %s", user, group)
225 }
DTabidze0d802592024-03-19 17:42:45 +0400226 return err
227 }
228 return nil
229}
230
231func (s *SQLiteStore) AddGroupOwner(user, group string) error {
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400232 _, err := s.db.Exec(`INSERT INTO owners (username, group_name) VALUES (?, ?)`, user, group)
DTabidze0d802592024-03-19 17:42:45 +0400233 if err != nil {
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400234 sqliteErr, ok := err.(*sqlite3.Error)
235 if ok && sqliteErr.ExtendedCode() == ErrorUniqueConstraintViolation {
236 return fmt.Errorf("%s is already an owner of group %s", user, group)
237 }
DTabidze0d802592024-03-19 17:42:45 +0400238 return err
239 }
240 return nil
241}
242
243func (s *SQLiteStore) getUsersByGroup(table, group string) ([]string, error) {
244 query := fmt.Sprintf("SELECT username FROM %s WHERE group_name = ?", table)
245 rows, err := s.db.Query(query, group)
246 if err != nil {
247 return nil, err
248 }
249 defer rows.Close()
250 var users []string
251 for rows.Next() {
252 var username string
253 if err := rows.Scan(&username); err != nil {
254 return nil, err
255 }
256 users = append(users, username)
257 }
258 if err := rows.Err(); err != nil {
259 return nil, err
260 }
261 return users, nil
262}
263
264func (s *SQLiteStore) GetGroupOwners(group string) ([]string, error) {
265 return s.getUsersByGroup("owners", group)
266}
267
268func (s *SQLiteStore) GetGroupMembers(group string) ([]string, error) {
269 return s.getUsersByGroup("user_to_group", group)
270}
271
272func (s *SQLiteStore) GetGroupDescription(group string) (string, error) {
273 var description string
274 query := `SELECT description FROM groups WHERE name = ?`
275 if err := s.db.QueryRow(query, group).Scan(&description); err != nil {
276 return "", err
277 }
278 return description, nil
279}
280
DTabidze908bb852024-03-25 20:07:57 +0400281func (s *SQLiteStore) DoesGroupExist(group string) (bool, error) {
282 query := `SELECT EXISTS (SELECT 1 FROM groups WHERE name = ?)`
283 var exists bool
284 if err := s.db.QueryRow(query, group).Scan(&exists); err != nil {
285 return false, err
286 }
287 return exists, nil
288}
289
DTabidze0d802592024-03-19 17:42:45 +0400290func (s *SQLiteStore) AddChildGroup(parent, child string) error {
DTabidze908bb852024-03-25 20:07:57 +0400291 if parent == child {
292 return fmt.Errorf("parent and child groups can not have same name")
293 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400294 exists, err := s.DoesGroupExist(parent)
295 if err != nil {
296 return fmt.Errorf("error checking parent group existence: %v", err)
DTabidze908bb852024-03-25 20:07:57 +0400297 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400298 if !exists {
299 return fmt.Errorf("parent group with name %s does not exist", parent)
300 }
301 exists, err = s.DoesGroupExist(child)
302 if err != nil {
303 return fmt.Errorf("error checking child group existence: %v", err)
304 }
305 if !exists {
306 return fmt.Errorf("child group with name %s does not exist", child)
DTabidze908bb852024-03-25 20:07:57 +0400307 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400308 parentGroups, err := s.GetAllTransitiveGroupsForGroup(parent)
309 if err != nil {
310 return err
311 }
312 for _, group := range parentGroups {
313 if group.Name == child {
314 return fmt.Errorf("circular reference detected: group %s is already a parent of group %s", child, parent)
315 }
316 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400317 _, err = s.db.Exec(`INSERT INTO group_to_group (parent_group, child_group) VALUES (?, ?)`, parent, child)
DTabidze0d802592024-03-19 17:42:45 +0400318 if err != nil {
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400319 sqliteErr, ok := err.(*sqlite3.Error)
320 if ok && sqliteErr.ExtendedCode() == ErrorUniqueConstraintViolation {
321 return fmt.Errorf("child group name %s already exists in group %s", child, parent)
322 }
DTabidze0d802592024-03-19 17:42:45 +0400323 return err
324 }
325 return nil
326}
327
DTabidzec0b4d8f2024-03-22 17:25:10 +0400328func (s *SQLiteStore) GetAllTransitiveGroupsForUser(user string) ([]Group, error) {
329 if groups, err := s.GetGroupsUserBelongsTo(user); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400330 return nil, err
DTabidzec0b4d8f2024-03-22 17:25:10 +0400331 } else {
332 visited := map[string]struct{}{}
333 return s.getAllParentGroupsRecursive(groups, visited)
DTabidzed7744a62024-03-20 14:09:15 +0400334 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400335}
336
337func (s *SQLiteStore) GetAllTransitiveGroupsForGroup(group string) ([]Group, error) {
338 if p, err := s.GetGroupsGroupBelongsTo(group); err != nil {
339 return nil, err
340 } else {
341 // Mark initial group as visited
342 visited := map[string]struct{}{
343 group: struct{}{},
344 }
345 return s.getAllParentGroupsRecursive(p, visited)
346 }
347}
348
349func (s *SQLiteStore) getAllParentGroupsRecursive(groups []Group, visited map[string]struct{}) ([]Group, error) {
350 var ret []Group
351 for _, g := range groups {
352 if _, ok := visited[g.Name]; ok {
353 continue
354 }
355 visited[g.Name] = struct{}{}
356 ret = append(ret, g)
357 if p, err := s.GetGroupsGroupBelongsTo(g.Name); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400358 return nil, err
DTabidzec0b4d8f2024-03-22 17:25:10 +0400359 } else if res, err := s.getAllParentGroupsRecursive(p, visited); err != nil {
360 return nil, err
361 } else {
362 ret = append(ret, res...)
DTabidzed7744a62024-03-20 14:09:15 +0400363 }
364 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400365 return ret, nil
DTabidzed7744a62024-03-20 14:09:15 +0400366}
367
DTabidzec0b4d8f2024-03-22 17:25:10 +0400368func (s *SQLiteStore) GetGroupsGroupBelongsTo(group string) ([]Group, error) {
369 query := `
370 SELECT groups.name, groups.description
371 FROM groups
372 JOIN group_to_group ON groups.name = group_to_group.parent_group
373 WHERE group_to_group.child_group = ?`
DTabidzed7744a62024-03-20 14:09:15 +0400374 rows, err := s.db.Query(query, group)
375 if err != nil {
376 return nil, err
377 }
378 defer rows.Close()
DTabidzec0b4d8f2024-03-22 17:25:10 +0400379 var parentGroups []Group
DTabidzed7744a62024-03-20 14:09:15 +0400380 for rows.Next() {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400381 var parentGroup Group
382 if err := rows.Scan(&parentGroup.Name, &parentGroup.Description); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400383 return nil, err
384 }
385 parentGroups = append(parentGroups, parentGroup)
386 }
387 if err := rows.Err(); err != nil {
388 return nil, err
389 }
390 return parentGroups, nil
391}
392
DTabidzec0b4d8f2024-03-22 17:25:10 +0400393func (s *SQLiteStore) GetDirectChildrenGroups(group string) ([]Group, error) {
394 query := `
395 SELECT groups.name, groups.description
396 FROM groups
397 JOIN group_to_group ON groups.name = group_to_group.child_group
398 WHERE group_to_group.parent_group = ?`
399 rows, err := s.db.Query(query, group)
400 if err != nil {
401 return nil, err
402 }
403 defer rows.Close()
404 var childrenGroups []Group
405 for rows.Next() {
406 var childGroup Group
407 if err := rows.Scan(&childGroup.Name, &childGroup.Description); err != nil {
408 return nil, err
409 }
410 childrenGroups = append(childrenGroups, childGroup)
411 }
412 if err := rows.Err(); err != nil {
413 return nil, err
414 }
415 return childrenGroups, nil
416}
417
DTabidze2b224bf2024-03-27 13:25:49 +0400418func (s *SQLiteStore) RemoveFromGroupToGroup(parent, child string) error {
419 query := `DELETE FROM group_to_group WHERE parent_group = ? AND child_group = ?`
420 rowDeleted, err := s.db.Exec(query, parent, child)
421 if err != nil {
422 return err
423 }
424 rowDeletedNumber, err := rowDeleted.RowsAffected()
425 if err != nil {
426 return err
427 }
428 if rowDeletedNumber == 0 {
429 return fmt.Errorf("pair of parent '%s' and child '%s' groups not found", parent, child)
430 }
431 return nil
432}
433
434func (s *SQLiteStore) RemoveUserFromTable(username, groupName, tableName string) error {
435 if tableName == "owners" {
436 owners, err := s.GetGroupOwners(groupName)
437 if err != nil {
438 return err
439 }
440 if len(owners) == 1 {
441 return fmt.Errorf("cannot remove the last owner of the group")
442 }
443 }
444 query := fmt.Sprintf("DELETE FROM %s WHERE username = ? AND group_name = ?", tableName)
445 rowDeleted, err := s.db.Exec(query, username, groupName)
446 if err != nil {
447 return err
448 }
449 rowDeletedNumber, err := rowDeleted.RowsAffected()
450 if err != nil {
451 return err
452 }
453 if rowDeletedNumber == 0 {
454 return fmt.Errorf("pair of group '%s' and user '%s' not found", groupName, username)
455 }
456 return nil
457}
458
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400459func (s *SQLiteStore) AddOwnerGroup(owner_group, owned_group string) error {
460 if owned_group == owner_group {
461 return fmt.Errorf("group can not own itself")
462 }
463 exists, err := s.DoesGroupExist(owned_group)
464 if err != nil {
465 return fmt.Errorf("error checking owned group existence: %v", err)
466 }
467 if !exists {
468 return fmt.Errorf("owned group with name %s does not exist", owned_group)
469 }
470 exists, err = s.DoesGroupExist(owner_group)
471 if err != nil {
472 return fmt.Errorf("error checking owner group existence: %v", err)
473 }
474 if !exists {
475 return fmt.Errorf("owner group with name %s does not exist", owner_group)
476 }
477 _, err = s.db.Exec(`INSERT INTO owner_groups (owner_group, owned_group) VALUES (?, ?)`, owner_group, owned_group)
478 if err != nil {
479 sqliteErr, ok := err.(*sqlite3.Error)
480 if ok && sqliteErr.ExtendedCode() == ErrorUniqueConstraintViolation {
481 return fmt.Errorf("group named %s is already owner of a group %s", owner_group, owned_group)
482 }
483 return err
484 }
485 return nil
486}
487
488func (s *SQLiteStore) GetGroupOwnerGroups(group string) ([]Group, error) {
489 query := `
490 SELECT groups.name, groups.description
491 FROM groups
492 JOIN owner_groups ON groups.name = owner_groups.owner_group
493 WHERE owner_groups.owned_group = ?`
494 return s.queryGroups(query, group)
495}
496
497func (s *SQLiteStore) IsMemberOfOwnerGroup(user, group string) (bool, error) {
498 query := `
499 SELECT EXISTS (
500 SELECT 1 FROM owner_groups
501 INNER JOIN user_to_group ON owner_groups.owner_group = user_to_group.group_name
502 WHERE owner_groups.owned_group = ? AND user_to_group.username = ?)`
503 var exists bool
504 err := s.db.QueryRow(query, group, user).Scan(&exists)
505 if err != nil {
506 return false, err
507 }
508 return exists, nil
509}
510
511func (s *SQLiteStore) GetAllGroups() ([]Group, error) {
512 query := `SELECT name, description FROM groups`
513 return s.queryGroups(query)
514}
515
DTabidze0d802592024-03-19 17:42:45 +0400516func getLoggedInUser(r *http.Request) (string, error) {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400517 if user := r.Header.Get("X-User"); user != "" {
518 return user, nil
519 } else {
520 return "", fmt.Errorf("unauthenticated")
521 }
DTabidze0d802592024-03-19 17:42:45 +0400522}
523
524type Status int
525
526const (
527 Owner Status = iota
528 Member
529)
530
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400531func (s *Server) Start() error {
532 e := make(chan error)
533 go func() {
534 r := mux.NewRouter()
535 r.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticResources)))
DTabidze078385f2024-03-27 14:49:05 +0400536 r.HandleFunc("/group/{group-name}/add-user/", s.addUserToGroupHandler)
537 r.HandleFunc("/group/{parent-group}/add-child-group", s.addChildGroupHandler)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400538 r.HandleFunc("/group/{owned-group}/add-owner-group", s.addOwnerGroupHandler)
DTabidze078385f2024-03-27 14:49:05 +0400539 r.HandleFunc("/group/{parent-group}/remove-child-group/{child-group}", s.removeChildGroupHandler)
540 r.HandleFunc("/group/{group-name}/remove-owner/{username}", s.removeOwnerFromGroupHandler)
541 r.HandleFunc("/group/{group-name}/remove-member/{username}", s.removeMemberFromGroupHandler)
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400542 r.HandleFunc("/group/{group-name}", s.groupHandler)
DTabidze5d735e32024-03-26 16:01:06 +0400543 r.HandleFunc("/user/{username}", s.userHandler)
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400544 r.HandleFunc("/create-group", s.createGroupHandler)
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400545 r.HandleFunc("/", s.homePageHandler)
546 e <- http.ListenAndServe(fmt.Sprintf(":%d", *port), r)
547 }()
548 go func() {
549 r := mux.NewRouter()
550 r.HandleFunc("/api/init", s.apiInitHandler)
551 r.HandleFunc("/api/user/{username}", s.apiMemberOfHandler)
552 e <- http.ListenAndServe(fmt.Sprintf(":%d", *apiPort), r)
553 }()
554 return <-e
DTabidze0d802592024-03-19 17:42:45 +0400555}
556
557type GroupData struct {
558 Group Group
559 Membership string
560}
561
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400562func (s *Server) checkIsOwner(w http.ResponseWriter, user, group string) error {
DTabidze0d802592024-03-19 17:42:45 +0400563 isOwner, err := s.store.IsGroupOwner(user, group)
564 if err != nil {
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400565 return err
DTabidze0d802592024-03-19 17:42:45 +0400566 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400567 if isOwner {
568 return nil
DTabidze0d802592024-03-19 17:42:45 +0400569 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400570 // TODO(dtabidze): right now this only checks if user is member of just one lvl upper group. should add transitive group check.
571 isMemberOfOwnerGroup, err := s.store.IsMemberOfOwnerGroup(user, group)
572 if err != nil {
573 return err
574 }
575 if !isMemberOfOwnerGroup {
576 return fmt.Errorf("you are not the owner or a member of any owner group of the group %s", group)
577 }
578 return nil
DTabidze0d802592024-03-19 17:42:45 +0400579}
580
DTabidze4b44ff42024-04-02 03:16:26 +0400581type templates struct {
582 group *template.Template
583 user *template.Template
584}
585
586func parseTemplates(fs embed.FS) (templates, error) {
587 base, err := template.ParseFS(fs, "memberships-tmpl/base.html")
588 if err != nil {
589 return templates{}, err
590 }
591 parse := func(path string) (*template.Template, error) {
592 if b, err := base.Clone(); err != nil {
593 return nil, err
594 } else {
595 return b.ParseFS(fs, path)
596 }
597 }
598 user, err := parse("memberships-tmpl/user.html")
599 if err != nil {
600 return templates{}, err
601 }
602 group, err := parse("memberships-tmpl/group.html")
603 if err != nil {
604 return templates{}, err
605 }
606 return templates{group, user}, nil
607}
608
DTabidze0d802592024-03-19 17:42:45 +0400609func (s *Server) homePageHandler(w http.ResponseWriter, r *http.Request) {
610 loggedInUser, err := getLoggedInUser(r)
611 if err != nil {
612 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
613 return
614 }
DTabidze5d735e32024-03-26 16:01:06 +0400615 http.Redirect(w, r, "/user/"+loggedInUser, http.StatusSeeOther)
616}
617
618func (s *Server) userHandler(w http.ResponseWriter, r *http.Request) {
619 loggedInUser, err := getLoggedInUser(r)
620 if err != nil {
621 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
622 return
623 }
DTabidze4b44ff42024-04-02 03:16:26 +0400624 errorMsg := r.URL.Query().Get("errorMessage")
DTabidze5d735e32024-03-26 16:01:06 +0400625 vars := mux.Vars(r)
626 user := strings.ToLower(vars["username"])
627 // TODO(dtabidze): should check if username exists or not.
628 loggedInUserPage := loggedInUser == user
629 ownerGroups, err := s.store.GetGroupsOwnedBy(user)
DTabidze0d802592024-03-19 17:42:45 +0400630 if err != nil {
631 http.Error(w, err.Error(), http.StatusInternalServerError)
632 return
633 }
DTabidze5d735e32024-03-26 16:01:06 +0400634 membershipGroups, err := s.store.GetGroupsUserBelongsTo(user)
DTabidze0d802592024-03-19 17:42:45 +0400635 if err != nil {
636 http.Error(w, err.Error(), http.StatusInternalServerError)
637 return
638 }
DTabidze5d735e32024-03-26 16:01:06 +0400639 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(user)
DTabidzec0b4d8f2024-03-22 17:25:10 +0400640 if err != nil {
641 http.Error(w, err.Error(), http.StatusInternalServerError)
642 return
643 }
DTabidze0d802592024-03-19 17:42:45 +0400644 data := struct {
645 OwnerGroups []Group
646 MembershipGroups []Group
DTabidzec0b4d8f2024-03-22 17:25:10 +0400647 TransitiveGroups []Group
DTabidze5d735e32024-03-26 16:01:06 +0400648 LoggedInUserPage bool
649 CurrentUser string
DTabidze4b44ff42024-04-02 03:16:26 +0400650 ErrorMessage string
DTabidze0d802592024-03-19 17:42:45 +0400651 }{
652 OwnerGroups: ownerGroups,
653 MembershipGroups: membershipGroups,
DTabidzec0b4d8f2024-03-22 17:25:10 +0400654 TransitiveGroups: transitiveGroups,
DTabidze5d735e32024-03-26 16:01:06 +0400655 LoggedInUserPage: loggedInUserPage,
656 CurrentUser: user,
DTabidze4b44ff42024-04-02 03:16:26 +0400657 ErrorMessage: errorMsg,
DTabidze0d802592024-03-19 17:42:45 +0400658 }
DTabidze4b44ff42024-04-02 03:16:26 +0400659 templates, err := parseTemplates(tmpls)
660 if err != nil {
661 http.Error(w, err.Error(), http.StatusInternalServerError)
662 return
663 }
664 if err := templates.user.Execute(w, data); err != nil {
DTabidze0d802592024-03-19 17:42:45 +0400665 http.Error(w, err.Error(), http.StatusInternalServerError)
666 return
667 }
668}
669
670func (s *Server) createGroupHandler(w http.ResponseWriter, r *http.Request) {
671 loggedInUser, err := getLoggedInUser(r)
672 if err != nil {
673 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
674 return
675 }
676 if r.Method != http.MethodPost {
677 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
678 return
679 }
680 if err := r.ParseForm(); err != nil {
681 http.Error(w, err.Error(), http.StatusInternalServerError)
682 return
683 }
684 var group Group
685 group.Name = r.PostFormValue("group-name")
DTabidze908bb852024-03-25 20:07:57 +0400686 if err := isValidGroupName(group.Name); err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400687 // http.Error(w, err.Error(), http.StatusBadRequest)
688 redirectURL := fmt.Sprintf("/user/%s?errorMessage=%s", loggedInUser, url.QueryEscape(err.Error()))
689 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze908bb852024-03-25 20:07:57 +0400690 return
691 }
DTabidze0d802592024-03-19 17:42:45 +0400692 group.Description = r.PostFormValue("description")
693 if err := s.store.CreateGroup(loggedInUser, group); err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400694 // http.Error(w, err.Error(), http.StatusInternalServerError)
695 redirectURL := fmt.Sprintf("/user/%s?errorMessage=%s", loggedInUser, url.QueryEscape(err.Error()))
696 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze0d802592024-03-19 17:42:45 +0400697 return
698 }
699 http.Redirect(w, r, "/", http.StatusSeeOther)
700}
701
702func (s *Server) groupHandler(w http.ResponseWriter, r *http.Request) {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400703 _, err := getLoggedInUser(r)
704 if err != nil {
705 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
706 return
707 }
DTabidze4b44ff42024-04-02 03:16:26 +0400708 errorMsg := r.URL.Query().Get("errorMessage")
DTabidzed7744a62024-03-20 14:09:15 +0400709 vars := mux.Vars(r)
710 groupName := vars["group-name"]
DTabidze908bb852024-03-25 20:07:57 +0400711 exists, err := s.store.DoesGroupExist(groupName)
712 if err != nil {
713 http.Error(w, err.Error(), http.StatusInternalServerError)
714 return
715 }
716 if !exists {
DTabidze4b44ff42024-04-02 03:16:26 +0400717 errorMsg = fmt.Sprintf("group with the name '%s' not found", groupName)
DTabidze908bb852024-03-25 20:07:57 +0400718 http.Error(w, errorMsg, http.StatusNotFound)
719 return
720 }
DTabidze0d802592024-03-19 17:42:45 +0400721 if err != nil {
722 http.Error(w, err.Error(), http.StatusInternalServerError)
723 return
724 }
725 owners, err := s.store.GetGroupOwners(groupName)
726 if err != nil {
727 http.Error(w, err.Error(), http.StatusInternalServerError)
728 return
729 }
730 members, err := s.store.GetGroupMembers(groupName)
731 if err != nil {
732 http.Error(w, err.Error(), http.StatusInternalServerError)
733 return
734 }
735 description, err := s.store.GetGroupDescription(groupName)
736 if err != nil {
737 http.Error(w, err.Error(), http.StatusInternalServerError)
738 return
739 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400740 allGroups, err := s.store.GetAllGroups()
DTabidze0d802592024-03-19 17:42:45 +0400741 if err != nil {
742 http.Error(w, err.Error(), http.StatusInternalServerError)
743 return
744 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400745 transitiveGroups, err := s.store.GetAllTransitiveGroupsForGroup(groupName)
746 if err != nil {
747 http.Error(w, err.Error(), http.StatusInternalServerError)
748 return
749 }
750 childGroups, err := s.store.GetDirectChildrenGroups(groupName)
751 if err != nil {
752 http.Error(w, err.Error(), http.StatusInternalServerError)
753 return
754 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400755 ownerGroups, err := s.store.GetGroupOwnerGroups(groupName)
756 if err != nil {
757 http.Error(w, err.Error(), http.StatusInternalServerError)
758 return
759 }
DTabidze0d802592024-03-19 17:42:45 +0400760 data := struct {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400761 GroupName string
762 Description string
763 Owners []string
764 Members []string
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400765 AllGroups []Group
DTabidzec0b4d8f2024-03-22 17:25:10 +0400766 TransitiveGroups []Group
767 ChildGroups []Group
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400768 OwnerGroups []Group
DTabidze4b44ff42024-04-02 03:16:26 +0400769 ErrorMessage string
DTabidze0d802592024-03-19 17:42:45 +0400770 }{
DTabidzec0b4d8f2024-03-22 17:25:10 +0400771 GroupName: groupName,
772 Description: description,
773 Owners: owners,
774 Members: members,
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400775 AllGroups: allGroups,
DTabidzec0b4d8f2024-03-22 17:25:10 +0400776 TransitiveGroups: transitiveGroups,
777 ChildGroups: childGroups,
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400778 OwnerGroups: ownerGroups,
DTabidze4b44ff42024-04-02 03:16:26 +0400779 ErrorMessage: errorMsg,
DTabidze0d802592024-03-19 17:42:45 +0400780 }
DTabidze4b44ff42024-04-02 03:16:26 +0400781 templates, err := parseTemplates(tmpls)
782 if err != nil {
783 http.Error(w, err.Error(), http.StatusInternalServerError)
784 return
785 }
786 if err := templates.group.Execute(w, data); err != nil {
DTabidze0d802592024-03-19 17:42:45 +0400787 http.Error(w, err.Error(), http.StatusInternalServerError)
788 return
789 }
790}
791
DTabidze2b224bf2024-03-27 13:25:49 +0400792func (s *Server) removeChildGroupHandler(w http.ResponseWriter, r *http.Request) {
793 loggedInUser, err := getLoggedInUser(r)
794 if err != nil {
795 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
796 return
797 }
798 if r.Method == http.MethodPost {
799 vars := mux.Vars(r)
800 parentGroup := vars["parent-group"]
801 childGroup := vars["child-group"]
802 if err := isValidGroupName(parentGroup); err != nil {
803 http.Error(w, err.Error(), http.StatusBadRequest)
804 return
805 }
806 if err := isValidGroupName(childGroup); err != nil {
807 http.Error(w, err.Error(), http.StatusBadRequest)
808 return
809 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400810 if err := s.checkIsOwner(w, loggedInUser, parentGroup); err != nil {
811 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
812 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze2b224bf2024-03-27 13:25:49 +0400813 return
814 }
815 err := s.store.RemoveFromGroupToGroup(parentGroup, childGroup)
816 if err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400817 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
818 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze2b224bf2024-03-27 13:25:49 +0400819 return
820 }
821 http.Redirect(w, r, "/group/"+parentGroup, http.StatusSeeOther)
822 }
823}
824
DTabidze078385f2024-03-27 14:49:05 +0400825func (s *Server) removeOwnerFromGroupHandler(w http.ResponseWriter, r *http.Request) {
DTabidze2b224bf2024-03-27 13:25:49 +0400826 loggedInUser, err := getLoggedInUser(r)
827 if err != nil {
828 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
829 return
830 }
831 if r.Method == http.MethodPost {
832 vars := mux.Vars(r)
833 username := vars["username"]
834 groupName := vars["group-name"]
DTabidze078385f2024-03-27 14:49:05 +0400835 tableName := "owners"
DTabidze2b224bf2024-03-27 13:25:49 +0400836 if err := isValidGroupName(groupName); err != nil {
837 http.Error(w, err.Error(), http.StatusBadRequest)
838 return
839 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400840 if err := s.checkIsOwner(w, loggedInUser, groupName); err != nil {
841 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
842 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze2b224bf2024-03-27 13:25:49 +0400843 return
844 }
845 err := s.store.RemoveUserFromTable(username, groupName, tableName)
846 if err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400847 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
848 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze2b224bf2024-03-27 13:25:49 +0400849 return
850 }
851 http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
852 }
853}
854
DTabidze078385f2024-03-27 14:49:05 +0400855func (s *Server) removeMemberFromGroupHandler(w http.ResponseWriter, r *http.Request) {
856 loggedInUser, err := getLoggedInUser(r)
857 if err != nil {
858 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
859 return
860 }
861 if r.Method == http.MethodPost {
862 vars := mux.Vars(r)
863 username := vars["username"]
864 groupName := vars["group-name"]
865 tableName := "user_to_group"
866 if err := isValidGroupName(groupName); err != nil {
867 http.Error(w, err.Error(), http.StatusBadRequest)
868 return
869 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400870 if err := s.checkIsOwner(w, loggedInUser, groupName); err != nil {
871 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
872 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze078385f2024-03-27 14:49:05 +0400873 return
874 }
875 err := s.store.RemoveUserFromTable(username, groupName, tableName)
876 if err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400877 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
878 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze078385f2024-03-27 14:49:05 +0400879 return
880 }
881 http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
882 }
883}
884
885func (s *Server) addUserToGroupHandler(w http.ResponseWriter, r *http.Request) {
DTabidze0d802592024-03-19 17:42:45 +0400886 if r.Method != http.MethodPost {
887 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
888 return
889 }
890 loggedInUser, err := getLoggedInUser(r)
891 if err != nil {
892 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
893 return
894 }
DTabidze078385f2024-03-27 14:49:05 +0400895 vars := mux.Vars(r)
896 groupName := vars["group-name"]
DTabidze908bb852024-03-25 20:07:57 +0400897 if err := isValidGroupName(groupName); err != nil {
898 http.Error(w, err.Error(), http.StatusBadRequest)
899 return
900 }
901 username := strings.ToLower(r.FormValue("username"))
902 if username == "" {
903 http.Error(w, "Username parameter is required", http.StatusBadRequest)
904 return
905 }
DTabidze0d802592024-03-19 17:42:45 +0400906 status, err := convertStatus(r.FormValue("status"))
907 if err != nil {
908 http.Error(w, err.Error(), http.StatusBadRequest)
909 return
910 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400911 if err := s.checkIsOwner(w, loggedInUser, groupName); err != nil {
912 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
913 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze0d802592024-03-19 17:42:45 +0400914 return
915 }
916 switch status {
917 case Owner:
918 err = s.store.AddGroupOwner(username, groupName)
919 case Member:
920 err = s.store.AddGroupMember(username, groupName)
921 default:
922 http.Error(w, "Invalid status", http.StatusBadRequest)
923 return
924 }
925 if err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400926 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
927 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze0d802592024-03-19 17:42:45 +0400928 return
929 }
930 http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
931}
932
933func (s *Server) addChildGroupHandler(w http.ResponseWriter, r *http.Request) {
934 // TODO(dtabidze): In future we might need to make one group OWNER of another and not just a member.
935 if r.Method != http.MethodPost {
936 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
937 return
938 }
939 loggedInUser, err := getLoggedInUser(r)
940 if err != nil {
941 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
942 return
943 }
DTabidze078385f2024-03-27 14:49:05 +0400944 vars := mux.Vars(r)
945 parentGroup := vars["parent-group"]
DTabidze908bb852024-03-25 20:07:57 +0400946 if err := isValidGroupName(parentGroup); err != nil {
947 http.Error(w, err.Error(), http.StatusBadRequest)
948 return
949 }
DTabidze0d802592024-03-19 17:42:45 +0400950 childGroup := r.FormValue("child-group")
DTabidze908bb852024-03-25 20:07:57 +0400951 if err := isValidGroupName(childGroup); err != nil {
952 http.Error(w, err.Error(), http.StatusBadRequest)
953 return
954 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400955 if err := s.checkIsOwner(w, loggedInUser, parentGroup); err != nil {
956 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
957 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze0d802592024-03-19 17:42:45 +0400958 return
959 }
960 if err := s.store.AddChildGroup(parentGroup, childGroup); err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400961 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
962 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze0d802592024-03-19 17:42:45 +0400963 return
964 }
965 http.Redirect(w, r, "/group/"+parentGroup, http.StatusSeeOther)
966}
967
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400968func (s *Server) addOwnerGroupHandler(w http.ResponseWriter, r *http.Request) {
969 if r.Method != http.MethodPost {
970 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
971 return
972 }
973 loggedInUser, err := getLoggedInUser(r)
974 if err != nil {
975 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
976 return
977 }
978 vars := mux.Vars(r)
979 ownedGroup := vars["owned-group"]
980 if err := isValidGroupName(ownedGroup); err != nil {
981 http.Error(w, err.Error(), http.StatusBadRequest)
982 return
983 }
984 ownerGroup := r.FormValue("owner-group")
985 if err := isValidGroupName(ownerGroup); err != nil {
986 http.Error(w, err.Error(), http.StatusBadRequest)
987 return
988 }
989 if err := s.checkIsOwner(w, loggedInUser, ownedGroup); err != nil {
990 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", ownedGroup, url.QueryEscape(err.Error()))
991 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
992 return
993 }
994 if err := s.store.AddOwnerGroup(ownerGroup, ownedGroup); err != nil {
995 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", ownedGroup, url.QueryEscape(err.Error()))
996 http.Redirect(w, r, redirectURL, http.StatusFound)
997 return
998 }
999 http.Redirect(w, r, "/group/"+ownedGroup, http.StatusSeeOther)
1000}
1001
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +04001002type initRequest struct {
1003 Owner string `json:"owner"`
1004 Groups []string `json:"groups"`
1005}
1006
1007func (s *Server) apiInitHandler(w http.ResponseWriter, r *http.Request) {
1008 var req initRequest
1009 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
1010 http.Error(w, err.Error(), http.StatusBadRequest)
1011 return
1012 }
1013 if err := s.store.Init(req.Owner, req.Groups); err != nil {
1014 http.Error(w, err.Error(), http.StatusInternalServerError)
1015 return
1016 }
1017}
1018
1019type userInfo struct {
DTabidzed7744a62024-03-20 14:09:15 +04001020 MemberOf []string `json:"memberOf"`
1021}
1022
1023func (s *Server) apiMemberOfHandler(w http.ResponseWriter, r *http.Request) {
1024 vars := mux.Vars(r)
1025 user, ok := vars["username"]
DTabidze908bb852024-03-25 20:07:57 +04001026 if !ok || user == "" {
DTabidzed7744a62024-03-20 14:09:15 +04001027 http.Error(w, "Username parameter is required", http.StatusBadRequest)
1028 return
1029 }
DTabidze908bb852024-03-25 20:07:57 +04001030 user = strings.ToLower(user)
DTabidzed7744a62024-03-20 14:09:15 +04001031 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(user)
1032 if err != nil {
1033 http.Error(w, err.Error(), http.StatusInternalServerError)
1034 return
1035 }
DTabidzec0b4d8f2024-03-22 17:25:10 +04001036 var groupNames []string
1037 for _, group := range transitiveGroups {
1038 groupNames = append(groupNames, group.Name)
1039 }
DTabidzed7744a62024-03-20 14:09:15 +04001040 w.Header().Set("Content-Type", "application/json")
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +04001041 if err := json.NewEncoder(w).Encode(userInfo{groupNames}); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +04001042 http.Error(w, err.Error(), http.StatusInternalServerError)
1043 return
1044 }
1045}
1046
DTabidze908bb852024-03-25 20:07:57 +04001047func convertStatus(status string) (Status, error) {
1048 switch status {
1049 case "Owner":
1050 return Owner, nil
1051 case "Member":
1052 return Member, nil
1053 default:
1054 return Owner, fmt.Errorf("invalid status: %s", status)
1055 }
1056}
1057
1058func isValidGroupName(group string) error {
1059 if strings.TrimSpace(group) == "" {
1060 return fmt.Errorf("group name can't be empty or contain only whitespaces")
1061 }
1062 validGroupName := regexp.MustCompile(`^[a-z0-9\-_:.\/ ]+$`)
1063 if !validGroupName.MatchString(group) {
1064 return fmt.Errorf("group name should contain only lowercase letters, digits, -, _, :, ., /")
1065 }
1066 return nil
1067}
1068
DTabidze0d802592024-03-19 17:42:45 +04001069func main() {
1070 flag.Parse()
DTabidzec0b4d8f2024-03-22 17:25:10 +04001071 db, err := sql.Open("sqlite3", *dbPath)
DTabidze0d802592024-03-19 17:42:45 +04001072 if err != nil {
1073 panic(err)
1074 }
DTabidzec0b4d8f2024-03-22 17:25:10 +04001075 store, err := NewSQLiteStore(db)
1076 if err != nil {
1077 panic(err)
1078 }
1079 s := Server{store}
Giorgi Lekveishvili329af572024-03-25 20:14:41 +04001080 log.Fatal(s.Start())
DTabidze0d802592024-03-19 17:42:45 +04001081}