diff --git a/modules/caddyhttp/caddyauth/caddyauth.go b/modules/caddyhttp/caddyauth/caddyauth.go index 792c198ee..de8e13f89 100644 --- a/modules/caddyhttp/caddyauth/caddyauth.go +++ b/modules/caddyhttp/caddyauth/caddyauth.go @@ -32,10 +32,10 @@ func init() { // Authentication is a middleware which provides user authentication. // Rejects requests with HTTP 401 if the request is not authenticated. // -// After a successful authentication, the placeholder +// When an authentication provider returns user information, the placeholder // `{http.auth.user.id}` will be set to the username, and also // `{http.auth.user.*}` placeholders may be set for any authentication -// modules that provide user metadata. +// modules that provide user metadata, even if authentication is rejected. // // In case of an error, the placeholder `{http.auth..error}` // will be set to the error message returned by the authentication @@ -91,6 +91,12 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c repl.Set("http.auth."+provName+".error", err.Error()) continue } + if authed || user.ID != "" || len(user.Metadata) > 0 { + repl.Set("http.auth.user.id", user.ID) + for k, v := range user.Metadata { + repl.Set("http.auth.user."+k, v) + } + } if authed { break } @@ -99,11 +105,6 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c return caddyhttp.Error(http.StatusUnauthorized, fmt.Errorf("not authenticated")) } - repl.Set("http.auth.user.id", user.ID) - for k, v := range user.Metadata { - repl.Set("http.auth.user."+k, v) - } - return next.ServeHTTP(w, r) } diff --git a/modules/caddyhttp/caddyauth/caddyauth_test.go b/modules/caddyhttp/caddyauth/caddyauth_test.go new file mode 100644 index 000000000..1f7211137 --- /dev/null +++ b/modules/caddyhttp/caddyauth/caddyauth_test.go @@ -0,0 +1,82 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyauth + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func TestAuthenticationSetsUserPlaceholdersOnUnauthorized(t *testing.T) { + repl := caddy.NewReplacer() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req = req.WithContext(context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)) + + a := Authentication{ + Providers: map[string]Authenticator{ + "test": staticAuthenticator{ + user: User{ + ID: "alice", + Metadata: map[string]string{ + "role": "admin", + }, + }, + }, + }, + } + + nextCalled := false + err := a.ServeHTTP(httptest.NewRecorder(), req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error { + nextCalled = true + return nil + })) + if err == nil { + t.Fatal("expected unauthorized error") + } + + var handlerErr caddyhttp.HandlerError + if !errors.As(err, &handlerErr) { + t.Fatalf("expected caddyhttp.HandlerError, got %T", err) + } + if handlerErr.StatusCode != http.StatusUnauthorized { + t.Fatalf("expected status %d, got %d", http.StatusUnauthorized, handlerErr.StatusCode) + } + if nextCalled { + t.Fatal("next handler was called") + } + + if got, ok := repl.GetString("http.auth.user.id"); !ok || got != "alice" { + t.Fatalf("expected http.auth.user.id to be alice, got %q (ok=%v)", got, ok) + } + if got, ok := repl.GetString("http.auth.user.role"); !ok || got != "admin" { + t.Fatalf("expected http.auth.user.role to be admin, got %q (ok=%v)", got, ok) + } +} + +type staticAuthenticator struct { + user User + authed bool + err error +} + +func (s staticAuthenticator) Authenticate(http.ResponseWriter, *http.Request) (User, bool, error) { + return s.user, s.authed, s.err +}