From 84ed9b278bea506fe914fc45fa58bb82f77804a1 Mon Sep 17 00:00:00 2001 From: Rustam Gilyazov <16064414+rusq@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:56:26 +1000 Subject: [PATCH] tool info auth --- cmd/slackdump/internal/diag/eztest.go | 12 ++-- cmd/slackdump/internal/diag/info.go | 27 +++++++- cmd/slackdump/internal/diag/info/auth.go | 62 ++++++++++++++++++ .../internal/diag/info/auth_posix.go | 23 +++++++ .../internal/diag/info/auth_windows.go | 63 +++++++++++++++++++ cmd/slackdump/internal/diag/rawoutput.go | 6 +- go.mod | 2 +- go.sum | 2 + 8 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 cmd/slackdump/internal/diag/info/auth.go create mode 100644 cmd/slackdump/internal/diag/info/auth_posix.go create mode 100644 cmd/slackdump/internal/diag/info/auth_windows.go diff --git a/cmd/slackdump/internal/diag/eztest.go b/cmd/slackdump/internal/diag/eztest.go index de01b024..319a3090 100644 --- a/cmd/slackdump/internal/diag/eztest.go +++ b/cmd/slackdump/internal/diag/eztest.go @@ -32,7 +32,7 @@ be printed and the test will be terminated. CustomFlags: true, } -type result struct { +type ezResult struct { Engine string `json:"engine,omitempty"` HasToken bool `json:"has_token,omitempty"` HasCookies bool `json:"has_cookies,omitempty"` @@ -64,7 +64,7 @@ func runEzLoginTest(ctx context.Context, cmd *base.Command, args []string) error } var ( - res result + res ezResult ) if *legacy { @@ -90,8 +90,8 @@ func runEzLoginTest(ctx context.Context, cmd *base.Command, args []string) error } -func tryPlaywrightAuth(ctx context.Context, wsp string) result { - var res = result{Engine: "playwright"} +func tryPlaywrightAuth(ctx context.Context, wsp string) ezResult { + var res = ezResult{Engine: "playwright"} if err := playwright.Install(&playwright.RunOptions{Browsers: []string{"firefox"}}); err != nil { res.Err = ptr(fmt.Sprintf("playwright installation error: %s", err)) @@ -117,8 +117,8 @@ func ptr[T any](t T) *T { return &t } -func tryRodAuth(ctx context.Context, wsp string) result { - ret := result{Engine: "rod"} +func tryRodAuth(ctx context.Context, wsp string) ezResult { + ret := ezResult{Engine: "rod"} prov, err := auth.NewRODAuth(ctx, auth.BrowserWithWorkspace(wsp)) if err != nil { ret.Err = ptr(err.Error()) diff --git a/cmd/slackdump/internal/diag/info.go b/cmd/slackdump/internal/diag/info.go index 2d42303e..0bd5bd70 100644 --- a/cmd/slackdump/internal/diag/info.go +++ b/cmd/slackdump/internal/diag/info.go @@ -3,6 +3,7 @@ package diag import ( "context" "encoding/json" + "io" "os" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/diag/info" @@ -14,14 +15,38 @@ var CmdInfo = &base.Command{ UsageLine: "slackdump tools info", Short: "show information about Slackdump environment", Run: runInfo, + Long: `# Info Command **Info** shows information about Slackdump environment, such as local system paths, etc. `, } +var infoParams = struct { + auth bool +}{ + auth: false, +} + +func init() { + CmdInfo.Flag.BoolVar(&infoParams.auth, "auth", false, "show authentication diagnostic information") +} + func runInfo(ctx context.Context, cmd *base.Command, args []string) error { - enc := json.NewEncoder(os.Stdout) + switch { + case infoParams.auth: + return runAuthInfo(ctx, os.Stdout) + default: + return runGeneralInfo(ctx, os.Stdout) + } +} + +func runAuthInfo(ctx context.Context, w io.Writer) error { + return info.CollectAuth(ctx, w) +} + +func runGeneralInfo(_ context.Context, w io.Writer) error { + enc := json.NewEncoder(w) enc.SetIndent("", " ") if err := enc.Encode(info.Collect()); err != nil { return err diff --git a/cmd/slackdump/internal/diag/info/auth.go b/cmd/slackdump/internal/diag/info/auth.go new file mode 100644 index 00000000..b5814877 --- /dev/null +++ b/cmd/slackdump/internal/diag/info/auth.go @@ -0,0 +1,62 @@ +package info + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "text/tabwriter" + + "github.com/rusq/encio" + "github.com/rusq/slackdump/v3/auth" + "github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg" + "github.com/rusq/slackdump/v3/internal/cache" +) + +func CollectAuth(ctx context.Context, w io.Writer) error { + // lg := logger.FromContext(ctx) + fmt.Fprintln(os.Stderr, "To confirm the operation, please enter your OS password.") + if err := osValidateUser(ctx, os.Stderr); err != nil { + return err + } + m, err := cache.NewManager(cfg.CacheDir()) + if err != nil { + return fmt.Errorf("cache error: %w", err) + } + cur, err := m.Current() + if err != nil { + return fmt.Errorf("cache error: %w", err) + } + fi, err := m.FileInfo(cur) + if err != nil { + return fmt.Errorf("cache error: %w", err) + } + f, err := encio.Open(filepath.Join(cfg.CacheDir(), fi.Name())) + if err != nil { + return fmt.Errorf("cache error: %w", err) + } + defer f.Close() + prov, err := auth.Load(f) + if err != nil { + return fmt.Errorf("cache error: %w", err) + } + if err := dumpCookiesMozilla(ctx, w, prov.Cookies()); err != nil { + return err + } + return nil +} + +// dumpCookiesMozilla dumps cookies in Mozilla format. +func dumpCookiesMozilla(_ context.Context, w io.Writer, cookies []*http.Cookie) error { + tw := tabwriter.NewWriter(w, 0, 8, 0, '\t', 0) + defer tw.Flush() + fmt.Fprintf(tw, "# name@domain\tvalue_len\tflag\tpath\tsecure\texpiration\n") + for _, c := range cookies { + fmt.Fprintf(tw, "%s\t%9d\t%s\t%s\t%s\t%d\n", + c.Name+"@"+c.Domain, len(c.Value), "TRUE", c.Path, strings.ToUpper(fmt.Sprintf("%v", c.Secure)), c.Expires.Unix()) + } + return nil +} diff --git a/cmd/slackdump/internal/diag/info/auth_posix.go b/cmd/slackdump/internal/diag/info/auth_posix.go new file mode 100644 index 00000000..42700531 --- /dev/null +++ b/cmd/slackdump/internal/diag/info/auth_posix.go @@ -0,0 +1,23 @@ +//go:build !windows + +package info + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" +) + +func osValidateUser(ctx context.Context, w io.Writer) error { + cmd := exec.CommandContext(ctx, "sudo", "-v") + cmd.Stdin = os.Stdin + cmd.Stdout = w + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + return fmt.Errorf("authentication failed: %w", err) + } + return nil +} diff --git a/cmd/slackdump/internal/diag/info/auth_windows.go b/cmd/slackdump/internal/diag/info/auth_windows.go new file mode 100644 index 00000000..e2bf462f --- /dev/null +++ b/cmd/slackdump/internal/diag/info/auth_windows.go @@ -0,0 +1,63 @@ +//go:build windows + +package info + +import ( + "context" + "fmt" + "io" + "syscall" + "unsafe" + + "golang.org/x/term" +) + +var ( + advapi32 = syscall.NewLazyDLL("advapi32.dll") + procLogonUserW = advapi32.NewProc("LogonUserW") +) + +func must[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} + +// Untested +func logonUser(username, domain, password string) (bool, error) { + var token syscall.Handle + r1, _, err := procLogonUserW.Call( + uintptr(unsafe.Pointer(must(syscall.UTF16PtrFromString(username)))), + uintptr(unsafe.Pointer(must(syscall.UTF16PtrFromString(domain)))), + uintptr(unsafe.Pointer(must(syscall.UTF16PtrFromString(password)))), + uintptr(2), // LOGON32_LOGON_INTERACTIVE + uintptr(0), // LOGON32_PROVIDER_DEFAULT + uintptr(unsafe.Pointer(&token)), + ) + if r1 == 0 { + return false, err + } + defer syscall.CloseHandle(token) + return true, nil +} + +func osValidateUser(_ context.Context, w io.Writer) error { + fmt.Fprint(w, "Enter username: ") + var username string + fmt.Scanln(&username) + fmt.Fprintf(w, "Enter password for %s: ", username) + password, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil { + return fmt.Errorf("failed to read password: %w", err) + } + domain := "." // Use "." for local account + ok, err := logonUser(username, domain, string(password)) + if err != nil { + return fmt.Errorf("authentication error: %w", err) + } + if !ok { + return fmt.Errorf("authentication failed") + } + return nil +} diff --git a/cmd/slackdump/internal/diag/rawoutput.go b/cmd/slackdump/internal/diag/rawoutput.go index f39718b4..2519ac30 100644 --- a/cmd/slackdump/internal/diag/rawoutput.go +++ b/cmd/slackdump/internal/diag/rawoutput.go @@ -41,13 +41,13 @@ Running this tool may be requested by developers. Commands: nil, } -type params struct { +type rawOutputParams struct { output string idOrURL string } -var p params +var p rawOutputParams func init() { CmdRawOutput.Run = runRawOutput @@ -74,7 +74,7 @@ const ( baseURL = domain + "/api/" ) -func run(ctx context.Context, p params) error { +func run(ctx context.Context, p rawOutputParams) error { prov, err := auth.FromContext(ctx) if err != nil { return err diff --git a/go.mod b/go.mod index ddaa6ee8..07b7ce4e 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/rusq/fsadapter v1.0.1 github.com/rusq/osenv/v2 v2.0.1 github.com/rusq/slack v0.9.6-0.20240211120639-93c163940e55 - github.com/rusq/slackauth v0.0.8 + github.com/rusq/slackauth v0.1.1 github.com/rusq/tracer v1.0.1 github.com/schollz/progressbar/v3 v3.13.0 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index cca7697e..f7c3751f 100644 --- a/go.sum +++ b/go.sum @@ -117,6 +117,8 @@ github.com/rusq/slack v0.9.6-0.20240211120639-93c163940e55 h1:JdOHVL0ES/T5bvEFmE github.com/rusq/slack v0.9.6-0.20240211120639-93c163940e55/go.mod h1:9O0zQAFN6W47z4KpTQbe6vOHOzBO76vMg1+gthPwaTI= github.com/rusq/slackauth v0.0.8 h1:qJVnEsfXeqQ32WsQUqigBA1yWJEOf1029/2at98qVog= github.com/rusq/slackauth v0.0.8/go.mod h1:4fDQ7VFncP2F1k/oHPXCOjIVw//IkoJcu1Ho3tgxVi8= +github.com/rusq/slackauth v0.1.1 h1:nTAjZQ6UHoGzfV02IQQUyUrAR0JeEqfy6snD7D6cMzk= +github.com/rusq/slackauth v0.1.1/go.mod h1:0fWxVftSfAgULWCcflvy4q20/qY39aB8v0jLDaDLejM= github.com/rusq/tracer v1.0.1 h1:5u4PCV8NGO97VuAINQA4gOVRkPoqHimLE2jpezRVNMU= github.com/rusq/tracer v1.0.1/go.mod h1:Rqu48C3/K8bA5NPmF20Hft73v431MQIdM+Co+113pME= github.com/schollz/progressbar/v3 v3.13.0 h1:9TeeWRcjW2qd05I8Kf9knPkW4vLM/hYoa6z9ABvxje8=