diff --git a/cmd/system-cli/commands.go b/cmd/system-cli/commands.go new file mode 100644 index 000000000..50e4fe126 --- /dev/null +++ b/cmd/system-cli/commands.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" + + systemCli "github.com/crusttech/crust/system/cli" +) + +func setupCobra() { + // Main command. + rootCmd := &cobra.Command{Use: "system-cli"} + + // User management commands. + var cmdUsers = &cobra.Command{ + Use: "users", + Short: "User management", + } + rootCmd.AddCommand(cmdUsers) + + // List users. + var cmdUsersList = &cobra.Command{ + Use: "list", + Short: "List users", + Run: func(cmd *cobra.Command, args []string) { + systemCli.UsersList() + }, + } + cmdUsers.AddCommand(cmdUsersList) + + // Role management commands. + var cmdRole = &cobra.Command{ + Use: "roles", + Short: "Role management", + } + rootCmd.AddCommand(cmdRole) + + // Reset permissions. + var cmdRolesReset = &cobra.Command{ + Use: "reset", + Short: "Reset roles", + Run: func(cmd *cobra.Command, args []string) { + systemCli.RolesReset() + }, + } + cmdRole.AddCommand(cmdRolesReset) + + err := rootCmd.Execute() + if err != nil { + fmt.Println(err) + } +} diff --git a/cmd/system-cli/main.go b/cmd/system-cli/main.go index fa467e2d5..1535156dc 100644 --- a/cmd/system-cli/main.go +++ b/cmd/system-cli/main.go @@ -4,10 +4,9 @@ import ( "log" "os" - "github.com/crusttech/crust/system" - systemService "github.com/crusttech/crust/system/service" - "github.com/crusttech/crust/internal/auth" + system "github.com/crusttech/crust/system" + systemService "github.com/crusttech/crust/system/service" ) func main() { @@ -15,33 +14,10 @@ func main() { log.SetOutput(os.Stdout) log.SetFlags(log.LstdFlags | log.Lshortfile) - flags("system", service.Flags, auth.Flags) + flags("system", system.Flags, auth.Flags) - service.InitDatabase() + system.InitDatabase() systemService.Init() - var commands []string - if len(os.Args) > 0 { - // @todo migrate to a proper solution (eg: https://github.com/spf13/cobra) - commands = os.Args[1:] - for a, arg := range os.Args { - if arg == "--" && a+1 < len(os.Args) { - commands = os.Args[a+1:] - } - } - } - - cliRouter(commands...) -} - -func cliRouter(commands ...string) { - if len(commands) == 0 { - return - } - - switch commands[0] { - case "users": - cliExecUsers(commands[1:]...) - default: - } + setupCobra() } diff --git a/cmd/system-cli/users.go b/cmd/system-cli/users.go deleted file mode 100644 index 5bc79a539..000000000 --- a/cmd/system-cli/users.go +++ /dev/null @@ -1,190 +0,0 @@ -package main - -import ( - "bufio" - "context" - "fmt" - "os" - "strings" - - crmRepository "github.com/crusttech/crust/crm/repository" - "github.com/crusttech/crust/internal/payload" - messagingRepository "github.com/crusttech/crust/messaging/repository" - "github.com/crusttech/crust/system/service" - "github.com/crusttech/crust/system/types" -) - -func cliExecUsers(commands ...string) { - if len(commands) == 0 { - return - } - - switch commands[0] { - case "list": - cliExecUsersList(commands[1:]...) - case "merge": - cliExecUsersMerge(commands[1:]...) - } -} - -func cliExecUsersList(params ...string) { - var ( - upd, del string - - err error - uu types.UserSet - ctx = context.Background() - ) - - uf := &types.UserFilter{ - OrderBy: "updated_at", - } - - if uu, err = service.DefaultUser.With(ctx).Find(uf); err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - - fmt.Println("ID Updated Deleted [email / name / username]") - for _, u := range uu { - upd, del = "---- -- --", "---- -- --" - - if u.UpdatedAt != nil { - upd = u.UpdatedAt.Format("2006-01-02") - } - - if u.DeletedAt != nil { - upd = u.DeletedAt.Format("2006-01-02") - } - - fmt.Printf( - "%20d %s %s %s\n", - u.ID, - upd, - del, - u.Email+" / "+u.Name+" / "+u.Username) - } -} - -func cliExecUsersMerge(params ...string) { - var ( - err error - uu = make([]*types.User, len(params)) - refs = make([]*userRefs, len(params)) - ids = payload.ParseUInt64s(params) - ctx = context.Background() - ) - - if len(ids) < 2 { - fmt.Printf("Expecting 2+ user IDs (2nd, 3rd ... user ID will be merged into first one\n") - os.Exit(1) - } - - for i, id := range ids { - if id == 0 { - fmt.Printf("Error: Invalid user ID %q\n", params[i]) - os.Exit(1) - } - - if uu[i], err = service.DefaultUser.With(ctx).FindByID(id); err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - } - - db := messagingRepository.DB(ctx) - - mergers := []struct { - label string - count func(userID uint64) (c int, err error) - merge func(userID, target uint64) (err error) - }{ - {label: "MsgOw", - count: messagingRepository.Message(ctx, db).CountOwned, - merge: messagingRepository.Message(ctx, db).ChangeOwner}, - {label: "MTags", - count: messagingRepository.Message(ctx, db).CountUserTags, - merge: messagingRepository.Message(ctx, db).ChangeUserTag}, - {label: "ChCre", - count: messagingRepository.Channel(ctx, db).CountCreated, - merge: messagingRepository.Channel(ctx, db).ChangeCreator}, - {label: "Membr", - count: messagingRepository.ChannelMember(ctx, db).CountMemberships, - merge: messagingRepository.ChannelMember(ctx, db).ChangeMembership}, - {label: "AttOw", - count: messagingRepository.Attachment(ctx, db).CountOwned, - merge: messagingRepository.Attachment(ctx, db).ChangeOwnership}, - {label: "Menti", - count: messagingRepository.Mention(ctx, db).CountMentions, - merge: messagingRepository.Mention(ctx, db).ChangeMention}, - {label: "Unrd", - count: messagingRepository.Unread(ctx, db).CountOwned, - merge: messagingRepository.Unread(ctx, db).ChangeOwner}, - {label: "CRAut", - count: crmRepository.Record(ctx, db).CountAuthored, - merge: crmRepository.Record(ctx, db).ChangeAuthor}, - {label: "CRRef", - count: crmRepository.Record(ctx, db).CountReferenced, - merge: crmRepository.Record(ctx, db).ChangeReferences}, - } - - count := func(u *types.User, r *userRefs) (out string) { - out = fmt.Sprintf( - "%20d | %-40s", - u.ID, - u.Email+" / "+u.Name+" / "+u.Username, - ) - - for _, m := range mergers { - if count, err := m.count(u.ID); err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } else { - out = out + fmt.Sprintf(" | %5d", count) - } - } - - return out + fmt.Sprintln() - } - - stats := fmt.Sprintf( - "%20s | %40s", - "ID", - "Email", - ) - - for _, m := range mergers { - stats = stats + fmt.Sprintf(" | %5s", m.label) - } - - stats = stats + fmt.Sprintln() + fmt.Sprintf("Merge %d users:\n", len(uu)-1) - for i := 1; i < len(uu); i++ { - stats = stats + count(uu[i], refs[i]) - } - stats = stats + fmt.Sprintln("Target") + count(uu[0], refs[0]) - - fmt.Println(stats) - - reader := bufio.NewReader(os.Stdin) - fmt.Print("Merge [y/N]? ") - text, _ := reader.ReadByte() - if "y" != strings.ToLower(string(text)) { - os.Exit(0) - } - - for i := 1; i < len(uu); i++ { - for _, m := range mergers { - if err := m.merge(uu[i].ID, uu[0].ID); err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - } - } - - fmt.Println("Done.") -} - -type userRefs struct { - messagesCreated int - messagesTagged int -} diff --git a/system/cli/roles.go b/system/cli/roles.go new file mode 100644 index 000000000..54b93c96a --- /dev/null +++ b/system/cli/roles.go @@ -0,0 +1,22 @@ +package cli + +import ( + "context" + "fmt" + "os" + + "github.com/crusttech/crust/system/internal/repository" +) + +func RolesReset() { + ctx := context.Background() + db := repository.DB(ctx) + + err := repository.Role(ctx, db).Reset() + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("Everyone and Administrators role were reset.") +} diff --git a/system/cli/users.go b/system/cli/users.go new file mode 100644 index 000000000..7f3ee6eb9 --- /dev/null +++ b/system/cli/users.go @@ -0,0 +1,42 @@ +package cli + +import ( + "context" + "fmt" + "os" + + "github.com/crusttech/crust/system/service" + "github.com/crusttech/crust/system/types" +) + +func UsersList() { + uf := &types.UserFilter{ + OrderBy: "updated_at", + } + + users, err := service.DefaultUser.With(context.Background()).Find(uf) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("ID Updated Deleted [email / name / username]") + for _, u := range users { + upd, del := "---- -- --", "---- -- --" + + if u.UpdatedAt != nil { + upd = u.UpdatedAt.Format("2006-01-02") + } + + if u.DeletedAt != nil { + upd = u.DeletedAt.Format("2006-01-02") + } + + fmt.Printf( + "%20d %s %s %s\n", + u.ID, + upd, + del, + u.Email+" / "+u.Name+" / "+u.Username) + } +} diff --git a/system/internal/repository/role.go b/system/internal/repository/role.go index d0bffd653..904c7ae1b 100644 --- a/system/internal/repository/role.go +++ b/system/internal/repository/role.go @@ -30,6 +30,8 @@ type ( MemberFindByRoleID(roleID uint64) ([]*types.RoleMember, error) MemberAddByID(roleID, userID uint64) error MemberRemoveByID(roleID, userID uint64) error + + Reset() error } role struct { @@ -147,3 +149,84 @@ func (r *role) MemberRemoveByID(roleID, userID uint64) error { } return r.db().Delete(r.members, mod, "rel_role", "rel_user") } + +func (r *role) Reset() error { + sql := `REPLACE INTO sys_role (id, name, handle) VALUES + (1, 'Everyone', 'everyone'), + (2, 'Administrators', 'admins') + ` + _, err := r.db().Exec(sql) + if err != nil { + return err + } + + // Value: Allow (2), Deny (1), Inherit(0) + sql = `REPLACE INTO sys_rules (rel_role, resource, operation, value) VALUES + -- Everyone + (1, 'compose:*', 'access', 2), + (1, 'messaging:*', 'access', 2), + -- Admins + (2, 'compose', 'namespace.create', 2), + (2, 'compose', 'access', 2), + (2, 'compose', 'grant', 2), + (2, 'compose:namespace:*', 'page.create', 2), + (2, 'compose:namespace:*', 'read', 2), + (2, 'compose:namespace:*', 'update', 2), + (2, 'compose:namespace:*', 'delete', 2), + (2, 'compose:namespace:*', 'module.create', 2), + (2, 'compose:namespace:*', 'chart.create', 2), + (2, 'compose:namespace:*', 'trigger.create', 2), + (2, 'compose:chart:*', 'read', 2), + (2, 'compose:chart:*', 'update', 2), + (2, 'compose:chart:*', 'delete', 2), + (2, 'compose:trigger:*', 'read', 2), + (2, 'compose:trigger:*', 'update', 2), + (2, 'compose:trigger:*', 'delete', 2), + (2, 'compose:page:*', 'read', 2), + (2, 'compose:page:*', 'update', 2), + (2, 'compose:page:*', 'delete', 2), + (2, 'system', 'access', 2), + (2, 'system', 'grant', 2), + (2, 'system', 'organisation.create', 2), + (2, 'system', 'role.create', 2), + (2, 'system:organisation:*', 'access', 2), + (2, 'system:role:*', 'read', 2), + (2, 'system:role:*', 'update', 2), + (2, 'system:role:*', 'delete', 2), + (2, 'system:role:*', 'members.manage', 2), + (2, 'messaging', 'access', 2), + (2, 'messaging', 'grant', 2), + (2, 'messaging', 'channel.public.create', 2), + (2, 'messaging', 'channel.private.create', 2), + (2, 'messaging', 'channel.group.create', 2), + (2, 'messaging:channel:*', 'update', 2), + (2, 'messaging:channel:*', 'leave', 2), + (2, 'messaging:channel:*', 'read', 2), + (2, 'messaging:channel:*', 'join', 2), + (2, 'messaging:channel:*', 'delete', 2), + (2, 'messaging:channel:*', 'undelete', 2), + (2, 'messaging:channel:*', 'archive', 2), + (2, 'messaging:channel:*', 'unarchive', 2), + (2, 'messaging:channel:*', 'members.manage', 2), + (2, 'messaging:channel:*', 'webhooks.manage', 2), + (2, 'messaging:channel:*', 'attachments.manage', 2), + (2, 'messaging:channel:*', 'message.attach', 2), + (2, 'messaging:channel:*', 'message.update.all', 2), + (2, 'messaging:channel:*', 'message.update.own', 2), + (2, 'messaging:channel:*', 'message.delete.all', 2), + (2, 'messaging:channel:*', 'message.delete.own', 2), + (2, 'messaging:channel:*', 'message.embed', 2), + (2, 'messaging:channel:*', 'message.send', 2), + (2, 'messaging:channel:*', 'message.reply', 2), + (2, 'messaging:channel:*', 'message.react', 2), + (2, 'compose:module:*', 'read', 2), + (2, 'compose:module:*', 'update', 2), + (2, 'compose:module:*', 'delete', 2), + (2, 'compose:module:*', 'record.create', 2), + (2, 'compose:module:*', 'record.read', 2), + (2, 'compose:module:*', 'record.update', 2), + (2, 'compose:module:*', 'record.delete', 2) + ` + _, err = r.db().Exec(sql) + return err +}