blob: 808d5a810f83b618883d8c1f7ca9231a36f0d9e0 [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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400127 return fmt.Errorf("Store already initialised")
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400128 }
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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400292 return fmt.Errorf("Parent and child groups can not have same name")
DTabidze908bb852024-03-25 20:07:57 +0400293 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400294 exists, err := s.DoesGroupExist(parent)
295 if err != nil {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400296 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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400299 return fmt.Errorf("Parent group with name %s does not exist", parent)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400300 }
301 exists, err = s.DoesGroupExist(child)
302 if err != nil {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400303 return fmt.Errorf("Error checking child group existence: %v", err)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400304 }
305 if !exists {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400306 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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400314 return fmt.Errorf("Circular reference detected: group %s is already a parent of group %s", child, parent)
DTabidzec0b4d8f2024-03-22 17:25:10 +0400315 }
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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400321 return fmt.Errorf("Child group name %s already exists in group %s", child, parent)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400322 }
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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400429 return fmt.Errorf("Pair of parent '%s' and child '%s' groups not found", parent, child)
DTabidze2b224bf2024-03-27 13:25:49 +0400430 }
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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400441 return fmt.Errorf("Cannot remove the last owner of the group")
DTabidze2b224bf2024-03-27 13:25:49 +0400442 }
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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400454 return fmt.Errorf("Pair of group '%s' and user '%s' not found", groupName, username)
DTabidze2b224bf2024-03-27 13:25:49 +0400455 }
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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400461 return fmt.Errorf("Group can not own itself")
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400462 }
463 exists, err := s.DoesGroupExist(owned_group)
464 if err != nil {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400465 return fmt.Errorf("Error checking owned group existence: %v", err)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400466 }
467 if !exists {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400468 return fmt.Errorf("Owned group with name %s does not exist", owned_group)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400469 }
470 exists, err = s.DoesGroupExist(owner_group)
471 if err != nil {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400472 return fmt.Errorf("Error checking owner group existence: %v", err)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400473 }
474 if !exists {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400475 return fmt.Errorf("Owner group with name %s does not exist", owner_group)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400476 }
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 {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400481 return fmt.Errorf("Group named %s is already owner of a group %s", owner_group, owned_group)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400482 }
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 }
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400522 // return "user", nil
DTabidze0d802592024-03-19 17:42:45 +0400523}
524
525type Status int
526
527const (
528 Owner Status = iota
529 Member
530)
531
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400532func (s *Server) Start() error {
533 e := make(chan error)
534 go func() {
535 r := mux.NewRouter()
536 r.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticResources)))
DTabidze078385f2024-03-27 14:49:05 +0400537 r.HandleFunc("/group/{group-name}/add-user/", s.addUserToGroupHandler)
538 r.HandleFunc("/group/{parent-group}/add-child-group", s.addChildGroupHandler)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400539 r.HandleFunc("/group/{owned-group}/add-owner-group", s.addOwnerGroupHandler)
DTabidze078385f2024-03-27 14:49:05 +0400540 r.HandleFunc("/group/{parent-group}/remove-child-group/{child-group}", s.removeChildGroupHandler)
541 r.HandleFunc("/group/{group-name}/remove-owner/{username}", s.removeOwnerFromGroupHandler)
542 r.HandleFunc("/group/{group-name}/remove-member/{username}", s.removeMemberFromGroupHandler)
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400543 r.HandleFunc("/group/{group-name}", s.groupHandler)
DTabidze5d735e32024-03-26 16:01:06 +0400544 r.HandleFunc("/user/{username}", s.userHandler)
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400545 r.HandleFunc("/create-group", s.createGroupHandler)
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400546 r.HandleFunc("/", s.homePageHandler)
547 e <- http.ListenAndServe(fmt.Sprintf(":%d", *port), r)
548 }()
549 go func() {
550 r := mux.NewRouter()
551 r.HandleFunc("/api/init", s.apiInitHandler)
552 r.HandleFunc("/api/user/{username}", s.apiMemberOfHandler)
553 e <- http.ListenAndServe(fmt.Sprintf(":%d", *apiPort), r)
554 }()
555 return <-e
DTabidze0d802592024-03-19 17:42:45 +0400556}
557
558type GroupData struct {
559 Group Group
560 Membership string
561}
562
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400563func (s *Server) checkIsOwner(w http.ResponseWriter, user, group string) error {
DTabidze0d802592024-03-19 17:42:45 +0400564 isOwner, err := s.store.IsGroupOwner(user, group)
565 if err != nil {
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400566 return err
DTabidze0d802592024-03-19 17:42:45 +0400567 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400568 if isOwner {
569 return nil
DTabidze0d802592024-03-19 17:42:45 +0400570 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400571 // TODO(dtabidze): right now this only checks if user is member of just one lvl upper group. should add transitive group check.
572 isMemberOfOwnerGroup, err := s.store.IsMemberOfOwnerGroup(user, group)
573 if err != nil {
574 return err
575 }
576 if !isMemberOfOwnerGroup {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +0400577 return fmt.Errorf("You are not the owner or a member of any owner group of the group %s", group)
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400578 }
579 return nil
DTabidze0d802592024-03-19 17:42:45 +0400580}
581
DTabidze4b44ff42024-04-02 03:16:26 +0400582type templates struct {
583 group *template.Template
584 user *template.Template
585}
586
587func parseTemplates(fs embed.FS) (templates, error) {
588 base, err := template.ParseFS(fs, "memberships-tmpl/base.html")
589 if err != nil {
590 return templates{}, err
591 }
592 parse := func(path string) (*template.Template, error) {
593 if b, err := base.Clone(); err != nil {
594 return nil, err
595 } else {
596 return b.ParseFS(fs, path)
597 }
598 }
599 user, err := parse("memberships-tmpl/user.html")
600 if err != nil {
601 return templates{}, err
602 }
603 group, err := parse("memberships-tmpl/group.html")
604 if err != nil {
605 return templates{}, err
606 }
607 return templates{group, user}, nil
608}
609
DTabidze0d802592024-03-19 17:42:45 +0400610func (s *Server) homePageHandler(w http.ResponseWriter, r *http.Request) {
611 loggedInUser, err := getLoggedInUser(r)
612 if err != nil {
613 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
614 return
615 }
DTabidze5d735e32024-03-26 16:01:06 +0400616 http.Redirect(w, r, "/user/"+loggedInUser, http.StatusSeeOther)
617}
618
619func (s *Server) userHandler(w http.ResponseWriter, r *http.Request) {
620 loggedInUser, err := getLoggedInUser(r)
621 if err != nil {
622 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
623 return
624 }
DTabidze4b44ff42024-04-02 03:16:26 +0400625 errorMsg := r.URL.Query().Get("errorMessage")
DTabidze5d735e32024-03-26 16:01:06 +0400626 vars := mux.Vars(r)
627 user := strings.ToLower(vars["username"])
628 // TODO(dtabidze): should check if username exists or not.
629 loggedInUserPage := loggedInUser == user
630 ownerGroups, err := s.store.GetGroupsOwnedBy(user)
DTabidze0d802592024-03-19 17:42:45 +0400631 if err != nil {
632 http.Error(w, err.Error(), http.StatusInternalServerError)
633 return
634 }
DTabidze5d735e32024-03-26 16:01:06 +0400635 membershipGroups, err := s.store.GetGroupsUserBelongsTo(user)
DTabidze0d802592024-03-19 17:42:45 +0400636 if err != nil {
637 http.Error(w, err.Error(), http.StatusInternalServerError)
638 return
639 }
DTabidze5d735e32024-03-26 16:01:06 +0400640 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(user)
DTabidzec0b4d8f2024-03-22 17:25:10 +0400641 if err != nil {
642 http.Error(w, err.Error(), http.StatusInternalServerError)
643 return
644 }
DTabidze0d802592024-03-19 17:42:45 +0400645 data := struct {
646 OwnerGroups []Group
647 MembershipGroups []Group
DTabidzec0b4d8f2024-03-22 17:25:10 +0400648 TransitiveGroups []Group
DTabidze5d735e32024-03-26 16:01:06 +0400649 LoggedInUserPage bool
650 CurrentUser string
DTabidze4b44ff42024-04-02 03:16:26 +0400651 ErrorMessage string
DTabidze0d802592024-03-19 17:42:45 +0400652 }{
653 OwnerGroups: ownerGroups,
654 MembershipGroups: membershipGroups,
DTabidzec0b4d8f2024-03-22 17:25:10 +0400655 TransitiveGroups: transitiveGroups,
DTabidze5d735e32024-03-26 16:01:06 +0400656 LoggedInUserPage: loggedInUserPage,
657 CurrentUser: user,
DTabidze4b44ff42024-04-02 03:16:26 +0400658 ErrorMessage: errorMsg,
DTabidze0d802592024-03-19 17:42:45 +0400659 }
DTabidze4b44ff42024-04-02 03:16:26 +0400660 templates, err := parseTemplates(tmpls)
661 if err != nil {
662 http.Error(w, err.Error(), http.StatusInternalServerError)
663 return
664 }
665 if err := templates.user.Execute(w, data); err != nil {
DTabidze0d802592024-03-19 17:42:45 +0400666 http.Error(w, err.Error(), http.StatusInternalServerError)
667 return
668 }
669}
670
671func (s *Server) createGroupHandler(w http.ResponseWriter, r *http.Request) {
672 loggedInUser, err := getLoggedInUser(r)
673 if err != nil {
674 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
675 return
676 }
677 if r.Method != http.MethodPost {
678 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
679 return
680 }
681 if err := r.ParseForm(); err != nil {
682 http.Error(w, err.Error(), http.StatusInternalServerError)
683 return
684 }
685 var group Group
686 group.Name = r.PostFormValue("group-name")
DTabidze908bb852024-03-25 20:07:57 +0400687 if err := isValidGroupName(group.Name); err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400688 // http.Error(w, err.Error(), http.StatusBadRequest)
689 redirectURL := fmt.Sprintf("/user/%s?errorMessage=%s", loggedInUser, url.QueryEscape(err.Error()))
690 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze908bb852024-03-25 20:07:57 +0400691 return
692 }
DTabidze0d802592024-03-19 17:42:45 +0400693 group.Description = r.PostFormValue("description")
694 if err := s.store.CreateGroup(loggedInUser, group); err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400695 // http.Error(w, err.Error(), http.StatusInternalServerError)
696 redirectURL := fmt.Sprintf("/user/%s?errorMessage=%s", loggedInUser, url.QueryEscape(err.Error()))
697 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze0d802592024-03-19 17:42:45 +0400698 return
699 }
700 http.Redirect(w, r, "/", http.StatusSeeOther)
701}
702
703func (s *Server) groupHandler(w http.ResponseWriter, r *http.Request) {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400704 _, err := getLoggedInUser(r)
705 if err != nil {
706 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
707 return
708 }
DTabidze4b44ff42024-04-02 03:16:26 +0400709 errorMsg := r.URL.Query().Get("errorMessage")
DTabidzed7744a62024-03-20 14:09:15 +0400710 vars := mux.Vars(r)
711 groupName := vars["group-name"]
DTabidze908bb852024-03-25 20:07:57 +0400712 exists, err := s.store.DoesGroupExist(groupName)
713 if err != nil {
714 http.Error(w, err.Error(), http.StatusInternalServerError)
715 return
716 }
717 if !exists {
DTabidze4b44ff42024-04-02 03:16:26 +0400718 errorMsg = fmt.Sprintf("group with the name '%s' not found", groupName)
DTabidze908bb852024-03-25 20:07:57 +0400719 http.Error(w, errorMsg, http.StatusNotFound)
720 return
721 }
DTabidze0d802592024-03-19 17:42:45 +0400722 if err != nil {
723 http.Error(w, err.Error(), http.StatusInternalServerError)
724 return
725 }
726 owners, err := s.store.GetGroupOwners(groupName)
727 if err != nil {
728 http.Error(w, err.Error(), http.StatusInternalServerError)
729 return
730 }
731 members, err := s.store.GetGroupMembers(groupName)
732 if err != nil {
733 http.Error(w, err.Error(), http.StatusInternalServerError)
734 return
735 }
736 description, err := s.store.GetGroupDescription(groupName)
737 if err != nil {
738 http.Error(w, err.Error(), http.StatusInternalServerError)
739 return
740 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400741 allGroups, err := s.store.GetAllGroups()
DTabidze0d802592024-03-19 17:42:45 +0400742 if err != nil {
743 http.Error(w, err.Error(), http.StatusInternalServerError)
744 return
745 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400746 transitiveGroups, err := s.store.GetAllTransitiveGroupsForGroup(groupName)
747 if err != nil {
748 http.Error(w, err.Error(), http.StatusInternalServerError)
749 return
750 }
751 childGroups, err := s.store.GetDirectChildrenGroups(groupName)
752 if err != nil {
753 http.Error(w, err.Error(), http.StatusInternalServerError)
754 return
755 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400756 ownerGroups, err := s.store.GetGroupOwnerGroups(groupName)
757 if err != nil {
758 http.Error(w, err.Error(), http.StatusInternalServerError)
759 return
760 }
DTabidze0d802592024-03-19 17:42:45 +0400761 data := struct {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400762 GroupName string
763 Description string
764 Owners []string
765 Members []string
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400766 AllGroups []Group
DTabidzec0b4d8f2024-03-22 17:25:10 +0400767 TransitiveGroups []Group
768 ChildGroups []Group
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400769 OwnerGroups []Group
DTabidze4b44ff42024-04-02 03:16:26 +0400770 ErrorMessage string
DTabidze0d802592024-03-19 17:42:45 +0400771 }{
DTabidzec0b4d8f2024-03-22 17:25:10 +0400772 GroupName: groupName,
773 Description: description,
774 Owners: owners,
775 Members: members,
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400776 AllGroups: allGroups,
DTabidzec0b4d8f2024-03-22 17:25:10 +0400777 TransitiveGroups: transitiveGroups,
778 ChildGroups: childGroups,
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400779 OwnerGroups: ownerGroups,
DTabidze4b44ff42024-04-02 03:16:26 +0400780 ErrorMessage: errorMsg,
DTabidze0d802592024-03-19 17:42:45 +0400781 }
DTabidze4b44ff42024-04-02 03:16:26 +0400782 templates, err := parseTemplates(tmpls)
783 if err != nil {
784 http.Error(w, err.Error(), http.StatusInternalServerError)
785 return
786 }
787 if err := templates.group.Execute(w, data); err != nil {
DTabidze0d802592024-03-19 17:42:45 +0400788 http.Error(w, err.Error(), http.StatusInternalServerError)
789 return
790 }
791}
792
DTabidze2b224bf2024-03-27 13:25:49 +0400793func (s *Server) removeChildGroupHandler(w http.ResponseWriter, r *http.Request) {
794 loggedInUser, err := getLoggedInUser(r)
795 if err != nil {
796 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
797 return
798 }
799 if r.Method == http.MethodPost {
800 vars := mux.Vars(r)
801 parentGroup := vars["parent-group"]
802 childGroup := vars["child-group"]
803 if err := isValidGroupName(parentGroup); err != nil {
804 http.Error(w, err.Error(), http.StatusBadRequest)
805 return
806 }
807 if err := isValidGroupName(childGroup); err != nil {
808 http.Error(w, err.Error(), http.StatusBadRequest)
809 return
810 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400811 if err := s.checkIsOwner(w, loggedInUser, parentGroup); err != nil {
812 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
813 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze2b224bf2024-03-27 13:25:49 +0400814 return
815 }
816 err := s.store.RemoveFromGroupToGroup(parentGroup, childGroup)
817 if err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400818 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
819 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze2b224bf2024-03-27 13:25:49 +0400820 return
821 }
822 http.Redirect(w, r, "/group/"+parentGroup, http.StatusSeeOther)
823 }
824}
825
DTabidze078385f2024-03-27 14:49:05 +0400826func (s *Server) removeOwnerFromGroupHandler(w http.ResponseWriter, r *http.Request) {
DTabidze2b224bf2024-03-27 13:25:49 +0400827 loggedInUser, err := getLoggedInUser(r)
828 if err != nil {
829 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
830 return
831 }
832 if r.Method == http.MethodPost {
833 vars := mux.Vars(r)
834 username := vars["username"]
835 groupName := vars["group-name"]
DTabidze078385f2024-03-27 14:49:05 +0400836 tableName := "owners"
DTabidze2b224bf2024-03-27 13:25:49 +0400837 if err := isValidGroupName(groupName); err != nil {
838 http.Error(w, err.Error(), http.StatusBadRequest)
839 return
840 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400841 if err := s.checkIsOwner(w, loggedInUser, groupName); err != nil {
842 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
843 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze2b224bf2024-03-27 13:25:49 +0400844 return
845 }
846 err := s.store.RemoveUserFromTable(username, groupName, tableName)
847 if err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400848 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
849 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze2b224bf2024-03-27 13:25:49 +0400850 return
851 }
852 http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
853 }
854}
855
DTabidze078385f2024-03-27 14:49:05 +0400856func (s *Server) removeMemberFromGroupHandler(w http.ResponseWriter, r *http.Request) {
857 loggedInUser, err := getLoggedInUser(r)
858 if err != nil {
859 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
860 return
861 }
862 if r.Method == http.MethodPost {
863 vars := mux.Vars(r)
864 username := vars["username"]
865 groupName := vars["group-name"]
866 tableName := "user_to_group"
867 if err := isValidGroupName(groupName); err != nil {
868 http.Error(w, err.Error(), http.StatusBadRequest)
869 return
870 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400871 if err := s.checkIsOwner(w, loggedInUser, groupName); err != nil {
872 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
873 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze078385f2024-03-27 14:49:05 +0400874 return
875 }
876 err := s.store.RemoveUserFromTable(username, groupName, tableName)
877 if err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400878 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
879 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze078385f2024-03-27 14:49:05 +0400880 return
881 }
882 http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
883 }
884}
885
886func (s *Server) addUserToGroupHandler(w http.ResponseWriter, r *http.Request) {
DTabidze0d802592024-03-19 17:42:45 +0400887 if r.Method != http.MethodPost {
888 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
889 return
890 }
891 loggedInUser, err := getLoggedInUser(r)
892 if err != nil {
893 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
894 return
895 }
DTabidze078385f2024-03-27 14:49:05 +0400896 vars := mux.Vars(r)
897 groupName := vars["group-name"]
DTabidze908bb852024-03-25 20:07:57 +0400898 if err := isValidGroupName(groupName); err != nil {
899 http.Error(w, err.Error(), http.StatusBadRequest)
900 return
901 }
902 username := strings.ToLower(r.FormValue("username"))
903 if username == "" {
904 http.Error(w, "Username parameter is required", http.StatusBadRequest)
905 return
906 }
DTabidze0d802592024-03-19 17:42:45 +0400907 status, err := convertStatus(r.FormValue("status"))
908 if err != nil {
909 http.Error(w, err.Error(), http.StatusBadRequest)
910 return
911 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400912 if err := s.checkIsOwner(w, loggedInUser, groupName); err != nil {
913 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
914 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze0d802592024-03-19 17:42:45 +0400915 return
916 }
917 switch status {
918 case Owner:
919 err = s.store.AddGroupOwner(username, groupName)
920 case Member:
921 err = s.store.AddGroupMember(username, groupName)
922 default:
923 http.Error(w, "Invalid status", http.StatusBadRequest)
924 return
925 }
926 if err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400927 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
928 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze0d802592024-03-19 17:42:45 +0400929 return
930 }
931 http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
932}
933
934func (s *Server) addChildGroupHandler(w http.ResponseWriter, r *http.Request) {
935 // TODO(dtabidze): In future we might need to make one group OWNER of another and not just a member.
936 if r.Method != http.MethodPost {
937 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
938 return
939 }
940 loggedInUser, err := getLoggedInUser(r)
941 if err != nil {
942 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
943 return
944 }
DTabidze078385f2024-03-27 14:49:05 +0400945 vars := mux.Vars(r)
946 parentGroup := vars["parent-group"]
DTabidze908bb852024-03-25 20:07:57 +0400947 if err := isValidGroupName(parentGroup); err != nil {
948 http.Error(w, err.Error(), http.StatusBadRequest)
949 return
950 }
DTabidze0d802592024-03-19 17:42:45 +0400951 childGroup := r.FormValue("child-group")
DTabidze908bb852024-03-25 20:07:57 +0400952 if err := isValidGroupName(childGroup); err != nil {
953 http.Error(w, err.Error(), http.StatusBadRequest)
954 return
955 }
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400956 if err := s.checkIsOwner(w, loggedInUser, parentGroup); err != nil {
957 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
958 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
DTabidze0d802592024-03-19 17:42:45 +0400959 return
960 }
961 if err := s.store.AddChildGroup(parentGroup, childGroup); err != nil {
DTabidze4b44ff42024-04-02 03:16:26 +0400962 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
963 http.Redirect(w, r, redirectURL, http.StatusFound)
DTabidze0d802592024-03-19 17:42:45 +0400964 return
965 }
966 http.Redirect(w, r, "/group/"+parentGroup, http.StatusSeeOther)
967}
968
Davit Tabidzec0d2bf52024-04-03 15:39:33 +0400969func (s *Server) addOwnerGroupHandler(w http.ResponseWriter, r *http.Request) {
970 if r.Method != http.MethodPost {
971 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
972 return
973 }
974 loggedInUser, err := getLoggedInUser(r)
975 if err != nil {
976 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
977 return
978 }
979 vars := mux.Vars(r)
980 ownedGroup := vars["owned-group"]
981 if err := isValidGroupName(ownedGroup); err != nil {
982 http.Error(w, err.Error(), http.StatusBadRequest)
983 return
984 }
985 ownerGroup := r.FormValue("owner-group")
986 if err := isValidGroupName(ownerGroup); err != nil {
987 http.Error(w, err.Error(), http.StatusBadRequest)
988 return
989 }
990 if err := s.checkIsOwner(w, loggedInUser, ownedGroup); err != nil {
991 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", ownedGroup, url.QueryEscape(err.Error()))
992 http.Redirect(w, r, redirectURL, http.StatusSeeOther)
993 return
994 }
995 if err := s.store.AddOwnerGroup(ownerGroup, ownedGroup); err != nil {
996 redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", ownedGroup, url.QueryEscape(err.Error()))
997 http.Redirect(w, r, redirectURL, http.StatusFound)
998 return
999 }
1000 http.Redirect(w, r, "/group/"+ownedGroup, http.StatusSeeOther)
1001}
1002
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +04001003type initRequest struct {
1004 Owner string `json:"owner"`
1005 Groups []string `json:"groups"`
1006}
1007
1008func (s *Server) apiInitHandler(w http.ResponseWriter, r *http.Request) {
1009 var req initRequest
1010 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
1011 http.Error(w, err.Error(), http.StatusBadRequest)
1012 return
1013 }
1014 if err := s.store.Init(req.Owner, req.Groups); err != nil {
1015 http.Error(w, err.Error(), http.StatusInternalServerError)
1016 return
1017 }
1018}
1019
1020type userInfo struct {
DTabidzed7744a62024-03-20 14:09:15 +04001021 MemberOf []string `json:"memberOf"`
1022}
1023
1024func (s *Server) apiMemberOfHandler(w http.ResponseWriter, r *http.Request) {
1025 vars := mux.Vars(r)
1026 user, ok := vars["username"]
DTabidze908bb852024-03-25 20:07:57 +04001027 if !ok || user == "" {
DTabidzed7744a62024-03-20 14:09:15 +04001028 http.Error(w, "Username parameter is required", http.StatusBadRequest)
1029 return
1030 }
DTabidze908bb852024-03-25 20:07:57 +04001031 user = strings.ToLower(user)
DTabidzed7744a62024-03-20 14:09:15 +04001032 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(user)
1033 if err != nil {
1034 http.Error(w, err.Error(), http.StatusInternalServerError)
1035 return
1036 }
DTabidzec0b4d8f2024-03-22 17:25:10 +04001037 var groupNames []string
1038 for _, group := range transitiveGroups {
1039 groupNames = append(groupNames, group.Name)
1040 }
DTabidzed7744a62024-03-20 14:09:15 +04001041 w.Header().Set("Content-Type", "application/json")
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +04001042 if err := json.NewEncoder(w).Encode(userInfo{groupNames}); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +04001043 http.Error(w, err.Error(), http.StatusInternalServerError)
1044 return
1045 }
1046}
1047
DTabidze908bb852024-03-25 20:07:57 +04001048func convertStatus(status string) (Status, error) {
1049 switch status {
1050 case "Owner":
1051 return Owner, nil
1052 case "Member":
1053 return Member, nil
1054 default:
1055 return Owner, fmt.Errorf("invalid status: %s", status)
1056 }
1057}
1058
1059func isValidGroupName(group string) error {
1060 if strings.TrimSpace(group) == "" {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +04001061 return fmt.Errorf("Group name can't be empty or contain only whitespaces")
DTabidze908bb852024-03-25 20:07:57 +04001062 }
1063 validGroupName := regexp.MustCompile(`^[a-z0-9\-_:.\/ ]+$`)
1064 if !validGroupName.MatchString(group) {
Davit Tabidze5f1a2c62024-07-17 17:57:27 +04001065 return fmt.Errorf("Group name should contain only lowercase letters, digits, -, _, :, ., /")
DTabidze908bb852024-03-25 20:07:57 +04001066 }
1067 return nil
1068}
1069
DTabidze0d802592024-03-19 17:42:45 +04001070func main() {
1071 flag.Parse()
DTabidzec0b4d8f2024-03-22 17:25:10 +04001072 db, err := sql.Open("sqlite3", *dbPath)
DTabidze0d802592024-03-19 17:42:45 +04001073 if err != nil {
1074 panic(err)
1075 }
DTabidzec0b4d8f2024-03-22 17:25:10 +04001076 store, err := NewSQLiteStore(db)
1077 if err != nil {
1078 panic(err)
1079 }
1080 s := Server{store}
Giorgi Lekveishvili329af572024-03-25 20:14:41 +04001081 log.Fatal(s.Start())
DTabidze0d802592024-03-19 17:42:45 +04001082}