loginsrv

Unnamed repository; edit this file 'description' to name the repository.
git clone git@jamesshield.xyz:repos/loginsrv.git
Log | Files | Refs | README | LICENSE

commit 31671ca4572bd30099c0139c059040d98e518ed5
parent 40a03158100dc3f5f740974a8e845c59a7c46681
Author: Sebastian Mancke <s.mancke@tarent.de>
Date:   Fri,  5 May 2017 19:52:29 +0200

Merge pull request #10 from tarent/fix-prefix-handling

Fix prefix handling
Diffstat:
Mcaddy/handler.go | 7++-----
Mcaddy/setup.go | 18++++++++----------
Mcaddy/setup_test.go | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mlogin/config.go | 5+++++
Mlogin/config_test.go | 4++++
Mlogin/handler.go | 13+++++++++----
Mlogin/handler_test.go | 22++++++++++++++++++----
Mlogin/login_form.go | 10++++------
Mmain_test.go | 2+-
Moauth2/manager.go | 1+
10 files changed, 107 insertions(+), 41 deletions(-)

diff --git a/caddy/handler.go b/caddy/handler.go @@ -5,21 +5,18 @@ import ( "github.com/tarent/loginsrv/login" _ "github.com/tarent/loginsrv/osiam" "net/http" - "path" "strings" ) type CaddyHandler struct { next httpserver.Handler - path string config *login.Config loginHandler *login.Handler } -func NewCaddyHandler(next httpserver.Handler, path string, loginHandler *login.Handler, config *login.Config) *CaddyHandler { +func NewCaddyHandler(next httpserver.Handler, loginHandler *login.Handler, config *login.Config) *CaddyHandler { h := &CaddyHandler{ next: next, - path: path, config: config, loginHandler: loginHandler, } @@ -27,7 +24,7 @@ func NewCaddyHandler(next httpserver.Handler, path string, loginHandler *login.H } func (h *CaddyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - if strings.HasPrefix(r.URL.Path, path.Join(h.path, "/login")) { + if strings.HasPrefix(r.URL.Path, h.config.LoginPath) { h.loginHandler.ServeHTTP(w, r) return 0, nil } else { diff --git a/caddy/setup.go b/caddy/setup.go @@ -11,6 +11,7 @@ import ( _ "github.com/tarent/loginsrv/oauth2" _ "github.com/tarent/loginsrv/osiam" "os" + "path" "strings" ) @@ -28,32 +29,29 @@ func setup(c *caddy.Controller) error { for c.Next() { args := c.RemainingArgs() - if len(args) < 1 { - return fmt.Errorf("Missing path argument for loginsrv directive (%v:%v)", c.File(), c.Line()) - } - - if len(args) > 1 { - return fmt.Errorf("To many arguments for loginsrv directive %q (%v:%v)", args, c.File(), c.Line()) - } - config, err := parseConfig(c) if err != nil { return err } + if len(args) == 1 { + logging.Logger.Warnf("DEPRECATED: Please set the loing path by parameter login_path and not as directive argument (%v:%v)", c.File(), c.Line()) + config.LoginPath = path.Join(args[0], "/login") + } + if e, isset := os.LookupEnv("JWT_SECRET"); isset { config.JwtSecret = e } else { os.Setenv("JWT_SECRET", config.JwtSecret) } - fmt.Printf("config %+v\n", config) + loginHandler, err := login.NewHandler(config) if err != nil { return err } httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return NewCaddyHandler(next, args[0], loginHandler, config) + return NewCaddyHandler(next, loginHandler, config) }) } diff --git a/caddy/setup_test.go b/caddy/setup_test.go @@ -16,18 +16,17 @@ func TestSetup(t *testing.T) { for j, test := range []struct { input string shouldErr bool - path string config login.Config }{ { //defaults - input: `loginsrv / { + input: `loginsrv { simple bob=secret }`, shouldErr: false, - path: "/", config: login.Config{ JwtSecret: "jwtsecret", SuccessUrl: "/", + LoginPath: "/login", CookieName: "jwt_token", CookieHttpOnly: true, Backends: login.Options{ @@ -38,18 +37,19 @@ func TestSetup(t *testing.T) { Oauth: login.Options{}, }}, { - input: `loginsrv / { + input: `loginsrv { success_url successurl + login_path /foo/bar cookie_name cookiename cookie_http_only false simple bob=secret osiam endpoint=http://localhost:8080,client_id=example-client,client_secret=secret }`, shouldErr: false, - path: "/", config: login.Config{ JwtSecret: "jwtsecret", SuccessUrl: "successurl", + LoginPath: "/foo/bar", CookieName: "cookiename", CookieHttpOnly: false, Backends: login.Options{ @@ -64,14 +64,59 @@ func TestSetup(t *testing.T) { }, Oauth: login.Options{}, }}, + { // backwards compatibility + // * login path as argument + // * '-' in parameter names + // * backend config by 'backend provider=' + input: `loginsrv /context { + backend provider=simple,bob=secret + cookie-name cookiename + }`, + shouldErr: false, + config: login.Config{ + JwtSecret: "jwtsecret", + SuccessUrl: "/", + LoginPath: "/context/login", + CookieName: "cookiename", + CookieHttpOnly: true, + Backends: login.Options{ + "simple": map[string]string{ + "bob": "secret", + }, + }, + Oauth: login.Options{}, + }}, + { // backwards compatibility + // * login path as argument + // * '-' in parameter names + // * backend config by 'backend provider=' + input: `loginsrv / { + backend provider=simple,bob=secret + cookie-name cookiename + }`, + shouldErr: false, + config: login.Config{ + JwtSecret: "jwtsecret", + SuccessUrl: "/", + LoginPath: "/login", + CookieName: "cookiename", + CookieHttpOnly: true, + Backends: login.Options{ + "simple": map[string]string{ + "bob": "secret", + }, + }, + Oauth: login.Options{}, + }}, + // error cases {input: "loginsrv {\n}", shouldErr: true}, {input: "loginsrv xx yy {\n}", shouldErr: true}, - {input: "loginsrv / {\n cookie_http_only 42 \n simple bob=secret \n}", shouldErr: true}, - {input: "loginsrv / {\n unknown property \n simple bob=secret \n}", shouldErr: true}, - {input: "loginsrv / {\n backend \n}", shouldErr: true}, - {input: "loginsrv / {\n backend provider=foo\n}", shouldErr: true}, - {input: "loginsrv / {\n backend kk\n}", shouldErr: true}, + {input: "loginsrv {\n cookie_http_only 42 \n simple bob=secret \n}", shouldErr: true}, + {input: "loginsrv {\n unknown property \n simple bob=secret \n}", shouldErr: true}, + {input: "loginsrv {\n backend \n}", shouldErr: true}, + {input: "loginsrv {\n backend provider=foo\n}", shouldErr: true}, + {input: "loginsrv {\n backend kk\n}", shouldErr: true}, } { c := caddy.NewTestController("http", test.input) err := setup(c) @@ -86,7 +131,6 @@ func TestSetup(t *testing.T) { continue } middleware := mids[len(mids)-1](nil).(*CaddyHandler) - assert.Equal(t, test.path, middleware.path) assert.Equal(t, &test.config, middleware.config) } } diff --git a/login/config.go b/login/config.go @@ -4,6 +4,7 @@ import ( "errors" "flag" "fmt" + "github.com/tarent/lib-compose/logging" "github.com/tarent/loginsrv/oauth2" "math/rand" "os" @@ -25,6 +26,7 @@ func DefaultConfig() *Config { LogLevel: "info", JwtSecret: jwtDefaultSecret, SuccessUrl: "/", + LoginPath: "/login", CookieName: "jwt_token", CookieHttpOnly: true, Backends: Options{}, @@ -41,6 +43,7 @@ type Config struct { TextLogging bool JwtSecret string SuccessUrl string + LoginPath string CookieName string CookieHttpOnly bool Backends Options @@ -83,9 +86,11 @@ func (c *Config) ConfigureFlagSet(f *flag.FlagSet) { f.StringVar(&c.CookieName, "cookie-name", c.CookieName, "The name of the jwt cookie") f.BoolVar(&c.CookieHttpOnly, "cookie-http-only", c.CookieHttpOnly, "Set the cookie with the http only flag") f.StringVar(&c.SuccessUrl, "success-url", c.SuccessUrl, "The url to redirect after login") + f.StringVar(&c.LoginPath, "login-path", c.LoginPath, "The path of the login resource") // the -backends is deprecated, but we support it for backwards compatibility deprecatedBackends := setFunc(func(optsKvList string) error { + logging.Logger.Warn("DEPRECATED: '-backend' is no loger supported. Please set the backends by explicit paramters") opts, err := parseOptions(optsKvList) if err != nil { return err diff --git a/login/config_test.go b/login/config_test.go @@ -26,6 +26,7 @@ func TestConfig_ReadConfig(t *testing.T) { "--text-logging=true", "--jwt-secret=jwtsecret", "--success-url=successurl", + "--login-path=loginpath", "--cookie-name=cookiename", "--cookie-http-only=false", "--backend=provider=simple", @@ -40,6 +41,7 @@ func TestConfig_ReadConfig(t *testing.T) { TextLogging: true, JwtSecret: "jwtsecret", SuccessUrl: "successurl", + LoginPath: "loginpath", CookieName: "cookiename", CookieHttpOnly: false, Backends: Options{ @@ -66,6 +68,7 @@ func TestConfig_ReadConfigFromEnv(t *testing.T) { assert.NoError(t, os.Setenv("LOGINSRV_TEXT_LOGGING", "true")) assert.NoError(t, os.Setenv("LOGINSRV_JWT_SECRET", "jwtsecret")) assert.NoError(t, os.Setenv("LOGINSRV_SUCCESS_URL", "successurl")) + assert.NoError(t, os.Setenv("LOGINSRV_LOGIN_PATH", "loginpath")) assert.NoError(t, os.Setenv("LOGINSRV_COOKIE_NAME", "cookiename")) assert.NoError(t, os.Setenv("LOGINSRV_COOKIE_HTTP_ONLY", "false")) assert.NoError(t, os.Setenv("LOGINSRV_SIMPLE", "foo=bar")) @@ -78,6 +81,7 @@ func TestConfig_ReadConfigFromEnv(t *testing.T) { TextLogging: true, JwtSecret: "jwtsecret", SuccessUrl: "successurl", + LoginPath: "loginpath", CookieName: "cookiename", CookieHttpOnly: false, Backends: Options{ diff --git a/login/handler.go b/login/handler.go @@ -59,6 +59,10 @@ func NewHandler(config *Config) (*Handler, error) { } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.URL.Path, h.config.LoginPath) { + h.respondNotFound(w, r) + return + } _, err := h.oauth.GetConfigFromRequest(r) if err == nil { @@ -113,7 +117,6 @@ func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) { h.deleteToken(w) writeLoginForm(w, loginFormData{ - Path: r.URL.Path, Config: h.config, }) return @@ -123,7 +126,6 @@ func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) { userInfo, valid := h.getToken(r) writeLoginForm(w, loginFormData{ - Path: r.URL.Path, Config: h.config, Authenticated: valid, UserInfo: userInfo, @@ -222,7 +224,6 @@ func (h *Handler) respondError(w http.ResponseWriter, r *http.Request) { username, _, _ := getCredentials(r) writeLoginForm(w, loginFormData{ - Path: r.URL.Path, Error: true, Config: h.config, UserInfo: model.UserInfo{Sub: username}, @@ -239,6 +240,11 @@ func (h *Handler) respondBadRequest(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Bad Request: Method or content-type not supported") } +func (h *Handler) respondNotFound(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(404) + fmt.Fprintf(w, "Not Found: The requested page does not exist") +} + func (h *Handler) respondAuthFailure(w http.ResponseWriter, r *http.Request) { if wantHtml(r) { w.Header().Set("Content-Type", contentTypeHtml) @@ -246,7 +252,6 @@ func (h *Handler) respondAuthFailure(w http.ResponseWriter, r *http.Request) { username, _, _ := getCredentials(r) writeLoginForm(w, loginFormData{ - Path: r.URL.Path, Failure: true, Config: h.config, UserInfo: model.UserInfo{Sub: username}, diff --git a/login/handler_test.go b/login/handler_test.go @@ -63,14 +63,24 @@ func TestHandler_NewFromConfig(t *testing.T) { func TestHandler_LoginForm(t *testing.T) { recorder := call(req("GET", "/context/login", "")) - assert.Equal(t, recorder.Code, 200) + assert.Equal(t, 200, recorder.Code) assert.Contains(t, recorder.Body.String(), `class="container`) assert.Equal(t, "no-cache, no-store, must-revalidate", recorder.Header().Get("Cache-Control")) } func TestHandler_HEAD(t *testing.T) { recorder := call(req("HEAD", "/context/login", "")) - assert.Equal(t, recorder.Code, 400) + assert.Equal(t, 400, recorder.Code) +} + +func TestHandler_404(t *testing.T) { + recorder := call(req("GET", "/context/", "")) + assert.Equal(t, 404, recorder.Code) + + recorder = call(req("GET", "/", "")) + assert.Equal(t, 404, recorder.Code) + + assert.Equal(t, "Not Found: The requested page does not exist", recorder.Body.String()) } func TestHandler_LoginJson(t *testing.T) { @@ -201,22 +211,26 @@ func TestHandler_getToken_InvalidNoToken(t *testing.T) { } func testHandler() *Handler { + cfg := DefaultConfig() + cfg.LoginPath = "/context/login" return &Handler{ backends: []Backend{ NewSimpleBackend(map[string]string{"bob": "secret"}), }, oauth: oauth2.NewManager(), - config: DefaultConfig(), + config: cfg, } } func testHandlerWithError() *Handler { + cfg := DefaultConfig() + cfg.LoginPath = "/context/login" return &Handler{ backends: []Backend{ errorTestBackend("test error"), }, oauth: oauth2.NewManager(), - config: DefaultConfig(), + config: cfg, } } diff --git a/login/login_form.go b/login/login_form.go @@ -66,13 +66,12 @@ const loginForm = `<!DOCTYPE html> <br/> {{if .Picture}}<img class="login-picture" src="{{.Picture}}?s=120">{{end}} {{if .Name}}<h3>{{.Name}}</h3>{{end}} - <br/> - <a class="btn btn-md btn-primary" href="login?logout=true">Logout</a> {{end}} + <br/> + <a class="btn btn-md btn-primary" href="{{ .Config.LoginPath }}?logout=true">Logout</a> {{else}} - {{ range $providerName, $opts := .Config.Oauth }} - <a class="btn btn-block btn-lg btn-social btn-{{ $providerName }}" href="login/{{ $providerName }}"> + <a class="btn btn-block btn-lg btn-social btn-{{ $providerName }}" href="{{ $.Config.LoginPath }}/{{ $providerName }}"> <span class="fa fa-{{ $providerName }}"></span> Sign in with {{ $providerName | ucfirst }} </a> {{end}} @@ -93,7 +92,7 @@ const loginForm = `<!DOCTYPE html> </div> </div> <div class="panel-body"> - <form accept-charset="UTF-8" role="form" method="POST" action="{{.Path}}"> + <form accept-charset="UTF-8" role="form" method="POST" action="{{.Config.LoginPath}}"> <fieldset> <div class="form-group"> <input class="form-control" placeholder="Username" name="username" value="{{.UserInfo.Sub}}" type="text"> @@ -116,7 +115,6 @@ const loginForm = `<!DOCTYPE html> </html>` type loginFormData struct { - Path string Error bool Failure bool Config *Config diff --git a/main_test.go b/main_test.go @@ -24,7 +24,7 @@ func Test_BasicEndToEnd(t *testing.T) { time.Sleep(time.Second) // success - req, err := http.NewRequest("POST", "http://localhost:3000/context/login", strings.NewReader(`{"username": "bob", "password": "secret"}`)) + req, err := http.NewRequest("POST", "http://localhost:3000/login", strings.NewReader(`{"username": "bob", "password": "secret"}`)) assert.NoError(t, err) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/jwt") diff --git a/oauth2/manager.go b/oauth2/manager.go @@ -87,6 +87,7 @@ func (manager *Manager) getConfigNameFromPath(path string) string { // Add a configuration for a provider func (manager *Manager) AddConfig(providerName string, opts map[string]string) error { p, exist := GetProvider(providerName) + if !exist { return fmt.Errorf("no provider for name %v", providerName) }