packages feed

bloodhound-1.0.0.0: tests/Test/SecurityAuthenticateSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.SecurityAuthenticateSpec (spec) where

import Data.Aeson
import Data.Aeson.KeyMap qualified as KM
import Data.ByteString.Lazy.Char8 qualified as L
import TestsUtils.Import
import Prelude

-- | @GET /_security/_authenticate@ response for the reserved @elastic@
-- user, including an @api_key@ sibling that should survive in 'authExtras'.
sampleAuthenticateBytes :: L.ByteString
sampleAuthenticateBytes =
  "{\
  \  \"username\": \"elastic\",\
  \  \"roles\": [\"superuser\"],\
  \  \"full_name\": null,\
  \  \"email\": null,\
  \  \"metadata\": {\"_reserved\": true},\
  \  \"enabled\": true,\
  \  \"authentication_realm\": {\"name\": \"reserved\", \"type\": \"reserved\"},\
  \  \"lookup_realm\": {\"name\": \"reserved\", \"type\": \"reserved\"},\
  \  \"authentication_provider\": \"reserved\",\
  \  \"principal\": \"elastic\",\
  \  \"api_key\": {\"id\": \"k1\", \"name\": \"key\"}\
  \}"

-- | @GET /_security/_whoami@ response (ES 7.16+) carrying the optional
-- @principal@ / @delegate@ fields.
sampleWhoAmIBytes :: L.ByteString
sampleWhoAmIBytes =
  "{\
  \  \"username\": \"radmin\",\
  \  \"roles\": [\"superuser\"],\
  \  \"principal\": \"radmin\",\
  \  \"delegate\": false,\
  \  \"authentication_realm\": {\"name\": \"file\", \"type\": \"file\"},\
  \  \"lookup_realm\": {\"name\": \"file\", \"type\": \"file\"}\
  \}"

spec :: Spec
spec = do
  describe "AuthenticateResponse" $ do
    it "decodes username, roles, realms, provider, principal" $ do
      let Just r = decode sampleAuthenticateBytes :: Maybe AuthenticateResponse
      authUsername r `shouldBe` "elastic"
      authRoles r `shouldBe` [RoleName "superuser"]
      authEnabled r `shouldBe` Just True
      authAuthenticationProvider r `shouldBe` Just "reserved"
      authPrincipal r `shouldBe` Just "elastic"
      case authAuthenticationRealm r of
        Just realm -> do
          srName realm `shouldBe` Just "reserved"
          srType realm `shouldBe` Just "reserved"
        Nothing -> expectationFailure "expected an authentication_realm"

    it "preserves unknown sibling fields (api_key) in extras and round-trips" $ do
      let Just r = decode sampleAuthenticateBytes :: Maybe AuthenticateResponse
      KM.lookup "api_key" (authExtras r) `shouldSatisfy` isJust
      let Just rt = decode (encode r) :: Maybe AuthenticateResponse
      KM.lookup "api_key" (authExtras rt) `shouldSatisfy` isJust

    it "tolerates a minimal response (just username)" $ do
      let Just r = decode "{\"username\":\"u\"}" :: Maybe AuthenticateResponse
      authUsername r `shouldBe` "u"
      authRoles r `shouldBe` []
      authAuthenticationRealm r `shouldBe` Nothing

  describe "WhoAmIResponse" $ do
    it "decodes principal / delegate / realms" $ do
      let Just resp = decode sampleWhoAmIBytes :: Maybe WhoAmIResponse
      wmUsername resp `shouldBe` "radmin"
      wmPrincipal resp `shouldBe` Just "radmin"
      wmDelegate resp `shouldBe` Just False
      case wmAuthenticationRealm resp of
        SecurityRealm {..} -> do
          srName `shouldBe` Just "file"
          srType `shouldBe` Just "file"

    it "tolerates an absent delegate / principal / lookup_realm" $ do
      let bytes =
            "{\
            \  \"username\": \"x\",\
            \  \"roles\": [],\
            \  \"authentication_realm\": {\"name\":\"n\",\"type\":\"t\"}\
            \}"
      let Just resp = decode bytes :: Maybe WhoAmIResponse
      wmPrincipal resp `shouldBe` Nothing
      wmDelegate resp `shouldBe` Nothing
      wmLookupRealm resp `shouldBe` Nothing

    it "round-trips" $ do
      let Just resp = decode sampleWhoAmIBytes :: Maybe WhoAmIResponse
      let Just rt = decode (encode resp) :: Maybe WhoAmIResponse
      wmUsername rt `shouldBe` wmUsername resp
      wmDelegate rt `shouldBe` wmDelegate resp

  describe "LogoutRequest" $ do
    it "encodes the token and omits an absent refresh_token" $ do
      encode (defaultLogoutRequest "abc123") `shouldBe` "{\"token\":\"abc123\"}"

    it "round-trips with an optional refresh_token" $ do
      let req =
            (defaultLogoutRequest "abc123")
              { lrRefreshToken = Just "rt456"
              }
      let Just rt = decode (encode req) :: Maybe LogoutRequest
      lrToken rt `shouldBe` "abc123"
      lrRefreshToken rt `shouldBe` Just "rt456"

  describe "LogoutResponse" $ do
    it "decodes {changed: true}" $ do
      let Just resp = decode "{\"changed\":true}" :: Maybe LogoutResponse
      loChanged resp `shouldBe` True

    it "decodes {changed: false}" $ do
      let Just resp = decode "{\"changed\":false}" :: Maybe LogoutResponse
      loChanged resp `shouldBe` False

    it "round-trips" $ do
      let resp = LogoutResponse {loChanged = True}
      decode (encode resp) `shouldBe` Just resp