Skip to content

Commit

Permalink
Merge pull request rusq#98 from rusq/creds-store
Browse files Browse the repository at this point in the history
Credentials encryption and storage
  • Loading branch information
rusq authored Aug 2, 2022
2 parents f3131d5 + b5a8795 commit da089ac
Show file tree
Hide file tree
Showing 34 changed files with 1,706 additions and 97 deletions.
20 changes: 20 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.241.1/containers/go/.devcontainer/base.Dockerfile

# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.18, 1.17, 1-bullseye, 1.18-bullseye, 1.17-bullseye, 1-buster, 1.18-buster, 1.17-buster
ARG VARIANT="1.18-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT}

# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>

# [Optional] Uncomment the next lines to use go get to install anything else you need
# USER vscode
# RUN go get -x <your-dependency-or-tool>

# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ secrets.*
cmd/slackdump/slackdump
cmd/sdconv/sdconv
/slackdump
/TODO.*
/*.txt
*~
.env
Expand Down
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM golang:1.18.4

WORKDIR /build

COPY . .


RUN go test ./...
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ clean:
test:
go test -race -cover -count=3 ./...

docker_test:
docker build .

man: slackdump.1

slackdump.1: README.rst
Expand Down
46 changes: 40 additions & 6 deletions auth/auth.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package auth

import (
"encoding/json"
"errors"
"io"
"net/http"
)

Expand Down Expand Up @@ -35,26 +37,26 @@ var (
)

type simpleProvider struct {
token string
cookies []http.Cookie
Token string
Cookie []http.Cookie
}

func (c simpleProvider) Validate() error {
if c.token == "" {
if c.Token == "" {
return ErrNoToken
}
if len(c.cookies) == 0 {
if len(c.Cookie) == 0 {
return ErrNoCookies
}
return nil
}

func (c simpleProvider) SlackToken() string {
return c.token
return c.Token
}

func (c simpleProvider) Cookies() []http.Cookie {
return c.cookies
return c.Cookie
}

// deref dereferences []*T to []T.
Expand All @@ -65,3 +67,35 @@ func deref[T any](cc []*T) []T {
}
return ret
}

// Load deserialises JSON data from reader and returns a ValueAuth, that can
// be used to authenticate Slackdump. It will return ErrNoToken or
// ErrNoCookie if the authentication information is missing.
func Load(r io.Reader) (ValueAuth, error) {
dec := json.NewDecoder(r)
var s simpleProvider
if err := dec.Decode(&s); err != nil {
return ValueAuth{}, err
}
return ValueAuth{s}, s.Validate()
}

// Save serialises authentication information to writer. It will return
// ErrNoToken or ErrNoCookie if provider fails validation.
func Save(w io.Writer, p Provider) error {
if err := p.Validate(); err != nil {
return err
}

var s = simpleProvider{
Token: p.SlackToken(),
Cookie: p.Cookies(),
}

enc := json.NewEncoder(w)
if err := enc.Encode(s); err != nil {
return err
}

return nil
}
109 changes: 109 additions & 0 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package auth

import (
"bytes"
"io"
"net/http"
"reflect"
"strings"
"testing"
)

func TestLoad(t *testing.T) {
type args struct {
r io.Reader
}
tests := []struct {
name string
args args
want ValueAuth
wantErr bool
}{
{
"loads valid data",
args{strings.NewReader(`{"Token":"token_value","Cookie":[{"Name":"d","Value":"abc","Path":"","Domain":"","Expires":"0001-01-01T00:00:00Z","RawExpires":"","MaxAge":0,"Secure":false,"HttpOnly":false,"SameSite":0,"Raw":"","Unparsed":null}]}`)},
ValueAuth{simpleProvider{Token: "token_value", Cookie: []http.Cookie{
{Name: "d", Value: "abc"},
}}},
false,
},
{
"corrupt data",
args{strings.NewReader(`{`)},
ValueAuth{},
true,
},
{
"no data",
args{strings.NewReader(``)},
ValueAuth{},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Load(tt.args.r)
if (err != nil) != tt.wantErr {
t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Load() = %v, want %v", got, tt.want)
}
})
}
}

func TestSave(t *testing.T) {
type args struct {
p Provider
}
tests := []struct {
name string
args args
wantW string
wantErr bool
}{
{
"all info present",
args{ValueAuth{simpleProvider{Token: "token_value", Cookie: []http.Cookie{
{Name: "d", Value: "abc"},
}}}},
`{"Token":"token_value","Cookie":[{"Name":"d","Value":"abc","Path":"","Domain":"","Expires":"0001-01-01T00:00:00Z","RawExpires":"","MaxAge":0,"Secure":false,"HttpOnly":false,"SameSite":0,"Raw":"","Unparsed":null}]}` + "\n",
false,
},
{
"token missing",
args{ValueAuth{simpleProvider{Token: "", Cookie: []http.Cookie{
{Name: "d", Value: "abc"},
}}}},
"",
true,
},
{
"cookies missing",
args{ValueAuth{simpleProvider{Token: "token_value", Cookie: []http.Cookie{}}}},
"",
true,
},
{
"token and cookie are missing",
args{ValueAuth{simpleProvider{}}},
"",
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &bytes.Buffer{}
if err := Save(w, tt.args.p); (err != nil) != tt.wantErr {
t.Errorf("Save() error = %v, wantErr %v", err, tt.wantErr)
return
}
gotW := w.String()
if gotW != tt.wantW {
t.Errorf("Save() = %v, want %v", gotW, tt.wantW)
}
})
}
}
4 changes: 2 additions & 2 deletions auth/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ func NewBrowserAuth(ctx context.Context, opts ...BrowserOption) (BrowserAuth, er
return br, err
}
br.simpleProvider = simpleProvider{
token: token,
cookies: cookies,
Token: token,
Cookie: cookies,
}

return br, nil
Expand Down
4 changes: 2 additions & 2 deletions auth/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ func NewCookieFileAuth(token string, cookieFile string) (CookieFileAuth, error)
}
fc := CookieFileAuth{
simpleProvider: simpleProvider{
token: token,
cookies: deref(ptrCookies),
Token: token,
Cookie: deref(ptrCookies),
},
}
return fc, nil
Expand Down
6 changes: 3 additions & 3 deletions auth/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ func NewValueAuth(token string, cookie string) (ValueAuth, error) {
return ValueAuth{}, ErrNoCookies
}
return ValueAuth{simpleProvider{
token: token,
cookies: []http.Cookie{
Token: token,
Cookie: []http.Cookie{
makeCookie("d", cookie),
makeCookie("d-s", fmt.Sprintf("%d", time.Now().Unix()-10)),
},
Expand All @@ -51,7 +51,7 @@ func makeCookie(key, val string) http.Cookie {
Value: val,
Path: defaultPath,
Domain: defaultDomain,
Expires: timeFunc().AddDate(10, 0, 0),
Expires: timeFunc().AddDate(10, 0, 0).UTC(),
Secure: true,
}
}
Expand Down
33 changes: 20 additions & 13 deletions cmd/slackdump/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"syscall"

"github.com/joho/godotenv"
"github.com/rusq/dlog"
"github.com/rusq/osenv/v2"
"github.com/rusq/tracer"
"github.com/slack-go/slack"
Expand Down Expand Up @@ -48,8 +49,9 @@ var secrets = []string{".env", ".env.txt", "secrets.txt"}

// params is the command line parameters
type params struct {
appCfg app.Config
creds app.SlackCreds
appCfg app.Config
creds app.SlackCreds
authReset bool

traceFile string // trace file
logFile string //log file, if not specified, outputs to stderr.
Expand All @@ -71,13 +73,16 @@ func main() {
fmt.Println(build)
return
}
if params.authReset {
if err := app.AuthReset(params.appCfg.Options.CacheDir); err != nil {
if !os.IsNotExist(err) {
dlog.Printf("auth reset error: %s", err)
}
}
}

if err := run(context.Background(), params); err != nil {
if params.verbose {
log.Fatalf("%+v", err)
} else {
log.Fatal(err)
}
log.Fatal(err)
}
}

Expand All @@ -89,6 +94,7 @@ func run(ctx context.Context, p params) error {
return err
}
defer logStopFn()
ctx = dlog.NewContext(ctx, lg)

// - setting the logger for slackdump package
p.appCfg.Options.Logger = lg
Expand All @@ -104,10 +110,9 @@ func run(ctx context.Context, p params) error {
ctx, task := trace.NewTask(ctx, "main.run")
defer task.End()

// init the authentication provider
provider, err := p.creds.AuthProvider(ctx, "")
provider, err := app.InitProvider(ctx, p.appCfg.Options.CacheDir, "", p.creds)
if err != nil {
return fmt.Errorf("failed to initialise the auth provider: %w", err)
return err
} else {
p.creds = app.SlackCreds{}
}
Expand All @@ -116,7 +121,7 @@ func run(ctx context.Context, p params) error {
trace.Logf(ctx, "info", "params: input: %+v", p)

// override default handler for SIGTERM and SIGQUIT signals.
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
defer stop()

// run the application
Expand All @@ -136,7 +141,7 @@ func run(ctx context.Context, p params) error {
// initialised logger, stop function and an error, if any. The stop function
// must be called in the deferred call, it will close the log file, if it is
// open. If the error is returned the stop function is nil.
func initLog(filename string, verbose bool) (logger.Interface, func(), error) {
func initLog(filename string, verbose bool) (*dlog.Logger, func(), error) {
lg := logger.Default
lg.SetDebug(verbose)

Expand Down Expand Up @@ -223,6 +228,7 @@ func parseCmdLine(args []string) (params, error) {
// authentication
fs.StringVar(&p.creds.Token, "t", osenv.Secret(slackTokenEnv, ""), "Specify slack `API_token`, (environment: "+slackTokenEnv+")")
fs.StringVar(&p.creds.Cookie, "cookie", osenv.Secret(slackCookieEnv, ""), "d= cookie `value` or a path to a cookie.txt file (environment: "+slackCookieEnv+")")
fs.BoolVar(&p.authReset, "auth-reset", false, "reset EZ-Login 3000 authentication.")

// operation mode
fs.BoolVar(&p.appCfg.ListFlags.Channels, "c", false, "same as -list-channels")
Expand Down Expand Up @@ -262,7 +268,8 @@ func parseCmdLine(args []string) (params, error) {
fs.IntVar(&p.appCfg.Options.ChannelsPerReq, "npr", slackdump.DefOptions.ChannelsPerReq, "number of `channels` per request.")
fs.IntVar(&p.appCfg.Options.RepliesPerReq, "rpr", slackdump.DefOptions.RepliesPerReq, "number of `replies` per request.")

// - user cache controls
// - cache controls
fs.StringVar(&p.appCfg.Options.CacheDir, "cache-dir", app.CacheDir(), "slackdump cache directory")
fs.StringVar(&p.appCfg.Options.UserCacheFilename, "user-cache-file", slackdump.DefOptions.UserCacheFilename, "user cache file`name`.")
fs.DurationVar(&p.appCfg.Options.MaxUserCacheAge, "user-cache-age", slackdump.DefOptions.MaxUserCacheAge, "user cache lifetime `duration`. Set this to 0 to disable cache.")
fs.BoolVar(&p.appCfg.Options.NoUserCache, "no-user-cache", slackdump.DefOptions.NoUserCache, "skip fetching users")
Expand Down
5 changes: 4 additions & 1 deletion cmd/slackdump/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ func Test_output_validFormat(t *testing.T) {
}

func Test_checkParameters(t *testing.T) {
// setup
slackdump.DefOptions.CacheDir = app.CacheDir()

// test
type args struct {
args []string
}
Expand All @@ -63,7 +67,6 @@ func Test_checkParameters(t *testing.T) {
Users: false,
Channels: true,
},

FilenameTemplate: defFilenameTemplate,
Input: app.Input{List: &structures.EntityList{}},
Output: app.Output{Filename: "-", Format: "text"},
Expand Down
Loading

0 comments on commit da089ac

Please sign in to comment.