Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BasicAuth: Allow disabling sending WWW-Authenticate header #1387

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion servant-client/test/Servant/ClientTestUtils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ basicAuthHandler =
if username == "servant" && password == "server"
then return (Authorized ())
else return Unauthorized
in BasicAuthCheck check
in BasicAuthCheck True check

basicServerContext :: Context '[ BasicAuthCheck () ]
basicServerContext = basicAuthHandler :. EmptyContext
Expand Down
2 changes: 1 addition & 1 deletion servant-http-streams/test/Servant/ClientSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ basicAuthHandler =
if username == "servant" && password == "server"
then return (Authorized ())
else return Unauthorized
in BasicAuthCheck check
in BasicAuthCheck True check

basicServerContext :: Context '[ BasicAuthCheck () ]
basicServerContext = basicAuthHandler :. EmptyContext
Expand Down
2 changes: 1 addition & 1 deletion servant-server/src/Servant/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ module Servant.Server
, descendIntoNamedContext

-- * Basic Authentication
, BasicAuthCheck(BasicAuthCheck, unBasicAuthCheck)
, BasicAuthCheck(BasicAuthCheck, basicAuthRunCheck, basicAuthPresentChallenge)
, BasicAuthResult(..)

-- * General Authentication
Expand Down
15 changes: 10 additions & 5 deletions servant-server/src/Servant/Server/Internal/BasicAuth.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ data BasicAuthResult usr
deriving (Eq, Show, Read, Generic, Typeable, Functor)

-- | Datatype wrapping a function used to check authentication.
newtype BasicAuthCheck usr = BasicAuthCheck
{ unBasicAuthCheck :: BasicAuthData
-> IO (BasicAuthResult usr)
data BasicAuthCheck usr
= BasicAuthCheck
{ basicAuthPresentChallenge :: Bool
-- ^ Decides if we'll send a @WWW-Authenticate@ HTTP header. Sending the header causes browser to
-- surface a prompt for user name and password, which may be undesirable for APIs.
, basicAuthRunCheck :: BasicAuthData -> IO (BasicAuthResult usr)
}
deriving (Generic, Typeable, Functor)

Expand All @@ -68,12 +71,14 @@ decodeBAHdr req = do
-- | Run and check basic authentication, returning the appropriate http error per
-- the spec.
runBasicAuth :: Request -> BS.ByteString -> BasicAuthCheck usr -> DelayedIO usr
runBasicAuth req realm (BasicAuthCheck ba) =
runBasicAuth req realm (BasicAuthCheck presentChallenge ba) =
case decodeBAHdr req of
Nothing -> plzAuthenticate
Just e -> liftIO (ba e) >>= \res -> case res of
BadPassword -> plzAuthenticate
NoSuchUser -> plzAuthenticate
Unauthorized -> delayedFailFatal err403
Authorized usr -> return usr
where plzAuthenticate = delayedFailFatal err401 { errHeaders = [mkBAChallengerHdr realm] }
where
plzAuthenticate =
delayedFailFatal err401 { errHeaders = [mkBAChallengerHdr realm | presentChallenge] }
2 changes: 1 addition & 1 deletion servant-server/test/Servant/Server/ErrorSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ errorOrderAuthCheck =
if username == "servant" && password == "server"
then return (Authorized ())
else return Unauthorized
in BasicAuthCheck check
in BasicAuthCheck True check

------------------------------------------------------------------------------
-- * Error Order {{{
Expand Down
23 changes: 18 additions & 5 deletions servant-server/test/Servant/ServerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ import qualified Servant.Types.SourceT as S
import Test.Hspec
(Spec, context, describe, it, shouldBe, shouldContain)
import Test.Hspec.Wai
(get, liftIO, matchHeaders, matchStatus, shouldRespondWith,
(get, liftIO, matchHeaders, MatchHeader(..), matchStatus, shouldRespondWith,
with, (<:>))
import qualified Test.Hspec.Wai as THW

Expand Down Expand Up @@ -742,9 +742,9 @@ basicAuthServer =
const (return jerry) :<|>
(Tagged $ \ _ sendResponse -> sendResponse $ responseLBS imATeapot418 [] "")

basicAuthContext :: Context '[ BasicAuthCheck () ]
basicAuthContext =
let basicHandler = BasicAuthCheck $ \(BasicAuthData usr pass) ->
basicAuthContext :: Bool -> Context '[ BasicAuthCheck () ]
basicAuthContext withRealm =
let basicHandler = BasicAuthCheck withRealm $ \(BasicAuthData usr pass) ->
if usr == "servant" && pass == "server"
then return (Authorized ())
else return Unauthorized
Expand All @@ -753,14 +753,27 @@ basicAuthContext =
basicAuthSpec :: Spec
basicAuthSpec = do
describe "Servant.API.BasicAuth" $ do
with (return (serveWithContext basicAuthApi basicAuthContext basicAuthServer)) $ do
with (return (serveWithContext basicAuthApi (basicAuthContext False) basicAuthServer)) $ do
context "Basic Authentication without realm" $ do
it "does not send WWW-Authenticate headers on 401" $ do
let noWWW =
MatchHeader $ \headers _ ->
if "WWW-Authenticate" `elem` map fst headers
then Just "WWW-Authenticate header is unexpected, "
else Nothing
get "/basic" `shouldRespondWith` "" {matchStatus = 401, matchHeaders = [noWWW]}

with (return (serveWithContext basicAuthApi (basicAuthContext True) basicAuthServer)) $ do

context "Basic Authentication" $ do
let basicAuthHeaders user password =
[("Authorization", "Basic " <> Base64.encode (user <> ":" <> password))]
it "returns 401 when no credentials given" $ do
get "/basic" `shouldRespondWith` 401

it "returns 401 WWW-Authenticate headers" $ do
get "/basic" `shouldRespondWith` "" {matchStatus = 401, matchHeaders = ["WWW-Authenticate" <:> "Basic realm=\"foo\""]}

it "returns 403 when invalid credentials given" $ do
THW.request methodGet "/basic" (basicAuthHeaders "servant" "wrong") ""
`shouldRespondWith` 403
Expand Down