diff --git a/cmd/filebrowser/main.go b/cmd/filebrowser/main.go index b8b32272..d1c3d634 100644 --- a/cmd/filebrowser/main.go +++ b/cmd/filebrowser/main.go @@ -2,18 +2,7 @@ package main import ( "fmt" - "io/ioutil" - "log" - "net" - "net/http" - "os" - "path/filepath" - "strings" - "github.com/asdine/storm" - - "gopkg.in/natefinch/lumberjack.v2" - "github.com/filebrowser/filebrowser" "github.com/filebrowser/filebrowser/bolt" h "github.com/filebrowser/filebrowser/http" @@ -21,7 +10,14 @@ import ( "github.com/hacdias/fileutils" flag "github.com/spf13/pflag" "github.com/spf13/viper" -) + "gopkg.in/natefinch/lumberjack.v2" + "io/ioutil" + "log" + "net" + "net/http" + "os" + "path/filepath" + "strings") var ( addr string @@ -38,13 +34,17 @@ var ( recaptchakey string recaptchasecret string port int - noAuth bool - allowCommands bool - allowEdit bool - allowNew bool - allowPublish bool - showVer bool - alterRecaptcha bool + auth struct { + method string + loginHeader string + } + noAuth bool + allowCommands bool + allowEdit bool + allowNew bool + allowPublish bool + showVer bool + alterRecaptcha bool ) func init() { @@ -63,6 +63,8 @@ func init() { flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users") flag.BoolVar(&allowEdit, "allow-edit", true, "Default allow edit option for new users") flag.BoolVar(&allowPublish, "allow-publish", true, "Default allow publish option for new users") + flag.StringVar(&auth.method, "auth.method", "default", "Switch between 'none', 'default' and 'proxy' authentication.") + flag.StringVar(&auth.loginHeader, "auth.loginHeader", "X-Forwarded-User", "The header name used for proxy authentication.") flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users") flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication") flag.BoolVar(&alterRecaptcha, "alternative-recaptcha", false, "Use recaptcha.net for serving and handling, useful in China") @@ -84,6 +86,8 @@ func setupViper() { viper.SetDefault("AllowPublish", true) viper.SetDefault("StaticGen", "") viper.SetDefault("Locale", "") + viper.SetDefault("AuthMethod", "default") + viper.SetDefault("LoginHeader", "X-Fowarded-User") viper.SetDefault("NoAuth", false) viper.SetDefault("BaseURL", "") viper.SetDefault("PrefixURL", "") @@ -104,6 +108,8 @@ func setupViper() { viper.BindPFlag("AllowPublish", flag.Lookup("allow-publish")) viper.BindPFlag("Locale", flag.Lookup("locale")) viper.BindPFlag("StaticGen", flag.Lookup("staticgen")) + viper.BindPFlag("AuthMethod", flag.Lookup("auth.method")) + viper.BindPFlag("LoginHeader", flag.Lookup("auth.loginHeader")) viper.BindPFlag("NoAuth", flag.Lookup("no-auth")) viper.BindPFlag("BaseURL", flag.Lookup("baseurl")) viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl")) @@ -168,6 +174,18 @@ func main() { }) } + // Validate the provided config before moving forward + if viper.GetString("AuthMethod") != "none" && viper.GetString("AuthMethod") != "default" && viper.GetString("AuthMethod") != "proxy" { + log.Fatal("The property 'auth.method' needs to be set to 'default' or 'proxy'.") + } + + if viper.GetString("AuthMethod") == "proxy" { + if viper.GetString("LoginHeader") == "" { + log.Fatal("The 'loginHeader' needs to be specified when 'proxy' authentication is used.") + } + log.Println("[WARN] Filebrowser authentication is configured to 'proxy' authentication. This can cause a huge security issue if the infrastructure is not configured correctly.") + } + // Builds the address and a listener. laddr := viper.GetString("Address") + ":" + viper.GetString("Port") listener, err := net.Listen("tcp", laddr) @@ -196,6 +214,8 @@ func handler() http.Handler { } fm := &filebrowser.FileBrowser{ + AuthMethod: viper.GetString("AuthMethod"), + LoginHeader: viper.GetString("LoginHeader"), NoAuth: viper.GetBool("NoAuth"), BaseURL: viper.GetString("BaseURL"), PrefixURL: viper.GetString("PrefixURL"), diff --git a/doc.go b/doc.go index 0930a602..23fabeec 100644 --- a/doc.go +++ b/doc.go @@ -16,6 +16,10 @@ to import "github.com/filebrowser/filebrowser/bolt". m := &fm.FileBrowser{ NoAuth: false, + Auth: { + Method: "default", + LoginHeader: "X-Fowarded-User" + }, DefaultUser: &fm.User{ AllowCommands: true, AllowEdit: true, diff --git a/filebrowser.go b/filebrowser.go index 3879bbb1..ed1ac681 100644 --- a/filebrowser.go +++ b/filebrowser.go @@ -71,6 +71,16 @@ type FileBrowser struct { // there will only exist one user, called "admin". NoAuth bool + // Define if which of the following authentication mechansims should be used: + // - 'default', which requires a user and a password. + // - 'proxy', which requires a valid user and the user name has to be provided through an + // http header. + // - 'none', which allows anyone to access the filebrowser instance. + AuthMethod string + + // When 'AuthMethod' is set to 'proxy' the header configured below is used to identify the user. + LoginHeader string + // ReCaptcha host, key and secret. ReCaptchaHost string ReCaptchaKey string diff --git a/http/auth.go b/http/auth.go index 341e299c..58d12212 100644 --- a/http/auth.go +++ b/http/auth.go @@ -51,20 +51,32 @@ func reCaptcha(host, secret, response string) (bool, error) { // authHandler processes the authentication for the user. func authHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, error) { - // NoAuth instances shouldn't call this method. if c.NoAuth { + // NoAuth instances shouldn't call this method. return 0, nil } + if c.AuthMethod == "proxy" { + // Receive the Username from the Header and check if it exists. + u, err := c.Store.Users.GetByUsername(r.Header.Get(c.LoginHeader), c.NewFS) + if err != nil { + return http.StatusForbidden, nil + } + + c.User = u + return printToken(c, w) + } + // Receive the credentials from the request and unmarshal them. var cred cred + if r.Body == nil { return http.StatusForbidden, nil } err := json.NewDecoder(r.Body).Decode(&cred) if err != nil { - return http.StatusForbidden, nil + return http.StatusForbidden, err } // If ReCaptcha is enabled, check the code. @@ -171,6 +183,16 @@ func validateAuth(c *fb.Context, r *http.Request) (bool, *fb.User) { return true, c.User } + // If proxy auth is used do not verify the JWT token if the header is provided. + if c.AuthMethod == "proxy" { + u, err := c.Store.Users.GetByUsername(r.Header.Get(c.LoginHeader), c.NewFS) + if err != nil { + return false, nil + } + c.User = u + return true, c.User + } + keyFunc := func(token *jwt.Token) (interface{}, error) { return c.Key, nil }