github.com/greenpau/go-authcrunch@v1.1.4/cmd/authdbctl/config.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "bufio" 19 "bytes" 20 "errors" 21 "fmt" 22 "github.com/greenpau/go-authcrunch/pkg/util" 23 fileutil "github.com/greenpau/go-authcrunch/pkg/util/file" 24 logutil "github.com/greenpau/go-authcrunch/pkg/util/log" 25 "github.com/urfave/cli/v2" 26 "go.uber.org/zap" 27 "gopkg.in/yaml.v3" 28 "os" 29 "path/filepath" 30 "strings" 31 "unicode" 32 ) 33 34 // Config holds the configuration for the CLI. 35 type Config struct { 36 BaseURL string `json:"base_url,omitempty" xml:"base_url,omitempty" yaml:"base_url,omitempty"` 37 TokenPath string `json:"token_path,omitempty" xml:"token_path,omitempty" yaml:"token_path,omitempty"` 38 Username string `json:"username,omitempty" xml:"username,omitempty" yaml:"username,omitempty"` 39 Password string `json:"password,omitempty" xml:"password,omitempty" yaml:"password,omitempty"` 40 Realm string `json:"realm,omitempty" xml:"realm,omitempty" yaml:"realm,omitempty"` 41 CookieName string `json:"cookie_name,omitempty" xml:"cookie_name,omitempty" yaml:"cookie_name,omitempty"` 42 path string 43 token string 44 } 45 46 type wrapper struct { 47 config *Config 48 logger *zap.Logger 49 browser *util.Browser 50 } 51 52 func (wr *wrapper) configure(c *cli.Context) error { 53 cfg := &Config{} 54 cfg.path = c.String("config") 55 56 if c.Bool("debug") { 57 wr.logger = logutil.NewLogger() 58 } else { 59 wr.logger = logutil.NewInfoLogger() 60 } 61 62 cfgBytes, err := fileutil.ReadFileBytes(cfg.path) 63 if err != nil { 64 switch { 65 case errors.Is(err, os.ErrNotExist): 66 wr.logger.Debug( 67 "configuration file does not exist", 68 zap.String("path", cfg.path), 69 ) 70 default: 71 return err 72 } 73 } else { 74 if err := yaml.Unmarshal(cfgBytes, cfg); err != nil { 75 return err 76 } 77 cfg.path = c.String("config") 78 } 79 80 if cfg.TokenPath == "" && c.String("token-path") != "" { 81 cfg.TokenPath = c.String("token-path") 82 } 83 84 cfg.TokenPath = fileutil.ExpandPath(cfg.TokenPath) 85 86 if cfg.BaseURL == "" { 87 return fmt.Errorf("the base_url configuration not found") 88 } 89 90 tokenBytes, err := fileutil.ReadFileBytes(cfg.TokenPath) 91 if err != nil { 92 switch { 93 case errors.Is(err, os.ErrNotExist): 94 wr.logger.Debug( 95 "token file does not exist", 96 zap.String("path", cfg.TokenPath), 97 ) 98 default: 99 return err 100 } 101 } else { 102 cfg.token = string(parseTokenBytes(tokenBytes)) 103 } 104 105 for _, s := range []string{"username", "realm"} { 106 var skip bool 107 switch { 108 case (s == "username") && (cfg.Username != ""): 109 skip = true 110 case (s == "realm") && (cfg.Realm != ""): 111 skip = true 112 } 113 if skip { 114 continue 115 } 116 input, err := wr.readUserInput(s) 117 if err != nil { 118 return err 119 } 120 switch s { 121 case "username": 122 cfg.Username = input 123 case "realm": 124 cfg.Realm = input 125 } 126 } 127 128 if cfg.CookieName == "" { 129 cfg.CookieName = "access_token" 130 } 131 132 wr.logger.Debug( 133 "runtime configuration", 134 zap.String("config_path", cfg.path), 135 zap.String("base_url", cfg.BaseURL), 136 zap.String("token_path", cfg.TokenPath), 137 zap.String("username", cfg.Username), 138 zap.String("realm", cfg.Realm), 139 ) 140 141 wr.config = cfg 142 143 browser, err := util.NewBrowser() 144 if err != nil { 145 return err 146 } 147 148 wr.browser = browser 149 return nil 150 } 151 152 func parseTokenBytes(b []byte) []byte { 153 if len(b) == 0 { 154 return b 155 } 156 f := func(c rune) bool { 157 return unicode.IsSpace(c) 158 } 159 i := bytes.IndexFunc(b, f) 160 if i < 0 { 161 return b 162 } 163 return b[:i] 164 } 165 166 func (wr *wrapper) readUserInput(s string) (string, error) { 167 reader := bufio.NewReader(os.Stdin) 168 fmt.Printf("Enter %s: ", s) 169 input, err := reader.ReadString('\n') 170 if err != nil { 171 wr.logger.Error( 172 "An error occured while reading input. Please try again.", 173 zap.Error(err), 174 ) 175 return "", err 176 } 177 input = strings.TrimSpace(input) 178 if len(input) == 0 { 179 wr.logger.Error("Empty input. Please try again.") 180 return "", fmt.Errorf("empty input") 181 } 182 return input, nil 183 } 184 185 func (wr *wrapper) commitToken() error { 186 fileDir := filepath.Dir(wr.config.TokenPath) 187 188 if _, err := os.Stat(fileDir); os.IsNotExist(err) { 189 wr.logger.Error("creating token file directory", zap.String("path", fileDir)) 190 if err := os.MkdirAll(fileDir, 0700); err != nil { 191 return fmt.Errorf("failed creating %q directory: %v", fileDir, err) 192 } 193 } 194 195 fh, err := os.OpenFile(wr.config.TokenPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) 196 if err != nil { 197 return fmt.Errorf("failed opening %q file: %v", wr.config.TokenPath, err) 198 } 199 if _, err := fh.WriteString(wr.config.token + "\n"); err != nil { 200 return fmt.Errorf("failed writing to %q file: %v", wr.config.TokenPath, err) 201 } 202 if err := fh.Close(); err != nil { 203 return fmt.Errorf("failed closing %q file: %v", wr.config.TokenPath, err) 204 } 205 206 wr.logger.Debug("wrote token to file", zap.String("path", wr.config.TokenPath)) 207 return nil 208 }