Group configuration by component ownership
This commit is contained in:
parent
48a5b8a433
commit
67cc0cb802
3 changed files with 486 additions and 258 deletions
|
|
@ -6,47 +6,67 @@ import Nuthatch.Config
|
|||
|
||||
testConfig :: Config
|
||||
testConfig = Config
|
||||
{ cfgListenHost = "127.0.0.1"
|
||||
, cfgListenPort = 8080
|
||||
, cfgTlsCertPath = "/tls/tls.crt"
|
||||
, cfgTlsKeyPath = "/tls/tls.key"
|
||||
, cfgPublicBaseUrl = "https://nuthatch.example"
|
||||
, cfgJwtSecret = secret "unit-test-session-secret-material-32"
|
||||
, cfgSessionHours = 1
|
||||
, cfgCookieName = "nuthatch_session"
|
||||
, cfgCsrfCookieName = "nuthatch_csrf"
|
||||
, cfgOidcTransactionCookieName = "nuthatch_oidc"
|
||||
, cfgOidcIssuer = "https://issuer.example"
|
||||
, cfgOidcClientId = "nuthatch"
|
||||
, cfgOidcAdditionalAudiences = []
|
||||
, cfgOidcClientSecret = secret "unit-test-client-secret"
|
||||
, cfgOidcRedirectUri = "https://nuthatch.example/oidc/callback"
|
||||
, cfgOidcTransactionMinutes = 5
|
||||
, cfgOidcMaxIdTokenSeconds = 3600
|
||||
, cfgPgConn = secret ""
|
||||
, cfgRedisHost = ""
|
||||
, cfgRedisPort = 6379
|
||||
, cfgRedisDb = 0
|
||||
, cfgS3Endpoint = "https://s3.example"
|
||||
, cfgS3Region = "test"
|
||||
, cfgS3Bucket = "test"
|
||||
, cfgS3AccessKey = secret "test"
|
||||
, cfgS3SecretKey = secret "test"
|
||||
, cfgS3PathStyle = True
|
||||
, cfgPersonalQuotaBytes = 1024
|
||||
, cfgSharedQuotaBytes = 1024
|
||||
, cfgMaxObjectBytes = 1024
|
||||
, cfgThrottleBytesPerSec = 1024
|
||||
, cfgThrottleBurstBytes = 1024
|
||||
, cfgListCacheTtlSec = 30
|
||||
, cfgBrowsePageSize = 100
|
||||
, cfgSearchMaxKeys = 100
|
||||
, cfgSearchTimeoutMs = 1000
|
||||
, cfgDeletePageSize = 100
|
||||
, cfgRecoveryGraceSec = 30
|
||||
, cfgIdempotencyWindowSec = 3600
|
||||
, cfgObjectRequestTimeoutSec = 300
|
||||
, cfgStartupAttempts = 3
|
||||
, cfgStartupDelayMs = 10
|
||||
, cfgAuditObjectKeys = False
|
||||
{ cfgRuntime = RuntimeConfig
|
||||
{ cfgStartupAttempts = 3
|
||||
, cfgStartupDelayMs = 10
|
||||
}
|
||||
, cfgHttp = HttpConfig
|
||||
{ cfgListenHost = "127.0.0.1"
|
||||
, cfgListenPort = 8080
|
||||
, cfgTlsCertPath = "/tls/tls.crt"
|
||||
, cfgTlsKeyPath = "/tls/tls.key"
|
||||
, cfgPublicBaseUrl = "https://nuthatch.example"
|
||||
, cfgJwtSecret = sessionSecret
|
||||
, cfgSessionHours = 1
|
||||
, cfgCookieName = "nuthatch_session"
|
||||
, cfgCsrfCookieName = "nuthatch_csrf"
|
||||
, cfgOidcTransactionCookieName = "nuthatch_oidc"
|
||||
}
|
||||
, cfgOidc = OidcConfig
|
||||
{ cfgOidcIssuer = "https://issuer.example"
|
||||
, cfgOidcClientId = "nuthatch"
|
||||
, cfgOidcAdditionalAudiences = []
|
||||
, cfgOidcClientSecret = secret "unit-test-client-secret"
|
||||
, cfgOidcRedirectUri = "https://nuthatch.example/oidc/callback"
|
||||
, cfgOidcTransactionMinutes = 5
|
||||
, cfgOidcMaxIdTokenSeconds = 3600
|
||||
, cfgOidcTransactionSecret = sessionSecret
|
||||
}
|
||||
, cfgPostgres = PostgresConfig
|
||||
{ cfgPgConn = secret ""
|
||||
}
|
||||
, cfgRedis = RedisConfig
|
||||
{ cfgRedisHost = ""
|
||||
, cfgRedisPort = 6379
|
||||
, cfgRedisDb = 0
|
||||
, cfgThrottleBytesPerSec = 1024
|
||||
, cfgThrottleBurstBytes = 1024
|
||||
}
|
||||
, cfgS3 = S3Config
|
||||
{ cfgS3Endpoint = "https://s3.example"
|
||||
, cfgS3Region = "test"
|
||||
, cfgS3Bucket = "test"
|
||||
, cfgS3AccessKey = secret "test"
|
||||
, cfgS3SecretKey = secret "test"
|
||||
, cfgS3PathStyle = True
|
||||
, cfgS3RequestTimeoutSec = 300
|
||||
}
|
||||
, cfgProduct = ProductConfig
|
||||
{ cfgPersonalQuotaBytes = 1024
|
||||
, cfgSharedQuotaBytes = 1024
|
||||
, cfgMaxObjectBytes = 1024
|
||||
, cfgListCacheTtlSec = 30
|
||||
, cfgBrowsePageSize = 100
|
||||
, cfgSearchMaxKeys = 100
|
||||
, cfgSearchTimeoutMs = 1000
|
||||
, cfgDeletePageSize = 100
|
||||
, cfgIdempotencyWindowSec = 3600
|
||||
, cfgAuditObjectKeys = False
|
||||
}
|
||||
, cfgRecovery = RecoveryConfig
|
||||
{ cfgRecoveryGraceSec = 30
|
||||
, cfgObjectRequestTimeoutSec = 300
|
||||
}
|
||||
}
|
||||
where
|
||||
sessionSecret = secret "unit-test-session-secret-material-32"
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import Network.Wai.Test
|
|||
, srequest
|
||||
)
|
||||
import Nuthatch.Api
|
||||
import Nuthatch.Config (Config)
|
||||
import Nuthatch.Config (Config (..), HttpConfig)
|
||||
import Nuthatch.Http.Auth
|
||||
( authenticationHandler
|
||||
, loginResponse
|
||||
|
|
@ -79,6 +79,9 @@ tests = testGroup "typed HTTP boundary"
|
|||
@?= "healthz"
|
||||
]
|
||||
|
||||
httpConfig :: HttpConfig
|
||||
httpConfig = cfgHttp testConfig
|
||||
|
||||
testRouteSelection :: IO ()
|
||||
testRouteSelection = do
|
||||
calls <- newIORef 0
|
||||
|
|
@ -324,11 +327,11 @@ typedTestServer calls =
|
|||
|
||||
testAuthenticationBoundary :: IO ()
|
||||
testAuthenticationBoundary = do
|
||||
tokenResult <- mintSession testConfig "issuer" "subject" Nothing
|
||||
tokenResult <- mintSession httpConfig "issuer" "subject" Nothing
|
||||
token <- either (fail . T.unpack) pure tokenResult
|
||||
let csrf = CsrfToken "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
responseHeaders = getHeaders
|
||||
(loginResponse testConfig token csrf)
|
||||
(loginResponse httpConfig token csrf)
|
||||
cookieHeaders =
|
||||
[ value
|
||||
| (name, value) <- responseHeaders
|
||||
|
|
@ -353,7 +356,7 @@ testAuthenticationBoundary = do
|
|||
(length (filter ("HttpOnly" `BS.isInfixOf`) cookieHeaders) == 2)
|
||||
|
||||
calls <- newIORef 0
|
||||
let app = productionAuthTestApplication testConfig calls
|
||||
let app = productionAuthTestApplication httpConfig calls
|
||||
cookie =
|
||||
"nuthatch_session=" <> TE.encodeUtf8 (unSessionToken token)
|
||||
<> "; nuthatch_csrf=" <> TE.encodeUtf8 (unCsrfToken csrf)
|
||||
|
|
@ -415,7 +418,7 @@ testCanonicalProductRoutes = do
|
|||
simpleStatus response @?= status404
|
||||
readIORef calls >>= (@?= 0)
|
||||
|
||||
productionAuthTestApplication :: Config -> IORef Int -> Application
|
||||
productionAuthTestApplication :: HttpConfig -> IORef Int -> Application
|
||||
productionAuthTestApplication cfg calls =
|
||||
utf8Boundary $
|
||||
serveWithContext
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import Data.Text (Text)
|
|||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as TE
|
||||
import Data.Time.Clock (UTCTime, addUTCTime, getCurrentTime)
|
||||
import Nuthatch.Config (Config (..))
|
||||
import Nuthatch.Config (Config (..), OidcConfig (..))
|
||||
import Nuthatch.Oidc (OidcIdentity (..), verifyIdToken)
|
||||
import Nuthatch.Test.Config (testConfig)
|
||||
import Test.Tasty (TestTree, testGroup)
|
||||
|
|
@ -47,6 +47,9 @@ tests = testGroup "OIDC validation"
|
|||
testIdTokenTimeAndSerializationRejections
|
||||
]
|
||||
|
||||
oidcConfig :: OidcConfig
|
||||
oidcConfig = cfgOidc testConfig
|
||||
|
||||
data TestIdTokenClaims = TestIdTokenClaims
|
||||
{ testIdTokenRegistered :: ClaimsSet
|
||||
, testIdTokenNonce :: Text
|
||||
|
|
@ -98,7 +101,7 @@ testValidIdToken = do
|
|||
compact <- signTestIdToken key RS256
|
||||
(testIdClaims now "https://issuer.example" ["nuthatch"] "nonce")
|
||||
result <- verifyIdToken
|
||||
testConfig
|
||||
oidcConfig
|
||||
(JWKSet [key])
|
||||
now
|
||||
"nonce"
|
||||
|
|
@ -119,13 +122,13 @@ testConfiguredAudiences = do
|
|||
missingAuthorizedParty <- signTestIdToken key RS256
|
||||
(testIdClaims now "https://issuer.example" ["project", "nuthatch"] "nonce")
|
||||
result <- verifyIdToken
|
||||
testConfig { cfgOidcAdditionalAudiences = ["project"] }
|
||||
oidcConfig { cfgOidcAdditionalAudiences = ["project"] }
|
||||
(JWKSet [key])
|
||||
now
|
||||
"nonce"
|
||||
compact
|
||||
rejected <- verifyIdToken
|
||||
testConfig { cfgOidcAdditionalAudiences = ["project"] }
|
||||
oidcConfig { cfgOidcAdditionalAudiences = ["project"] }
|
||||
(JWKSet [key])
|
||||
now
|
||||
"nonce"
|
||||
|
|
@ -151,12 +154,12 @@ testIdTokenCryptographicRejections = do
|
|||
multipleAudience <- signTestIdToken key RS256
|
||||
(testIdClaims now "https://issuer.example" ["nuthatch", "other"] "nonce")
|
||||
let rejected =
|
||||
[ verifyIdToken testConfig (JWKSet [key]) now "other-nonce" valid
|
||||
, verifyIdToken testConfig (JWKSet [key]) now "nonce" wrongAlgorithm
|
||||
, verifyIdToken testConfig (JWKSet [key]) now "nonce" wrongIssuer
|
||||
, verifyIdToken testConfig (JWKSet [key]) now "nonce" wrongSignature
|
||||
, verifyIdToken testConfig (JWKSet [key]) now "nonce" wrongAudience
|
||||
, verifyIdToken testConfig (JWKSet [key]) now "nonce" multipleAudience
|
||||
[ verifyIdToken oidcConfig (JWKSet [key]) now "other-nonce" valid
|
||||
, verifyIdToken oidcConfig (JWKSet [key]) now "nonce" wrongAlgorithm
|
||||
, verifyIdToken oidcConfig (JWKSet [key]) now "nonce" wrongIssuer
|
||||
, verifyIdToken oidcConfig (JWKSet [key]) now "nonce" wrongSignature
|
||||
, verifyIdToken oidcConfig (JWKSet [key]) now "nonce" wrongAudience
|
||||
, verifyIdToken oidcConfig (JWKSet [key]) now "nonce" multipleAudience
|
||||
]
|
||||
results <- sequence rejected
|
||||
assertBool "every invalid cryptographic or binding case is rejected"
|
||||
|
|
@ -197,11 +200,11 @@ testIdTokenTimeAndSerializationRejections = do
|
|||
["nuthatch"]
|
||||
"nonce")
|
||||
results <- sequence
|
||||
[ verifyIdToken testConfig (JWKSet [key]) now "nonce" "not.compact"
|
||||
, verifyIdToken testConfig (JWKSet [key]) now "nonce" missingIssuedAt
|
||||
, verifyIdToken testConfig (JWKSet [key]) now "nonce" future
|
||||
, verifyIdToken testConfig (JWKSet [key]) now "nonce" expired
|
||||
, verifyIdToken testConfig (JWKSet [key]) now "nonce" overlong
|
||||
[ verifyIdToken oidcConfig (JWKSet [key]) now "nonce" "not.compact"
|
||||
, verifyIdToken oidcConfig (JWKSet [key]) now "nonce" missingIssuedAt
|
||||
, verifyIdToken oidcConfig (JWKSet [key]) now "nonce" future
|
||||
, verifyIdToken oidcConfig (JWKSet [key]) now "nonce" expired
|
||||
, verifyIdToken oidcConfig (JWKSet [key]) now "nonce" overlong
|
||||
]
|
||||
assertBool "every invalid serialization or time case is rejected"
|
||||
(all isLeft results)
|
||||
|
|
|
|||
Loading…
Reference in a new issue