From 9060feb436a5fd00424df345f404a45aad568927 Mon Sep 17 00:00:00 2001
From: Jehiah Czebotar <jehiah@gmail.com>
Date: Sun, 9 Nov 2014 21:07:02 -0500
Subject: [PATCH] better environment parsing

---
 README.md      |  2 +-
 env_options.go | 33 +++++++++++++++++++++++++++++++++
 htpasswd.go    |  1 -
 main.go        | 27 ++++++++-------------------
 oauthproxy.go  |  1 +
 options.go     | 22 +++++++++++-----------
 validator.go   |  3 ++-
 7 files changed, 56 insertions(+), 33 deletions(-)
 create mode 100644 env_options.go

diff --git a/README.md b/README.md
index 63ec13ce..95946377 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ Usage of google_auth_proxy:
 
 ### Environment variables
 
-The environment variables `google_auth_client_id`, `google_auth_secret` and `google_auth_cookie_secret` can be used in place of the corresponding command-line arguments.
+The environment variables `GOOGLE_AUTH_PROXY_CLIENT_ID`, `GOOGLE_AUTH_PROXY_CLIENT_SECRET`, `GOOGLE_AUTH_PROXY_COOKIE_SECRET`, `GOOGLE_AUTH_PROXY_COOKIE_DOMAIN` and `GOOGLE_AUTH_PROXY_COOKIE_EXPIRE` can be used in place of the corresponding command-line arguments.
 
 ### Example Nginx Configuration
 
diff --git a/env_options.go b/env_options.go
new file mode 100644
index 00000000..2016ff81
--- /dev/null
+++ b/env_options.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+	"os"
+	"reflect"
+	"strings"
+)
+
+func LoadOptionsFromEnv(options interface{}, cfg map[string]interface{}) {
+	val := reflect.ValueOf(options).Elem()
+	typ := val.Type()
+	for i := 0; i < typ.NumField(); i++ {
+		// pull out the struct tags:
+		//    flag - the name of the command line flag
+		//    deprecated - (optional) the name of the deprecated command line flag
+		//    cfg - (optional, defaults to underscored flag) the name of the config file option
+		field := typ.Field(i)
+		flagName := field.Tag.Get("flag")
+		envName := field.Tag.Get("env")
+		cfgName := field.Tag.Get("cfg")
+		if cfgName == "" && flagName != "" {
+			cfgName = strings.Replace(flagName, "-", "_", -1)
+		}
+		if envName == "" || cfgName == "" {
+			// resolvable fields must have the `env` and `cfg` struct tag
+			continue
+		}
+		v := os.Getenv(envName)
+		if v != "" {
+			cfg[cfgName] = v
+		}
+	}
+}
diff --git a/htpasswd.go b/htpasswd.go
index a620d58f..5988c7c6 100644
--- a/htpasswd.go
+++ b/htpasswd.go
@@ -17,7 +17,6 @@ type HtpasswdFile struct {
 }
 
 func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
-	log.Printf("using htpasswd file %s", path)
 	r, err := os.Open(path)
 	if err != nil {
 		return nil, err
diff --git a/main.go b/main.go
index 6d3f0520..17d4adb5 100644
--- a/main.go
+++ b/main.go
@@ -35,12 +35,17 @@ func main() {
 	flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption")
 
 	flagSet.String("cookie-secret", "", "the seed string for secure cookies")
-	flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)")
+	flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*")
 	flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
 	flagSet.Bool("cookie-https-only", false, "set HTTPS only cookie")
 
 	flagSet.Parse(os.Args[1:])
 
+	if *showVersion {
+		fmt.Printf("google_auth_proxy v%s\n", VERSION)
+		return
+	}
+
 	opts := NewOptions()
 
 	var cfg map[string]interface{}
@@ -50,26 +55,9 @@ func main() {
 			log.Fatalf("ERROR: failed to load config file %s - %s", *config, err)
 		}
 	}
-
+	LoadOptionsFromEnv(opts, cfg)
 	options.Resolve(opts, flagSet, cfg)
 
-	// Try to use env for secrets if no flag is set
-	// TODO: better parsing of these values
-	if opts.ClientID == "" {
-		opts.ClientID = os.Getenv("google_auth_client_id")
-	}
-	if opts.ClientSecret == "" {
-		opts.ClientSecret = os.Getenv("google_auth_secret")
-	}
-	if opts.CookieSecret == "" {
-		opts.CookieSecret = os.Getenv("google_auth_cookie_secret")
-	}
-
-	if *showVersion {
-		fmt.Printf("google_auth_proxy v%s\n", VERSION)
-		return
-	}
-
 	err := opts.Validate()
 	if err != nil {
 		log.Printf("%s", err)
@@ -88,6 +76,7 @@ func main() {
 	}
 
 	if opts.HtpasswdFile != "" {
+		log.Printf("using htpasswd file %s", opts.HtpasswdFile)
 		oauthproxy.HtpasswdFile, err = NewHtpasswdFromFile(opts.HtpasswdFile)
 		if err != nil {
 			log.Fatalf("FATAL: unable to open %s %s", opts.HtpasswdFile, err)
diff --git a/oauthproxy.go b/oauthproxy.go
index e7267809..73f04faa 100644
--- a/oauthproxy.go
+++ b/oauthproxy.go
@@ -54,6 +54,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
 	redirectUrl := opts.redirectUrl
 	redirectUrl.Path = oauthCallbackPath
 
+	log.Printf("OauthProxy configured for %s", opts.ClientID)
 	return &OauthProxy{
 		CookieKey:       "_oauthproxy",
 		CookieSeed:      opts.CookieSecret,
diff --git a/options.go b/options.go
index c2a7b216..2d829695 100644
--- a/options.go
+++ b/options.go
@@ -11,13 +11,13 @@ import (
 type Options struct {
 	HttpAddress             string        `flag:"http-address" cfg:"http_address"`
 	RedirectUrl             string        `flag:"redirect-url" cfg:"redirect_url"`
-	ClientID                string        `flag:"client-id" cfg:"client_id"`
-	ClientSecret            string        `flag:"client-secret" cfg:"client_secret"`
+	ClientID                string        `flag:"client-id" cfg:"client_id" env:"GOOGLE_AUTH_PROXY_CLIENT_ID"`
+	ClientSecret            string        `flag:"client-secret" cfg:"client_secret" env:"GOOGLE_AUTH_PROXY_CLIENT_SECRET"`
 	PassBasicAuth           bool          `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
 	HtpasswdFile            string        `flag:"htpasswd-file" cfg:"htpasswd_file"`
-	CookieSecret            string        `flag:"cookie-secret" cfg:"cookie_secret"`
-	CookieDomain            string        `flag:"cookie-domain" cfg:"cookie_domain"`
-	CookieExpire            time.Duration `flag:"cookie-expire" cfg:"cookie_expire"`
+	CookieSecret            string        `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"`
+	CookieDomain            string        `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"`
+	CookieExpire            time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"`
 	CookieHttpsOnly         bool          `flag:"cookie-https-only" cfg:"cookie_https_only"`
 	AuthenticatedEmailsFile string        `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
 	GoogleAppsDomains       []string      `flag:"google-apps-domain" cfg:"google_apps_domains"`
@@ -34,28 +34,28 @@ func NewOptions() *Options {
 
 func (o *Options) Validate() error {
 	if len(o.Upstreams) < 1 {
-		return errors.New("missing -upstream")
+		return errors.New("missing setting: upstream")
 	}
 	if o.CookieSecret == "" {
-		errors.New("missing -cookie-secret")
+		errors.New("missing setting: cookie-secret")
 	}
 	if o.ClientID == "" {
-		return errors.New("missing -client-id")
+		return errors.New("missing setting: client-id")
 	}
 	if o.ClientSecret == "" {
-		return errors.New("missing -client-secret")
+		return errors.New("missing setting: client-secret")
 	}
 
 	redirectUrl, err := url.Parse(o.RedirectUrl)
 	if err != nil {
-		return fmt.Errorf("error parsing -redirect-url=%q %s", o.RedirectUrl, err)
+		return fmt.Errorf("error parsing redirect-url=%q %s", o.RedirectUrl, err)
 	}
 	o.redirectUrl = redirectUrl
 
 	for _, u := range o.Upstreams {
 		upstreamUrl, err := url.Parse(u)
 		if err != nil {
-			return fmt.Errorf("error parsing -upstream=%q %s", upstreamUrl, err)
+			return fmt.Errorf("error parsing upstream=%q %s", upstreamUrl, err)
 		}
 		if upstreamUrl.Path == "" {
 			upstreamUrl.Path = "/"
diff --git a/validator.go b/validator.go
index 4caa5ec4..81a1bf8e 100644
--- a/validator.go
+++ b/validator.go
@@ -12,9 +12,10 @@ func NewValidator(domains []string, usersFile string) func(string) bool {
 	validUsers := make(map[string]bool)
 
 	if usersFile != "" {
+		log.Printf("using authenticated emails file %s", usersFile)
 		r, err := os.Open(usersFile)
 		if err != nil {
-			log.Fatalf("failed opening -authenticated-emails-file=%v, %s", usersFile, err.Error())
+			log.Fatalf("failed opening authenticated-emails-file=%q, %s", usersFile, err)
 		}
 		csv_reader := csv.NewReader(r)
 		csv_reader.Comma = ','