2016-02-14 18:10:21 +09:00
|
|
|
|
{- This file is part of Vervis.
|
|
|
|
|
-
|
2019-01-15 07:08:44 +09:00
|
|
|
|
- Written in 2016, 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
2016-02-14 18:10:21 +09:00
|
|
|
|
-
|
|
|
|
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
|
|
|
|
-
|
|
|
|
|
- The author(s) have dedicated all copyright and related and neighboring
|
|
|
|
|
- rights to this software to the public domain worldwide. This software is
|
|
|
|
|
- distributed without any warranty.
|
|
|
|
|
-
|
|
|
|
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
|
|
|
|
- with this software. If not, see
|
|
|
|
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
|
|
|
-}
|
|
|
|
|
|
2016-02-23 17:45:03 +09:00
|
|
|
|
module Vervis.Foundation where
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Control.Concurrent.Chan
|
2019-03-03 04:13:51 +09:00
|
|
|
|
import Control.Concurrent.STM.TVar
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Control.Monad
|
2019-01-19 10:44:21 +09:00
|
|
|
|
import Control.Monad.Logger.CallStack (logWarn)
|
2019-02-04 00:05:28 +09:00
|
|
|
|
import Control.Monad.Trans.Except
|
2016-05-27 01:25:23 +09:00
|
|
|
|
import Control.Monad.Trans.Maybe
|
2019-01-19 10:44:21 +09:00
|
|
|
|
import Crypto.Error (CryptoFailable (..))
|
2019-04-26 13:15:07 +09:00
|
|
|
|
import Crypto.Hash
|
2019-03-20 21:27:40 +09:00
|
|
|
|
import Data.Char
|
2019-02-15 08:27:40 +09:00
|
|
|
|
import Data.Either (isRight)
|
2019-03-03 04:13:51 +09:00
|
|
|
|
import Data.HashMap.Strict (HashMap)
|
2019-04-26 12:23:49 +09:00
|
|
|
|
import Data.List.NonEmpty (NonEmpty (..))
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Data.Text (Text)
|
|
|
|
|
import Data.Text.Encoding
|
|
|
|
|
import Data.Time.Calendar
|
|
|
|
|
import Data.Time.Clock
|
2019-01-19 10:44:21 +09:00
|
|
|
|
import Data.Time.Interval (TimeInterval, fromTimeUnit, toTimeUnit)
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Data.Traversable
|
|
|
|
|
import Data.Vector (Vector)
|
2019-04-18 19:38:01 +09:00
|
|
|
|
import Database.Persist.Postgresql
|
2016-02-13 12:35:30 +09:00
|
|
|
|
import Database.Persist.Sql (ConnectionPool, runSqlPool)
|
2018-05-26 19:27:05 +09:00
|
|
|
|
import Graphics.SVGFonts.ReadFont (PreparedFont)
|
2019-04-26 03:05:02 +09:00
|
|
|
|
import Network.HTTP.Client (Manager, HasHttpManager (..))
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Network.HTTP.Types.Header
|
2019-02-06 11:48:23 +09:00
|
|
|
|
import Network.URI (URI, uriAuthority, uriFragment, uriRegName, parseURI)
|
2019-04-26 03:05:02 +09:00
|
|
|
|
import Network.Wai
|
2018-03-04 06:33:59 +09:00
|
|
|
|
import Text.Shakespeare.Text (textFile)
|
2016-02-13 12:35:30 +09:00
|
|
|
|
import Text.Hamlet (hamletFile)
|
2016-05-06 19:23:31 +09:00
|
|
|
|
--import Text.Jasmine (minifym)
|
2019-03-29 12:25:32 +09:00
|
|
|
|
import Web.Hashids
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Yesod.Auth
|
2018-03-04 06:33:59 +09:00
|
|
|
|
import Yesod.Auth.Account
|
|
|
|
|
import Yesod.Auth.Account.Message (AccountMsg (MsgUsernameExists))
|
2016-02-16 20:41:13 +09:00
|
|
|
|
import Yesod.Auth.Message (AuthMessage (IdentifierNotFound))
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Yesod.Core hiding (logWarn)
|
|
|
|
|
import Yesod.Core.Types
|
2018-03-06 09:55:52 +09:00
|
|
|
|
import Yesod.Default.Util (addStaticContentExternal)
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Yesod.Form.Fields
|
|
|
|
|
import Yesod.Form.Functions
|
|
|
|
|
import Yesod.Form.Types
|
|
|
|
|
import Yesod.Persist.Core
|
|
|
|
|
import Yesod.Static
|
2018-03-06 09:55:52 +09:00
|
|
|
|
|
2019-01-19 10:44:21 +09:00
|
|
|
|
import qualified Data.ByteString.Char8 as BC (unpack)
|
|
|
|
|
import qualified Data.ByteString.Lazy as BL (ByteString)
|
2019-03-03 04:13:51 +09:00
|
|
|
|
import qualified Data.HashMap.Strict as M (lookup, insert)
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import qualified Data.Time.Units as U
|
2019-05-23 06:50:30 +09:00
|
|
|
|
import qualified Database.Esqueleto as E
|
2019-01-19 10:44:21 +09:00
|
|
|
|
import qualified Yesod.Core.Unsafe as Unsafe
|
|
|
|
|
--import qualified Data.CaseInsensitive as CI
|
2019-03-20 21:27:40 +09:00
|
|
|
|
import qualified Data.Text as T
|
2019-01-19 10:44:21 +09:00
|
|
|
|
--import qualified Data.Text.Encoding as TE
|
|
|
|
|
|
2019-04-26 03:05:02 +09:00
|
|
|
|
import Network.HTTP.Digest
|
|
|
|
|
import Network.HTTP.Signature hiding (Algorithm (..), requestHeaders)
|
2018-03-18 07:59:40 +09:00
|
|
|
|
import Yesod.Auth.Unverified
|
|
|
|
|
import Yesod.Auth.Unverified.Creds
|
2018-03-04 06:33:59 +09:00
|
|
|
|
import Yesod.Mail.Send
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
2019-01-19 10:44:21 +09:00
|
|
|
|
import qualified Network.HTTP.Signature as S (Algorithm (..))
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
2019-04-16 23:27:50 +09:00
|
|
|
|
import Control.Concurrent.ResultShare
|
2019-03-11 08:15:42 +09:00
|
|
|
|
import Crypto.PublicVerifKey
|
2019-02-08 08:08:28 +09:00
|
|
|
|
import Network.FedURI
|
2019-03-10 06:21:36 +09:00
|
|
|
|
import Web.ActivityAccess
|
2019-03-29 12:25:32 +09:00
|
|
|
|
import Web.ActivityPub
|
2019-04-26 12:23:49 +09:00
|
|
|
|
import Yesod.ActivityPub
|
2019-03-29 12:25:32 +09:00
|
|
|
|
import Yesod.Hashids
|
2019-04-18 19:38:01 +09:00
|
|
|
|
import Yesod.MonadSite
|
2019-01-22 00:54:57 +09:00
|
|
|
|
|
|
|
|
|
import Text.Email.Local
|
2016-05-06 19:23:31 +09:00
|
|
|
|
import Text.Jasmine.Local (discardm)
|
2019-05-21 08:51:06 +09:00
|
|
|
|
import Yesod.Paginate.Local
|
2019-01-22 00:54:57 +09:00
|
|
|
|
|
2019-01-27 07:22:49 +09:00
|
|
|
|
import Vervis.Access
|
2019-04-26 12:23:49 +09:00
|
|
|
|
import Vervis.ActorKey
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Vervis.Model
|
2016-05-27 01:25:23 +09:00
|
|
|
|
import Vervis.Model.Group
|
2016-05-23 21:24:14 +09:00
|
|
|
|
import Vervis.Model.Ident
|
2016-06-06 18:03:49 +09:00
|
|
|
|
import Vervis.Model.Role
|
2019-03-10 00:40:02 +09:00
|
|
|
|
import Vervis.RemoteActorStore
|
2019-06-15 17:24:08 +09:00
|
|
|
|
import Vervis.Settings
|
|
|
|
|
import Vervis.Style
|
2016-05-18 17:35:13 +09:00
|
|
|
|
import Vervis.Widget (breadcrumbsW, revisionW)
|
2016-05-02 23:16:51 +09:00
|
|
|
|
|
2019-04-23 11:57:53 +09:00
|
|
|
|
data ActivityReport = ActivityReport
|
|
|
|
|
{ _arTime :: UTCTime
|
|
|
|
|
, _arMessage :: Text
|
|
|
|
|
, _arContentTypes :: [ContentType]
|
|
|
|
|
, _arBody :: BL.ByteString
|
|
|
|
|
}
|
2019-03-22 07:57:15 +09:00
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
|
-- | The foundation datatype for your application. This can be a good place to
|
|
|
|
|
-- keep settings and values requiring initialization before your application
|
|
|
|
|
-- starts running, such as database connections. Every handler will have
|
|
|
|
|
-- access to the data present here.
|
|
|
|
|
data App = App
|
2019-02-09 06:54:22 +09:00
|
|
|
|
{ appSettings :: AppSettings
|
|
|
|
|
, appStatic :: Static -- ^ Settings for static file serving.
|
|
|
|
|
, appConnPool :: ConnectionPool -- ^ Database connection pool.
|
|
|
|
|
, appHttpManager :: Manager
|
|
|
|
|
, appLogger :: Logger
|
|
|
|
|
, appMailQueue :: Maybe (Chan (MailRecipe App))
|
|
|
|
|
, appSvgFont :: PreparedFont Double
|
|
|
|
|
, appActorKeys :: TVar (ActorKey, ActorKey, Bool)
|
2019-03-10 00:40:02 +09:00
|
|
|
|
, appInstanceMutex :: InstanceMutex
|
2019-03-10 06:21:36 +09:00
|
|
|
|
, appCapSignKey :: AccessTokenSecretKey
|
2019-03-29 12:25:32 +09:00
|
|
|
|
, appHashidsContext :: HashidsContext
|
2019-04-18 19:38:01 +09:00
|
|
|
|
, appActorFetchShare :: ActorFetchShare App
|
2019-01-19 10:44:21 +09:00
|
|
|
|
|
2019-04-26 07:46:27 +09:00
|
|
|
|
, appActivities :: Maybe (Int, TVar (Vector ActivityReport))
|
2016-02-13 12:35:30 +09:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-29 12:25:32 +09:00
|
|
|
|
-- Aliases for the routes file, because it doesn't like spaces in path piece
|
|
|
|
|
-- type names.
|
|
|
|
|
type OutboxItemKeyHashid = KeyHashid OutboxItem
|
|
|
|
|
type MessageKeyHashid = KeyHashid Message
|
|
|
|
|
type LocalMessageKeyHashid = KeyHashid LocalMessage
|
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
|
-- This is where we define all of the routes in our application. For a full
|
|
|
|
|
-- explanation of the syntax, please see:
|
|
|
|
|
-- http://www.yesodweb.com/book/routing-and-handlers
|
|
|
|
|
--
|
|
|
|
|
-- Note that this is really half the story; in Application.hs, mkYesodDispatch
|
|
|
|
|
-- generates the rest of the code. Please see the following documentation
|
|
|
|
|
-- for an explanation for this split:
|
|
|
|
|
-- http://www.yesodweb.com/book/scaffolding-and-the-site-template#scaffolding-and-the-site-template_foundation_and_application_modules
|
|
|
|
|
--
|
|
|
|
|
-- This function also generates the following type synonyms:
|
2019-05-23 18:12:24 +09:00
|
|
|
|
-- type Handler = HandlerFor App
|
|
|
|
|
-- type Widget = WidgetFor App ()
|
2016-02-13 12:35:30 +09:00
|
|
|
|
mkYesodData "App" $(parseRoutesFile "config/routes")
|
|
|
|
|
|
|
|
|
|
-- | A convenient synonym for creating forms.
|
2019-05-23 18:12:24 +09:00
|
|
|
|
type Form a = Html -> MForm (HandlerFor App) (FormResult a, Widget)
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
2016-05-06 01:30:30 +09:00
|
|
|
|
type AppDB = YesodDB App
|
|
|
|
|
|
2019-04-18 19:38:01 +09:00
|
|
|
|
type Worker = WorkerFor App
|
|
|
|
|
|
|
|
|
|
type WorkerDB = PersistConfigBackend (SitePersistConfig App) Worker
|
|
|
|
|
|
|
|
|
|
instance Site App where
|
|
|
|
|
type SitePersistConfig App = PostgresConf
|
|
|
|
|
siteApproot = ("https://" <>) . appInstanceHost . appSettings
|
|
|
|
|
sitePersistConfig = appDatabaseConf . appSettings
|
|
|
|
|
sitePersistPool = appConnPool
|
|
|
|
|
siteLogger = appLogger
|
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
|
-- Please see the documentation for the Yesod typeclass. There are a number
|
|
|
|
|
-- of settings which can be configured by overriding methods here.
|
|
|
|
|
instance Yesod App where
|
|
|
|
|
-- Controls the base of generated URLs. For more information on modifying,
|
|
|
|
|
-- see: https://github.com/yesodweb/yesod/wiki/Overriding-approot
|
2019-04-18 19:38:01 +09:00
|
|
|
|
approot = ApprootMaster siteApproot
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
|
|
-- Store session data on the client in encrypted cookies,
|
|
|
|
|
-- default session idle timeout is 120 minutes
|
2018-07-01 17:15:23 +09:00
|
|
|
|
makeSessionBackend app =
|
2016-02-17 20:31:01 +09:00
|
|
|
|
-- sslOnlySessions $
|
2018-07-01 17:15:23 +09:00
|
|
|
|
let s = appSettings app
|
2019-06-15 17:24:08 +09:00
|
|
|
|
t = fromIntegral
|
|
|
|
|
(toTimeUnit $ appClientSessionTimeout s :: U.Minute)
|
2018-07-01 17:15:23 +09:00
|
|
|
|
k = appClientSessionKeyFile s
|
|
|
|
|
in Just <$> defaultClientSessionBackend t k
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
|
|
-- Yesod Middleware allows you to run code before and after each handler function.
|
|
|
|
|
-- The defaultYesodMiddleware adds the response header "Vary: Accept, Accept-Language" and performs authorization checks.
|
|
|
|
|
-- The defaultCsrfMiddleware:
|
|
|
|
|
-- a) Sets a cookie with a CSRF token in it.
|
|
|
|
|
-- b) Validates that incoming write requests include that token in either a header or POST parameter.
|
|
|
|
|
-- For details, see the CSRF documentation in the Yesod.Core.Handler module of the yesod-core package.
|
2018-07-02 00:04:33 +09:00
|
|
|
|
yesodMiddleware
|
2016-02-17 20:31:01 +09:00
|
|
|
|
-- sslOnlyMiddleware 120 .
|
2018-07-02 00:04:33 +09:00
|
|
|
|
= defaultCsrfSetCookieMiddleware
|
|
|
|
|
. (\ handler ->
|
|
|
|
|
csrfCheckMiddleware
|
|
|
|
|
handler
|
|
|
|
|
(getCurrentRoute >>= \ mr -> case mr of
|
|
|
|
|
Nothing -> return False
|
2019-04-21 19:58:57 +09:00
|
|
|
|
Just (SharerInboxR _) -> return False
|
|
|
|
|
Just (ProjectInboxR _ _) -> return False
|
2018-07-02 00:04:33 +09:00
|
|
|
|
Just (GitUploadRequestR _ _) -> return False
|
|
|
|
|
Just r -> isWriteRequest r
|
|
|
|
|
)
|
|
|
|
|
defaultCsrfHeaderName
|
|
|
|
|
defaultCsrfParamName
|
|
|
|
|
)
|
2019-06-15 02:36:31 +09:00
|
|
|
|
. ( \ handler ->
|
|
|
|
|
if developmentMode
|
|
|
|
|
then handler
|
|
|
|
|
else do
|
|
|
|
|
host <- getsYesod $ appInstanceHost . appSettings
|
|
|
|
|
bs <- lookupHeaders hHost
|
|
|
|
|
case bs of
|
|
|
|
|
[b] | b == encodeUtf8 host -> handler
|
|
|
|
|
_ -> invalidArgs [hostMismatch host bs]
|
2019-03-17 02:15:31 +09:00
|
|
|
|
)
|
2018-07-02 00:04:33 +09:00
|
|
|
|
. defaultYesodMiddleware
|
2019-03-17 02:15:31 +09:00
|
|
|
|
where
|
|
|
|
|
hostMismatch h l = T.concat
|
|
|
|
|
[ "Request host mismatch: Expected "
|
|
|
|
|
, h
|
|
|
|
|
, " but instead got "
|
|
|
|
|
, T.pack (show l)
|
|
|
|
|
]
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
|
|
defaultLayout widget = do
|
|
|
|
|
master <- getYesod
|
|
|
|
|
mmsg <- getMessage
|
2019-05-23 06:50:30 +09:00
|
|
|
|
mperson <- do
|
|
|
|
|
mperson' <- maybeAuthAllowUnverified
|
|
|
|
|
for mperson' $ \ (p@(Entity pid person), verified) -> runDB $ do
|
|
|
|
|
sharer <- getJust $ personIdent person
|
2019-05-24 20:49:39 +09:00
|
|
|
|
unread <- do
|
2019-06-09 22:16:32 +09:00
|
|
|
|
vs <- countUnread $ personInbox person
|
2019-05-24 20:49:39 +09:00
|
|
|
|
case vs :: [E.Value Int] of
|
|
|
|
|
[E.Value i] -> return i
|
|
|
|
|
_ -> error $ "countUnread returned " ++ show vs
|
|
|
|
|
return (p, verified, sharer, unread)
|
2016-05-06 19:26:11 +09:00
|
|
|
|
(title, bcs) <- breadcrumbs
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
|
|
-- We break up the default layout into two components:
|
|
|
|
|
-- default-layout is the contents of the body tag, and
|
|
|
|
|
-- default-layout-wrapper is the entire page. Since the final
|
|
|
|
|
-- value passed to hamletToRepHtml cannot be a widget, this allows
|
|
|
|
|
-- you to use normal widget features in default-layout.
|
|
|
|
|
|
|
|
|
|
pc <- widgetToPageContent $ do
|
2016-05-06 19:26:11 +09:00
|
|
|
|
setTitle $ toHtml $
|
2018-02-25 18:55:55 +09:00
|
|
|
|
T.intercalate " → " (map snd bcs) <> " → " <> title
|
2019-05-06 07:00:26 +09:00
|
|
|
|
let settings = appSettings master
|
|
|
|
|
instanceHost = appInstanceHost settings
|
2019-05-05 20:36:10 +09:00
|
|
|
|
federationPage :: Text
|
|
|
|
|
federationPage =
|
|
|
|
|
"https://dev.angeley.es\
|
|
|
|
|
\/s/fr33domlover/r/vervis/s/FEDERATION.md"
|
2019-05-06 07:00:26 +09:00
|
|
|
|
federationDisabled = not $ appFederation settings
|
|
|
|
|
federatedServers = appInstances settings
|
2016-02-13 12:35:30 +09:00
|
|
|
|
$(widgetFile "default-layout")
|
|
|
|
|
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
2019-05-23 06:50:30 +09:00
|
|
|
|
where
|
2019-06-09 22:16:32 +09:00
|
|
|
|
countUnread ibid =
|
2019-05-23 06:50:30 +09:00
|
|
|
|
E.select $ E.from $ \ (ib `E.LeftOuterJoin` ibl `E.LeftOuterJoin` ibr) -> do
|
|
|
|
|
E.on $ E.just (ib E.^. InboxItemId) E.==. ibr E.?. InboxItemRemoteItem
|
|
|
|
|
E.on $ E.just (ib E.^. InboxItemId) E.==. ibl E.?. InboxItemLocalItem
|
|
|
|
|
E.where_ $
|
2019-06-09 22:16:32 +09:00
|
|
|
|
( E.isNothing (ibr E.?. InboxItemRemoteInbox) E.||.
|
|
|
|
|
ibr E.?. InboxItemRemoteInbox E.==. E.just (E.val ibid)
|
2019-05-23 06:50:30 +09:00
|
|
|
|
)
|
|
|
|
|
E.&&.
|
2019-06-09 22:16:32 +09:00
|
|
|
|
( E.isNothing (ibl E.?. InboxItemLocalInbox) E.||.
|
|
|
|
|
ibl E.?. InboxItemLocalInbox E.==. E.just (E.val ibid)
|
2019-05-23 06:50:30 +09:00
|
|
|
|
)
|
|
|
|
|
E.&&.
|
|
|
|
|
ib E.^. InboxItemUnread E.==. E.val True
|
|
|
|
|
return $ E.count $ ib E.^. InboxItemId
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
|
|
-- The page to be redirected to when authentication is required.
|
|
|
|
|
authRoute _ = Just $ AuthR LoginR
|
|
|
|
|
|
2016-02-25 12:10:30 +09:00
|
|
|
|
-- Who can access which pages.
|
2016-05-25 15:53:22 +09:00
|
|
|
|
isAuthorized r w = case (r, w) of
|
2018-03-18 09:13:22 +09:00
|
|
|
|
(AuthR a , True)
|
|
|
|
|
| a == resendVerifyR -> personFromResendForm
|
|
|
|
|
(AuthR (PluginR "account" ["verify", u, _]), False) -> personUnver u
|
|
|
|
|
|
2019-05-05 19:20:55 +09:00
|
|
|
|
(SharerInboxR shr , False) -> person shr
|
2019-05-23 06:50:30 +09:00
|
|
|
|
(NotificationsR shr , _ ) -> person shr
|
2019-06-16 23:58:00 +09:00
|
|
|
|
(SharerOutboxR shr , True) -> person shr
|
2019-01-22 00:54:57 +09:00
|
|
|
|
|
2016-06-07 04:41:22 +09:00
|
|
|
|
(GroupsR , True) -> personAny
|
|
|
|
|
(GroupNewR , _ ) -> personAny
|
|
|
|
|
(GroupMembersR grp , True) -> groupAdmin grp
|
|
|
|
|
(GroupMemberNewR grp , _ ) -> groupAdmin grp
|
|
|
|
|
(GroupMemberR grp _memb , True) -> groupAdmin grp
|
|
|
|
|
|
|
|
|
|
(KeysR , _ ) -> personAny
|
|
|
|
|
(KeyR _key , _ ) -> personAny
|
|
|
|
|
(KeyNewR , _ ) -> personAny
|
|
|
|
|
|
2016-06-08 01:31:55 +09:00
|
|
|
|
(ClaimRequestsPersonR , _ ) -> personAny
|
2016-06-07 19:01:57 +09:00
|
|
|
|
|
2016-06-07 04:41:22 +09:00
|
|
|
|
(ProjectRolesR shr , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(ProjectRoleNewR shr , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(ProjectRoleR shr _rl , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(ProjectRoleOpsR shr _rl , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(ProjectRoleOpNewR shr _rl , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
|
2016-06-07 14:23:00 +09:00
|
|
|
|
(ReposR shr , True) -> personOrGroupAdmin shr
|
|
|
|
|
(RepoNewR shr , _ ) -> personOrGroupAdmin shr
|
2016-06-07 04:41:22 +09:00
|
|
|
|
(RepoR shar _ , True) -> person shar
|
|
|
|
|
(RepoEditR shr _rp , _ ) -> person shr
|
|
|
|
|
(RepoDevsR shr _rp , _ ) -> person shr
|
|
|
|
|
(RepoDevNewR shr _rp , _ ) -> person shr
|
|
|
|
|
(RepoDevR shr _rp _dev , _ ) -> person shr
|
|
|
|
|
|
2016-06-07 14:23:00 +09:00
|
|
|
|
(ProjectsR shr , True) -> personOrGroupAdmin shr
|
|
|
|
|
(ProjectNewR shr , _ ) -> personOrGroupAdmin shr
|
2016-06-07 04:41:22 +09:00
|
|
|
|
(ProjectR shr _prj , True) -> person shr
|
|
|
|
|
(ProjectEditR shr _prj , _ ) -> person shr
|
2016-06-01 17:52:14 +09:00
|
|
|
|
(ProjectDevsR shr _prj , _ ) -> person shr
|
|
|
|
|
(ProjectDevNewR shr _prj , _ ) -> person shr
|
|
|
|
|
(ProjectDevR shr _prj _dev , _ ) -> person shr
|
2016-05-27 01:25:23 +09:00
|
|
|
|
|
2016-08-08 20:05:19 +09:00
|
|
|
|
-- (GlobalWorkflowsR , _ ) -> serverAdmin
|
|
|
|
|
-- (GlobalWorkflowNewR , _ ) -> serverAdmin
|
|
|
|
|
-- (GlobalWorkflowR _wfl , _ ) -> serverAdmin
|
|
|
|
|
|
|
|
|
|
(WorkflowsR shr , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(WorkflowNewR shr , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(WorkflowR shr _wfl , _ ) -> personOrGroupAdmin shr
|
2016-08-08 23:01:06 +09:00
|
|
|
|
(WorkflowFieldsR shr _ , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(WorkflowFieldNewR shr _ , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(WorkflowFieldR shr _ _ , _ ) -> personOrGroupAdmin shr
|
2016-08-08 23:48:38 +09:00
|
|
|
|
(WorkflowEnumsR shr _ , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(WorkflowEnumNewR shr _ , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(WorkflowEnumR shr _ _ , _ ) -> personOrGroupAdmin shr
|
2016-08-09 02:05:09 +09:00
|
|
|
|
(WorkflowEnumCtorsR shr _ _ , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(WorkflowEnumCtorNewR shr _ _ , _ ) -> personOrGroupAdmin shr
|
|
|
|
|
(WorkflowEnumCtorR shr _ _ _ , _ ) -> personOrGroupAdmin shr
|
2016-08-08 20:05:19 +09:00
|
|
|
|
|
2018-05-26 15:59:54 +09:00
|
|
|
|
(TicketsR s j , True) -> projOp ProjOpOpenTicket s j
|
|
|
|
|
(TicketNewR s j , _ ) -> projOp ProjOpOpenTicket s j
|
2016-06-07 04:41:22 +09:00
|
|
|
|
(TicketR user _ _ , True) -> person user
|
|
|
|
|
(TicketEditR user _ _ , _ ) -> person user
|
2016-08-11 16:58:51 +09:00
|
|
|
|
(TicketAcceptR s j _ , _ ) -> projOp ProjOpAcceptTicket s j
|
|
|
|
|
(TicketCloseR s j _ , _ ) -> projOp ProjOpCloseTicket s j
|
|
|
|
|
(TicketOpenR s j _ , _ ) -> projOp ProjOpReopenTicket s j
|
2016-06-07 04:41:22 +09:00
|
|
|
|
(TicketClaimR s j _ , _ ) -> projOp ProjOpClaimTicket s j
|
|
|
|
|
(TicketUnclaimR s j _ , _ ) -> projOp ProjOpUnclaimTicket s j
|
2016-06-07 16:33:19 +09:00
|
|
|
|
(TicketAssignR s j _ , _ ) -> projOp ProjOpAssignTicket s j
|
|
|
|
|
(TicketUnassignR s j _ , _ ) -> projOp ProjOpUnassignTicket s j
|
2016-06-08 10:28:18 +09:00
|
|
|
|
(ClaimRequestsTicketR s j _, True) -> projOp ProjOpRequestTicket s j
|
|
|
|
|
(ClaimRequestNewR s j _ , _ ) -> projOp ProjOpRequestTicket s j
|
2016-06-07 04:41:22 +09:00
|
|
|
|
(TicketDiscussionR _ _ _ , True) -> personAny
|
|
|
|
|
(TicketMessageR _ _ _ _ , True) -> personAny
|
|
|
|
|
(TicketTopReplyR _ _ _ , _ ) -> personAny
|
|
|
|
|
(TicketReplyR _ _ _ _ , _ ) -> personAny
|
2016-07-29 01:40:10 +09:00
|
|
|
|
(TicketDepsR s j _ , True) -> projOp ProjOpAddTicketDep s j
|
|
|
|
|
(TicketDepNewR s j _ , _ ) -> projOp ProjOpAddTicketDep s j
|
|
|
|
|
(TicketDepR s j _ _ , True) -> projOp ProjOpRemoveTicketDep s j
|
2016-06-07 04:41:22 +09:00
|
|
|
|
_ -> return Authorized
|
2016-05-27 01:25:23 +09:00
|
|
|
|
where
|
2016-06-07 16:33:19 +09:00
|
|
|
|
nobody :: Handler AuthResult
|
|
|
|
|
nobody = return $ Unauthorized "This operation is currently disabled"
|
|
|
|
|
|
2016-08-08 20:05:19 +09:00
|
|
|
|
serverAdmin :: Handler AuthResult
|
|
|
|
|
serverAdmin = nobody
|
|
|
|
|
|
2016-05-27 01:25:23 +09:00
|
|
|
|
personAnd
|
|
|
|
|
:: (Entity Person -> Handler AuthResult) -> Handler AuthResult
|
|
|
|
|
personAnd f = do
|
|
|
|
|
mp <- maybeAuth
|
|
|
|
|
case mp of
|
|
|
|
|
Nothing -> return AuthenticationRequired
|
|
|
|
|
Just p -> f p
|
|
|
|
|
|
2018-03-18 09:13:22 +09:00
|
|
|
|
personUnverifiedAnd
|
|
|
|
|
:: (Entity Person -> Handler AuthResult) -> Handler AuthResult
|
|
|
|
|
personUnverifiedAnd f = do
|
|
|
|
|
mp <- maybeUnverifiedAuth
|
|
|
|
|
case mp of
|
|
|
|
|
Nothing -> return AuthenticationRequired
|
|
|
|
|
Just p -> f p
|
|
|
|
|
|
2016-05-27 01:25:23 +09:00
|
|
|
|
personAny :: Handler AuthResult
|
|
|
|
|
personAny = personAnd $ \ _p -> return Authorized
|
|
|
|
|
|
|
|
|
|
person :: ShrIdent -> Handler AuthResult
|
|
|
|
|
person ident = personAnd $ \ (Entity _ p) -> do
|
|
|
|
|
let sid = personIdent p
|
|
|
|
|
sharer <- runDB $ getJust sid
|
|
|
|
|
return $ if ident == sharerIdent sharer
|
|
|
|
|
then Authorized
|
|
|
|
|
else Unauthorized "No access to this operation"
|
|
|
|
|
|
2018-03-18 09:13:22 +09:00
|
|
|
|
personUnver :: Text -> Handler AuthResult
|
|
|
|
|
personUnver uname = personUnverifiedAnd $ \ p ->
|
|
|
|
|
if username p == uname
|
|
|
|
|
then return Authorized
|
|
|
|
|
else do
|
2019-01-19 10:44:21 +09:00
|
|
|
|
logWarn $ T.concat
|
2018-03-18 09:13:22 +09:00
|
|
|
|
[ "User ", username p, " tried to verify user ", uname
|
|
|
|
|
]
|
|
|
|
|
return $ Unauthorized "You can't verify other users"
|
|
|
|
|
|
|
|
|
|
personFromResendForm :: Handler AuthResult
|
|
|
|
|
personFromResendForm = personUnverifiedAnd $ \ p -> do
|
|
|
|
|
((result, _), _) <-
|
|
|
|
|
runFormPost $ renderDivs $ resendVerifyEmailForm ""
|
|
|
|
|
case result of
|
|
|
|
|
FormSuccess uname ->
|
|
|
|
|
if username p == uname
|
|
|
|
|
then return Authorized
|
|
|
|
|
else do
|
2019-01-19 10:44:21 +09:00
|
|
|
|
logWarn $ T.concat
|
2018-03-18 09:13:22 +09:00
|
|
|
|
[ "User ", username p, " tried to POST to \
|
|
|
|
|
\verification email resend for user ", uname
|
|
|
|
|
]
|
|
|
|
|
return $
|
|
|
|
|
Unauthorized
|
|
|
|
|
"You can't do that for other users"
|
|
|
|
|
_ -> do
|
2019-01-19 10:44:21 +09:00
|
|
|
|
logWarn $ T.concat
|
2018-03-18 09:13:22 +09:00
|
|
|
|
[ "User ", username p, " tried to POST to \
|
|
|
|
|
\verification email resend for invalid username"
|
|
|
|
|
]
|
|
|
|
|
return $
|
|
|
|
|
Unauthorized "Requesting resend for invalid username"
|
|
|
|
|
|
2016-05-27 01:25:23 +09:00
|
|
|
|
groupRole :: (GroupRole -> Bool) -> ShrIdent -> Handler AuthResult
|
2016-06-07 05:10:28 +09:00
|
|
|
|
groupRole role grp = personAnd $ \ (Entity pid _p) -> runDB $ do
|
|
|
|
|
Entity sid _s <- getBy404 $ UniqueSharer grp
|
|
|
|
|
Entity gid _g <- getBy404 $ UniqueGroup sid
|
|
|
|
|
mem <- getBy $ UniqueGroupMember pid gid
|
|
|
|
|
let mrole = groupMemberRole . entityVal <$> mem
|
2016-05-27 01:25:23 +09:00
|
|
|
|
return $ case mrole of
|
|
|
|
|
Nothing -> Unauthorized "Not a member of the group"
|
|
|
|
|
Just r ->
|
|
|
|
|
if role r
|
|
|
|
|
then Authorized
|
|
|
|
|
else Unauthorized "Not the expected group role"
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
2016-06-07 04:41:22 +09:00
|
|
|
|
groupAdmin :: ShrIdent -> Handler AuthResult
|
|
|
|
|
groupAdmin = groupRole (== GRAdmin)
|
|
|
|
|
|
|
|
|
|
personOrGroupAdmin :: ShrIdent -> Handler AuthResult
|
|
|
|
|
personOrGroupAdmin shr = personAnd $ \ (Entity vpid _vp) -> runDB $ do
|
2016-06-07 05:10:28 +09:00
|
|
|
|
Entity sid _ <- getBy404 $ UniqueSharer shr
|
|
|
|
|
mep <- getBy $ UniquePersonIdent sid
|
|
|
|
|
case mep of
|
|
|
|
|
Just (Entity pid _p) ->
|
|
|
|
|
return $ if pid == vpid
|
|
|
|
|
then Authorized
|
|
|
|
|
else Unauthorized "Can’t access other people’s roles"
|
|
|
|
|
Nothing -> do
|
|
|
|
|
meg <- getBy $ UniqueGroup sid
|
|
|
|
|
case meg of
|
2016-06-07 04:41:22 +09:00
|
|
|
|
Nothing -> do
|
2019-01-19 10:44:21 +09:00
|
|
|
|
logWarn $
|
2016-06-07 05:10:28 +09:00
|
|
|
|
"Found non-person non-group sharer: " <>
|
|
|
|
|
shr2text shr
|
|
|
|
|
return $ error "Zombie sharer"
|
|
|
|
|
Just (Entity gid _g) -> do
|
|
|
|
|
mem <- getBy $ UniqueGroupMember vpid gid
|
|
|
|
|
return $ case mem of
|
|
|
|
|
Nothing -> Unauthorized "Not a group member"
|
|
|
|
|
Just (Entity _mid m) ->
|
|
|
|
|
if groupMemberRole m == GRAdmin
|
|
|
|
|
then Authorized
|
|
|
|
|
else Unauthorized "Not a group admin"
|
2016-06-07 04:41:22 +09:00
|
|
|
|
|
2016-06-06 18:03:49 +09:00
|
|
|
|
projOp
|
|
|
|
|
:: ProjectOperation -> ShrIdent -> PrjIdent -> Handler AuthResult
|
2019-01-27 07:22:49 +09:00
|
|
|
|
projOp op shr prj = do
|
|
|
|
|
mpid <- maybeAuthId
|
|
|
|
|
oas <- runDB $ checkProjectAccess mpid op shr prj
|
|
|
|
|
return $
|
|
|
|
|
case oas of
|
|
|
|
|
ObjectAccessAllowed -> Authorized
|
|
|
|
|
_ ->
|
|
|
|
|
Unauthorized
|
|
|
|
|
"You need a project role with that operation enabled"
|
2016-06-06 18:03:49 +09:00
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
|
-- This function creates static content files in the static folder
|
|
|
|
|
-- and names them based on a hash of their content. This allows
|
|
|
|
|
-- expiration dates to be set far in the future without worry of
|
|
|
|
|
-- users receiving stale content.
|
|
|
|
|
addStaticContent ext mime content = do
|
|
|
|
|
master <- getYesod
|
|
|
|
|
addStaticContentExternal
|
2016-05-06 19:23:31 +09:00
|
|
|
|
discardm
|
2016-02-13 12:35:30 +09:00
|
|
|
|
genFileName
|
2019-06-15 17:56:20 +09:00
|
|
|
|
appStaticDir
|
2016-02-13 12:35:30 +09:00
|
|
|
|
(StaticR . flip StaticRoute [])
|
|
|
|
|
ext
|
|
|
|
|
mime
|
|
|
|
|
content
|
|
|
|
|
where
|
|
|
|
|
-- Generate a unique filename based on the content itself
|
|
|
|
|
genFileName lbs = "autogen-" ++ base64md5 lbs
|
|
|
|
|
|
|
|
|
|
-- What messages should be logged. The following includes all messages when
|
|
|
|
|
-- in development, and warnings and errors in production.
|
2018-12-05 12:41:19 +09:00
|
|
|
|
shouldLogIO app _source level = pure $
|
2016-02-13 12:35:30 +09:00
|
|
|
|
appShouldLogAll (appSettings app)
|
|
|
|
|
|| level == LevelWarn
|
|
|
|
|
|| level == LevelError
|
|
|
|
|
|
|
|
|
|
makeLogger = return . appLogger
|
|
|
|
|
|
|
|
|
|
-- How to run database actions.
|
|
|
|
|
instance YesodPersist App where
|
|
|
|
|
type YesodPersistBackend App = SqlBackend
|
2019-04-18 19:38:01 +09:00
|
|
|
|
runDB = runSiteDB
|
2016-02-13 12:35:30 +09:00
|
|
|
|
instance YesodPersistRunner App where
|
|
|
|
|
getDBRunner = defaultGetDBRunner appConnPool
|
|
|
|
|
|
2018-03-04 06:33:59 +09:00
|
|
|
|
instance YesodMailSend App where
|
|
|
|
|
data MailMessage App
|
2018-04-01 10:29:50 +09:00
|
|
|
|
= MailVerifyAccount (Route App)
|
|
|
|
|
| MailResetPassphrase (Route App)
|
2018-03-04 06:33:59 +09:00
|
|
|
|
formatMailMessage _reply _mname msg =
|
|
|
|
|
case msg of
|
|
|
|
|
MailVerifyAccount url ->
|
|
|
|
|
( "Verify your Vervis account"
|
|
|
|
|
, $(textFile "templates/person/email/verify-account.md")
|
|
|
|
|
)
|
|
|
|
|
MailResetPassphrase url ->
|
|
|
|
|
( "Reset your Vervis passphrase"
|
|
|
|
|
, $(textFile "templates/person/email/reset-passphrase.md")
|
|
|
|
|
)
|
|
|
|
|
getMailSettings = getsYesod $ appMail . appSettings
|
|
|
|
|
getSubmitMail = do
|
|
|
|
|
mchan <- getsYesod appMailQueue
|
|
|
|
|
case mchan of
|
|
|
|
|
Nothing -> return Nothing
|
|
|
|
|
Just chan -> return $ Just $ liftIO . writeChan chan
|
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
|
instance YesodAuth App where
|
2016-02-16 20:41:13 +09:00
|
|
|
|
type AuthId App = PersonId
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
|
|
-- Where to send a user after successful login
|
|
|
|
|
loginDest _ = HomeR
|
|
|
|
|
-- Where to send a user after logout
|
|
|
|
|
logoutDest _ = HomeR
|
|
|
|
|
-- Override the above two destinations when a Referer: header is present
|
|
|
|
|
redirectToReferer _ = True
|
|
|
|
|
|
2018-12-05 12:41:19 +09:00
|
|
|
|
authenticate creds = liftHandler $ do
|
2016-02-16 20:41:13 +09:00
|
|
|
|
let ident = credsIdent creds
|
|
|
|
|
mpid <- runDB $ getBy $ UniquePersonLogin $ credsIdent creds
|
|
|
|
|
return $ case mpid of
|
|
|
|
|
Nothing -> UserError $ IdentifierNotFound ident
|
|
|
|
|
Just (Entity pid _) -> Authenticated pid
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
|
|
-- You can add other plugins like BrowserID, email or OAuth here
|
2018-03-04 06:33:59 +09:00
|
|
|
|
authPlugins _ = [accountPlugin]
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
2019-01-14 11:30:39 +09:00
|
|
|
|
authHttpManager = error "authHttpManager"
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
2018-03-18 07:59:40 +09:00
|
|
|
|
onLogout = clearUnverifiedCreds False
|
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
|
instance YesodAuthPersist App
|
|
|
|
|
|
2018-03-04 06:33:59 +09:00
|
|
|
|
newtype AccountPersistDB' a = AccountPersistDB'
|
|
|
|
|
{ unAccountPersistDB' :: Handler a
|
|
|
|
|
}
|
|
|
|
|
deriving (Functor, Applicative, Monad, MonadIO)
|
|
|
|
|
|
|
|
|
|
morphAPDB :: AccountPersistDB App Person a -> AccountPersistDB' a
|
|
|
|
|
morphAPDB = AccountPersistDB' . runAccountPersistDB
|
|
|
|
|
|
|
|
|
|
instance AccountDB AccountPersistDB' where
|
|
|
|
|
type UserAccount AccountPersistDB' = Entity Person
|
|
|
|
|
|
2018-05-16 08:54:12 +09:00
|
|
|
|
loadUser = morphAPDB . loadUser
|
|
|
|
|
loadUserByEmailAddress = morphAPDB . loadUserByEmailAddress
|
2018-03-04 06:33:59 +09:00
|
|
|
|
|
|
|
|
|
addNewUser name email key pwd = AccountPersistDB' $ runDB $ do
|
|
|
|
|
now <- liftIO getCurrentTime
|
|
|
|
|
let sharer = Sharer
|
|
|
|
|
{ sharerIdent = text2shr name
|
|
|
|
|
, sharerName = Nothing
|
|
|
|
|
, sharerCreated = now
|
|
|
|
|
}
|
|
|
|
|
msid <- insertBy sharer
|
|
|
|
|
case msid of
|
|
|
|
|
Left _ -> do
|
|
|
|
|
mr <- getMessageRender
|
|
|
|
|
return $ Left $ mr $ MsgUsernameExists name
|
|
|
|
|
Right sid -> do
|
2019-06-09 22:16:32 +09:00
|
|
|
|
ibid <- insert Inbox
|
2019-06-16 03:51:26 +09:00
|
|
|
|
obid <- insert Outbox
|
2018-04-01 12:02:35 +09:00
|
|
|
|
let defTime = UTCTime (ModifiedJulianDay 0) 0
|
2019-01-27 08:39:13 +09:00
|
|
|
|
person = Person
|
|
|
|
|
{ personIdent = sid
|
|
|
|
|
, personLogin = name
|
|
|
|
|
, personPassphraseHash = pwd
|
|
|
|
|
, personEmail = email
|
|
|
|
|
, personVerified = False
|
|
|
|
|
, personVerifiedKey = key
|
|
|
|
|
, personVerifiedKeyCreated = now
|
|
|
|
|
, personResetPassKey = ""
|
|
|
|
|
, personResetPassKeyCreated = defTime
|
|
|
|
|
, personAbout = ""
|
2019-06-09 22:16:32 +09:00
|
|
|
|
, personInbox = ibid
|
2019-06-16 03:51:26 +09:00
|
|
|
|
, personOutbox = obid
|
2019-01-27 08:39:13 +09:00
|
|
|
|
}
|
2018-03-04 06:33:59 +09:00
|
|
|
|
pid <- insert person
|
|
|
|
|
return $ Right $ Entity pid person
|
|
|
|
|
|
2018-05-16 08:54:12 +09:00
|
|
|
|
verifyAccount = morphAPDB . verifyAccount
|
|
|
|
|
setVerifyKey = (morphAPDB .) . setVerifyKey
|
|
|
|
|
setNewPasswordKey = (morphAPDB .) . setNewPasswordKey
|
|
|
|
|
setNewPassword = (morphAPDB .) . setNewPassword
|
2018-03-04 06:33:59 +09:00
|
|
|
|
|
|
|
|
|
instance AccountSendEmail App where
|
|
|
|
|
sendVerifyEmail uname email url = do
|
|
|
|
|
sent <- sendMail (Address (Just uname) email) (MailVerifyAccount url)
|
|
|
|
|
unless sent $ do
|
2018-04-09 09:38:40 +09:00
|
|
|
|
setMessage "Mail sending disabled, please contact admin"
|
2018-04-01 10:29:50 +09:00
|
|
|
|
ur <- getUrlRender
|
2019-01-19 10:44:21 +09:00
|
|
|
|
logWarn $ T.concat
|
2018-03-04 06:33:59 +09:00
|
|
|
|
[ "Verification email NOT SENT for user "
|
2018-03-06 11:26:27 +09:00
|
|
|
|
, uname, " <", emailText email, ">: "
|
2018-04-01 10:29:50 +09:00
|
|
|
|
, ur url
|
2018-03-04 06:33:59 +09:00
|
|
|
|
]
|
|
|
|
|
sendNewPasswordEmail uname email url = do
|
|
|
|
|
sent <- sendMail (Address (Just uname) email) (MailResetPassphrase url)
|
|
|
|
|
unless sent $ do
|
2018-04-09 09:38:40 +09:00
|
|
|
|
setMessage "Mail sending disabled, please contact admin"
|
2018-04-01 10:29:50 +09:00
|
|
|
|
ur <- getUrlRender
|
2019-01-19 10:44:21 +09:00
|
|
|
|
logWarn $ T.concat
|
2018-03-04 06:33:59 +09:00
|
|
|
|
["Password reset email NOT SENT for user "
|
2018-03-06 11:26:27 +09:00
|
|
|
|
, uname, " <", emailText email, ">: "
|
2018-04-01 10:29:50 +09:00
|
|
|
|
, ur url
|
2018-03-04 06:33:59 +09:00
|
|
|
|
]
|
|
|
|
|
|
2018-03-18 07:59:40 +09:00
|
|
|
|
instance YesodAuthVerify App where
|
|
|
|
|
verificationRoute _ = ResendVerifyEmailR
|
|
|
|
|
|
2018-03-04 06:33:59 +09:00
|
|
|
|
instance YesodAuthAccount AccountPersistDB' App where
|
2019-03-04 23:51:51 +09:00
|
|
|
|
requireEmailVerification = appEmailVerification . appSettings
|
2019-06-15 17:24:08 +09:00
|
|
|
|
emailVerifyKeyDuration _ = Just $ fromTimeUnit (1 :: U.Day)
|
|
|
|
|
passphraseResetKeyDuration _ = Just $ fromTimeUnit (1 :: U.Day)
|
2018-04-11 20:09:42 +09:00
|
|
|
|
allowLoginByEmailAddress _ = True
|
2018-04-01 12:02:35 +09:00
|
|
|
|
runAccountDB = unAccountPersistDB'
|
2019-03-04 17:14:36 +09:00
|
|
|
|
|
|
|
|
|
unregisteredLogin u = do
|
2018-12-05 12:41:19 +09:00
|
|
|
|
setUnverifiedCreds True $ Creds "account" (username u) []
|
2018-03-18 07:59:40 +09:00
|
|
|
|
return mempty
|
2019-03-04 17:14:36 +09:00
|
|
|
|
registrationAllowed = do
|
|
|
|
|
settings <- getsYesod appSettings
|
|
|
|
|
if appRegister settings
|
|
|
|
|
then do
|
|
|
|
|
room <- case appAccounts settings of
|
|
|
|
|
Nothing -> return True
|
|
|
|
|
Just cap -> do
|
|
|
|
|
current <- runDB $ count ([] :: [Filter Person])
|
|
|
|
|
return $ current < cap
|
|
|
|
|
return $
|
|
|
|
|
if room
|
|
|
|
|
then Nothing
|
|
|
|
|
else Just $ setMessage "Maximal number of registered users reached"
|
|
|
|
|
else return $ Just $ setMessage "User registration disabled"
|
2018-03-04 06:33:59 +09:00
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
|
-- This instance is required to use forms. You can modify renderMessage to
|
|
|
|
|
-- achieve customized and internationalized form validation messages.
|
|
|
|
|
instance RenderMessage App FormMessage where
|
|
|
|
|
renderMessage _ _ = defaultFormMessage
|
|
|
|
|
|
|
|
|
|
-- Useful when writing code that is re-usable outside of the Handler context.
|
|
|
|
|
-- An example is background jobs that send email.
|
2016-02-16 20:41:13 +09:00
|
|
|
|
-- This can also be useful for writing code that works across multiple Yesod
|
|
|
|
|
-- applications.
|
2019-03-10 00:40:02 +09:00
|
|
|
|
instance HasHttpManager App where
|
|
|
|
|
getHttpManager = appHttpManager
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
|
|
unsafeHandler :: App -> Handler a -> IO a
|
|
|
|
|
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
|
|
|
|
|
|
|
|
|
|
-- Note: Some functionality previously present in the scaffolding has been
|
|
|
|
|
-- moved to documentation in the Wiki. Following are some hopefully helpful
|
|
|
|
|
-- links:
|
|
|
|
|
--
|
|
|
|
|
-- https://github.com/yesodweb/yesod/wiki/Sending-email
|
|
|
|
|
-- https://github.com/yesodweb/yesod/wiki/Serve-static-files-from-a-separate-domain
|
|
|
|
|
-- https://github.com/yesodweb/yesod/wiki/i18n-messages-in-the-scaffolding
|
2016-02-27 14:41:36 +09:00
|
|
|
|
|
2019-03-29 12:25:32 +09:00
|
|
|
|
instance YesodHashids App where
|
|
|
|
|
siteHashidsContext = appHashidsContext
|
|
|
|
|
|
2019-03-10 00:40:02 +09:00
|
|
|
|
instance YesodRemoteActorStore App where
|
|
|
|
|
siteInstanceMutex = appInstanceMutex
|
|
|
|
|
siteInstanceRoomMode = appMaxInstanceKeys . appSettings
|
|
|
|
|
siteActorRoomMode = appMaxActorKeys . appSettings
|
|
|
|
|
siteRejectOnMaxKeys = appRejectOnMaxKeys . appSettings
|
2019-04-16 23:27:50 +09:00
|
|
|
|
siteActorFetchShare = appActorFetchShare
|
2019-02-17 06:47:58 +09:00
|
|
|
|
|
2019-04-26 12:23:49 +09:00
|
|
|
|
instance YesodActivityPub App where
|
2019-06-04 06:52:34 +09:00
|
|
|
|
siteInstanceHost = appInstanceHost . appSettings
|
2019-04-26 12:23:49 +09:00
|
|
|
|
sitePostSignedHeaders _ =
|
2019-05-10 06:26:33 +09:00
|
|
|
|
hRequestTarget :| [hHost, hDate, hDigest, hActivityPubActor]
|
2019-04-26 12:23:49 +09:00
|
|
|
|
siteGetHttpSign = do
|
|
|
|
|
(akey1, akey2, new1) <- liftIO . readTVarIO =<< asksSite appActorKeys
|
|
|
|
|
renderUrl <- askUrlRender
|
|
|
|
|
let (keyID, akey) =
|
|
|
|
|
if new1
|
|
|
|
|
then (renderUrl ActorKey1R, akey1)
|
|
|
|
|
else (renderUrl ActorKey2R, akey2)
|
|
|
|
|
return (KeyId $ encodeUtf8 keyID, actorKeySign akey)
|
|
|
|
|
|
2019-05-21 08:51:06 +09:00
|
|
|
|
instance YesodPaginate App where
|
|
|
|
|
sitePageParamName _ = "page"
|
|
|
|
|
|
2016-05-02 19:03:29 +09:00
|
|
|
|
instance YesodBreadcrumbs App where
|
|
|
|
|
breadcrumb route = return $ case route of
|
2016-05-25 06:48:21 +09:00
|
|
|
|
StaticR _ -> ("", Nothing)
|
|
|
|
|
FaviconR -> ("", Nothing)
|
|
|
|
|
RobotsR -> ("", Nothing)
|
|
|
|
|
|
2019-03-22 14:17:54 +09:00
|
|
|
|
PublishR -> ("Publish", Just HomeR)
|
2019-03-22 14:17:58 +09:00
|
|
|
|
InboxR -> ("Inbox", Just HomeR)
|
2019-06-16 23:58:00 +09:00
|
|
|
|
SharerOutboxR shr -> ("Outbox", Just $ SharerR shr)
|
|
|
|
|
SharerOutboxItemR shr hid -> ( "#" <> keyHashidText hid
|
|
|
|
|
, Just $ SharerOutboxR shr
|
2019-03-29 12:25:32 +09:00
|
|
|
|
)
|
2019-02-15 07:17:04 +09:00
|
|
|
|
ActorKey1R -> ("Actor Key 1", Nothing)
|
|
|
|
|
ActorKey2R -> ("Actor Key 2", Nothing)
|
2019-01-19 11:20:49 +09:00
|
|
|
|
|
2016-05-02 19:03:29 +09:00
|
|
|
|
HomeR -> ("Home", Nothing)
|
2018-03-18 07:16:02 +09:00
|
|
|
|
ResendVerifyEmailR -> ( "Resend verification email"
|
|
|
|
|
, Nothing
|
|
|
|
|
)
|
2019-03-22 14:17:58 +09:00
|
|
|
|
AuthR _ -> ("Auth", Just HomeR)
|
2016-05-25 06:48:21 +09:00
|
|
|
|
|
|
|
|
|
SharersR -> ("Sharers", Just HomeR)
|
|
|
|
|
SharerR shar -> (shr2text shar, Just SharersR)
|
2019-04-21 19:58:57 +09:00
|
|
|
|
SharerInboxR shr -> ("Inbox", Just $ SharerR shr)
|
2019-05-23 06:50:30 +09:00
|
|
|
|
NotificationsR shr -> ( "Notifications"
|
|
|
|
|
, Just $ SharerR shr
|
|
|
|
|
)
|
2016-05-02 19:03:29 +09:00
|
|
|
|
|
|
|
|
|
PeopleR -> ("People", Just HomeR)
|
|
|
|
|
|
2016-05-26 01:03:58 +09:00
|
|
|
|
GroupsR -> ("Groups", Just HomeR)
|
|
|
|
|
GroupNewR -> ("New", Just GroupsR)
|
2019-02-15 07:13:58 +09:00
|
|
|
|
GroupMembersR shar -> ("Members", Just $ SharerR shar)
|
2016-05-29 22:17:55 +09:00
|
|
|
|
GroupMemberNewR shar -> ("New", Just $ GroupMembersR shar)
|
|
|
|
|
GroupMemberR grp memb -> ( shr2text memb
|
|
|
|
|
, Just $ GroupMembersR grp
|
|
|
|
|
)
|
2016-05-26 01:03:58 +09:00
|
|
|
|
|
2016-05-24 05:46:54 +09:00
|
|
|
|
KeysR -> ("Keys", Just HomeR)
|
|
|
|
|
KeyNewR -> ("New", Just KeysR)
|
|
|
|
|
KeyR key -> (ky2text key, Just KeysR)
|
2016-05-02 19:03:29 +09:00
|
|
|
|
|
2016-06-08 01:31:55 +09:00
|
|
|
|
ClaimRequestsPersonR -> ( "Ticket Claim Requests"
|
2016-06-07 19:01:57 +09:00
|
|
|
|
, Just HomeR
|
|
|
|
|
)
|
|
|
|
|
|
2016-06-07 04:41:22 +09:00
|
|
|
|
ProjectRolesR shr -> ( "Project Roles"
|
|
|
|
|
, Just $ SharerR shr
|
|
|
|
|
)
|
|
|
|
|
ProjectRoleNewR shr -> ("New", Just $ ProjectRolesR shr)
|
|
|
|
|
ProjectRoleR shr rl -> ( rl2text rl
|
|
|
|
|
, Just $ ProjectRolesR shr
|
|
|
|
|
)
|
|
|
|
|
ProjectRoleOpsR shr rl -> ( "Operations"
|
|
|
|
|
, Just $ ProjectRoleR shr rl
|
|
|
|
|
)
|
|
|
|
|
ProjectRoleOpNewR shr rl -> ( "New"
|
|
|
|
|
, Just $ ProjectRoleOpsR shr rl
|
|
|
|
|
)
|
2016-06-01 17:52:14 +09:00
|
|
|
|
|
2019-02-15 07:13:58 +09:00
|
|
|
|
ReposR shar -> ("Repos", Just $ SharerR shar)
|
2016-05-02 19:03:29 +09:00
|
|
|
|
RepoNewR shar -> ("New", Just $ ReposR shar)
|
2016-05-24 05:46:54 +09:00
|
|
|
|
RepoR shar repo -> (rp2text repo, Just $ ReposR shar)
|
2016-06-06 06:11:05 +09:00
|
|
|
|
RepoEditR shr rp -> ("Edit", Just $ RepoR shr rp)
|
2016-05-05 16:29:19 +09:00
|
|
|
|
RepoSourceR shar repo [] -> ("Files", Just $ RepoR shar repo)
|
|
|
|
|
RepoSourceR shar repo refdir -> ( last refdir
|
2016-05-02 19:03:29 +09:00
|
|
|
|
, Just $
|
2016-05-05 16:29:19 +09:00
|
|
|
|
RepoSourceR shar repo $
|
|
|
|
|
init refdir
|
2016-05-02 19:03:29 +09:00
|
|
|
|
)
|
2016-05-06 01:30:30 +09:00
|
|
|
|
RepoHeadChangesR shar repo -> ("Changes", Just $ RepoR shar repo)
|
|
|
|
|
RepoChangesR shar repo ref -> ( ref
|
|
|
|
|
, Just $ RepoHeadChangesR shar repo
|
|
|
|
|
)
|
2018-05-25 06:44:13 +09:00
|
|
|
|
RepoPatchR shr rp hash -> ( "Patch " <> hash
|
|
|
|
|
, Just $ RepoR shr rp
|
|
|
|
|
)
|
2016-06-01 17:52:14 +09:00
|
|
|
|
RepoDevsR shr rp -> ( "Collaboratots"
|
|
|
|
|
, Just $ RepoR shr rp
|
|
|
|
|
)
|
2016-05-29 23:13:25 +09:00
|
|
|
|
RepoDevNewR shr rp -> ("New", Just $ RepoDevsR shr rp)
|
|
|
|
|
RepoDevR shr rp dev -> ( shr2text dev
|
|
|
|
|
, Just $ RepoDevsR shr rp
|
|
|
|
|
)
|
2016-05-02 19:03:29 +09:00
|
|
|
|
|
2016-05-25 06:48:21 +09:00
|
|
|
|
DarcsDownloadR _ _ _ -> ("", Nothing)
|
|
|
|
|
|
|
|
|
|
GitRefDiscoverR _ _ -> ("", Nothing)
|
2019-01-30 07:24:32 +09:00
|
|
|
|
GitUploadRequestR _ _ -> ("", Nothing)
|
2016-05-25 06:48:21 +09:00
|
|
|
|
|
2019-02-15 07:13:58 +09:00
|
|
|
|
ProjectsR shar -> ("Projects", Just $ SharerR shar)
|
2016-05-02 19:03:29 +09:00
|
|
|
|
ProjectNewR shar -> ("New", Just $ ProjectsR shar)
|
2016-05-24 05:46:54 +09:00
|
|
|
|
ProjectR shar proj -> ( prj2text proj
|
|
|
|
|
, Just $ ProjectsR shar
|
|
|
|
|
)
|
2019-04-21 19:58:57 +09:00
|
|
|
|
ProjectInboxR shr prj -> ("Inbox", Just $ ProjectR shr prj)
|
2019-06-17 06:37:31 +09:00
|
|
|
|
ProjectOutboxR shr prj -> ("Outbox", Just $ ProjectR shr prj)
|
|
|
|
|
ProjectOutboxItemR shr prj hid -> ( "#" <> keyHashidText hid
|
|
|
|
|
, Just $ ProjectOutboxR shr prj
|
|
|
|
|
)
|
2016-06-05 19:43:28 +09:00
|
|
|
|
ProjectEditR shr prj -> ("Edit", Just $ ProjectR shr prj)
|
2016-06-01 17:52:14 +09:00
|
|
|
|
ProjectDevsR shr prj -> ( "Collaborators"
|
|
|
|
|
, Just $ ProjectR shr prj
|
|
|
|
|
)
|
|
|
|
|
ProjectDevNewR shr prj -> ( "New"
|
|
|
|
|
, Just $ ProjectDevsR shr prj
|
|
|
|
|
)
|
|
|
|
|
ProjectDevR shr prj dev -> ( shr2text dev
|
|
|
|
|
, Just $ ProjectDevsR shr prj
|
|
|
|
|
)
|
2016-05-02 19:03:29 +09:00
|
|
|
|
|
2016-08-08 20:05:19 +09:00
|
|
|
|
WorkflowsR shr -> ("Workflows", Just $ SharerR shr)
|
|
|
|
|
WorkflowNewR shr -> ("New", Just $ WorkflowsR shr)
|
|
|
|
|
WorkflowR shr wfl -> ( wfl2text wfl
|
|
|
|
|
, Just $ WorkflowsR shr
|
|
|
|
|
)
|
2016-08-08 23:01:06 +09:00
|
|
|
|
WorkflowFieldsR shr wfl -> ( "Fields"
|
|
|
|
|
, Just $ WorkflowR shr wfl
|
|
|
|
|
)
|
|
|
|
|
WorkflowFieldNewR shr wfl -> ( "New"
|
|
|
|
|
, Just $ WorkflowFieldsR shr wfl
|
|
|
|
|
)
|
|
|
|
|
WorkflowFieldR shr wfl fld -> ( fld2text fld
|
|
|
|
|
, Just $ WorkflowFieldsR shr wfl
|
|
|
|
|
)
|
2016-08-08 23:48:38 +09:00
|
|
|
|
WorkflowEnumsR shr wfl -> ( "Enums"
|
|
|
|
|
, Just $ WorkflowR shr wfl
|
|
|
|
|
)
|
|
|
|
|
WorkflowEnumNewR shr wfl -> ( "New"
|
|
|
|
|
, Just $ WorkflowEnumsR shr wfl
|
|
|
|
|
)
|
2016-08-09 02:05:09 +09:00
|
|
|
|
WorkflowEnumR shr wfl enm -> ( enm2text enm
|
2016-08-08 23:48:38 +09:00
|
|
|
|
, Just $ WorkflowEnumsR shr wfl
|
|
|
|
|
)
|
2016-08-09 02:05:09 +09:00
|
|
|
|
WorkflowEnumCtorsR shr wfl enm -> ( "Ctors"
|
|
|
|
|
, Just $ WorkflowEnumR shr wfl enm
|
|
|
|
|
)
|
|
|
|
|
WorkflowEnumCtorNewR shr wfl enm -> ( "New"
|
|
|
|
|
, Just $
|
|
|
|
|
WorkflowEnumCtorsR shr wfl enm
|
|
|
|
|
)
|
|
|
|
|
WorkflowEnumCtorR shr wfl enm c -> ( c
|
|
|
|
|
, Just $
|
|
|
|
|
WorkflowEnumCtorsR shr wfl enm
|
|
|
|
|
)
|
2016-08-08 20:05:19 +09:00
|
|
|
|
|
2019-03-29 12:25:32 +09:00
|
|
|
|
MessageR shr lmhid -> ( "#" <> keyHashidText lmhid
|
|
|
|
|
, Just $ SharerR shr
|
|
|
|
|
)
|
2019-03-23 05:46:42 +09:00
|
|
|
|
|
2016-05-02 19:03:29 +09:00
|
|
|
|
TicketsR shar proj -> ( "Tickets"
|
|
|
|
|
, Just $ ProjectR shar proj
|
|
|
|
|
)
|
2016-08-04 16:36:24 +09:00
|
|
|
|
TicketTreeR shr prj -> ( "Tree", Just $ TicketsR shr prj)
|
2016-05-02 19:03:29 +09:00
|
|
|
|
TicketNewR shar proj -> ("New", Just $ TicketsR shar proj)
|
|
|
|
|
TicketR shar proj num -> ( T.pack $ '#' : show num
|
|
|
|
|
, Just $ TicketsR shar proj
|
|
|
|
|
)
|
|
|
|
|
TicketEditR shar proj num -> ( "Edit"
|
|
|
|
|
, Just $ TicketR shar proj num
|
|
|
|
|
)
|
2016-08-11 16:58:51 +09:00
|
|
|
|
TicketAcceptR _shr _prj _num -> ("", Nothing)
|
2016-06-02 04:50:41 +09:00
|
|
|
|
TicketCloseR _shar _proj _num -> ("", Nothing)
|
|
|
|
|
TicketOpenR _shar _proj _num -> ("", Nothing)
|
2016-06-06 18:03:49 +09:00
|
|
|
|
TicketClaimR _shar _proj _num -> ("", Nothing)
|
|
|
|
|
TicketUnclaimR _shar _proj _num -> ("", Nothing)
|
2016-06-07 16:33:19 +09:00
|
|
|
|
TicketAssignR shr prj num -> ( "Assign"
|
|
|
|
|
, Just $ TicketR shr prj num
|
|
|
|
|
)
|
|
|
|
|
TicketUnassignR _shr _prj _num -> ("", Nothing)
|
2016-06-08 01:31:55 +09:00
|
|
|
|
ClaimRequestsProjectR shr prj -> ( "Ticket Claim Requests"
|
2016-06-08 00:29:26 +09:00
|
|
|
|
, Just $ ProjectR shr prj
|
|
|
|
|
)
|
2016-06-08 01:31:55 +09:00
|
|
|
|
ClaimRequestsTicketR shr prj num -> ( "Ticket Claim Requests"
|
|
|
|
|
, Just $ TicketR shr prj num
|
|
|
|
|
)
|
2016-06-08 10:28:18 +09:00
|
|
|
|
ClaimRequestNewR shr prj num -> ( "New"
|
|
|
|
|
, Just $
|
|
|
|
|
ClaimRequestsTicketR shr prj num
|
|
|
|
|
)
|
2016-05-25 06:48:21 +09:00
|
|
|
|
TicketDiscussionR shar proj num -> ( "Discussion"
|
|
|
|
|
, Just $ TicketR shar proj num
|
|
|
|
|
)
|
2019-05-06 06:02:26 +09:00
|
|
|
|
TicketMessageR shr prj num mkhid -> ( "#" <> keyHashidText mkhid
|
2016-05-25 06:48:21 +09:00
|
|
|
|
, Just $
|
2019-05-06 06:02:26 +09:00
|
|
|
|
TicketDiscussionR shr prj num
|
2016-05-25 06:48:21 +09:00
|
|
|
|
)
|
|
|
|
|
TicketTopReplyR shar proj num -> ( "New topic"
|
|
|
|
|
, Just $
|
|
|
|
|
TicketDiscussionR shar proj num
|
|
|
|
|
)
|
|
|
|
|
TicketReplyR shar proj num cnum -> ( "Reply"
|
|
|
|
|
, Just $
|
|
|
|
|
TicketMessageR shar proj num cnum
|
|
|
|
|
)
|
2016-06-08 05:16:15 +09:00
|
|
|
|
TicketDepsR shr prj num -> ( "Dependencies"
|
|
|
|
|
, Just $ TicketR shr prj num
|
|
|
|
|
)
|
2016-07-29 01:40:10 +09:00
|
|
|
|
TicketDepNewR shr prj num -> ( "New dependency"
|
|
|
|
|
, Just $ TicketDepsR shr prj num
|
|
|
|
|
)
|
|
|
|
|
TicketDepR shr prj pnum cnum -> ( T.pack $ '#' : show cnum
|
|
|
|
|
, Just $ TicketDepsR shr prj pnum
|
|
|
|
|
)
|
2016-07-27 17:35:50 +09:00
|
|
|
|
TicketReverseDepsR shr prj num -> ( "Dependants"
|
|
|
|
|
, Just $ TicketR shr prj num
|
|
|
|
|
)
|
2019-05-26 19:32:56 +09:00
|
|
|
|
TicketParticipantsR shr prj num -> ( "Participants"
|
|
|
|
|
, Just $ TicketR shr prj num
|
|
|
|
|
)
|
|
|
|
|
TicketTeamR shr prj num -> ( "Team"
|
|
|
|
|
, Just $ TicketR shr prj num
|
|
|
|
|
)
|
2019-06-04 06:52:34 +09:00
|
|
|
|
TicketEventsR shr prj num -> ( "Events"
|
|
|
|
|
, Just $ TicketR shr prj num
|
|
|
|
|
)
|
2016-06-04 15:57:54 +09:00
|
|
|
|
|
|
|
|
|
WikiPageR shr prj _page -> ("Wiki", Just $ ProjectR shr prj)
|