package main import "log" import "sync" import "strconv" import "encoding/json" var permUpdateMutex sync.Mutex var BlankPerms Perms var BlankForumPerms ForumPerms var GuestPerms Perms var ReadForumPerms ForumPerms var ReadReplyForumPerms ForumPerms var ReadWriteForumPerms ForumPerms var AllPerms Perms var AllForumPerms ForumPerms var AllPluginPerms = make(map[string]bool) // ? - Can we avoid duplicating the items in this list in a bunch of places? var LocalPermList = []string{ "ViewTopic", "LikeItem", "CreateTopic", "EditTopic", "DeleteTopic", "CreateReply", "EditReply", "DeleteReply", "PinTopic", "CloseTopic", } // ? - Can we avoid duplicating the items in this list in a bunch of places? var GlobalPermList = []string{ "BanUsers", "ActivateUsers", "EditUser", "EditUserEmail", "EditUserPassword", "EditUserGroup", "EditUserGroupSuperMod", "EditUserGroupAdmin", "EditGroup", "EditGroupLocalPerms", "EditGroupGlobalPerms", "EditGroupSuperMod", "EditGroupAdmin", "ManageForums", "EditSettings", "ManageThemes", "ManagePlugins", "ViewAdminLogs", "ViewIPs", "UploadFiles", } // Permission Structure: ActionComponent[Subcomponent]Flag type Perms struct { // Global Permissions BanUsers bool ActivateUsers bool EditUser bool EditUserEmail bool EditUserPassword bool EditUserGroup bool EditUserGroupSuperMod bool EditUserGroupAdmin bool EditGroup bool EditGroupLocalPerms bool EditGroupGlobalPerms bool EditGroupSuperMod bool EditGroupAdmin bool ManageForums bool // This could be local, albeit limited for per-forum managers? EditSettings bool ManageThemes bool ManagePlugins bool ViewAdminLogs bool ViewIPs bool // Global non-staff permissions UploadFiles bool // TODO: Add a permission for enabling avatars // Forum permissions ViewTopic bool LikeItem bool CreateTopic bool EditTopic bool DeleteTopic bool CreateReply bool //CreateReplyToOwn bool EditReply bool //EditOwnReply bool DeleteReply bool PinTopic bool CloseTopic bool //CloseOwnTopic bool //ExtData map[string]bool } /* Inherit from group permissions for ones we don't have */ type ForumPerms struct { ViewTopic bool LikeItem bool CreateTopic bool EditTopic bool DeleteTopic bool CreateReply bool //CreateReplyToOwn bool EditReply bool //EditOwnReply bool DeleteReply bool PinTopic bool CloseTopic bool //CloseOwnTopic bool Overrides bool ExtData map[string]bool } func init() { BlankPerms = Perms{ //ExtData: make(map[string]bool), } BlankForumPerms = ForumPerms{ ExtData: make(map[string]bool), } GuestPerms = Perms{ ViewTopic: true, //ExtData: make(map[string]bool), } AllPerms = Perms{ BanUsers: true, ActivateUsers: true, EditUser: true, EditUserEmail: true, EditUserPassword: true, EditUserGroup: true, EditUserGroupSuperMod: true, EditUserGroupAdmin: true, EditGroup: true, EditGroupLocalPerms: true, EditGroupGlobalPerms: true, EditGroupSuperMod: true, EditGroupAdmin: true, ManageForums: true, EditSettings: true, ManageThemes: true, ManagePlugins: true, ViewAdminLogs: true, ViewIPs: true, UploadFiles: true, ViewTopic: true, LikeItem: true, CreateTopic: true, EditTopic: true, DeleteTopic: true, CreateReply: true, EditReply: true, DeleteReply: true, PinTopic: true, CloseTopic: true, //ExtData: make(map[string]bool), } AllForumPerms = ForumPerms{ ViewTopic: true, LikeItem: true, CreateTopic: true, EditTopic: true, DeleteTopic: true, CreateReply: true, EditReply: true, DeleteReply: true, PinTopic: true, CloseTopic: true, Overrides: true, ExtData: make(map[string]bool), } ReadWriteForumPerms = ForumPerms{ ViewTopic: true, LikeItem: true, CreateTopic: true, CreateReply: true, Overrides: true, ExtData: make(map[string]bool), } ReadReplyForumPerms = ForumPerms{ ViewTopic: true, LikeItem: true, CreateReply: true, Overrides: true, ExtData: make(map[string]bool), } ReadForumPerms = ForumPerms{ ViewTopic: true, Overrides: true, ExtData: make(map[string]bool), } guestUser.Perms = GuestPerms if dev.DebugMode { log.Printf("Guest Perms: %+v\n", GuestPerms) log.Printf("All Perms: %+v\n", AllPerms) } } func presetToPermmap(preset string) (out map[string]ForumPerms) { out = make(map[string]ForumPerms) switch preset { case "all": out["guests"] = ReadForumPerms out["members"] = ReadWriteForumPerms out["staff"] = AllForumPerms out["admins"] = AllForumPerms case "announce": out["guests"] = ReadForumPerms out["members"] = ReadReplyForumPerms out["staff"] = AllForumPerms out["admins"] = AllForumPerms case "members": out["guests"] = BlankForumPerms out["members"] = ReadWriteForumPerms out["staff"] = AllForumPerms out["admins"] = AllForumPerms case "staff": out["guests"] = BlankForumPerms out["members"] = BlankForumPerms out["staff"] = ReadWriteForumPerms out["admins"] = AllForumPerms case "admins": out["guests"] = BlankForumPerms out["members"] = BlankForumPerms out["staff"] = BlankForumPerms out["admins"] = AllForumPerms case "archive": out["guests"] = ReadForumPerms out["members"] = ReadForumPerms out["staff"] = ReadForumPerms out["admins"] = ReadForumPerms //CurateForumPerms. Delete / Edit but no create? default: out["guests"] = BlankForumPerms out["members"] = BlankForumPerms out["staff"] = BlankForumPerms out["admins"] = BlankForumPerms } return out } func permmapToQuery(permmap map[string]ForumPerms, fid int) error { permUpdateMutex.Lock() defer permUpdateMutex.Unlock() _, err := deleteForumPermsByForumStmt.Exec(fid) if err != nil { return err } perms, err := json.Marshal(permmap["admins"]) if err != nil { return err } _, err = addForumPermsToForumAdminsStmt.Exec(fid, "", perms) if err != nil { return err } perms, err = json.Marshal(permmap["staff"]) if err != nil { return err } _, err = addForumPermsToForumStaffStmt.Exec(fid, "", perms) if err != nil { return err } perms, err = json.Marshal(permmap["members"]) if err != nil { return err } _, err = addForumPermsToForumMembersStmt.Exec(fid, "", perms) if err != nil { return err } perms, err = json.Marshal(permmap["guests"]) if err != nil { return err } _, err = addForumPermsToGroupStmt.Exec(6, fid, "", perms) if err != nil { return err } return rebuildForumPermissions(fid) } // TODO: Need a more thread-safe way of doing this. Possibly with sync.Map? func rebuildForumPermissions(fid int) error { if dev.DebugMode { log.Print("Loading the forum permissions") } fids, err := fstore.GetAllIDs() if err != nil { return err } rows, err := db.Query("select gid, permissions from forums_permissions where fid = ? order by gid asc", fid) if err != nil { return err } defer rows.Close() if dev.DebugMode { log.Print("Updating the forum permissions") } for rows.Next() { var gid int var perms []byte var pperms ForumPerms err := rows.Scan(&gid, &perms) if err != nil { return err } err = json.Unmarshal(perms, &pperms) if err != nil { return err } pperms.ExtData = make(map[string]bool) pperms.Overrides = true _, ok := forumPerms[gid] if !ok { forumPerms[gid] = make(map[int]ForumPerms) } forumPerms[gid][fid] = pperms } groups, err := gstore.GetAll() if err != nil { return err } for _, group := range groups { if dev.DebugMode { log.Print("Updating the forum permissions for Group #" + strconv.Itoa(group.ID)) } group.Forums = []ForumPerms{BlankForumPerms} group.CanSee = []int{} for _, ffid := range fids { forumPerm, ok := forumPerms[group.ID][ffid] if ok { //log.Print("Overriding permissions for forum #" + strconv.Itoa(fid)) group.Forums = append(group.Forums, forumPerm) } else { //log.Print("Inheriting from default for forum #" + strconv.Itoa(fid)) forumPerm = BlankForumPerms group.Forums = append(group.Forums, forumPerm) } if forumPerm.Overrides { if forumPerm.ViewTopic { group.CanSee = append(group.CanSee, ffid) } } else if group.Perms.ViewTopic { group.CanSee = append(group.CanSee, ffid) } } if dev.SuperDebug { log.Printf("group.CanSee %+v\n", group.CanSee) log.Printf("group.Forums %+v\n", group.Forums) log.Print("len(group.CanSee)", len(group.CanSee)) log.Print("len(group.Forums)", len(group.Forums)) // This counts blank aka 0 } } return nil } // ? - We could have buildForumPermissions and rebuildForumPermissions call a third function containing common logic? func buildForumPermissions() error { fids, err := fstore.GetAllIDs() if err != nil { return err } if dev.SuperDebug { log.Print("fids: ", fids) } rows, err := getForumsPermissionsStmt.Query() if err != nil { return err } defer rows.Close() if dev.DebugMode { log.Print("Adding the forum permissions") if dev.SuperDebug { log.Print("forumPerms[gid][fid]") } } // Temporarily store the forum perms in a map before transferring it to a much faster and thread-safe slice forumPerms = make(map[int]map[int]ForumPerms) for rows.Next() { var gid, fid int var perms []byte var pperms ForumPerms err = rows.Scan(&gid, &fid, &perms) if err != nil { return err } if dev.SuperDebug { log.Print("perms: ", string(perms)) } err = json.Unmarshal(perms, &pperms) if err != nil { return err } pperms.ExtData = make(map[string]bool) pperms.Overrides = true _, ok := forumPerms[gid] if !ok { forumPerms[gid] = make(map[int]ForumPerms) } if dev.SuperDebug { log.Print("gid: ", gid) log.Print("fid: ", fid) log.Printf("perms: %+v;", pperms) } forumPerms[gid][fid] = pperms } groups, err := gstore.GetAll() if err != nil { return err } for _, group := range groups { if dev.DebugMode { log.Print("Adding the forum permissions for Group #" + strconv.Itoa(group.ID) + " - " + group.Name) } group.Forums = []ForumPerms{BlankForumPerms} group.CanSee = []int{} for _, fid := range fids { if dev.SuperDebug { log.Printf("Forum #%+v\n", fid) } forumPerm, ok := forumPerms[group.ID][fid] if ok { // Override group perms //log.Print("Overriding permissions for forum #" + strconv.Itoa(fid)) group.Forums = append(group.Forums, forumPerm) } else { // Inherit from Group //log.Print("Inheriting from default for forum #" + strconv.Itoa(fid)) forumPerm = BlankForumPerms group.Forums = append(group.Forums, forumPerm) } if forumPerm.Overrides { if forumPerm.ViewTopic { group.CanSee = append(group.CanSee, fid) } } else if group.Perms.ViewTopic { group.CanSee = append(group.CanSee, fid) } if dev.SuperDebug { log.Print("group.ID: ", group.ID) log.Printf("forumPerm: %+v\n", forumPerm) log.Print("group.CanSee: ", group.CanSee) } } if dev.SuperDebug { log.Printf("group.CanSee %+v\n", group.CanSee) log.Printf("group.Forums %+v\n", group.Forums) log.Print("len(group.CanSee)", len(group.CanSee)) log.Print("len(group.Forums)", len(group.Forums)) // This counts blank aka 0 } } return nil } func forumPermsToGroupForumPreset(fperms ForumPerms) string { if !fperms.Overrides { return "default" } if !fperms.ViewTopic { return "no_access" } var canPost = (fperms.LikeItem && fperms.CreateTopic && fperms.CreateReply) var canModerate = (canPost && fperms.EditTopic && fperms.DeleteTopic && fperms.EditReply && fperms.DeleteReply && fperms.PinTopic && fperms.CloseTopic) if canModerate { return "can_moderate" } if fperms.EditTopic || fperms.DeleteTopic || fperms.EditReply || fperms.DeleteReply || fperms.PinTopic || fperms.CloseTopic { if !canPost { return "custom" } return "quasi_mod" } if canPost { return "can_post" } if fperms.ViewTopic && !fperms.LikeItem && !fperms.CreateTopic && !fperms.CreateReply { return "read_only" } return "custom" } func groupForumPresetToForumPerms(preset string) (fperms ForumPerms, changed bool) { switch preset { case "read_only": return ReadForumPerms, true case "can_post": return ReadWriteForumPerms, true case "can_moderate": return AllForumPerms, true case "no_access": return ForumPerms{Overrides: true, ExtData: make(map[string]bool)}, true case "default": return BlankForumPerms, true //case "custom": return fperms, false } return fperms, false } func stripInvalidGroupForumPreset(preset string) string { switch preset { case "read_only", "can_post", "can_moderate", "no_access", "default", "custom": return preset } return "" } func stripInvalidPreset(preset string) string { switch preset { case "all", "announce", "members", "staff", "admins", "archive", "custom": return preset default: return "" } } // TODO: Move this into the phrase system? func presetToLang(preset string) string { switch preset { case "all": return "Public" case "announce": return "Announcements" case "members": return "Member Only" case "staff": return "Staff Only" case "admins": return "Admin Only" case "archive": return "Archive" case "custom": return "Custom" default: return "" } } // TODO: Is this racey? func rebuildGroupPermissions(gid int) error { var permstr []byte log.Print("Reloading a group") err := db.QueryRow("select permissions from users_groups where gid = ?", gid).Scan(&permstr) if err != nil { return err } tmpPerms := Perms{ //ExtData: make(map[string]bool), } err = json.Unmarshal(permstr, &tmpPerms) if err != nil { return err } group, err := gstore.Get(gid) if err != nil { return err } group.Perms = tmpPerms return nil } func overridePerms(perms *Perms, status bool) { if status { *perms = AllPerms } else { *perms = BlankPerms } } // TODO: We need a better way of overriding forum perms rather than setting them one by one func overrideForumPerms(perms *Perms, status bool) { perms.ViewTopic = status perms.LikeItem = status perms.CreateTopic = status perms.EditTopic = status perms.DeleteTopic = status perms.CreateReply = status perms.EditReply = status perms.DeleteReply = status perms.PinTopic = status perms.CloseTopic = status } func registerPluginPerm(name string) { AllPluginPerms[name] = true } func deregisterPluginPerm(name string) { delete(AllPluginPerms, name) }