I2KHGVD44KT4MQJXGCTVSQKMBO6TVCY72F26TLXGWRL6PHGF6RNQC LD4GLVSF6YTA7OZWIGJ45H6TUXGM4WKUIYXKWQFNUP36WDMYSMXAC W35DDBFYF6Z4ZPCFEO5RPAONZLYCSNTXUSTS6FIUUVZHCI6Q7GHAC BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC IZEVQF627FA7VV25KJAWYWGGC35LZUUBBQRPN5ZAINTQLPEDRTEAC VJPT6HDRMJAJD5PT3VOYJYW43ISKLICEHLSDWSROX2XZWO2OFZPQC WZUHEZSBRKHQMNWDKVG4X6DDIQEAXTGI6IGAJ5ERPRQ3W2KUMX4QC EMVTF2IWNQGRL44FC4JNG5FYYQTZSFPNM6SOM7IAEH6T7PPK2NVAC BXGLKYRXO2O4NRM3BLNWQ7AWVPQXAMFS57MFYHJNOZZEZZW5BH6AC 5XFJNUAZUCQ3WCGW4QRIAWR764QYDOPHOIVO2TRMGSSG7UDX2M2AC Z7KS5XHHC6PAMTVBHXY7KUSS3BWAOU6FSYIITUCFOOJZU4OUJHBAC {-# LANGUAGE NoImplicitPrelude #-}module Api.Worklog (resource) whereimport ClassyPreludeimport Control.Applicative ((<$>))import Control.Concurrent.STM (atomically, modifyTVar, readTVar)import Control.Monad.Error (throwError)import Control.Monad.Reader (ReaderT, asks)import Control.Monad.Trans (liftIO)import Data.Set (Set)import qualified Data.Foldable as Fimport qualified Data.Set as Setimport qualified Data.Text as Timport Rest (Handler, ListHandler, Range (count, offset), Resource, Void, domainReason, mkInputHandler, mkListing, mkResourceReader, named, singleRead,withListing, xmlJsonE, xmlJsonI, xmlJsonO)import qualified Rest.Resource as Rimport ApiTypes (BlogApi, ServerData (..))import Type.User (User)import Type.UserInfo (UserInfo (..))import Type.UserSignupError (UserSignupError (..))import qualified Type.User as Userimport qualified Type.UserInfo as UserInforesource ::
module Quixotic.Snaplet.Auth whereimport ClassyPreludeimport Control.Lensimport Control.Monad.Stateimport Data.ByteString (split)import Data.Attoparsec.ByteStringimport qualified Data.ByteString.Base64 as B64import Quixoticimport Quixotic.Databaseimport Quixotic.Snapletimport Snap.Coreimport Snap.Snapletimport Snap.Snaplet.PostgresqlSimpleimport qualified Snap.Snaplet.Auth as AUtype AuthHeader = (Text, ByteString)authHeaderParser :: Parser AuthHeaderauthHeaderParser = dolet isBase64Char w = (w >= 47 && w <= 57 ) ||(w >= 64 && w <= 90 ) ||(w >= 97 && w <= 122) ||(w == 43 || w == 61 )b64 <- string "Basic" *> takeWhile1 isBase64Chardecoded <- either fail pure $ B64.decode b64case split 58 decoded of(uname : pwd : []) -> pure $ (decodeUtf8 uname, pwd)_ -> fail "Could not unpack auth header into username and password components"requireLogin :: Handler App App AU.AuthUserrequireLogin = doreq <- getRequestrawHeader <- maybe throwChallenge pure $ getHeader "Authorization" req(uname, pwd) <- either (throwDenied . AU.AuthError) pure $ parseOnly authHeaderParser rawHeaderauthResult <- with auth $ AU.loginByUsername uname (AU.ClearText pwd) Falseeither throwDenied pure authResultrequireUser :: Handler App App AU.AuthUserrequireUser = docurrentUser <- with auth AU.currentUsermaybe requireLogin pure currentUserrequireUserId :: Handler App App UserIdrequireUserId = doQDB{..} <- view qdb <$> with qm getcurrentUser <- requireLoginqdbUser <- case UserName . AU.unUid <$> AU.userId currentUser ofNothing -> snapError 403 "User is authenticated, but session lacks user identifier"Just n -> liftPG . runReaderT $ findUserByUserName ncase qdbUser ofNothing -> snapError 403 "Unable to retrieve user record for authenticated user"Just u -> pure (u ^. userId)requireProjectAccess :: UserId -> Handler App App ProjectIdrequireProjectAccess uid = dopidMay <- getParam "projectId"case ProjectId <$> (readMay =<< fmap decodeUtf8 pidMay) ofNothing -> snapError 403 "Value of parameter projectId could not be parsed to a valid value."Just pid -> error $ "FIXME: implement project access check - got pid " ++ " " ++ show uid ++ " " ++ show pidthrowChallenge :: MonadSnap m => m athrowChallenge = domodifyResponse $ (setResponseStatus 401 "Unauthorized") .(setHeader "WWW-Authenticate" "Basic realm=quixotic")getResponse >>= finishWiththrowDenied :: MonadSnap m => AU.AuthFailure -> m athrowDenied failure = domodifyResponse $ setResponseStatus 403 "Access Denied"writeText $ "Access Denied: " <> tshow failuregetResponse >>= finishWith
data AuthHeader = AuthHeader Text ByteStringauthHeaderParser :: Parser AuthHeaderauthHeaderParser = dolet isBase64Char w = (w >= 47 && w <= 57 ) ||(w >= 64 && w <= 90 ) ||(w >= 97 && w <= 122) ||(w == 43 || w == 61 )b64 <- string "Basic" *> takeWhile1 isBase64Chardecoded <- either fail pure $ B64.decode b64case split 58 decoded of(uname : pwd : []) -> pure $ AuthHeader (decodeUtf8 uname) pwd_ -> fail "Could not unpack auth header into username and password components"
loginHandler :: (AU.AuthUser -> Handler App App a) -> Handler App App aloginHandler onSuccess = doreq <- getRequestrawHeader <- maybe throwChallenge pure $ getHeader "Authorization" reqlet parsedHeader = parseOnly authHeaderParser rawHeader(AuthHeader uname pwd) <- either (throwDenied . AU.AuthError) pure parsedHeaderauthResult <- with auth $ AU.loginByUsername uname (AU.ClearText pwd) Falseeither throwDenied onSuccess authResult
throwChallenge :: MonadSnap m => m athrowChallenge = domodifyResponse $ (setResponseStatus 401 "Unauthorized") .(setHeader "WWW-Authenticate" "Basic realm=quixotic")getResponse >>= finishWiththrowDenied :: MonadSnap m => AU.AuthFailure -> m athrowDenied failure = domodifyResponse $ setResponseStatus 403 "Access Denied"writeText $ "Access Denied: " <> tshow failuregetResponse >>= finishWith
requireLogin :: Handler App App a -> Handler App App arequireLogin = AU.requireUser auth (redirect "/login")requireUserId :: (UserId -> Handler App App a) -> Handler App App arequireUserId hf = AU.requireUser auth (redirect "/login") $ doQDB{..} <- view qdb <$> with qm getauthedUser <- with auth AU.currentUserqdbUser <- case UserName . AU.unUid <$> (AU.userId =<< authedUser) ofNothing -> snapError 403 "User is authenticated, but session lacks user identifier"Just n -> liftPG . runReaderT $ findUserByUserName ncase qdbUser ofNothing -> snapError 403 "Unable to retrieve user record for authenticated user"Just u -> hf (u ^. userId)checkProjectAccess :: ProjectId -> UserId -> Handler App App acheckProjectAccess = undefined