PWFile auth-plugin
This commit is contained in:
parent
3f5fab8d2f
commit
acd100bca7
@ -19,6 +19,7 @@ should-log-all: "_env:LOG_ALL:false"
|
|||||||
# mutable-static: false
|
# mutable-static: false
|
||||||
# skip-combining: false
|
# skip-combining: false
|
||||||
auth-dummy-login: "_env:DUMMY_LOGIN:false"
|
auth-dummy-login: "_env:DUMMY_LOGIN:false"
|
||||||
|
auth-pwfile: "_env:PWFILE:"
|
||||||
allow-deprecated: "_env:ALLOW_DEPRECATED:false"
|
allow-deprecated: "_env:ALLOW_DEPRECATED:false"
|
||||||
|
|
||||||
# NB: If you need a numeric value (e.g. 123) to parse as a String, wrap it in single quotes (e.g. "_env:PGPASS:'123'")
|
# NB: If you need a numeric value (e.g. 123) to parse as a String, wrap it in single quotes (e.g. "_env:PGPASS:'123'")
|
||||||
|
|||||||
3
models
3
models
@ -1,4 +1,4 @@
|
|||||||
User
|
User json
|
||||||
plugin Text
|
plugin Text
|
||||||
ident Text
|
ident Text
|
||||||
matrikelnummer Text Maybe
|
matrikelnummer Text Maybe
|
||||||
@ -8,6 +8,7 @@ User
|
|||||||
theme Theme default='default'
|
theme Theme default='default'
|
||||||
UniqueAuthentication plugin ident
|
UniqueAuthentication plugin ident
|
||||||
UniqueEmail email
|
UniqueEmail email
|
||||||
|
deriving Show
|
||||||
UserAdmin
|
UserAdmin
|
||||||
user UserId
|
user UserId
|
||||||
school SchoolId
|
school SchoolId
|
||||||
|
|||||||
@ -18,6 +18,7 @@ module Application
|
|||||||
-- * for GHCI
|
-- * for GHCI
|
||||||
, handler
|
, handler
|
||||||
, db
|
, db
|
||||||
|
, addPWEntry
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.Logger (liftLoc, runLoggingT)
|
import Control.Monad.Logger (liftLoc, runLoggingT)
|
||||||
@ -37,6 +38,15 @@ import Network.Wai.Middleware.RequestLogger (Destination (Logger),
|
|||||||
import System.Log.FastLogger (defaultBufSize, newStdoutLoggerSet,
|
import System.Log.FastLogger (defaultBufSize, newStdoutLoggerSet,
|
||||||
toLogStr)
|
toLogStr)
|
||||||
|
|
||||||
|
import qualified Crypto.KDF.Argon2 as Argon2
|
||||||
|
import Crypto.Error (CryptoFailable(..))
|
||||||
|
import qualified Data.ByteString.Base64.URL as Base64 (encode)
|
||||||
|
import qualified Data.Text.Encoding as Text
|
||||||
|
import Crypto.Random (MonadRandom(..))
|
||||||
|
|
||||||
|
import qualified Data.ByteString.Char8 as BS
|
||||||
|
import qualified Data.Yaml as Yaml
|
||||||
|
|
||||||
-- Import all relevant handler modules here.
|
-- Import all relevant handler modules here.
|
||||||
-- (HPack takes care to add new modules to our cabal file nowadays.)
|
-- (HPack takes care to add new modules to our cabal file nowadays.)
|
||||||
import Handler.Common
|
import Handler.Common
|
||||||
@ -197,3 +207,14 @@ handler h = getAppDevSettings >>= makeFoundation >>= flip unsafeHandler h
|
|||||||
-- | Run DB queries
|
-- | Run DB queries
|
||||||
db :: ReaderT SqlBackend (HandlerT UniWorX IO) a -> IO a
|
db :: ReaderT SqlBackend (HandlerT UniWorX IO) a -> IO a
|
||||||
db = handler . runDB
|
db = handler . runDB
|
||||||
|
|
||||||
|
addPWEntry :: FilePath {-^ Password file -}
|
||||||
|
-> User
|
||||||
|
-> Text {-^ Password -}
|
||||||
|
-> IO ()
|
||||||
|
addPWEntry pwFile pwUser (Text.encodeUtf8 -> pw) = do
|
||||||
|
pwSalt'@(Text.decodeUtf8 . Base64.encode -> pwSalt) <- getRandomBytes 32
|
||||||
|
let pwEntry = PWEntry{..}
|
||||||
|
CryptoPassed (Text.decodeUtf8 . Base64.encode -> pwHash) = Argon2.hash Argon2.defaultOptions pw pwSalt' 256
|
||||||
|
c <- either (const []) id <$> Yaml.decodeFileEither pwFile
|
||||||
|
Yaml.encodeFile pwFile $ c ++ [pwEntry]
|
||||||
|
|||||||
@ -38,10 +38,12 @@ import qualified Data.Text.Encoding as TE
|
|||||||
import Data.ByteArray (convert)
|
import Data.ByteArray (convert)
|
||||||
import Crypto.Hash (Digest, SHAKE256)
|
import Crypto.Hash (Digest, SHAKE256)
|
||||||
import Crypto.Hash.Conduit (sinkHash)
|
import Crypto.Hash.Conduit (sinkHash)
|
||||||
|
import qualified Crypto.KDF.Argon2 as Argon2
|
||||||
|
import Crypto.Error (CryptoFailable(..))
|
||||||
|
|
||||||
import qualified Data.CryptoID (CryptoID) -- for DisplayAble instance only
|
import qualified Data.CryptoID (CryptoID) -- for DisplayAble instance only
|
||||||
|
|
||||||
import qualified Data.ByteString.Base64.URL as Base64 (encode)
|
import qualified Data.ByteString.Base64.URL as Base64 (encode, decodeLenient)
|
||||||
|
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import qualified Data.ByteString.Lazy as Lazy.ByteString
|
import qualified Data.ByteString.Lazy as Lazy.ByteString
|
||||||
@ -74,6 +76,13 @@ import Handler.Utils.DateTime
|
|||||||
import Control.Lens
|
import Control.Lens
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
|
|
||||||
|
import Data.Aeson
|
||||||
|
import Data.Aeson.TH
|
||||||
|
import qualified Data.Yaml as Yaml
|
||||||
|
|
||||||
|
import Text.Shakespeare.Text (st)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- -- TODO: Move the following to the appropriate place, if DisplayAble is kept
|
-- -- TODO: Move the following to the appropriate place, if DisplayAble is kept
|
||||||
instance DisplayAble TermId where
|
instance DisplayAble TermId where
|
||||||
@ -936,6 +945,14 @@ instance YesodAuth UniWorX where
|
|||||||
-- Override the above two destinations when a Referer: header is present
|
-- Override the above two destinations when a Referer: header is present
|
||||||
redirectToReferer _ = True
|
redirectToReferer _ = True
|
||||||
|
|
||||||
|
loginHandler = do
|
||||||
|
tp <- getRouteToParent
|
||||||
|
lift . authLayout $ do
|
||||||
|
master <- getYesod
|
||||||
|
let authPlugins' = authPlugins master
|
||||||
|
$logDebugS "Auth" $ "Enabled plugins: " <> Text.intercalate ", " (map apName authPlugins')
|
||||||
|
forM_ authPlugins' $ flip apLogin tp
|
||||||
|
|
||||||
authenticate creds@(Creds{..}) = runDB . fmap (either id id) . runExceptT $ do
|
authenticate creds@(Creds{..}) = runDB . fmap (either id id) . runExceptT $ do
|
||||||
let (userPlugin, userIdent)
|
let (userPlugin, userIdent)
|
||||||
| isDummy
|
| isDummy
|
||||||
@ -944,11 +961,12 @@ instance YesodAuth UniWorX where
|
|||||||
| otherwise
|
| otherwise
|
||||||
= (credsPlugin, credsIdent)
|
= (credsPlugin, credsIdent)
|
||||||
isDummy = credsPlugin == "dummy"
|
isDummy = credsPlugin == "dummy"
|
||||||
|
isPWFile = credsPlugin == "PWFile"
|
||||||
uAuth = UniqueAuthentication userPlugin userIdent
|
uAuth = UniqueAuthentication userPlugin userIdent
|
||||||
|
|
||||||
$logDebugS "auth" $ tshow ((userPlugin, userIdent), creds)
|
$logDebugS "auth" $ tshow ((userPlugin, userIdent), creds)
|
||||||
|
|
||||||
when isDummy . (throwError =<<) . lift $
|
when (isDummy || isPWFile) . (throwError =<<) . lift $
|
||||||
maybe (UserError $ IdentifierNotFound credsIdent) (Authenticated . entityKey) <$> getBy uAuth
|
maybe (UserError $ IdentifierNotFound credsIdent) (Authenticated . entityKey) <$> getBy uAuth
|
||||||
|
|
||||||
let
|
let
|
||||||
@ -991,9 +1009,38 @@ instance YesodAuth UniWorX where
|
|||||||
authPlugins app = [genericAuthLDAP $ ldapConfig app] ++ extraAuthPlugins
|
authPlugins app = [genericAuthLDAP $ ldapConfig app] ++ extraAuthPlugins
|
||||||
-- Enable authDummy login if enabled.
|
-- Enable authDummy login if enabled.
|
||||||
where extraAuthPlugins = [authDummy | appAuthDummyLogin $ appSettings app]
|
where extraAuthPlugins = [authDummy | appAuthDummyLogin $ appSettings app]
|
||||||
|
++ [authPWFile fp | fp <- maybeToList . appAuthPWFile $ appSettings app]
|
||||||
authHttpManager = getHttpManager
|
authHttpManager = getHttpManager
|
||||||
|
|
||||||
|
authPWFile :: FilePath -> AuthPlugin UniWorX
|
||||||
|
authPWFile fp = AuthPlugin{..}
|
||||||
|
where
|
||||||
|
apName = "PWFile"
|
||||||
|
apLogin = mempty
|
||||||
|
apDispatch "GET" [] = do
|
||||||
|
authData <- lookupBasicAuth
|
||||||
|
pwdata <- liftIO $ Yaml.decodeFileEither fp
|
||||||
|
case (authData, pwdata) of
|
||||||
|
(_, Left _) -> permissionDenied "Invalid password file"
|
||||||
|
(Nothing, _) -> do
|
||||||
|
addHeader "WWW-Authenticate" [st|Basic realm="uni2work maintenance auth" charset="UTF-8"|]
|
||||||
|
notAuthenticated
|
||||||
|
(Just (usr, (Text.encodeUtf8 -> pw)), Right pwdata')
|
||||||
|
| [ PWEntry{ pwUser = pwUser@(User{..}), pwHash = (Base64.decodeLenient . Text.encodeUtf8 -> pwHash), pwSalt = (Base64.decodeLenient . Text.encodeUtf8 -> pwSalt) } ]
|
||||||
|
<- [ pwe | pwe@PWEntry{..} <- pwdata'
|
||||||
|
, let User{..} = pwUser
|
||||||
|
, userIdent == usr
|
||||||
|
, userPlugin == apName
|
||||||
|
]
|
||||||
|
, CryptoPassed hash <- Argon2.hash Argon2.defaultOptions pw pwSalt 256
|
||||||
|
, hash == pwHash
|
||||||
|
-> lift $ do
|
||||||
|
runDB . void $ insertUnique pwUser
|
||||||
|
setCredsRedirect $ Creds apName userIdent []
|
||||||
|
| otherwise -> permissionDenied "Invalid auth"
|
||||||
|
apDispatch _ _ = notFound
|
||||||
|
|
||||||
|
|
||||||
ldapConfig :: UniWorX -> LDAPConfig
|
ldapConfig :: UniWorX -> LDAPConfig
|
||||||
ldapConfig _app@(appSettings -> settings) = LDAPConfig
|
ldapConfig _app@(appSettings -> settings) = LDAPConfig
|
||||||
{ usernameFilter = \u -> principalName <> "=" <> u
|
{ usernameFilter = \u -> principalName <> "=" <> u
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import Database.Persist.Quasi
|
|||||||
-- import Data.Time
|
-- import Data.Time
|
||||||
-- import Data.ByteString
|
-- import Data.ByteString
|
||||||
import Model.Types
|
import Model.Types
|
||||||
|
import Data.Aeson.TH
|
||||||
|
|
||||||
-- You can define all of your database entities in the entities file.
|
-- You can define all of your database entities in the entities file.
|
||||||
-- You can find more information on persistent and how to declare entities
|
-- You can find more information on persistent and how to declare entities
|
||||||
@ -25,5 +26,8 @@ import Model.Types
|
|||||||
share [mkPersist sqlSettings, mkDeleteCascade sqlSettings, mkMigrate "migrateAll"]
|
share [mkPersist sqlSettings, mkDeleteCascade sqlSettings, mkMigrate "migrateAll"]
|
||||||
$(persistFileWith lowerCaseSettings "models")
|
$(persistFileWith lowerCaseSettings "models")
|
||||||
|
|
||||||
|
data PWEntry = PWEntry
|
||||||
|
{ pwUser :: User
|
||||||
|
, pwSalt, pwHash :: Text
|
||||||
|
} deriving (Show)
|
||||||
|
$(deriveJSON defaultOptions ''PWEntry)
|
||||||
|
|||||||
@ -262,6 +262,7 @@ data Theme --Simply add Themes to this type only. CamelCase will be conver
|
|||||||
| SkyLove
|
| SkyLove
|
||||||
deriving (Eq,Ord,Bounded,Enum)
|
deriving (Eq,Ord,Bounded,Enum)
|
||||||
|
|
||||||
|
$(deriveJSON defaultOptions ''Theme)
|
||||||
$(deriveShowWith uncamel ''Theme) -- show for internal use in css/js
|
$(deriveShowWith uncamel ''Theme) -- show for internal use in css/js
|
||||||
$(deriveSimpleWith ''DisplayAble 'display camelSpace ''Theme) -- display to display at user
|
$(deriveSimpleWith ''DisplayAble 'display camelSpace ''Theme) -- display to display at user
|
||||||
|
|
||||||
|
|||||||
@ -71,6 +71,8 @@ data AppSettings = AppSettings
|
|||||||
|
|
||||||
, appAuthDummyLogin :: Bool
|
, appAuthDummyLogin :: Bool
|
||||||
-- ^ Indicate if auth dummy login should be enabled.
|
-- ^ Indicate if auth dummy login should be enabled.
|
||||||
|
, appAuthPWFile :: Maybe FilePath
|
||||||
|
-- ^ If set authenticate against a local password file
|
||||||
, appAllowDeprecated :: Bool
|
, appAllowDeprecated :: Bool
|
||||||
-- ^ Indicate if deprecated routes are accessible for everyone
|
-- ^ Indicate if deprecated routes are accessible for everyone
|
||||||
}
|
}
|
||||||
@ -106,6 +108,7 @@ instance FromJSON AppSettings where
|
|||||||
appCryptoIDKeyFile <- o .: "cryptoid-keyfile"
|
appCryptoIDKeyFile <- o .: "cryptoid-keyfile"
|
||||||
|
|
||||||
appAuthDummyLogin <- o .:? "auth-dummy-login" .!= defaultDev
|
appAuthDummyLogin <- o .:? "auth-dummy-login" .!= defaultDev
|
||||||
|
appAuthPWFile <- ((\f -> f <$ guard (not $ null f)) =<<) <$> o .:? "auth-pwfile"
|
||||||
appAllowDeprecated <- o .:? "allow-deprecated" .!= defaultDev
|
appAllowDeprecated <- o .:? "allow-deprecated" .!= defaultDev
|
||||||
|
|
||||||
return AppSettings {..}
|
return AppSettings {..}
|
||||||
|
|||||||
1
start.sh
1
start.sh
@ -5,5 +5,6 @@ export DETAILED_LOGGING=true
|
|||||||
export LOG_ALL=true
|
export LOG_ALL=true
|
||||||
export DUMMY_LOGIN=true
|
export DUMMY_LOGIN=true
|
||||||
export ALLOW_DEPRECATED=true
|
export ALLOW_DEPRECATED=true
|
||||||
|
export PWFILE=users.yml
|
||||||
|
|
||||||
exec -- stack exec -- yesod devel
|
exec -- stack exec -- yesod devel
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user