From 10f20deafa294b4ea2360b264e9aa420a9f664fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Ny=C3=ADri?= Date: Sat, 13 Jan 2024 21:12:11 +0100 Subject: [PATCH] users and groups commands: added subcommands to be prepared for editing capabilities later --- README.md | 8 +++-- cli/cmd/groups.go | 70 +++++--------------------------------- cli/cmd/groupsAdd.go | 19 +++++++++++ cli/cmd/groupsDelete.go | 19 +++++++++++ cli/cmd/groupsList.go | 69 +++++++++++++++++++++++++++++++++++++ cli/cmd/passwordChange.go | 19 +++++++++++ cli/cmd/users.go | 70 +++++--------------------------------- cli/cmd/usersCreate.go | 19 +++++++++++ cli/cmd/usersDelete.go | 19 +++++++++++ cli/cmd/usersList.go | 69 +++++++++++++++++++++++++++++++++++++ gateway.webp | Bin 0 -> 7906 bytes 11 files changed, 254 insertions(+), 127 deletions(-) create mode 100644 cli/cmd/groupsAdd.go create mode 100644 cli/cmd/groupsDelete.go create mode 100644 cli/cmd/groupsList.go create mode 100644 cli/cmd/passwordChange.go create mode 100644 cli/cmd/usersCreate.go create mode 100644 cli/cmd/usersDelete.go create mode 100644 cli/cmd/usersList.go create mode 100644 gateway.webp diff --git a/README.md b/README.md index 2e670f7..4f4152a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ If all goes fine, later this repository will provide you both a GoLang SDK and a - manage users - etc. +![gateway image](gateway.webp) + ## TODOs * [ ] Create json output for machines. Improve documentation accordingly. * [x] Add retries. `status` command produces `PORT_ERROR` quite frequently while second try works fine. @@ -31,7 +33,7 @@ Available Commands: completion Generate the autocompletion script for the specified shell discover Discover Hörmann BiSecur gateways on the local network get-name Queries the name of the Hörmann BiSecur gateway - groups Manages users defined in your Hörmann BiSecur gateway. + groups Manages doors defined in your Hörmann BiSecur gateway. help Help about any command login logout @@ -95,7 +97,7 @@ INFO[2023-12-26T21:00:19+01:00] Token: 0x7974DB57 ### Get users ```bash -$ ./dist/halsecur users +$ ./dist/halsecur users list DEBU[2023-12-26T21:01:38+01:00] Connecting to 192.168.3.232:4000 DEBU[2023-12-26T21:01:38+01:00] Request: SrcMAC=0x000000000009, DstMAC=0x5410EC8528BB, BodyLength=0x0, packet=[Tag=0x1, Token=0x7974DB57, CommandID=0x6 (0x6), payload=[Jcmp: {"CMD":"GET_USERS"}], Checksum=0x0, isResponse=false], Checksum=0x0, isResponse: false DEBU[2023-12-26T21:01:38+01:00] Request bytes: 30303030303030303030303935343130454338353238424230303143303137393734444235373036374232323433344434343232334132323437343535343546353535333435353235333232374441314431 @@ -107,7 +109,7 @@ INFO[2023-12-26T21:01:38+01:00] Users: [ID=0, Name="admin", IsAdmin=true, Groups ### Get groups ```bash -$ ./dist/halsecur groups +$ ./dist/halsecur groups list DEBU[2023-12-26T21:02:20+01:00] Connecting to 192.168.3.232:4000 DEBU[2023-12-26T21:02:20+01:00] Request: SrcMAC=0x000000000009, DstMAC=0x5410EC8528BB, BodyLength=0x0, packet=[Tag=0x1, Token=0x7974DB57, CommandID=0x6 (0x6), payload=[Jcmp: {"CMD":"GET_GROUPS"}], Checksum=0x0, isResponse=false], Checksum=0x0, isResponse: false DEBU[2023-12-26T21:02:20+01:00] Request bytes: 303030303030303030303039353431304543383532384242303031443031373937344442353730363742323234333444343432323341323234373435353435463437353234463535353035333232374446303446 diff --git a/cli/cmd/groups.go b/cli/cmd/groups.go index 7a6a34c..166b096 100644 --- a/cli/cmd/groups.go +++ b/cli/cmd/groups.go @@ -1,71 +1,17 @@ package cmd import ( - "bisecur/cli" - "bisecur/sdk" - "github.com/spf13/viper" - "os" - "github.com/spf13/cobra" ) -func init() { - groupsCmd := &cobra.Command{ - Use: GroupsCmdName, - Short: "Manages users defined in your Hörmann BiSecur gateway.", - Long: ``, - PreRunE: preRunFuncs, - Run: func(cmd *cobra.Command, args []string) { - // TODO implement query user list and rights, add and delete user, password change of an already existing user - - deviceMac := viper.GetString(ArgNameDeviceMac) - host := viper.GetString(ArgNameHost) - port := viper.GetInt(ArgNamePort) - token := viper.GetUint32(ArgNameToken) - - mac, err := cli.ParesMacString(deviceMac) - if err != nil { - log.Fatalf("%v", err) - os.Exit(1) - } - - err = listGroups(localMac, mac, host, port, token) - if err != nil { - log.Fatalf("%v", err) - os.Exit(2) - } - }, - } - - rootCmd.AddCommand(groupsCmd) +var groupsCmd = &cobra.Command{ + Use: GroupsCmdName, + Short: "Manages doors defined in your Hörmann BiSecur gateway.", + Long: ``, + PreRunE: preRunFuncs, + Run: nil, } -func listGroups(localMac [6]byte, mac [6]byte, host string, port int, token uint32) error { - client := sdk.NewClient(log, localMac, mac, host, port, token) - err := client.Open() - if err != nil { - return err - } - - defer func() { - err2 := client.Close() - if err2 != nil { - log.Errorf("%v", err) - } - }() - - var groups *sdk.Groups - err = retry(func() error { - var err2 error - groups, err2 = client.GetGroups() - return err2 - }) - - if err != nil { - return err - } - - log.Infof("Groups: %s", groups.String()) - - return nil +func init() { + rootCmd.AddCommand(groupsCmd) } diff --git a/cli/cmd/groupsAdd.go b/cli/cmd/groupsAdd.go new file mode 100644 index 0000000..4e6c591 --- /dev/null +++ b/cli/cmd/groupsAdd.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var groupsCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new gateway group", + Long: `Create a new gateway group`, + PreRunE: preRunFuncs, + Run: func(cmd *cobra.Command, args []string) { + log.Fatalf("Not implemented yet") + }, +} + +func init() { + groupsCmd.AddCommand(groupsCreateCmd) +} diff --git a/cli/cmd/groupsDelete.go b/cli/cmd/groupsDelete.go new file mode 100644 index 0000000..4275f98 --- /dev/null +++ b/cli/cmd/groupsDelete.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var groupsDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete a gateway group", + Long: `Delete a gateway group`, + PreRunE: preRunFuncs, + Run: func(cmd *cobra.Command, args []string) { + log.Fatalf("Not implemented yet") + }, +} + +func init() { + groupsCmd.AddCommand(groupsDeleteCmd) +} diff --git a/cli/cmd/groupsList.go b/cli/cmd/groupsList.go new file mode 100644 index 0000000..677bc1f --- /dev/null +++ b/cli/cmd/groupsList.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "bisecur/cli" + "bisecur/sdk" + "github.com/spf13/viper" + "os" + + "github.com/spf13/cobra" +) + +var groupsListCmd = &cobra.Command{ + Use: "list", + Short: "List current gateway groups", + Long: `List current gateway groups`, + PreRunE: preRunFuncs, + Run: func(cmd *cobra.Command, args []string) { + deviceMac := viper.GetString(ArgNameDeviceMac) + host := viper.GetString(ArgNameHost) + port := viper.GetInt(ArgNamePort) + token := viper.GetUint32(ArgNameToken) + + mac, err := cli.ParesMacString(deviceMac) + if err != nil { + log.Fatalf("%v", err) + os.Exit(1) + } + + err = listGroups(localMac, mac, host, port, token) + if err != nil { + log.Fatalf("%v", err) + os.Exit(2) + } + }, +} + +func init() { + groupsCmd.AddCommand(groupsListCmd) +} + +func listGroups(localMac [6]byte, mac [6]byte, host string, port int, token uint32) error { + client := sdk.NewClient(log, localMac, mac, host, port, token) + err := client.Open() + if err != nil { + return err + } + + defer func() { + err2 := client.Close() + if err2 != nil { + log.Errorf("%v", err) + } + }() + + var groups *sdk.Groups + err = retry(func() error { + var err2 error + groups, err2 = client.GetGroups() + return err2 + }) + + if err != nil { + return err + } + + log.Infof("Groups: %s", groups.String()) + + return nil +} diff --git a/cli/cmd/passwordChange.go b/cli/cmd/passwordChange.go new file mode 100644 index 0000000..23dc59a --- /dev/null +++ b/cli/cmd/passwordChange.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// passwordChangeCmd represents the passwordChange command +var passwordChangeCmd = &cobra.Command{ + Use: "passwordChange", + Short: "Change password of a gateway user", + Long: `Change password of a gateway user`, + Run: func(cmd *cobra.Command, args []string) { + log.Fatalf("Not implemented yet") + }, +} + +func init() { + usersCmd.AddCommand(passwordChangeCmd) +} diff --git a/cli/cmd/users.go b/cli/cmd/users.go index 31a2a96..4adc67a 100644 --- a/cli/cmd/users.go +++ b/cli/cmd/users.go @@ -1,71 +1,17 @@ package cmd import ( - "bisecur/cli" - "bisecur/sdk" - "github.com/spf13/viper" - "os" - "github.com/spf13/cobra" ) -func init() { - usersCmd := &cobra.Command{ - Use: UsersCmdUse, - Short: "Manages users defined in your Hörmann BiSecur gateway.", - Long: ``, - PreRunE: preRunFuncs, - Run: func(cmd *cobra.Command, args []string) { - // TODO implement query user list and rights, add and delete user, password change of an already existing user - - deviceMac := viper.GetString(ArgNameDeviceMac) - host := viper.GetString(ArgNameHost) - port := viper.GetInt(ArgNamePort) - token := viper.GetUint32(ArgNameToken) - - mac, err := cli.ParesMacString(deviceMac) - if err != nil { - log.Fatalf("%v", err) - os.Exit(1) - } - - err = listUsers(localMac, mac, host, port, token) - if err != nil { - log.Fatalf("%v", err) - os.Exit(2) - } - }, - } - - rootCmd.AddCommand(usersCmd) +var usersCmd = &cobra.Command{ + Use: UsersCmdUse, + Short: "Manages users defined in your Hörmann BiSecur gateway.", + Long: ``, + PreRunE: preRunFuncs, + Run: nil, } -func listUsers(localMac [6]byte, mac [6]byte, host string, port int, token uint32) error { - client := sdk.NewClient(log, localMac, mac, host, port, token) - err := client.Open() - if err != nil { - return err - } - - defer func() { - err2 := client.Close() - if err2 != nil { - log.Errorf("%v", err) - } - }() - - var users *sdk.Users - err = retry(func() error { - var err2 error - users, err2 = client.GetUsers() - return err2 - }) - - if err != nil { - return err - } - - log.Infof("Users: %s", users.String()) - - return nil +func init() { + rootCmd.AddCommand(usersCmd) } diff --git a/cli/cmd/usersCreate.go b/cli/cmd/usersCreate.go new file mode 100644 index 0000000..fd800aa --- /dev/null +++ b/cli/cmd/usersCreate.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var usersCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new gateway user", + Long: `Create a new gateway user`, + PreRunE: preRunFuncs, + Run: func(cmd *cobra.Command, args []string) { + log.Fatalf("Not implemented yet") + }, +} + +func init() { + usersCmd.AddCommand(usersCreateCmd) +} diff --git a/cli/cmd/usersDelete.go b/cli/cmd/usersDelete.go new file mode 100644 index 0000000..28593b6 --- /dev/null +++ b/cli/cmd/usersDelete.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var usersDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete a gateway user", + Long: `Delete a gateway user`, + PreRunE: preRunFuncs, + Run: func(cmd *cobra.Command, args []string) { + log.Fatalf("Not implemented yet") + }, +} + +func init() { + usersCmd.AddCommand(usersDeleteCmd) +} diff --git a/cli/cmd/usersList.go b/cli/cmd/usersList.go new file mode 100644 index 0000000..da93ce8 --- /dev/null +++ b/cli/cmd/usersList.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "bisecur/cli" + "bisecur/sdk" + "github.com/spf13/viper" + "os" + + "github.com/spf13/cobra" +) + +var usersListCmd = &cobra.Command{ + Use: "list", + Short: "List current gateway users", + Long: `List current gateway users`, + PreRunE: preRunFuncs, + Run: func(cmd *cobra.Command, args []string) { + deviceMac := viper.GetString(ArgNameDeviceMac) + host := viper.GetString(ArgNameHost) + port := viper.GetInt(ArgNamePort) + token := viper.GetUint32(ArgNameToken) + + mac, err := cli.ParesMacString(deviceMac) + if err != nil { + log.Fatalf("%v", err) + os.Exit(1) + } + + err = listUsers(localMac, mac, host, port, token) + if err != nil { + log.Fatalf("%v", err) + os.Exit(2) + } + }, +} + +func init() { + usersCmd.AddCommand(usersListCmd) +} + +func listUsers(localMac [6]byte, mac [6]byte, host string, port int, token uint32) error { + client := sdk.NewClient(log, localMac, mac, host, port, token) + err := client.Open() + if err != nil { + return err + } + + defer func() { + err2 := client.Close() + if err2 != nil { + log.Errorf("%v", err) + } + }() + + var users *sdk.Users + err = retry(func() error { + var err2 error + users, err2 = client.GetUsers() + return err2 + }) + + if err != nil { + return err + } + + log.Infof("Users: %s", users.String()) + + return nil +} diff --git a/gateway.webp b/gateway.webp new file mode 100644 index 0000000000000000000000000000000000000000..0a3cd82822badb267499c53a3779fbb8e9de5df9 GIT binary patch literal 7906 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 M000000000000)*l#Q*>R literal 0 HcmV?d00001