Skip to content

Commit

Permalink
Implement OIDC auth for swagger-ui (#30)
Browse files Browse the repository at this point in the history
* Implement OIDC auth for swagger-ui

* OpenAPI 3.2.0 compat
  • Loading branch information
maksbotan authored Feb 6, 2022
1 parent 51c8203 commit 999c704
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 12 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.3.11] - 2022-02-06
### Added
- Custom `swagger-ui` wrapper that allows to use BIOCAD's OIDC authorization from the UI.
- Compatibility with `openapi3` 3.2.0 and higher.

## [0.1.3.10] - 2021-08-03
### Added
- Create OIDC config with provided tls manager.
Expand Down
88 changes: 88 additions & 0 deletions index.html.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Script to reload .../swagger-ui to .../swagger-ui/ -->
<script type="text/javascript">
if (window.location.href.match(/\/index.html\/$/)) {
// drop trailing slash in "index.html/"
window.location.href = window.location.href.replace(/.$/, "");
} else if (!window.location.href.match(/\/(?:index.html)?$/)) {
// servant-swagger-ui: redirect to location with a trailing slash
// we need this because assets are served from relative paths
window.location.href = window.location.href + '/';
}
</script>
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}

*,
*:before,
*:after
{
box-sizing: inherit;
}

body
{
margin:0;
background: #fafafa;
}
</style>
</head>

<body>
<div id="swagger-ui"></div>

<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script>
window.onload = function() {
// servant-swagger-ui addition
var ssuDir = "SERVANT_SWAGGER_UI_DIR";
var ssuSchema = "SERVANT_SWAGGER_UI_SCHEMA";
// more concretely:
// replace /swagger-ui/ or /swagger-ui/index.html with /swagger.json
// we always have trailing slash!
var re = new RegExp("\/" + ssuDir + "/(?:index\\.html)?$","g");
var url = window.location.pathname.replace(re, "/" + ssuSchema);

// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: url,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
// End Swagger UI call region

// AbsCan addition: enable PKCE flow, prefill OIDC client id and scopes
ui.initOAuth({
usePkceWithAuthorizationCodeGrant: true,
clientId: "BIOCAD_OIDC_CLIENT_ID",
scopes: ['openid', 'profile', 'email']
});

window.ui = ui;
};
</script>
</body>
</html>
63 changes: 55 additions & 8 deletions src/Web/Template/Servant/Auth.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

module Web.Template.Servant.Auth
( CbdAuth
Expand All @@ -12,12 +13,13 @@ module Web.Template.Servant.Auth
, oidcCfgWithManager
, OIDCUser (..)
, Permit
, swaggerSchemaUIBCDServer
) where

-- after https://www.stackage.org/haddock/lts-15.15/servant-server-0.16.2/src/Servant.Server.Experimental.Auth.html

import Control.Applicative ((<|>))
import Control.Lens (At (at), ix, (&), (.~), (<&>), (?~), (^..), (^?))
import Control.Lens (Iso', at, coerced, ix, (&), (.~), (<&>), (?~), (^..), (^?))
import Control.Monad.Except (runExceptT, unless)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.IORef (readIORef, writeIORef)
Expand All @@ -34,18 +36,23 @@ import Crypto.JWT (ClaimsSet, JWTError, JWTValidat
SignedJWT, audiencePredicate, decodeCompact,
defaultJWTValidationSettings, issuerPredicate,
string, unregisteredClaims, uri, verifyClaims)
import Data.Aeson (Value)
import Data.Aeson.Lens (AsPrimitive (_String), key, values)
import Data.ByteString (ByteString, stripPrefix)
import qualified Data.ByteString.Lazy as LB
import Data.Cache (Cache)
import qualified Data.Cache as Cache
import Data.OpenApi (Definitions, OpenApi, URL (..))
import Data.OpenApi.Internal (ApiKeyLocation (..), ApiKeyParams (..),
HttpSchemeType (..), SecurityRequirement (..),
SecurityScheme (..), SecuritySchemeType (..))
HttpSchemeType (..), SecurityDefinitions (..),
SecurityRequirement (..), SecurityScheme (..),
SecuritySchemeType (..))
import Data.OpenApi.Lens (components, description, security, securitySchemes)
import Data.OpenApi.Operation (allOperations, setResponse)
import qualified Data.Text as T
import Data.Time.Clock (NominalDiffTime, UTCTime, addUTCTime, diffUTCTime,
getCurrentTime, nominalDiffTimeToSeconds)
import FileEmbedLzma (embedText)
import Network.HTTP.Client (Manager, httpLbs)
import Network.HTTP.Client.TLS (newTlsManager)
import Network.HTTP.Types.Header (hContentType)
Expand All @@ -58,6 +65,8 @@ import Servant.Server (HasContextEntry (getContextEntr
ServerError (..), err401, err403, err500)
import Servant.Server.Internal (DelayedIO, addAuthCheck, delayedFailFatal,
withRequest)
import Servant.Swagger.UI (SwaggerSchemaUI', swaggerUiFiles)
import Servant.Swagger.UI.Core (swaggerSchemaUIServerImpl)
import System.Clock (TimeSpec (..))
import Web.Cookie (parseCookiesText)

Expand Down Expand Up @@ -103,7 +112,7 @@ instance HasServer api context => HasServer (CbdAuth :> api) context where

instance HasOpenApi api => HasOpenApi (CbdAuth :> api) where
toOpenApi _ = toOpenApi @api Proxy
& components . securitySchemes . at "cbdCookie" ?~ idCookie
& components . securitySchemes . securityDefinitions . at "cbdCookie" ?~ idCookie
& allOperations . security .~ [SecurityRequirement $ mempty & at "cbdCookie" ?~ []]
& setResponse 401 (return $ mempty & description .~ "Authorization failed")
where
Expand Down Expand Up @@ -254,8 +263,6 @@ instance ( HasServer api context
(die ERROR unauth500)
(uncurry discoSuccess)
where
appWellKnown u@URI {..} = u {uriPath = uriPath <> "/.well-known/openid-configuration"}

discoSuccess disco mbDiscoExp = liftIO $ do
now <- getCurrentTime
Cache.insert' oidcDiscoCache (expiration now mbDiscoExp oidcDefaultExpiration) () disco
Expand Down Expand Up @@ -292,13 +299,22 @@ instance ( HasServer api context

instance HasOpenApi api => HasOpenApi (OIDCAuth :> api) where
toOpenApi _ = toOpenApi @api Proxy
& components . securitySchemes . at "cbdJWT" ?~ idJWT
& allOperations . security .~ [SecurityRequirement $ mempty & at "cbdJWT" ?~ []]
& components . securitySchemes . securityDefinitions . at "cbdJWT" ?~ idJWT
& components . securitySchemes . securityDefinitions . at "cbdOIDC" ?~ idOIDC
& allOperations . security .~
[ SecurityRequirement $ mempty & at "cbdJWT" ?~ []
, SecurityRequirement $ mempty & at "cbdOIDC" ?~ []
]
& setResponse 401 (return $ mempty & description .~ "Authorization failed")
where
idJWT = SecurityScheme
(SecuritySchemeHttp $ HttpSchemeBearer $ Just "jwt")
(Just "jwt token")
idOIDC = SecurityScheme
-- It is expected that client will update this field in runtime before serving
-- generated swagegr, as there is no easy way to pass it to this instance.
(SecuritySchemeOpenIdConnect $ URL "NOT SET")
(Just "BIOCAD's OpenID Connect auth")

-- | Adds authenthication via roles
--
Expand Down Expand Up @@ -378,3 +394,34 @@ unauth500 = delayedFailFatal $ err500
{ errBody = "{\"error\": \"Internal server error\"}"
, errHeaders = [(hContentType, "application/json")]
}

swaggerUiIndexBCDTemplate :: Text
swaggerUiIndexBCDTemplate = $(embedText "index.html.tmpl")

-- | Version of 'Servant.Swagger.UI.swaggerSchemaUIServer' that uses
-- our @index.html@ template to enable PKCE auth flow and prefill
-- OpenID client id.
swaggerSchemaUIBCDServer
:: Monad m
=> ServerT api m ~ m Value
=> OIDCConfig
-> OpenApi
-> ServerT (SwaggerSchemaUI' dir api) m
swaggerSchemaUIBCDServer oidcConfig openapi =
swaggerSchemaUIServerImpl swaggerUiIndexTemplateFilled swaggerUiFiles openapiWithOidcUrl
where
swaggerUiIndexTemplateFilled =
T.replace "BIOCAD_OIDC_CLIENT_ID" (oidcClientId oidcConfig)
swaggerUiIndexBCDTemplate
openapiWithOidcUrl = openapi
& components . securitySchemes . securityDefinitions . at "cbdOIDC" ?~
SecurityScheme
(SecuritySchemeOpenIdConnect $ URL $ T.pack $ show $ appWellKnown $ oidcIssuer oidcConfig)
(Just "BIOCAD's OpenID Connect auth")

-- | Append OIDC's @.well-known/openid-configuration" part to the base of OIDC issuer URI.
appWellKnown :: URI -> URI
appWellKnown u@URI {..} = u {uriPath = uriPath <> "/.well-known/openid-configuration"}

securityDefinitions :: Iso' SecurityDefinitions (Definitions SecurityScheme)
securityDefinitions = coerced
12 changes: 8 additions & 4 deletions web-template.cabal
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: web-template
version: 0.1.3.10
version: 0.1.3.11
synopsis: Web template
description:
Web template includes:
Expand All @@ -15,7 +15,9 @@ maintainer: neterebskiy@biocad.ru
copyright: (c) 2017, BIOCAD CBD
category: Web
build-type: Simple
extra-source-files: README.md
extra-source-files:
README.md
index.html.tmpl
cabal-version: >=1.10

library
Expand Down Expand Up @@ -45,6 +47,7 @@ library
, cookie
, data-default
, fast-logger
, file-embed-lzma
, generic-override >= 0.2.0.0
, generic-override-aeson
, http-client
Expand All @@ -55,14 +58,15 @@ library
, lens-aeson
, mtl
, network-uri
, openapi3 >= 3.1.0
, openapi3 >= 3.2.0
, openid-connect >= 0.1.1
, resourcet
, scotty
, servant >= 0.18
, servant-openapi3
, servant-server >= 0.18
, servant-swagger-ui >= 0.3.5
, servant-swagger-ui >= 0.3.5.4.5.0
, servant-swagger-ui-core >= 0.3.5
, text
, time
, time
Expand Down

0 comments on commit 999c704

Please sign in to comment.