2019-01-22 00:54:57 +09:00
{- This file is part of Vervis.
-
2023-04-29 19:40:44 +09:00
- Written in 2019 , 2020 , 2021 , 2022 , 2023
- by fr33domlover < fr33domlover @ riseup . net >.
2019-01-22 00:54:57 +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 />.
- }
2023-05-29 15:50:17 +09:00
{- # LANGUAGE StrictData # -}
2019-01-22 00:54:57 +09:00
module Web.ActivityPub
2019-02-22 08:59:53 +09:00
( -- * Type-safe manipulation tools
--
-- Types and functions that make handling URIs and JSON-LD contexts less
-- error-prone and safer by recording safety checks in the type and
-- placing the checks in a single clear place.
ActivityPub ( .. )
, Doc ( .. )
-- * Actor
2019-01-22 00:54:57 +09:00
--
-- ActivityPub actor document including a public key, with a 'FromJSON'
-- instance for fetching and a 'ToJSON' instance for publishing.
2019-02-22 08:59:53 +09:00
, ActorType ( .. )
2019-03-11 08:15:42 +09:00
--, Algorithm (..)
2019-02-22 08:59:53 +09:00
, Owner ( .. )
2019-01-22 00:54:57 +09:00
, PublicKey ( .. )
2019-10-17 17:37:48 +09:00
, SshKeyAlgorithm ( .. )
, SshPublicKey ( .. )
2022-07-25 01:52:28 +09:00
, ActorLocal ( .. )
, ActorDetail ( .. )
2019-01-22 00:54:57 +09:00
, Actor ( .. )
2019-09-09 09:27:45 +09:00
, Repo ( .. )
2022-07-25 01:52:28 +09:00
, TicketTracker ( .. )
2019-05-18 07:42:01 +09:00
, CollectionType ( .. )
, Collection ( .. )
2019-05-21 08:51:06 +09:00
, CollectionPageType ( .. )
, CollectionPage ( .. )
2019-05-18 07:42:01 +09:00
, Recipient ( .. )
2022-08-28 22:51:43 +09:00
, Resource ( .. )
2019-01-22 00:54:57 +09:00
2019-06-04 06:52:34 +09:00
-- * Content objects
2019-02-12 20:53:24 +09:00
, Note ( .. )
2019-07-12 07:18:30 +09:00
, TicketDependency ( .. )
2020-07-14 18:56:13 +09:00
, PatchLocal ( .. )
2020-05-25 18:40:48 +09:00
, Patch ( .. )
2020-08-13 19:26:20 +09:00
, BundleLocal ( .. )
, Bundle ( .. )
2019-06-06 19:25:16 +09:00
, TicketLocal ( .. )
2020-05-25 21:39:25 +09:00
, MergeRequest ( .. )
2019-06-04 06:52:34 +09:00
, Ticket ( .. )
2019-08-06 22:23:11 +09:00
, Author ( .. )
, Hash ( .. )
, Commit ( .. )
2019-08-29 00:31:40 +09:00
, Branch ( .. )
2022-07-26 02:15:22 +09:00
, Role ( .. )
2023-05-29 15:50:17 +09:00
, Duration ( .. )
2023-05-30 20:34:37 +09:00
, Usage ( .. )
2019-06-04 06:52:34 +09:00
-- * Activity
2019-03-14 08:37:58 +09:00
, Accept ( .. )
2020-09-10 19:57:02 +09:00
, AddObject ( .. )
, Add ( .. )
2022-06-23 18:09:02 +09:00
, Apply ( .. )
2020-02-10 23:51:32 +09:00
, CreateObject ( .. )
2019-02-12 20:53:24 +09:00
, Create ( .. )
2019-03-14 08:37:58 +09:00
, Follow ( .. )
2022-07-26 02:15:22 +09:00
, Grant ( .. )
2022-09-06 01:19:52 +09:00
, Invite ( .. )
2022-11-15 00:11:25 +09:00
, Join ( .. )
2020-06-18 19:38:04 +09:00
, OfferObject ( .. )
2019-06-06 23:16:48 +09:00
, Offer ( .. )
2019-08-29 00:31:40 +09:00
, Push ( .. )
2019-03-14 08:37:58 +09:00
, Reject ( .. )
2020-07-23 23:27:11 +09:00
, Resolve ( .. )
2019-09-25 19:43:05 +09:00
, Undo ( .. )
2019-03-14 11:30:36 +09:00
, Audience ( .. )
2023-05-30 15:48:21 +09:00
, ProofConfig ( .. )
, Proof ( .. )
2019-03-14 08:37:58 +09:00
, SpecificActivity ( .. )
2023-04-29 19:40:44 +09:00
, activityType
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
, Action ( .. )
, makeActivity
2019-01-22 00:54:57 +09:00
, Activity ( .. )
-- * Utilities
2020-05-02 02:48:01 +09:00
, emptyAudience
2020-02-03 23:53:12 +09:00
, emptyActivity
2019-02-07 19:34:33 +09:00
, hActivityPubActor
2019-01-22 00:54:57 +09:00
, provideAP
2019-06-29 08:15:08 +09:00
, provideAP'
2019-01-22 00:54:57 +09:00
, APGetError ( .. )
, httpGetAP
2019-03-05 17:26:41 +09:00
, APPostError ( .. )
2019-05-02 08:13:22 +09:00
, hActivityPubForwarder
, hForwardingSignature
, hForwardedSignature
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
, Envelope ()
, Errand ()
, sending
, retrying
, deliver
, forwarding
, forward
2019-02-06 11:48:23 +09:00
, Fetched ( .. )
2020-06-18 19:38:04 +09:00
, fetchAP
2022-09-25 06:15:40 +09:00
, fetchAP_T
2019-02-22 08:59:53 +09:00
, fetchAPID
2019-04-16 23:27:50 +09:00
, fetchAPID'
2022-09-21 21:50:26 +09:00
, fetchTip
2019-05-18 07:42:01 +09:00
, fetchRecipient
2022-08-28 22:51:43 +09:00
, fetchResource
When verifying HTTP sig with known shared key, verify actor lists the key
Previously, when verifying an HTTP signature and we fetched the key and
discovered it's shared, we'd fetch the actor and make sure it lists the key URI
in the `publicKey` field. But if we already knew the key, had it cached in our
DB, we wouldn't check the actor at all, despite not knowing whether it lists
the key.
With this patch, we now always GET the actor when the key is shared,
determining the actor URI from the `ActivityPub-Actor` request header, and we
verify that the actor lists the key URI. We do that regardless of whether or
not we have the key in the DB, although these two cases and handled in
different parts of the code right now (for a new key, it's in Web.ActivityPub
fetchKey; for a known key, it's in Vervis.Foundation httpVerifySig).
2019-02-18 18:20:13 +09:00
, keyListedByActor
2019-02-24 02:17:52 +09:00
, fetchUnknownKey
, fetchKnownPersonalKey
, fetchKnownSharedKey
2020-06-18 19:38:04 +09:00
, Obj ( .. )
2019-01-22 00:54:57 +09:00
)
where
2019-02-04 08:39:56 +09:00
import Control.Applicative ( ( <|> ) , optional )
import Control.Exception ( Exception , displayException , try )
2019-07-23 22:59:48 +09:00
import Control.Monad
2019-01-22 00:54:57 +09:00
import Control.Monad.IO.Class
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
import Control.Monad.Trans.Class
2019-02-04 08:39:56 +09:00
import Control.Monad.Trans.Except
2019-01-22 00:54:57 +09:00
import Control.Monad.Trans.Writer ( Writer )
2019-04-26 00:49:15 +09:00
import Crypto.Hash hiding ( Context )
2019-01-22 00:54:57 +09:00
import Data.Aeson
2019-02-22 08:59:53 +09:00
import Data.Aeson.Encoding ( pair )
import Data.Aeson.Types ( Parser , typeMismatch , listEncoding )
import Data.Bifunctor
2020-09-10 19:57:02 +09:00
import Data.Bitraversable
2019-01-22 00:54:57 +09:00
import Data.ByteString ( ByteString )
2019-08-06 22:23:11 +09:00
import Data.Char
2019-02-17 09:14:05 +09:00
import Data.Foldable ( for_ )
2019-07-12 07:18:30 +09:00
import Data.List
2019-07-23 22:59:48 +09:00
import Data.List.NonEmpty ( NonEmpty ( .. ) )
2019-02-22 08:59:53 +09:00
import Data.Proxy
2019-02-24 10:21:42 +09:00
import Data.Semigroup ( Endo , First ( .. ) )
2019-01-22 00:54:57 +09:00
import Data.Text ( Text )
2019-10-17 17:37:48 +09:00
import Data.Text.Encoding ( encodeUtf8 , decodeUtf8 , decodeUtf8' )
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
import Data.Time.Clock
2019-03-23 11:05:30 +09:00
import Data.Traversable
2019-02-22 08:59:53 +09:00
import Network.HTTP.Client hiding ( Proxy , proxy )
2019-01-22 00:54:57 +09:00
import Network.HTTP.Client.Conduit.ActivityPub ( httpAPEither )
import Network.HTTP.Simple ( JSONException )
import Network.HTTP.Types.Header ( HeaderName , hContentType )
2019-08-06 22:23:11 +09:00
import Text.Email.Parser ( EmailAddress )
2023-05-29 15:50:17 +09:00
import Text.Read ( readMaybe )
2019-01-22 00:54:57 +09:00
import Yesod.Core.Content ( ContentType )
import Yesod.Core.Handler ( ProvidedRep , provideRepType )
2019-04-28 19:18:50 +09:00
import Network.HTTP.Client.Signature
2019-08-06 22:23:11 +09:00
import qualified Data.Attoparsec.ByteString as A
2023-05-30 15:48:21 +09:00
import qualified Data.ByteArray as BA
import qualified Data.ByteString as B
import qualified Data.ByteString.Base58 as B58
2019-10-17 17:37:48 +09:00
import qualified Data.ByteString.Base64 as B64
2019-03-11 08:15:42 +09:00
import qualified Data.ByteString.Char8 as BC
2019-05-04 06:04:53 +09:00
import qualified Data.ByteString.Lazy as BL
2019-06-06 19:25:16 +09:00
import qualified Data.HashMap.Strict as M
2019-08-29 00:31:40 +09:00
import qualified Data.List.NonEmpty as NE
2019-07-23 22:59:48 +09:00
import qualified Data.Text as T
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
import qualified Data.Text.Encoding as TE
2019-03-14 11:30:36 +09:00
import qualified Data.Vector as V
2019-03-11 08:15:42 +09:00
import qualified Network.HTTP.Signature as S
2019-08-06 22:23:11 +09:00
import qualified Text.Email.Parser as E
2019-01-22 00:54:57 +09:00
2019-03-11 08:15:42 +09:00
import Crypto.PublicVerifKey
2020-08-15 06:16:33 +09:00
import Development.PatchMediaType
import Development.PatchMediaType.JSON
2019-02-08 08:08:28 +09:00
import Network.FedURI
2019-04-26 00:49:15 +09:00
import Network.HTTP.Digest
2022-09-21 21:50:26 +09:00
import Web.Text
2019-02-08 08:08:28 +09:00
2019-02-03 20:01:36 +09:00
import Data.Aeson.Local
2019-01-22 00:54:57 +09:00
2023-05-30 15:48:21 +09:00
-- JSON CANONICALIZATION
--
-- In order to produce JSON-based sigs, we need the ability to produce a
-- canonical ByteString from a given ToJSON-able object. Is aeson's encoder
-- already compatible?
--
-- * Before aeson-2, clearly no, because a HashMap is used for objects
-- * After aeson-2, possibly, since ordered-map mode exists and on by default
--
-- I'm gonna list requirements here and then we can compare this with aeson.
--
-- - [ ] JSON number data MUST be expressible as IEEE 754 [IEEE754]
-- double-precision values. For applications needing higher precision or longer
-- integers than offered by IEEE 754 double precision, it is RECOMMENDED to
-- represent such numbers as JSON strings
-- - [ ] objects must be sorted by key
-- - [ ] The sorting process is applied to property name strings in their "raw" (unescaped) form. That is, a newline character is treated as U+000A
-- - [ ] Property name strings to be sorted are formatted as arrays of UTF-16 [UNICODE] code units. The sorting is based on pure value comparisons, where code units are treated as unsigned integers, independent of locale settings
--
-- Looks like the primary things to verify are key ordering and number
-- serialization.
--
-- When to encode? We need to encode the activity and then:
--
-- 1. Put it in the DB
-- 2. Send to local actors via system
-- 3. Send to remote actors via HTTP
2023-04-29 19:40:44 +09:00
{-
data Link = Link
{ linkHref :: URI
, linkRel ::
, linkMediaType ::
, linkName ::
, linkHreflang ::
, linkHeight ::
, linkWidth ::
, linkPreview ::
, linkRest :: Object
}
data X = X
{ xId :: LocalURI
, x
}
data Object' u = Object'
{ objectId :: ObjURI
, objectType ::
, objectSubject ::
, objectRelationship ::
, objectActor ::
, objectAttributedTo ::
, objectAttachment ::
, objectBcc ::
, objectBto ::
, objectCc ::
, objectContext ::
, objectCurrent ::
, objectFirst ::
, objectGenerator ::
, objectIcon ::
, objectImage ::
, objectInReplyTo ::
, objectItems ::
, objectInstrument ::
, objectOrderedItems ::
, objectLast ::
, objectLocation ::
, objectNext ::
, objectObject ::
, objectOneOf ::
, objectAnyOf ::
, objectClosed ::
, objectOrigin ::
, objectAccuracy ::
, objectPrev ::
, objectPreview ::
, objectProvider ::
, objectReplies ::
, objectResult ::
, objectAudience ::
, objectPartOf ::
, objectTag ::
, objectTags ::
, objectTarget ::
, objectTo ::
, objectUrl ::
, objectAltitude ::
, objectContent ::
, objectContentMap ::
, objectName ::
, objectNameMap ::
, objectDuration ::
, objectEndTime ::
, objectHeight ::
, objectHref ::
, objectHreflang ::
, objectLatitude ::
, objectLongitude ::
, objectMediaType ::
, objectPublished ::
, objectRadius ::
, objectRating ::
, objectRel ::
, objectStartIndex ::
, objectStartTime ::
, objectSummary ::
, objectSummaryMap ::
, objectTotalItems ::
, objectUnits ::
, objectUpdated ::
, objectWidth ::
, objectDescribes ::
, objectFormerType ::
, objectDeleted ::
, objectEndpoints ::
, objectFollowing ::
, objectFollowers ::
, objectInbox ::
, objectLiked ::
, objectShares ::
, objectLikes ::
, objectOauthAuthorizationEndpoint ::
, objectOauthTokenEndpoint ::
, objectOutbox ::
, objectPreferredUsername ::
, objectProvideClientKey ::
, objectProxyUrl ::
, objectSharedInbox ::
, objectSignClientKey ::
, objectSource ::
, objectStreams ::
, objectUploadMedia ::
, objectRest :: Object
}
- }
2019-07-23 22:59:48 +09:00
proxy :: a u -> Proxy a
2019-02-22 08:59:53 +09:00
proxy _ = Proxy
2019-07-23 22:59:48 +09:00
as2Context :: Text
as2Context = " https://www.w3.org/ns/activitystreams "
2019-01-22 00:54:57 +09:00
2019-07-23 22:59:48 +09:00
secContext :: Text
2022-06-23 18:09:02 +09:00
secContext = " https://w3id.org/security/v2 "
2019-06-12 09:11:24 +09:00
2019-07-23 22:59:48 +09:00
forgeContext :: Text
2022-06-23 18:09:02 +09:00
forgeContext = " https://forgefed.org/ns "
2019-06-12 09:11:24 +09:00
2019-07-23 22:59:48 +09:00
publicURI :: Text
publicURI = " https://www.w3.org/ns/activitystreams#Public "
2019-03-22 04:13:36 +09:00
2019-02-22 08:59:53 +09:00
class ActivityPub a where
2019-07-23 22:59:48 +09:00
jsonldContext :: Proxy a -> [ Text ]
parseObject :: UriMode u => Object -> Parser ( Authority u , a u )
toSeries :: UriMode u => Authority u -> a u -> Series
2019-02-22 08:59:53 +09:00
2019-07-23 22:59:48 +09:00
data Doc a u = Doc
{ docAuthority :: Authority u
, docValue :: a u
2019-02-22 08:59:53 +09:00
}
2019-07-23 22:59:48 +09:00
instance ( ActivityPub a , UriMode u ) => FromJSON ( Doc a u ) where
2019-06-12 09:11:24 +09:00
parseJSON = withObject " Doc " $ \ o -> uncurry Doc <$> parseObject o
2019-02-22 08:59:53 +09:00
2019-07-23 22:59:48 +09:00
instance ( ActivityPub a , UriMode u ) => ToJSON ( Doc a u ) where
2019-02-22 08:59:53 +09:00
toJSON = error " toJSON Doc "
toEncoding ( Doc h v ) =
pairs
2019-06-12 09:11:24 +09:00
$ context ( jsonldContext $ proxy v )
2019-02-22 08:59:53 +09:00
<> toSeries h v
2019-06-12 09:11:24 +09:00
where
context [] = mempty
context [ t ] = " @context " .= t
context ts = " @context " .= ts
2019-01-22 00:54:57 +09:00
2022-08-16 03:22:46 +09:00
data ActorType
= ActorTypePerson
| ActorTypeRepo
| ActorTypeTicketTracker
| ActorTypePatchTracker
| ActorTypeOther Text
2019-06-11 21:19:51 +09:00
deriving Eq
2019-01-22 00:54:57 +09:00
instance FromJSON ActorType where
2019-03-20 19:36:00 +09:00
parseJSON = withText " ActorType " $ pure . parse
where
parse t
2022-07-25 01:52:28 +09:00
| t == " Person " = ActorTypePerson
| t == " Repository " = ActorTypeRepo
| t == " TicketTracker " = ActorTypeTicketTracker
2022-08-16 03:22:46 +09:00
| t == " PatchTracker " = ActorTypePatchTracker
2022-07-25 01:52:28 +09:00
| otherwise = ActorTypeOther t
2019-01-22 00:54:57 +09:00
instance ToJSON ActorType where
toJSON = error " toJSON ActorType "
toEncoding at =
toEncoding $ case at of
2022-07-25 01:52:28 +09:00
ActorTypePerson -> " Person "
ActorTypeRepo -> " Repository "
ActorTypeTicketTracker -> " TicketTracker "
2022-08-16 03:22:46 +09:00
ActorTypePatchTracker -> " PatchTracker "
2022-07-25 01:52:28 +09:00
ActorTypeOther t -> t
2019-01-22 00:54:57 +09:00
2019-02-22 08:59:53 +09:00
data Owner = OwnerInstance | OwnerActor LocalURI
ownerShared :: Owner -> Bool
ownerShared OwnerInstance = True
ownerShared ( OwnerActor _ ) = False
2019-07-23 22:59:48 +09:00
data PublicKey u = PublicKey
{ publicKeyId :: LocalRefURI
2019-03-11 08:15:42 +09:00
, publicKeyExpires :: Maybe UTCTime
, publicKeyOwner :: Owner
, publicKeyMaterial :: PublicVerifKey
2019-01-22 00:54:57 +09:00
}
2019-02-22 08:59:53 +09:00
instance ActivityPub PublicKey where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ secContext ]
2019-02-22 08:59:53 +09:00
parseObject o = do
2019-02-04 08:39:56 +09:00
mtyp <- optional $ o .: " @type " <|> o .: " type "
2019-02-22 08:59:53 +09:00
for_ mtyp $ \ t ->
2020-05-25 18:37:15 +09:00
unless ( t == ( " Key " :: Text ) || t == " CryptographicKey " ) $
fail " PublicKey @type isn't Key or CryptographicKey "
2019-07-23 22:59:48 +09:00
RefURI authority id_ <- o .: " @id " <|> o .: " id "
2019-06-12 09:11:24 +09:00
shared <- o .:|? " isShared " .!= False
2019-07-23 22:59:48 +09:00
fmap ( authority , ) $
2019-02-22 08:59:53 +09:00
PublicKey id_
<$> o .:? " expires "
2019-07-23 22:59:48 +09:00
<*> ( mkOwner shared =<< withAuthorityO authority ( o .: " owner " ) )
2019-03-11 08:15:42 +09:00
<*> ( either fail return . decodePublicVerifKeyPEM =<<
o .: " publicKeyPem "
)
2019-01-22 00:54:57 +09:00
where
2019-07-23 22:59:48 +09:00
mkOwner True lu
| lu == topLocalURI = return OwnerInstance
mkOwner True _ = fail " Shared key but owner isn't instance URI "
mkOwner False lu = return $ OwnerActor lu
toSeries authority ( PublicKey id_ mexpires owner mat )
= " @id " .= RefURI authority id_
2019-06-12 09:11:24 +09:00
<> " expires " .=? mexpires
2019-07-23 22:59:48 +09:00
<> " owner " .= mkOwner authority owner
2019-06-12 09:11:24 +09:00
<> " publicKeyPem " .= encodePublicVerifKeyPEM mat
<> " isShared " .= ownerShared owner
2019-02-22 08:59:53 +09:00
where
2019-07-23 22:59:48 +09:00
mkOwner a OwnerInstance = ObjURI a topLocalURI
mkOwner a ( OwnerActor lu ) = ObjURI a lu
2019-01-22 00:54:57 +09:00
2019-07-23 22:59:48 +09:00
parsePublicKeySet
:: UriMode u
=> Value
-> Parser ( Authority u , [ Either LocalURI ( PublicKey u ) ] )
2019-02-22 08:59:53 +09:00
parsePublicKeySet v =
case v of
Array a ->
2019-03-20 18:31:08 +09:00
case V . toList a of
[] -> fail " No public keys "
k : ks -> do
2019-07-23 22:59:48 +09:00
( a , e ) <- parseKey k
es <- traverse ( withAuthorityT a . parseKey ) ks
return ( a , e : es )
2019-03-20 18:31:08 +09:00
_ -> second ( : [] ) <$> parseKey v
2019-02-22 08:59:53 +09:00
where
2019-07-23 22:59:48 +09:00
parseKey v @ ( String _ ) = second Left . f2l <$> parseJSON v
where
f2l ( ObjURI a l ) = ( a , l )
parseKey ( Object o ) = second Right <$> parseObject o
parseKey v = typeMismatch " PublicKeySet Item " v
2019-02-22 08:59:53 +09:00
2019-07-23 22:59:48 +09:00
encodePublicKeySet
:: UriMode u => Authority u -> [ Either LocalURI ( PublicKey u ) ] -> Encoding
encodePublicKeySet authority es =
2019-03-20 18:31:08 +09:00
case es of
[ e ] -> renderKey e
_ -> listEncoding renderKey es
2019-02-22 08:59:53 +09:00
where
2019-07-23 22:59:48 +09:00
renderKey ( Left lu ) = toEncoding $ ObjURI authority lu
renderKey ( Right pk ) = pairs $ toSeries authority pk
Support remote actors specifying 2 keys, and DB storage of these keys
It's now possible for activities we be attributed to actors that have more than
one key. We allow up to 2 keys. We also store in the DB. Scaling to support any
number of keys is trivial, but I'm limiting to 2 to avoid potential trouble and
because 2 is the actual number we need.
By having 2 keys, and replacing only one of them in each rotation, we avoid
race conditions. With 1 key, the following can happen:
1. We send an activity to another server
2. We rotate our key
3. The server reaches the activity in its processing queue, tries to verify our
request signature, but fails because it can't fetch the key. It's the old
key and we discarded it already, replaced it with the new one
When we use 2 keys, the previous key remains available and other servers have
time to finish processing our requests signed with that key. We can safely
rotate, without worrying about whether the user sent anything right before the
rotation time.
Caveat: With this feature, we allow OTHER servers to rotate freely. It's safe
because it's optional, but it's just Vervis right now. Once Vervis itself
starts using 2 keys, it will be able to rotate freely without race condition
risk, but probably Mastodon etc. won't accept its signatures because of the use
of 2 keys and because they're server-scope keys.
Maybe I can get these features adopted by the fediverse?
2019-02-05 04:38:50 +09:00
2019-10-17 17:37:48 +09:00
data SshKeyAlgorithm
= SshKeyAlgorithmRSA
| SshKeyAlgorithmDSA
| SshKeyAlgorithmECDSA
| SshKeyAlgorithmEd25519
instance FromJSON SshKeyAlgorithm where
parseJSON = withText " SshKeyAlgorithm " parse
where
parse t
| t == " ssh-rsa " = pure SshKeyAlgorithmRSA
| t == " ssh-dsa " = pure SshKeyAlgorithmDSA
| t == " ssh-ecdsa " = pure SshKeyAlgorithmECDSA
| t == " ssh-ed25519 " = pure SshKeyAlgorithmEd25519
| otherwise =
fail $ " Unrecognized ssh key algo: " ++ T . unpack t
instance ToJSON SshKeyAlgorithm where
toJSON = error " toJSON SshKeyAlgorithm "
toEncoding = toEncoding . render
where
render :: SshKeyAlgorithm -> Text
render SshKeyAlgorithmRSA = " ssh-rsa "
render SshKeyAlgorithmDSA = " ssh-dsa "
render SshKeyAlgorithmECDSA = " ssh-ecdsa "
render SshKeyAlgorithmEd25519 = " ssh-ed25519 "
data SshPublicKey u = SshPublicKey
{ sshPublicKeyId :: LocalURI
, sshPublicKeyExpires :: Maybe UTCTime
, sshPublicKeyOwner :: LocalURI
, sshPublicKeyAlgorithm :: SshKeyAlgorithm
, sshPublicKeyMaterial :: ByteString
}
instance ActivityPub SshPublicKey where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ secContext , forgeContext ]
2019-10-17 17:37:48 +09:00
parseObject o = do
mtyp <- optional $ o .: " @type " <|> o .: " type "
for_ mtyp $ \ t ->
when ( t /= ( " SshKey " :: Text ) ) $
fail " SshKey @type isn't SshKey "
mediaType <- o .: " mediaType "
unless ( mediaType == ( " application/octet-stream " :: Text ) ) $
fail " mediaType isn't octet-stream "
ObjURI authority luId <- o .: " @id " <|> o .: " id "
fmap ( authority , ) $
SshPublicKey luId
<$> o .:? " expires "
<*> withAuthorityO authority ( o .: " owner " )
<*> o .: " sshKeyType "
<*> ( decodeBase64 . encodeUtf8 =<< o .: " content " )
where
decodeBase64 = either fail return . B64 . decode
toSeries authority ( SshPublicKey luId mexpires owner algo mat )
= " @id " .= ObjURI authority luId
<> " expires " .=? mexpires
<> " owner " .= ObjURI authority owner
<> " sshKeyType " .= algo
<> " mediaType " .= ( " application/octet-stream " :: Text )
<> " content " .= decodeUtf8 ( B64 . encode mat )
2022-07-25 01:52:28 +09:00
data ActorLocal u = ActorLocal
2019-02-22 08:59:53 +09:00
{ actorId :: LocalURI
, actorInbox :: LocalURI
2019-05-21 09:36:05 +09:00
, actorOutbox :: Maybe LocalURI
2019-06-11 21:19:51 +09:00
, actorFollowers :: Maybe LocalURI
2019-10-19 17:15:48 +09:00
, actorFollowing :: Maybe LocalURI
2019-07-23 22:59:48 +09:00
, actorPublicKeys :: [ Either LocalURI ( PublicKey u ) ]
2019-10-17 17:37:48 +09:00
, actorSshKeys :: [ LocalURI ]
2019-01-22 00:54:57 +09:00
}
2022-07-25 01:52:28 +09:00
parseActorLocal :: UriMode u => Object -> Parser ( Maybe ( Authority u , ActorLocal u ) )
parseActorLocal o = do
mid <- o .:? " id "
case mid of
Nothing -> do
verifyNothing " inbox "
verifyNothing " outbox "
verifyNothing " followers "
verifyNothing " following "
verifyNothing " publicKey "
verifyNothing " sshKey "
return Nothing
Just ( ObjURI a id_ ) ->
fmap ( Just . ( a , ) ) $
ActorLocal
<$> pure id_
<*> withAuthorityO a ( o .: " inbox " )
<*> withAuthorityMaybeO a ( o .:? " outbox " )
<*> withAuthorityMaybeO a ( o .:? " followers " )
<*> withAuthorityMaybeO a ( o .:? " following " )
<*> withAuthorityT a ( parsePublicKeySet =<< o .: " publicKey " )
<*> ( traverse ( withAuthorityO a . return ) =<< o .:? " sshKey " .!= [] )
where
verifyNothing t =
if t ` M . member ` o
then fail $ T . unpack t ++ " field found, expected none "
else return ()
encodeActorLocal :: UriMode u => Authority u -> ActorLocal u -> Series
encodeActorLocal a ( ActorLocal id_ inbox outbox followers following pkeys skeys )
= " id " .= ObjURI a id_
<> " inbox " .= ObjURI a inbox
<> " outbox " .=? ( ObjURI a <$> outbox )
<> " followers " .=? ( ObjURI a <$> followers )
<> " following " .=? ( ObjURI a <$> following )
<> " publicKey " ` pair ` encodePublicKeySet a pkeys
<> " sshKey " .=% map ( ObjURI a ) skeys
data ActorDetail = ActorDetail
{ actorType :: ActorType
, actorUsername :: Maybe Text
, actorName :: Maybe Text
, actorSummary :: Maybe Text
}
parseActorDetail :: Object -> Parser ActorDetail
parseActorDetail o =
ActorDetail
<$> o .: " type "
<*> o .:? " preferredUsername "
<*> o .:? " name "
<*> o .:? " summary "
encodeActorDetail :: ActorDetail -> Series
encodeActorDetail ( ActorDetail typ musername mname msummary )
= " type " .= typ
<> " preferredUsername " .=? musername
<> " name " .=? mname
<> " summary " .=? msummary
data Actor u = Actor
{ actorLocal :: ActorLocal u
, actorDetail :: ActorDetail
}
2019-02-22 08:59:53 +09:00
instance ActivityPub Actor where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ as2Context , secContext , forgeContext ]
2019-02-22 08:59:53 +09:00
parseObject o = do
2022-07-25 01:52:28 +09:00
mlocal <- parseActorLocal o
( h , local ) <-
case mlocal of
Nothing -> fail " No ActorLocal "
Just l -> return l
detail <- parseActorDetail o
return ( h , Actor local detail )
toSeries h ( Actor local detail ) =
encodeActorLocal h local <> encodeActorDetail detail
2019-06-11 21:19:51 +09:00
2019-09-09 09:27:45 +09:00
data Repo u = Repo
{ repoActor :: Actor u
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
, repoTeam :: Maybe LocalURI
2020-08-15 06:16:33 +09:00
, repoVcs :: VersionControlSystem
2022-09-17 17:31:22 +09:00
, repoLoom :: Maybe LocalURI
2022-09-22 15:02:14 +09:00
, repoClone :: NonEmpty LocalURI
2019-09-09 09:27:45 +09:00
}
instance ActivityPub Repo where
2020-08-15 06:16:33 +09:00
jsonldContext _ = [ as2Context , secContext , forgeContext ]
2019-09-09 09:27:45 +09:00
parseObject o = do
( h , a ) <- parseObject o
2022-07-25 01:52:28 +09:00
unless ( actorType ( actorDetail a ) == ActorTypeRepo ) $
2019-09-09 09:27:45 +09:00
fail " Actor type isn't Repository "
fmap ( h , ) $
Repo a
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
<$> withAuthorityMaybeO h ( o .:|? " team " )
2020-08-15 06:16:33 +09:00
<*> o .: " versionControlSystem "
2022-09-17 17:31:22 +09:00
<*> withAuthorityMaybeO h ( o .:? " sendPatchesTo " )
2022-09-22 15:02:14 +09:00
<*> ( traverse ( withAuthorityO h . pure ) =<< o .:*+ " cloneUri " )
toSeries authority ( Repo actor team vcs loom clone )
2019-09-09 09:27:45 +09:00
= toSeries authority actor
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
<> " team " .= ( ObjURI authority <$> team )
2020-08-15 06:16:33 +09:00
<> " versionControlSystem " .= vcs
2022-09-17 17:31:22 +09:00
<> " sendPatchesTo " .=? ( ObjURI authority <$> loom )
2022-09-22 15:02:14 +09:00
<> " cloneUri " .=*+ ( ObjURI authority <$> clone )
2019-09-09 09:27:45 +09:00
2022-07-25 01:52:28 +09:00
data TicketTracker u = TicketTracker
{ ticketTrackerActor :: Actor u
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
, ticketTrackerTeam :: Maybe LocalURI
2019-06-11 21:19:51 +09:00
}
2022-07-25 18:10:24 +09:00
instance ActivityPub TicketTracker where
jsonldContext _ = [ as2Context , secContext , forgeContext ]
2019-06-11 21:19:51 +09:00
parseObject o = do
( h , a ) <- parseObject o
2022-07-25 01:52:28 +09:00
unless ( actorType ( actorDetail a ) == ActorTypeTicketTracker ) $
fail " Actor type isn't TicketTracker "
2019-06-11 21:19:51 +09:00
fmap ( h , ) $
2022-07-25 01:52:28 +09:00
TicketTracker a
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
<$> withAuthorityMaybeO h ( o .:|? " team " )
2022-07-25 01:52:28 +09:00
toSeries authority ( TicketTracker actor team )
2019-07-23 22:59:48 +09:00
= toSeries authority actor
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
<> " team " .= ( ObjURI authority <$> team )
2019-03-10 15:42:03 +09:00
2019-05-18 07:42:01 +09:00
data CollectionType = CollectionTypeUnordered | CollectionTypeOrdered
instance FromJSON CollectionType where
parseJSON = withText " CollectionType " parse
where
parse " Collection " = pure CollectionTypeUnordered
parse " OrderedCollection " = pure CollectionTypeOrdered
parse t = fail $ " Unknown collection type: " ++ T . unpack t
instance ToJSON CollectionType where
toJSON = error " toJSON CollectionType "
toEncoding ct =
toEncoding $ case ct of
CollectionTypeUnordered -> " Collection " :: Text
CollectionTypeOrdered -> " OrderedCollection "
2019-07-23 22:59:48 +09:00
data Collection a u = Collection
2019-05-18 07:42:01 +09:00
{ collectionId :: LocalURI
, collectionType :: CollectionType
, collectionTotalItems :: Maybe Int
, collectionCurrent :: Maybe LocalURI
2019-05-21 08:51:06 +09:00
, collectionFirst :: Maybe LocalPageURI
, collectionLast :: Maybe LocalPageURI
2019-05-18 07:42:01 +09:00
, collectionItems :: [ a ]
}
instance ( FromJSON a , ToJSON a ) => ActivityPub ( Collection a ) where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ as2Context , forgeContext ]
2019-05-18 07:42:01 +09:00
parseObject o = do
2019-07-23 22:59:48 +09:00
ObjURI authority id_ <- o .: " id "
fmap ( authority , ) $
2019-05-18 07:42:01 +09:00
Collection id_
<$> o .: " type "
<*> o .:? " totalItems "
2019-07-23 22:59:48 +09:00
<*> withAuthorityMaybeO authority ( o .:? " current " )
<*> withAuthorityMaybeP authority ( o .:? " first " )
<*> withAuthorityMaybeP authority ( o .:? " last " )
2019-05-18 07:42:01 +09:00
<*> optional ( o .: " items " <|> o .: " orderedItems " ) .!= []
2019-07-23 22:59:48 +09:00
toSeries authority ( Collection id_ typ total curr firzt last items )
= " id " .= ObjURI authority id_
2019-05-18 07:42:01 +09:00
<> " type " .= typ
<> " totalItems " .=? total
2019-07-23 22:59:48 +09:00
<> " current " .=? ( ObjURI authority <$> curr )
<> " first " .=? ( PageURI authority <$> firzt )
<> " last " .=? ( PageURI authority <$> last )
2019-11-07 05:46:21 +09:00
<> itemsProp .=% items
where
itemsProp =
case typ of
CollectionTypeUnordered -> " items "
CollectionTypeOrdered -> " orderedItems "
2019-05-21 08:51:06 +09:00
data CollectionPageType
= CollectionPageTypeUnordered
| CollectionPageTypeOrdered
instance FromJSON CollectionPageType where
parseJSON = withText " CollectionPageType " parse
where
parse " CollectionPage " = pure CollectionPageTypeUnordered
parse " OrderedCollectionPage " = pure CollectionPageTypeOrdered
parse t = fail $ " Unknown collection page type: " ++ T . unpack t
instance ToJSON CollectionPageType where
toJSON = error " toJSON CollectionPageType "
toEncoding ct =
toEncoding $ case ct of
CollectionPageTypeUnordered -> " CollectionPage " :: Text
CollectionPageTypeOrdered -> " OrderedCollectionPage "
2019-07-23 22:59:48 +09:00
data CollectionPage a u = CollectionPage
2019-05-21 08:51:06 +09:00
{ collectionPageId :: LocalPageURI
, collectionPageType :: CollectionPageType
, collectionPageTotalItems :: Maybe Int
, collectionPageCurrent :: Maybe LocalPageURI
, collectionPageFirst :: Maybe LocalPageURI
, collectionPageLast :: Maybe LocalPageURI
, collectionPagePartOf :: LocalURI
, collectionPagePrev :: Maybe LocalPageURI
, collectionPageNext :: Maybe LocalPageURI
, collectionPageStartIndex :: Maybe Int
, collectionPageItems :: [ a ]
}
instance ( FromJSON a , ToJSON a ) => ActivityPub ( CollectionPage a ) where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ as2Context , forgeContext ]
2019-05-21 08:51:06 +09:00
parseObject o = do
2019-07-23 22:59:48 +09:00
PageURI authority id_ <- o .: " id "
fmap ( authority , ) $
2019-05-21 08:51:06 +09:00
CollectionPage id_
<$> o .: " type "
<*> o .:? " totalItems "
2019-07-23 22:59:48 +09:00
<*> withAuthorityMaybeP authority ( o .:? " current " )
<*> withAuthorityMaybeP authority ( o .:? " first " )
<*> withAuthorityMaybeP authority ( o .:? " last " )
<*> withAuthorityO authority ( o .: " partOf " )
<*> withAuthorityMaybeP authority ( o .:? " prev " )
<*> withAuthorityMaybeP authority ( o .:? " next " )
2019-05-21 08:51:06 +09:00
<*> o .:? " startIndex "
<*> optional ( o .: " items " <|> o .: " orderedItems " ) .!= []
2019-07-23 22:59:48 +09:00
toSeries authority ( CollectionPage id_ typ total curr firzt last partOf prev next ind items )
= " id " .= PageURI authority id_
2019-05-21 08:51:06 +09:00
<> " type " .= typ
<> " totalItems " .=? total
2019-07-23 22:59:48 +09:00
<> " current " .=? ( PageURI authority <$> curr )
<> " first " .=? ( PageURI authority <$> firzt )
<> " last " .=? ( PageURI authority <$> last )
<> " partOf " .= ( ObjURI authority partOf )
<> " prev " .=? ( PageURI authority <$> prev )
<> " next " .=? ( PageURI authority <$> next )
2019-05-21 08:51:06 +09:00
<> " startIndex " .=? ind
2019-11-07 05:46:21 +09:00
<> itemsProp .=% items
where
itemsProp =
case typ of
CollectionPageTypeUnordered -> " items "
CollectionPageTypeOrdered -> " orderedItems "
2019-05-18 07:42:01 +09:00
2019-07-23 22:59:48 +09:00
data Recipient u = RecipientActor ( Actor u ) | RecipientCollection ( Collection ( ObjURI u ) u )
2019-05-18 07:42:01 +09:00
instance ActivityPub Recipient where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ as2Context , secContext , forgeContext ]
2019-05-18 07:42:01 +09:00
parseObject o =
second RecipientActor <$> parseObject o <|>
second RecipientCollection <$> parseObject o
toSeries h ( RecipientActor a ) = toSeries h a
toSeries h ( RecipientCollection c ) = toSeries h c
2022-08-28 22:51:43 +09:00
data Resource u = ResourceActor ( Actor u ) | ResourceChild LocalURI LocalURI
instance ActivityPub Resource where
jsonldContext _ = [ as2Context , secContext , forgeContext ]
parseObject o =
second ResourceActor <$> parseObject o <|> do
ObjURI h luId <- o .: " id " <|> o .: " @id "
( h , ) . ResourceChild luId <$> withAuthorityO h ( o .: " managedBy " )
toSeries h ( ResourceActor a ) = toSeries h a
toSeries h ( ResourceChild luId luManager )
= " id " .= ObjURI h luId
<> " managedBy " .= ObjURI h luManager
2019-07-23 22:59:48 +09:00
data Audience u = Audience
{ audienceTo :: [ ObjURI u ]
, audienceBto :: [ ObjURI u ]
, audienceCc :: [ ObjURI u ]
, audienceBcc :: [ ObjURI u ]
, audienceGeneral :: [ ObjURI u ]
, audienceNonActors :: [ ObjURI u ]
2019-03-23 11:57:34 +09:00
}
2019-07-23 22:59:48 +09:00
newtype AdaptAudience u = AdaptAudience
{ unAdapt :: ObjURI u
2019-03-23 11:57:34 +09:00
}
2019-07-23 22:59:48 +09:00
instance UriMode u => FromJSON ( AdaptAudience u ) where
2019-05-17 06:22:45 +09:00
parseJSON = fmap AdaptAudience . parseJSON . adapt
2019-03-23 11:57:34 +09:00
where
adapt v =
case v of
String t
2019-07-23 22:59:48 +09:00
| t == " as:Public " -> String " Public "
| t == publicURI -> String " Public "
2019-03-23 11:57:34 +09:00
_ -> v
2019-07-23 22:59:48 +09:00
parseAudience :: UriMode u => Object -> Parser ( Audience u )
2019-03-23 11:57:34 +09:00
parseAudience o =
Audience
2019-04-02 08:40:29 +09:00
<$> o .:& " to "
<*> o .:& " bto "
<*> o .:& " cc "
<*> o .:& " bcc "
<*> o .:& " audience "
2019-06-12 09:11:24 +09:00
<*> o .:|& " nonActors "
2019-03-23 11:57:34 +09:00
where
obj .:& key = do
2019-04-02 08:40:29 +09:00
l <- obj .:? key .!= []
return $ map unAdapt l
2019-06-12 09:11:24 +09:00
obj .:|& key = do
l <- obj .:|? key .!= []
return $ map unAdapt l
2019-03-23 11:57:34 +09:00
2019-07-23 22:59:48 +09:00
encodeAudience :: UriMode u => Audience u -> Series
2019-05-17 19:47:53 +09:00
encodeAudience ( Audience to bto cc bcc aud nons )
2019-06-12 09:11:24 +09:00
= " to " .=% to
<> " bto " .=% bto
<> " cc " .=% cc
<> " bcc " .=% bcc
<> " audience " .=% aud
<> " nonActors " .=% nons
2019-03-23 11:57:34 +09:00
2019-07-23 22:59:48 +09:00
data Note u = Note
2019-03-23 11:05:30 +09:00
{ noteId :: Maybe LocalURI
2019-03-23 05:46:42 +09:00
, noteAttrib :: LocalURI
2019-07-23 22:59:48 +09:00
, noteAudience :: Audience u
, noteReplyTo :: Maybe ( ObjURI u )
, noteContext :: Maybe ( ObjURI u )
2019-03-22 07:57:15 +09:00
, notePublished :: Maybe UTCTime
2022-10-16 20:26:24 +09:00
, noteSource :: PandocMarkdown
, noteContent :: HTML
2019-01-22 00:54:57 +09:00
}
2019-07-23 22:59:48 +09:00
withAuthorityT a m = do
( a' , v ) <- m
if a == a'
2019-03-23 05:46:42 +09:00
then return v
2019-07-23 22:59:48 +09:00
else fail " URI authority mismatch "
2019-03-23 05:46:42 +09:00
2019-07-23 22:59:48 +09:00
withAuthorityO a m = do
ObjURI a' v <- m
if a == a'
then return v
else fail " URI authority mismatch "
withAuthorityS a m = do
SubURI a' v <- m
if a == a'
then return v
else fail " URI authority mismatch "
withAuthorityP a m = do
PageURI a' v <- m
if a == a'
then return v
else fail " URI authority mismatch "
2020-09-10 19:57:02 +09:00
withAuthorityD a m = do
Doc a' v <- m
if a == a'
then return v
else fail " URI authority mismatch "
2019-07-23 22:59:48 +09:00
withAuthorityMaybeT a m = do
mu <- m
for mu $ \ ( a' , v ) ->
if a == a'
then return v
else fail " URI authority mismatch "
withAuthorityMaybeO a m = do
mu <- m
for mu $ \ ( ObjURI a' v ) ->
if a == a'
then return v
else fail " URI authority mismatch "
withAuthorityMaybeS a m = do
mu <- m
for mu $ \ ( SubURI a' v ) ->
if a == a'
then return v
else fail " URI authority mismatch "
withAuthorityMaybeP a m = do
mu <- m
for mu $ \ ( PageURI a' v ) ->
if a == a'
2019-03-23 11:05:30 +09:00
then return v
2019-07-23 22:59:48 +09:00
else fail " URI authority mismatch "
2019-03-23 11:05:30 +09:00
2019-03-23 05:46:42 +09:00
instance ActivityPub Note where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ as2Context ]
2019-03-23 05:46:42 +09:00
parseObject o = do
typ <- o .: " type "
2019-06-02 23:41:51 +09:00
unless ( typ == ( " Note " :: Text ) ) $
fail " type isn't Note "
mediaType <- o .: " mediaType "
unless ( mediaType == ( " text/html " :: Text ) ) $
fail " mediaType isn't HTML "
source <- o .: " source "
sourceType <- source .: " mediaType "
unless ( sourceType == ( " text/markdown; variant=Pandoc " :: Text ) ) $
fail " source mediaType isn't Pandoc Markdown "
2019-07-23 22:59:48 +09:00
ObjURI a attrib <- o .: " attributedTo "
fmap ( a , ) $
2019-03-23 11:05:30 +09:00
Note
2019-07-23 22:59:48 +09:00
<$> withAuthorityMaybeO a ( o .:? " id " )
2019-03-23 11:05:30 +09:00
<*> pure attrib
2019-03-23 11:57:34 +09:00
<*> parseAudience o
2019-03-23 05:46:42 +09:00
<*> o .:? " inReplyTo "
<*> o .:? " context "
<*> o .:? " published "
2019-06-02 23:41:51 +09:00
<*> source .: " content "
2022-10-16 20:26:24 +09:00
<*> o .: " content "
2019-07-23 22:59:48 +09:00
toSeries authority ( Note mid attrib aud mreply mcontext mpublished src content )
2019-03-23 05:46:42 +09:00
= " type " .= ( " Note " :: Text )
2019-07-23 22:59:48 +09:00
<> " id " .=? ( ObjURI authority <$> mid )
<> " attributedTo " .= ObjURI authority attrib
2019-03-23 11:57:34 +09:00
<> encodeAudience aud
2019-03-23 05:46:42 +09:00
<> " inReplyTo " .=? mreply
<> " context " .=? mcontext
<> " published " .=? mpublished
2019-06-02 23:41:51 +09:00
<> " source " .= object
[ " content " .= src
, " mediaType " .= ( " text/markdown; variant=Pandoc " :: Text )
]
2019-03-23 05:46:42 +09:00
<> " content " .= content
2019-06-02 23:41:51 +09:00
<> " mediaType " .= ( " text/html " :: Text )
2019-03-23 05:46:42 +09:00
2019-07-12 07:18:30 +09:00
data RelationshipProperty = RelDependsOn deriving Eq
2019-07-12 00:14:16 +09:00
instance FromJSON RelationshipProperty where
parseJSON = withText " RelationshipProperty " parse
where
parse t
| t == " dependsOn " = pure RelDependsOn
| otherwise = fail $ " Unrecognized relationship: " ++ T . unpack t
instance ToJSON RelationshipProperty where
toJSON = error " toJSON RelationshipProperty "
toEncoding at =
toEncoding $ case at of
RelDependsOn -> " dependsOn " :: Text
2019-07-23 22:59:48 +09:00
data Relationship u = Relationship
{ relationshipId :: Maybe ( ObjURI u )
2019-07-12 07:18:30 +09:00
, relationshipExtraTypes :: [ Text ]
2019-07-23 22:59:48 +09:00
, relationshipSubject :: ObjURI u
2019-07-12 00:14:16 +09:00
, relationshipProperty :: Either RelationshipProperty Text
2019-07-23 22:59:48 +09:00
, relationshipObject :: ObjURI u
2019-07-12 00:14:16 +09:00
, relationshipAttributedTo :: LocalURI
, relationshipPublished :: Maybe UTCTime
, relationshipUpdated :: Maybe UTCTime
}
instance ActivityPub Relationship where
jsonldContext _ = [ as2Context , forgeContext ]
parseObject o = do
2019-07-12 07:18:30 +09:00
typs <- o .: " type "
unless ( ( " Relationship " :: Text ) ` elem ` typs ) $
2019-07-12 00:14:16 +09:00
fail " type isn't Relationship "
2019-07-23 22:59:48 +09:00
ObjURI a attributedTo <- o .: " attributedTo "
2019-07-12 00:14:16 +09:00
2019-07-23 22:59:48 +09:00
fmap ( a , ) $
2019-07-12 00:14:16 +09:00
Relationship
<$> o .:? " id "
2019-07-12 07:18:30 +09:00
<*> pure ( delete " Relationship " typs )
2019-07-12 00:14:16 +09:00
<*> o .: " subject "
<*> o .:+ " relationship "
<*> o .: " object "
<*> pure attributedTo
<*> o .:? " published "
<*> o .:? " updated "
2019-07-23 22:59:48 +09:00
toSeries authority
2019-07-12 07:18:30 +09:00
( Relationship id_ typs subject property object attributedTo published
2020-06-18 19:38:04 +09:00
updated )
2019-07-12 00:14:16 +09:00
= " id " .=? id_
2019-07-12 07:18:30 +09:00
<> " type " .= ( " Relationship " : typs )
2019-07-12 00:14:16 +09:00
<> " subject " .= subject
<> " relationship " .=+ property
<> " object " .= object
2019-07-23 22:59:48 +09:00
<> " attributedTo " .= ObjURI authority attributedTo
2019-07-12 00:14:16 +09:00
<> " published " .=? published
<> " updated " .=? updated
2019-07-23 22:59:48 +09:00
data TicketDependency u = TicketDependency
{ ticketDepId :: Maybe ( ObjURI u )
, ticketDepParent :: ObjURI u
, ticketDepChild :: ObjURI u
2019-07-12 07:18:30 +09:00
, ticketDepAttributedTo :: LocalURI
, ticketDepPublished :: Maybe UTCTime
, ticketDepUpdated :: Maybe UTCTime
}
instance ActivityPub TicketDependency where
jsonldContext _ = [ as2Context , forgeContext ]
parseObject o = do
2019-07-23 22:59:48 +09:00
( a , rel ) <- parseObject o
2019-07-12 07:18:30 +09:00
unless ( " TicketDependency " ` elem ` relationshipExtraTypes rel ) $
fail " type isn't TicketDependency "
unless ( relationshipProperty rel == Left RelDependsOn ) $
fail " relationship isn't dependsOn "
2019-07-23 22:59:48 +09:00
return ( a , rel2td rel )
2019-07-12 07:18:30 +09:00
where
rel2td rel = TicketDependency
{ ticketDepId = relationshipId rel
, ticketDepParent = relationshipSubject rel
, ticketDepChild = relationshipObject rel
, ticketDepAttributedTo = relationshipAttributedTo rel
, ticketDepPublished = relationshipPublished rel
, ticketDepUpdated = relationshipUpdated rel
}
2019-07-23 22:59:48 +09:00
toSeries a = toSeries a . td2rel
2019-07-12 07:18:30 +09:00
where
td2rel td = Relationship
{ relationshipId = ticketDepId td
, relationshipExtraTypes = [ " TicketDependency " ]
, relationshipSubject = ticketDepParent td
, relationshipProperty = Left RelDependsOn
, relationshipObject = ticketDepChild td
, relationshipAttributedTo = ticketDepAttributedTo td
, relationshipPublished = ticketDepPublished td
, relationshipUpdated = ticketDepUpdated td
}
2020-07-14 18:56:13 +09:00
data PatchLocal = PatchLocal
2020-08-13 19:26:20 +09:00
{ patchId :: LocalURI
, patchContext :: LocalURI
2020-07-14 18:56:13 +09:00
}
parsePatchLocal
:: UriMode u => Object -> Parser ( Maybe ( Authority u , PatchLocal ) )
parsePatchLocal o = do
mid <- o .:? " id "
case mid of
Nothing -> do
verifyNothing " context "
return Nothing
Just ( ObjURI a id_ ) ->
fmap ( Just . ( a , ) ) $
PatchLocal
<$> pure id_
<*> withAuthorityO a ( o .: " context " )
where
verifyNothing t =
if t ` M . member ` o
then fail $ T . unpack t ++ " field found, expected none "
else return ()
encodePatchLocal :: UriMode u => Authority u -> PatchLocal -> Series
2020-08-13 19:26:20 +09:00
encodePatchLocal a ( PatchLocal id_ context )
2020-07-14 18:56:13 +09:00
= " id " .= ObjURI a id_
<> " context " .= ObjURI a context
data Patch u = Patch
{ patchLocal :: Maybe ( Authority u , PatchLocal )
, patchAttributedTo :: LocalURI
2020-07-15 22:00:58 +09:00
, patchPublished :: Maybe UTCTime
2020-08-15 06:16:33 +09:00
, patchType :: PatchMediaType
2020-05-25 18:40:48 +09:00
, patchContent :: Text
}
instance ActivityPub Patch where
jsonldContext _ = [ as2Context , forgeContext ]
parseObject o = do
typ <- o .: " type "
unless ( typ == ( " Patch " :: Text ) ) $
fail " type isn't Patch "
2020-07-14 18:56:13 +09:00
ObjURI a attrib <- o .: " attributedTo "
2020-05-25 18:40:48 +09:00
fmap ( a , ) $
2020-07-14 18:56:13 +09:00
Patch
<$> parsePatchLocal o
<*> pure attrib
2020-07-15 22:00:58 +09:00
<*> o .:? " published "
2020-05-25 18:40:48 +09:00
<*> o .: " mediaType "
<*> o .: " content "
2020-07-14 17:50:57 +09:00
2020-07-14 18:56:13 +09:00
toSeries a ( Patch local attrib published typ content )
= maybe mempty ( uncurry encodePatchLocal ) local
2020-07-14 17:50:57 +09:00
<> " type " .= ( " Patch " :: Text )
2020-07-14 18:56:13 +09:00
<> " attributedTo " .= ObjURI a attrib
2020-07-15 22:00:58 +09:00
<> " published " .=? published
2020-07-14 17:50:57 +09:00
<> " mediaType " .= typ
<> " content " .= content
2020-05-25 18:40:48 +09:00
2020-08-13 19:26:20 +09:00
data BundleLocal = BundleLocal
{ bundleId :: LocalURI
, bundleContext :: LocalURI
, bundlePrevVersions :: [ LocalURI ]
, bundleCurrentVersion :: Maybe LocalURI
}
parseBundleLocal
:: UriMode u => Object -> Parser ( Maybe ( Authority u , BundleLocal ) )
parseBundleLocal o = do
mid <- o .:? " id "
case mid of
Nothing -> do
verifyNothing " context "
verifyNothing " previousVersions "
verifyNothing " currentVersion "
return Nothing
Just ( ObjURI a id_ ) ->
fmap ( Just . ( a , ) ) $
BundleLocal
<$> pure id_
<*> withAuthorityO a ( o .: " context " )
<*> ( traverse ( withAuthorityO a . return ) =<< o .:? " previousVersions " .!= [] )
<*> withAuthorityMaybeO a ( o .:? " currentVersion " )
where
verifyNothing t =
if t ` M . member ` o
then fail $ T . unpack t ++ " field found, expected none "
else return ()
encodeBundleLocal :: UriMode u => Authority u -> BundleLocal -> Series
encodeBundleLocal a ( BundleLocal id_ context versions mcurrent )
= " id " .= ObjURI a id_
<> " context " .= ObjURI a context
<> " previousVersions " .= map ( ObjURI a ) versions
<> " currentVersion " .=? ( ObjURI a <$> mcurrent )
data Bundle u
= BundleHosted ( Maybe BundleLocal ) ( NonEmpty LocalURI )
| BundleOffer ( Maybe ( Authority u , BundleLocal ) ) ( NonEmpty ( Patch u ) )
instance ActivityPub Bundle where
jsonldContext _ = [ as2Context , forgeContext ]
parseObject o = do
typ <- o .: " type "
unless ( typ == ( " OrderedCollection " :: Text ) ) $
fail " type isn't OrderedCollection "
mlocal <- parseBundleLocal o
mtotal <- o .:? " totalItems "
items <- toEither <$> o .: " orderedItems " <|> o .: " items "
case items of
Left ( ObjURI h lu :| us ) -> do
for_ mlocal $ \ ( h' , _ ) ->
unless ( h == h' ) $
fail " Patches in bundle not on the same host as bundle "
unless ( all ( == h ) $ map objUriAuthority us ) $
fail " Patches in bundle on different hosts "
for_ mtotal $ \ total ->
unless ( length us + 1 == total ) $
fail " Incorrect totalItems "
return ( h , BundleHosted ( snd <$> mlocal ) $ lu :| map objUriLocal us )
Right ( Doc h p :| ps ) -> do
unless ( all ( == h ) $ map docAuthority ps ) $
fail " Patches in bundle have different authors "
for_ mtotal $ \ total ->
unless ( length ps + 1 == total ) $
fail " Incorrect totalItems "
return ( h , BundleOffer mlocal $ p :| map docValue ps )
toSeries hBundle ( BundleHosted mlocal lus )
= maybe mempty ( encodeBundleLocal hBundle ) mlocal
<> " type " .= ( " OrderedCollection " :: Text )
<> " totalItems " .= length lus
<> " orderedItems " .= NE . map ( ObjURI hBundle ) lus
toSeries hAttrib ( BundleOffer mlocal patches )
= maybe mempty ( uncurry encodeBundleLocal ) mlocal
<> " type " .= ( " OrderedCollection " :: Text )
<> " totalItems " .= length patches
<> " orderedItems " .= NE . map ( Doc hAttrib ) patches
2019-06-06 19:25:16 +09:00
data TicketLocal = TicketLocal
{ ticketId :: LocalURI
, ticketReplies :: LocalURI
, ticketParticipants :: LocalURI
2020-05-24 18:17:49 +09:00
, ticketTeam :: Maybe LocalURI
2019-06-06 19:25:16 +09:00
, ticketEvents :: LocalURI
2019-07-12 00:53:55 +09:00
, ticketDeps :: LocalURI
, ticketReverseDeps :: LocalURI
2019-06-06 19:25:16 +09:00
}
2019-07-23 22:59:48 +09:00
parseTicketLocal :: UriMode u => Object -> Parser ( Maybe ( Authority u , TicketLocal ) )
2019-06-06 19:25:16 +09:00
parseTicketLocal o = do
2019-07-23 22:59:48 +09:00
mid <- o .:? " id "
2019-06-06 19:25:16 +09:00
case mid of
Nothing -> do
verifyNothing " replies "
2019-06-12 09:11:24 +09:00
verifyNothing " participants "
2020-06-18 19:38:04 +09:00
verifyNothing " followers "
2019-06-12 09:11:24 +09:00
verifyNothing " team "
verifyNothing " history "
2019-07-12 00:53:55 +09:00
verifyNothing " dependencies "
verifyNothing " dependants "
2019-06-06 19:25:16 +09:00
return Nothing
2019-07-23 22:59:48 +09:00
Just ( ObjURI a id_ ) ->
fmap ( Just . ( a , ) ) $
2019-06-06 19:25:16 +09:00
TicketLocal
<$> pure id_
2019-07-23 22:59:48 +09:00
<*> withAuthorityO a ( o .: " replies " )
2020-06-18 19:38:04 +09:00
<*> withAuthorityO a ( o .: " participants " <|> o .: " followers " )
2020-05-24 18:17:49 +09:00
<*> withAuthorityMaybeO a ( o .:? " team " )
2019-07-23 22:59:48 +09:00
<*> withAuthorityO a ( o .: " history " )
<*> withAuthorityO a ( o .: " dependencies " )
<*> withAuthorityO a ( o .: " dependants " )
2019-06-06 19:25:16 +09:00
where
verifyNothing t =
if t ` M . member ` o
then fail $ T . unpack t ++ " field found, expected none "
else return ()
2019-07-23 22:59:48 +09:00
encodeTicketLocal :: UriMode u => Authority u -> TicketLocal -> Series
2019-07-12 00:53:55 +09:00
encodeTicketLocal
2020-06-18 19:38:04 +09:00
a ( TicketLocal id_ replies followers team events deps rdeps )
2019-07-23 22:59:48 +09:00
= " id " .= ObjURI a id_
<> " replies " .= ObjURI a replies
2020-06-18 19:38:04 +09:00
<> " followers " .= ObjURI a followers
2020-05-24 18:17:49 +09:00
<> " team " .=? ( ObjURI a <$> team )
2019-07-23 22:59:48 +09:00
<> " history " .= ObjURI a events
<> " dependencies " .= ObjURI a deps
<> " dependants " .= ObjURI a rdeps
2020-05-25 21:39:25 +09:00
data MergeRequest u = MergeRequest
2022-09-19 00:55:42 +09:00
{ mrOrigin :: Maybe ( Either ( ObjURI u ) ( Authority u , Branch u ) )
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
, mrTarget :: Either LocalURI ( Branch u )
2022-09-19 00:55:42 +09:00
, mrBundle :: Maybe ( Either ( ObjURI u ) ( Authority u , Bundle u ) )
2020-05-25 21:39:25 +09:00
}
instance ActivityPub MergeRequest where
jsonldContext _ = [ as2Context , forgeContext ]
parseObject o = do
typ <- o .: " type "
unless ( typ == ( " Offer " :: Text ) ) $
fail " type isn't Offer "
2020-07-14 20:10:43 +09:00
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
target <- o .:+ " target "
let ( a , target' ) =
case target of
Left ( ObjURI h lu ) -> ( h , Left lu )
Right ( Doc h branch ) -> ( h , Right branch )
2020-07-14 20:10:43 +09:00
fmap ( a , ) $
2020-05-25 21:39:25 +09:00
MergeRequest
2022-09-19 00:55:42 +09:00
<$> ( fmap ( second fromDoc ) <$> o .:+? " origin " )
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
<*> pure target'
2022-09-19 00:55:42 +09:00
<*> ( fmap ( second fromDoc ) <$> o .:+? " object " )
2020-07-14 20:25:37 +09:00
where
fromDoc ( Doc h v ) = ( h , v )
2020-05-25 21:39:25 +09:00
2020-08-13 19:26:20 +09:00
toSeries h ( MergeRequest morigin target bundle )
2020-05-25 21:39:25 +09:00
= " type " .= ( " Offer " :: Text )
2022-09-19 00:55:42 +09:00
<> " origin " .=+? fmap ( second $ uncurry Doc ) morigin
Switch to new actor layout
This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:
- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
I haven't tested them yet on a read DB so there may be errors there; I'll fix
them in the next patches if I find any (probably going to test on the main
instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
removed, I haven't removed them; that's because I wonder if at some point
users can have a tree of inboxes much like in email; I don't have an excuse
for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
they already look like user@host/repo regardless of who "controls" that repo
2022-08-15 22:57:42 +09:00
<> " target " .=+ bimap ( ObjURI h ) ( Doc h ) target
2022-09-19 00:55:42 +09:00
<> " object " .=+? fmap ( second $ uncurry Doc ) bundle
2020-05-25 21:39:25 +09:00
2019-07-23 22:59:48 +09:00
data Ticket u = Ticket
{ ticketLocal :: Maybe ( Authority u , TicketLocal )
2019-06-06 19:25:16 +09:00
, ticketAttributedTo :: LocalURI
2019-06-04 06:52:34 +09:00
, ticketPublished :: Maybe UTCTime
, ticketUpdated :: Maybe UTCTime
2020-02-11 23:14:52 +09:00
, ticketContext :: Maybe ( ObjURI u )
2020-02-03 23:53:12 +09:00
-- , ticketName :: Maybe Text
2022-09-21 21:50:26 +09:00
, ticketSummary :: Escaped
, ticketContent :: HTML
, ticketSource :: PandocMarkdown
2019-07-23 22:59:48 +09:00
, ticketAssignedTo :: Maybe ( ObjURI u )
2020-08-05 19:11:16 +09:00
, ticketResolved :: Maybe ( Maybe ( ObjURI u ) , Maybe UTCTime )
2020-05-25 21:39:25 +09:00
, ticketAttachment :: Maybe ( Authority u , MergeRequest u )
2019-06-04 06:52:34 +09:00
}
instance ActivityPub Ticket where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ as2Context , forgeContext ]
2019-06-04 06:52:34 +09:00
parseObject o = do
typ <- o .: " type "
unless ( typ == ( " Ticket " :: Text ) ) $
fail " type isn't Ticket "
mediaType <- o .: " mediaType "
unless ( mediaType == ( " text/html " :: Text ) ) $
fail " mediaType isn't HTML "
source <- o .: " source "
sourceType <- source .: " mediaType "
unless ( sourceType == ( " text/markdown; variant=Pandoc " :: Text ) ) $
fail " source mediaType isn't Pandoc Markdown "
2019-07-23 22:59:48 +09:00
ObjURI a attributedTo <- o .: " attributedTo "
2019-06-04 06:52:34 +09:00
2020-08-05 19:11:16 +09:00
mresolved <- do
is <- o .:? " isResolved " .!= False
if is
then do
at <- o .:? " resolved "
by <- o .:? " resolvedBy "
return $ Just ( by , at )
else do
verifyNothing " resolved "
verifyNothing " resolvedBy "
return Nothing
2019-07-23 22:59:48 +09:00
fmap ( a , ) $
2019-06-04 06:52:34 +09:00
Ticket
2019-06-06 19:25:16 +09:00
<$> parseTicketLocal o
<*> pure attributedTo
2019-06-04 06:52:34 +09:00
<*> o .:? " published "
<*> o .:? " updated "
2020-02-11 23:14:52 +09:00
<*> o .:? " context "
2020-02-03 23:53:12 +09:00
-- <*> o .:? "name"
2022-09-21 21:50:26 +09:00
<*> o .: " summary "
<*> o .: " content "
2019-06-04 06:52:34 +09:00
<*> source .: " content "
2019-06-12 09:11:24 +09:00
<*> o .:? " assignedTo "
2020-08-05 19:11:16 +09:00
<*> pure mresolved
2020-05-25 21:39:25 +09:00
<*> ( traverse parseObject =<< o .:? " attachment " )
2020-08-05 19:11:16 +09:00
where
verifyNothing t =
if t ` M . member ` o
then fail $ T . unpack t ++ " field found, expected none "
else return ()
2019-06-04 06:52:34 +09:00
2019-07-23 22:59:48 +09:00
toSeries authority
2020-02-11 23:14:52 +09:00
( Ticket local attributedTo published updated context {- name -}
2020-08-05 19:11:16 +09:00
summary content source assignedTo mresolved mmr )
2019-06-06 19:25:16 +09:00
= maybe mempty ( uncurry encodeTicketLocal ) local
<> " type " .= ( " Ticket " :: Text )
2019-07-23 22:59:48 +09:00
<> " attributedTo " .= ObjURI authority attributedTo
2019-06-04 06:52:34 +09:00
<> " published " .=? published
<> " updated " .=? updated
2020-02-11 23:14:52 +09:00
<> " context " .=? context
2020-02-03 23:53:12 +09:00
-- <> "name" .=? name
2019-06-04 06:52:34 +09:00
<> " summary " .= summary
<> " content " .= content
<> " mediaType " .= ( " text/html " :: Text )
<> " source " .= object
[ " content " .= source
, " mediaType " .= ( " text/markdown; variant=Pandoc " :: Text )
]
2019-06-12 09:11:24 +09:00
<> " assignedTo " .=? assignedTo
2020-08-05 19:11:16 +09:00
<> maybe
( " isResolved " .= False )
( \ ( mby , mat )
-> " isResolved " .= True
<> " resolvedBy " .=? mby
<> " resolved " .=? mat
)
mresolved
2020-05-25 21:39:25 +09:00
<> maybe
mempty
( \ ( h , mr ) -> " attachment " ` pair ` pairs ( toSeries h mr ) )
mmr
2019-06-04 06:52:34 +09:00
2019-08-06 22:23:11 +09:00
data Author = Author
{ authorName :: Text
, authorEmail :: EmailAddress
}
instance FromJSON Author where
parseJSON = withObject " Author " $ \ o ->
Author
<$> o .: " name "
<*> ( parseMailto =<< o .: " mbox " )
where
parseMailto =
either fail return .
A . parseOnly ( A . string " mailto: " *> E . addrSpec <* A . endOfInput ) .
encodeUtf8
instance ToJSON Author where
toJSON = error " toJSON Author "
toEncoding ( Author name email ) =
pairs
$ " name " .= name
<> " mbox " .= ( " mailto: " <> decodeUtf8 ( E . toByteString email ) )
newtype Hash = Hash ByteString
instance FromJSON Hash where
parseJSON = withText " Hash " $ \ t ->
let b = encodeUtf8 t
in if not ( BC . null b ) && BC . all isHexDigit b
then return $ Hash b
else fail " Hash should be a non-empty hex string "
instance ToJSON Hash where
toJSON ( Hash b ) = toJSON $ decodeUtf8 b
toEncoding ( Hash b ) = toEncoding $ decodeUtf8 b
data Commit u = Commit
{ commitId :: LocalURI
, commitRepository :: LocalURI
, commitAuthor :: Either Author ( ObjURI u )
, commitCommitter :: Maybe ( Either Author ( ObjURI u ) )
, commitTitle :: Text
, commitHash :: Hash
, commitDescription :: Maybe Text
, commitWritten :: UTCTime
, commitCommitted :: Maybe UTCTime
}
instance ActivityPub Commit where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ as2Context , forgeContext ]
2019-08-06 22:23:11 +09:00
parseObject o = do
typ <- o .: " type "
unless ( typ == ( " Commit " :: Text ) ) $
fail " type isn't Commit "
mdesc <- o .:? " description "
mdescContent <- for mdesc $ \ desc -> do
descType <- desc .: " mediaType "
unless ( descType == ( " text/plain " :: Text ) ) $
fail " description mediaType isn't \ " text/plain \ " "
desc .: " content "
ObjURI a id_ <- o .: " id "
fmap ( a , ) $
Commit id_
2019-08-29 01:20:19 +09:00
<$> withAuthorityO a ( o .: " context " )
2019-08-06 22:23:11 +09:00
<*> o .:+ " attributedTo "
<*> o .:+? " committedBy "
<*> o .: " name "
<*> o .: " hash "
<*> pure mdescContent
<*> o .: " created "
<*> o .:? " committed "
toSeries authority
( Commit id_ repo author committer title hash mdesc written mcommitted )
= " id " .= ObjURI authority id_
<> " type " .= ( " Commit " :: Text )
2019-10-11 01:48:57 +09:00
<> " context " .= ObjURI authority repo
2019-08-06 22:23:11 +09:00
<> " attributedTo " .=+ author
<> " committedBy " .=+? committer
<> " name " .= title
<> " hash " .= hash
<> maybe
mempty
( \ desc -> " description " .= object
[ " content " .= desc
, " mediaType " .= ( " text/plain " :: Text )
]
)
mdesc
<> " created " .= written
<> " committed " .=? mcommitted
2019-08-29 00:31:40 +09:00
data Branch u = Branch
{ branchName :: Text
, branchRef :: Text
, branchRepo :: LocalURI
}
instance ActivityPub Branch where
jsonldContext _ = [ as2Context , forgeContext ]
parseObject o = do
typ <- o .: " type "
unless ( typ == ( " Branch " :: Text ) ) $
fail " type isn't Branch "
ObjURI a repo <- o .: " context "
fmap ( a , ) $
Branch
<$> o .: " name "
<*> o .: " ref "
<*> pure repo
toSeries authority ( Branch name ref repo )
= " type " .= ( " Branch " :: Text )
<> " name " .= name
<> " ref " .= ref
<> " context " .= ObjURI authority repo
2022-07-26 02:15:22 +09:00
data Role = RoleAdmin deriving Eq
instance FromJSON Role where
parseJSON = withText " Role " parse
where
parse " https://forgefed.org/ns#admin " = pure RoleAdmin
parse t = fail $ " Unknown role: " ++ T . unpack t
instance ToJSON Role where
toJSON = error " toJSON Role "
toEncoding r =
toEncoding $ case r of
RoleAdmin -> " https://forgefed.org/ns#admin " :: Text
2023-05-29 15:50:17 +09:00
data Duration = Duration Int
instance FromJSON Duration where
parseJSON = withText " Duration " parse
where
parse t =
case T . stripSuffix " S " =<< T . stripPrefix " PT " t of
Nothing -> fail $ " Not in PTS format: " ++ T . unpack t
Just t' ->
case readMaybe $ T . unpack t' of
Nothing -> fail $ " Not an Int: " ++ T . unpack t'
Just n -> do
guard $ n > 0
return $ Duration n
instance ToJSON Duration where
toJSON = error " toJSON Duration "
toEncoding ( Duration i ) =
toEncoding $ T . concat [ " PT " , T . pack $ show i , " S " ]
2023-05-30 20:34:37 +09:00
data Usage = GatherAndConvey | Distribute | Invoke deriving Eq
instance FromJSON Usage where
parseJSON = withText " Usage " parse
where
parse " gatherAndConvey " = pure GatherAndConvey
parse " distribute " = pure Distribute
parse " invoke " = pure Invoke
parse t = fail $ " Unknown usage: " ++ T . unpack t
instance ToJSON Usage where
toJSON = error " toJSON Usage "
toEncoding u =
toEncoding $ case u of
GatherAndConvey -> " gatherAndConvey " :: Text
Distribute -> " distribute "
Invoke -> " invoke "
2019-07-23 22:59:48 +09:00
data Accept u = Accept
{ acceptObject :: ObjURI u
2019-09-11 17:12:20 +09:00
, acceptResult :: Maybe LocalURI
2019-03-14 08:37:58 +09:00
}
2019-07-23 22:59:48 +09:00
parseAccept :: UriMode u => Authority u -> Object -> Parser ( Accept u )
parseAccept a o =
2019-06-26 10:12:11 +09:00
Accept
<$> o .: " object "
2019-09-11 17:12:20 +09:00
<*> withAuthorityMaybeO a ( o .:? " result " )
2019-03-14 08:37:58 +09:00
2019-07-23 22:59:48 +09:00
encodeAccept :: UriMode u => Authority u -> Accept u -> Series
2019-09-11 17:12:20 +09:00
encodeAccept authority ( Accept obj mresult )
= " object " .= obj
<> " result " .=? ( ObjURI authority <$> mresult )
2019-03-14 08:37:58 +09:00
2020-09-10 19:57:02 +09:00
data AddObject u = AddBundle ( NonEmpty ( Patch u ) )
instance ActivityPub AddObject where
jsonldContext = error " jsonldContext AddObject "
parseObject o = do
( h , b ) <- parseObject o
patches <-
case b of
BundleHosted _ _ -> fail " Patches specified as URIs "
BundleOffer mlocal pts -> do
for_ mlocal $ \ _ -> fail " Bundle 'id' specified "
return pts
return ( h , AddBundle patches )
toSeries h ( AddBundle ps ) = toSeries h $ BundleOffer Nothing ps
data Add u = Add
{ addObject :: Either ( ObjURI u ) ( AddObject u )
, addTarget :: ObjURI u
}
parseAdd :: UriMode u => Object -> Authority u -> Parser ( Add u )
parseAdd o h = Add
<$> ( bitraverse pure ( withAuthorityD h . pure ) =<<
toEither <$> o .: " object "
)
<*> o .: " target "
encodeAdd :: UriMode u => Authority u -> Add u -> Series
encodeAdd h ( Add obj target )
= case obj of
Left u -> " object " .= u
Right o -> " object " ` pair ` pairs ( toSeries h o )
<> " target " .= target
2022-06-23 18:09:02 +09:00
data Apply u = Apply
{ applyObject :: ObjURI u
2022-09-24 00:58:54 +09:00
, applyTarget :: Either ( ObjURI u ) ( Authority u , Branch u )
2022-06-23 18:09:02 +09:00
}
parseApply :: UriMode u => Object -> Parser ( Apply u )
parseApply o =
Apply
<$> o .: " object "
2022-09-24 00:58:54 +09:00
<*> ( second fromDoc <$> o .:+ " target " )
where
fromDoc ( Doc h v ) = ( h , v )
2022-06-23 18:09:02 +09:00
encodeApply :: UriMode u => Apply u -> Series
encodeApply ( Apply obj target )
= " object " .= obj
2022-09-24 00:58:54 +09:00
<> " target " .=+ second ( uncurry Doc ) target
2022-06-23 18:09:02 +09:00
2022-07-25 01:52:28 +09:00
data CreateObject u
= CreateNote ( Authority u ) ( Note u )
| CreateTicket ( Authority u ) ( Ticket u )
| CreateTicketTracker ActorDetail ( Maybe ( Authority u , ActorLocal u ) )
2022-09-16 19:34:44 +09:00
| CreateRepository ActorDetail VersionControlSystem ( Maybe ( Authority u , ActorLocal u ) )
2022-09-17 17:31:22 +09:00
| CreatePatchTracker ActorDetail ( NonEmpty ( ObjURI u ) ) ( Maybe ( Authority u , ActorLocal u ) )
2022-07-25 01:52:28 +09:00
parseCreateObject :: UriMode u => Object -> Parser ( CreateObject u )
parseCreateObject o
= uncurry CreateNote <$> parseObject o
<|> uncurry CreateTicket <$> parseObject o
<|> do d <- parseActorDetail o
unless ( actorType d == ActorTypeTicketTracker ) $
fail " type isn't TicketTracker "
ml <- parseActorLocal o
return $ CreateTicketTracker d ml
2022-09-16 19:34:44 +09:00
<|> do d <- parseActorDetail o
unless ( actorType d == ActorTypeRepo ) $
fail " type isn't Repository "
vcs <- o .: " versionControlSystem "
ml <- parseActorLocal o
return $ CreateRepository d vcs ml
2022-09-17 17:31:22 +09:00
<|> do d <- parseActorDetail o
unless ( actorType d == ActorTypePatchTracker ) $
fail " type isn't PatchTracker "
repos <- o .:*+ " tracksPatchesFor "
ml <- parseActorLocal o
return $ CreatePatchTracker d repos ml
2022-07-25 01:52:28 +09:00
encodeCreateObject :: UriMode u => CreateObject u -> Series
encodeCreateObject ( CreateNote h note ) = toSeries h note
encodeCreateObject ( CreateTicket h ticket ) = toSeries h ticket
encodeCreateObject ( CreateTicketTracker d ml ) =
encodeActorDetail d <> maybe mempty ( uncurry encodeActorLocal ) ml
2022-09-16 19:34:44 +09:00
encodeCreateObject ( CreateRepository d vcs ml )
= encodeActorDetail d
<> " versionControlSystem " .= vcs
<> maybe mempty ( uncurry encodeActorLocal ) ml
2022-09-17 17:31:22 +09:00
encodeCreateObject ( CreatePatchTracker d repos ml )
= encodeActorDetail d
<> " tracksPatchesFor " .=*+ repos
<> maybe mempty ( uncurry encodeActorLocal ) ml
2020-02-10 23:51:32 +09:00
2019-07-23 22:59:48 +09:00
data Create u = Create
2020-02-10 23:51:32 +09:00
{ createObject :: CreateObject u
2020-02-18 22:26:28 +09:00
, createTarget :: Maybe ( ObjURI u )
2019-02-12 20:53:24 +09:00
}
2019-07-23 22:59:48 +09:00
parseCreate :: UriMode u => Object -> Authority u -> LocalURI -> Parser ( Create u )
parseCreate o a luActor = do
2022-07-25 01:52:28 +09:00
obj <- parseCreateObject =<< o .: " object "
case obj of
CreateNote h note ->
unless ( a == h && luActor == noteAttrib note ) $
fail " Create actor != note attrib "
CreateTicket h ticket ->
unless ( a == h && luActor == ticketAttributedTo ticket ) $
fail " Create actor != note attrib "
CreateTicketTracker _ _ -> return ()
2022-09-16 19:34:44 +09:00
CreateRepository _ _ _ -> return ()
2022-09-17 17:31:22 +09:00
CreatePatchTracker _ _ _ -> return ()
2020-02-18 22:26:28 +09:00
Create obj <$> o .:? " target "
2019-03-10 15:42:03 +09:00
2022-07-25 01:52:28 +09:00
encodeCreate :: UriMode u => Create u -> Series
encodeCreate ( Create obj target )
= " object " ` pair ` pairs ( encodeCreateObject obj )
2020-02-18 22:26:28 +09:00
<> " target " .=? target
2019-02-12 20:53:24 +09:00
2019-07-23 22:59:48 +09:00
data Follow u = Follow
2019-09-25 19:43:05 +09:00
{ followObject :: ObjURI u
2022-10-25 13:54:56 +09:00
, followContext :: Maybe LocalURI
2019-09-25 19:43:05 +09:00
, followHide :: Bool
2019-03-14 08:37:58 +09:00
}
2019-07-23 22:59:48 +09:00
parseFollow :: UriMode u => Object -> Parser ( Follow u )
2022-10-25 13:54:56 +09:00
parseFollow o = do
u @ ( ObjURI h _ ) <- o .: " object "
Follow u
<$> withAuthorityMaybeO h ( o .:? " context " )
2019-11-03 22:43:59 +09:00
<*> o .:? " hide " .!= False
2019-03-14 08:37:58 +09:00
2019-07-23 22:59:48 +09:00
encodeFollow :: UriMode u => Follow u -> Series
2019-09-25 19:43:05 +09:00
encodeFollow ( Follow obj mcontext hide )
= " object " .= obj
2022-10-25 13:54:56 +09:00
<> " context " .=? ( ObjURI ( objUriAuthority obj ) <$> mcontext )
2019-09-25 19:43:05 +09:00
<> " hide " .= hide
2019-03-14 08:37:58 +09:00
2022-07-26 02:15:22 +09:00
data Grant u = Grant
{ grantObject :: Either Role ( ObjURI u )
2023-05-29 15:50:17 +09:00
, grantContext :: LocalURI
2022-07-26 02:15:22 +09:00
, grantTarget :: ObjURI u
2023-05-29 15:50:17 +09:00
, grantResult :: Maybe ( LocalURI , Maybe Duration )
2023-05-29 16:47:41 +09:00
, grantStart :: Maybe UTCTime
, grantEnd :: Maybe UTCTime
2023-05-30 20:34:37 +09:00
, grantAllows :: Usage
, grantDelegates :: Maybe ( ObjURI u )
2022-07-26 02:15:22 +09:00
}
2023-05-29 15:50:17 +09:00
parseGrant :: UriMode u => Authority u -> Object -> Parser ( Grant u )
parseGrant h o =
2022-07-26 02:15:22 +09:00
Grant
2022-09-06 01:19:52 +09:00
<$> o .:+ " object "
2023-05-29 15:50:17 +09:00
<*> withAuthorityO h ( o .: " context " )
2022-07-26 02:15:22 +09:00
<*> o .: " target "
2023-05-29 16:47:41 +09:00
<*> ( do mres <- o .:+? " result "
2023-05-29 15:50:17 +09:00
for mres $ \ case
Left u -> ( , Nothing ) <$> withAuthorityO h ( pure u )
Right r ->
( , ) <$> withAuthorityO h ( r .: " id " ) <*> r .:? " duration "
2023-05-29 16:47:41 +09:00
)
<*> o .:? " startTime "
<*> o .:? " endTime "
2023-05-30 20:34:37 +09:00
<*> o .: " allows "
<*> o .:? " delegates "
2023-05-29 15:50:17 +09:00
encodeGrant :: UriMode u => Authority u -> Grant u -> Series
2023-05-30 20:34:37 +09:00
encodeGrant h ( Grant obj context target mresult mstart mend allows mdelegates )
2022-09-06 01:19:52 +09:00
= " object " .=+ obj
2023-05-29 15:50:17 +09:00
<> " context " .= ObjURI h context
2022-09-06 01:19:52 +09:00
<> " target " .= target
2023-05-29 16:47:41 +09:00
<> ( case mresult of
2023-05-29 15:50:17 +09:00
Nothing -> mempty
Just ( result , mduration ) ->
" result " ` pair ` pairs
( " id " .= ObjURI h result
<> " duration " .=? mduration
)
2023-05-29 16:47:41 +09:00
)
<> " startTime " .=? mstart
<> " endTime " .=? mend
2023-05-30 20:34:37 +09:00
<> " allows " .= allows
<> " delegates " .=? mdelegates
2022-09-06 01:19:52 +09:00
data Invite u = Invite
{ inviteInstrument :: Either Role ( ObjURI u )
, inviteObject :: ObjURI u
, inviteTarget :: ObjURI u
}
parseInvite :: UriMode u => Object -> Parser ( Invite u )
parseInvite o =
Invite
<$> o .:+ " instrument "
<*> o .: " object "
<*> o .: " target "
encodeInvite :: UriMode u => Invite u -> Series
encodeInvite ( Invite obj context target )
= " object " .=+ obj
2022-07-26 02:15:22 +09:00
<> " context " .= context
<> " target " .= target
2022-11-15 00:11:25 +09:00
data Join u = Join
{ joinInstrument :: Either Role ( ObjURI u )
, joinObject :: ObjURI u
}
parseJoin :: UriMode u => Object -> Parser ( Join u )
parseJoin o =
Join
<$> o .:+ " instrument "
<*> o .: " object "
encodeJoin :: UriMode u => Join u -> Series
encodeJoin ( Join obj context )
= " object " .=+ obj
<> " context " .= context
2020-06-18 19:38:04 +09:00
data OfferObject u = OfferTicket ( Ticket u ) | OfferDep ( TicketDependency u )
instance ActivityPub OfferObject where
jsonldContext = error " jsonldContext OfferObject "
parseObject o
= second OfferTicket <$> parseObject o
<|> second OfferDep <$> parseObject o
toSeries h ( OfferTicket t ) = toSeries h t
toSeries h ( OfferDep d ) = toSeries h d
2019-07-23 22:59:48 +09:00
data Offer u = Offer
2020-06-18 19:38:04 +09:00
{ offerObject :: OfferObject u
2019-07-23 22:59:48 +09:00
, offerTarget :: ObjURI u
2019-06-06 23:16:48 +09:00
}
2019-07-23 22:59:48 +09:00
parseOffer :: UriMode u => Object -> Authority u -> LocalURI -> Parser ( Offer u )
parseOffer o a luActor = do
2020-06-18 19:38:04 +09:00
obj <- withAuthorityT a $ parseObject =<< o .: " object "
2019-07-23 22:59:48 +09:00
target @ ( ObjURI hTarget luTarget ) <- o .: " target "
2020-06-18 19:38:04 +09:00
case obj of
OfferTicket ticket -> do
unless ( luActor == ticketAttributedTo ticket ) $
fail " Offer actor != Ticket attrib "
for_ ( ticketContext ticket ) $ \ ( ObjURI hContext luContext ) -> do
unless ( hTarget == hContext ) $
fail " Offer target host != Ticket context host "
unless ( luTarget == luContext ) $
fail " Offer target != Ticket context "
OfferDep dep -> do
unless ( luActor == ticketDepAttributedTo dep ) $
fail " Offer actor != TicketDependency attrib "
return $ Offer obj target
2019-06-06 23:16:48 +09:00
2019-07-23 22:59:48 +09:00
encodeOffer :: UriMode u => Authority u -> LocalURI -> Offer u -> Series
encodeOffer authority actor ( Offer obj target )
= " object " ` pair ` pairs ( toSeries authority obj )
2019-06-06 23:16:48 +09:00
<> " target " .= target
2019-08-29 00:31:40 +09:00
data Push u = Push
2019-09-09 09:27:45 +09:00
{ pushCommitsLast :: NonEmpty ( Commit u )
, pushCommitsFirst :: Maybe ( NonEmpty ( Commit u ) )
2019-08-29 00:31:40 +09:00
, pushCommitsTotal :: Int
2022-10-26 19:47:38 +09:00
, pushTarget :: Either LocalURI ( Branch u )
, pushAttrib :: ObjURI u
2019-09-09 09:27:45 +09:00
, pushHashBefore :: Maybe Text
2019-10-11 01:41:34 +09:00
, pushHashAfter :: Maybe Text
2019-08-29 00:31:40 +09:00
}
parsePush :: UriMode u => Authority u -> Object -> Parser ( Push u )
parsePush a o = do
c <- o .: " object "
Push
2019-11-07 05:46:21 +09:00
<$> ( traverse ( withAuthorityT a . parseObject ) =<< c .: " items " <|> c .: " orderedItems " )
2019-09-09 09:27:45 +09:00
<*> ( traverse ( traverse $ withAuthorityT a . parseObject ) =<< c .:? " earlyItems " )
2019-08-29 00:31:40 +09:00
<*> c .: " totalItems "
2022-10-26 19:47:38 +09:00
<*> ( do target <- o .:+ " target "
let ( h , target' ) =
case target of
Left ( ObjURI h lu ) -> ( h , Left lu )
Right ( Doc h branch ) -> ( h , Right branch )
unless ( h == a ) $ fail " target host != Push host "
return target'
)
<*> o .: " attributedTo "
2019-09-09 09:27:45 +09:00
<*> o .:? " hashBefore "
2019-10-11 01:41:34 +09:00
<*> o .:? " hashAfter "
2019-08-29 00:31:40 +09:00
encodePush :: UriMode u => Authority u -> Push u -> Series
2022-10-26 19:47:38 +09:00
encodePush a ( Push lateCommits earlyCommits total target attrib before after )
= " object " ` pair ` pairs
2019-08-29 00:31:40 +09:00
( " type " .= ( " OrderedCollection " :: Text )
2019-11-07 05:46:21 +09:00
<> pair " orderedItems " ( objectList lateCommits )
2019-09-09 09:27:45 +09:00
<> maybe mempty ( pair " earlyItems " . objectList ) earlyCommits
2019-08-29 00:31:40 +09:00
<> " totalItems " .= total
)
2022-10-26 19:47:38 +09:00
<> " target " .=+ bimap ( ObjURI a ) ( Doc a ) target
<> " attributedTo " .= attrib
<> " hashBefore " .=? before
<> " hashAfter " .=? after
2019-09-09 09:27:45 +09:00
where
objectList items = listEncoding ( pairs . toSeries a ) ( NE . toList items )
2019-08-29 00:31:40 +09:00
2019-07-23 22:59:48 +09:00
data Reject u = Reject
{ rejectObject :: ObjURI u
2019-03-14 08:37:58 +09:00
}
2019-07-23 22:59:48 +09:00
parseReject :: UriMode u => Object -> Parser ( Reject u )
2019-03-14 08:37:58 +09:00
parseReject o = Reject <$> o .: " object "
2019-07-23 22:59:48 +09:00
encodeReject :: UriMode u => Reject u -> Series
2019-03-14 08:37:58 +09:00
encodeReject ( Reject obj ) = " object " .= obj
2020-07-23 23:27:11 +09:00
data Resolve u = Resolve
{ resolveObject :: ObjURI u
}
parseResolve :: UriMode u => Object -> Parser ( Resolve u )
parseResolve o = Resolve <$> o .: " object "
encodeResolve :: UriMode u => Resolve u -> Series
encodeResolve ( Resolve obj ) = " object " .= obj
2019-09-25 19:43:05 +09:00
data Undo u = Undo
2020-08-05 17:28:58 +09:00
{ undoObject :: ObjURI u
2019-09-25 19:43:05 +09:00
}
parseUndo :: UriMode u => Authority u -> Object -> Parser ( Undo u )
2020-08-05 17:28:58 +09:00
parseUndo a o = Undo <$> o .: " object "
2019-09-25 19:43:05 +09:00
encodeUndo :: UriMode u => Authority u -> Undo u -> Series
2020-08-05 17:28:58 +09:00
encodeUndo a ( Undo obj ) = " object " .= obj
2019-09-25 19:43:05 +09:00
2023-05-30 15:48:21 +09:00
data ProofConfig u = ProofConfig
{ proofKey :: LocalRefURI
, proofCreated :: UTCTime
}
instance ActivityPub ProofConfig where
jsonldContext _ = []
parseObject o = do
typ <- o .: " type "
guard $ typ == ( " DataIntegrityProof " :: Text )
purpose <- o .: " proofPurpose "
guard $ purpose == ( " assertionMethod " :: Text )
suite <- o .: " cryptosuite "
guard $ suite == ( " jcs-eddsa-2022 " :: Text )
RefURI h lruKey <- o .: " verificationMethod "
fmap ( h , ) $ ProofConfig
<$> pure lruKey
<*> o .: " created "
toSeries h ( ProofConfig lruKey created )
= " type " .= ( " DataIntegrityProof " :: Text )
<> " proofPurpose " .= ( " assertionMethod " :: Text )
<> " cryptosuite " .= ( " jcs-eddsa-2022 " :: Text )
<> " verificationMethod " .= RefURI h lruKey
<> " created " .= created
data Proof u = Proof
{ proofConfig :: ProofConfig u
, proofValue :: ByteString
}
instance ActivityPub Proof where
jsonldContext _ = []
parseObject o = do
( h , config ) <- parseObject o
value <- do
t <- o .: " proofValue "
t58 <-
case T . uncons t of
Just ( 'z' , t' ) -> return t'
_ -> fail $ " No multibase 'z' prefix: " ++ T . unpack t
let b = TE . encodeUtf8 t58
case B58 . decodeBase58 B58 . bitcoinAlphabet b of
Nothing ->
fail $ " base58-btc decoding failed: " ++ T . unpack t
Just val -> return val
return ( h , Proof config value )
toSeries h ( Proof config sig )
= toSeries h config
<> " proofValue " .=
T . cons 'z' ( TE . decodeUtf8 $ B58 . encodeBase58 B58 . bitcoinAlphabet sig )
2019-07-23 22:59:48 +09:00
data SpecificActivity u
2022-06-23 18:09:02 +09:00
= AcceptActivity ( Accept u )
2020-09-10 19:57:02 +09:00
| AddActivity ( Add u )
2022-06-23 18:09:02 +09:00
| ApplyActivity ( Apply u )
| CreateActivity ( Create u )
| FollowActivity ( Follow u )
2022-07-26 02:15:22 +09:00
| GrantActivity ( Grant u )
2022-09-06 01:19:52 +09:00
| InviteActivity ( Invite u )
2022-11-15 00:11:25 +09:00
| JoinActivity ( Join u )
2022-06-23 18:09:02 +09:00
| OfferActivity ( Offer u )
| PushActivity ( Push u )
| RejectActivity ( Reject u )
2020-07-23 23:27:11 +09:00
| ResolveActivity ( Resolve u )
2022-06-23 18:09:02 +09:00
| UndoActivity ( Undo u )
2019-03-14 08:37:58 +09:00
2023-04-29 19:40:44 +09:00
activityType :: SpecificActivity u -> Text
activityType ( AcceptActivity _ ) = " Accept "
activityType ( AddActivity _ ) = " Add "
activityType ( ApplyActivity _ ) = " Apply "
activityType ( CreateActivity _ ) = " Create "
activityType ( FollowActivity _ ) = " Follow "
activityType ( GrantActivity _ ) = " Grant "
activityType ( InviteActivity _ ) = " Invite "
activityType ( JoinActivity _ ) = " Join "
activityType ( OfferActivity _ ) = " Offer "
activityType ( PushActivity _ ) = " Push "
activityType ( RejectActivity _ ) = " Reject "
activityType ( ResolveActivity _ ) = " Resolve "
activityType ( UndoActivity _ ) = " Undo "
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
data Action u = Action
{ actionCapability :: Maybe ( ObjURI u )
, actionSummary :: Maybe HTML
, actionAudience :: Audience u
, actionFulfills :: [ ObjURI u ]
, actionSpecific :: SpecificActivity u
}
makeActivity :: LocalURI -> LocalURI -> Action u -> Activity u
makeActivity luId luActor Action { .. } = Activity
{ activityId = Just luId
, activityActor = luActor
, activityCapability = actionCapability
, activitySummary = actionSummary
, activityAudience = actionAudience
, activityFulfills = actionFulfills
2023-05-30 15:48:21 +09:00
, activityProof = Nothing
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
, activitySpecific = actionSpecific
}
2019-07-23 22:59:48 +09:00
data Activity u = Activity
2022-06-22 16:39:38 +09:00
{ activityId :: Maybe LocalURI
, activityActor :: LocalURI
, activityCapability :: Maybe ( ObjURI u )
2022-09-21 21:50:26 +09:00
, activitySummary :: Maybe HTML
2022-06-22 16:39:38 +09:00
, activityAudience :: Audience u
2022-08-16 23:55:21 +09:00
, activityFulfills :: [ ObjURI u ]
2023-05-30 15:48:21 +09:00
, activityProof :: Maybe ( Proof u )
2022-06-22 16:39:38 +09:00
, activitySpecific :: SpecificActivity u
2019-03-14 08:37:58 +09:00
}
2019-02-12 20:53:24 +09:00
2019-03-10 15:42:03 +09:00
instance ActivityPub Activity where
2022-07-25 18:10:24 +09:00
jsonldContext _ = [ as2Context , forgeContext ]
2019-03-10 15:42:03 +09:00
parseObject o = do
2019-07-23 22:59:48 +09:00
ObjURI a actor <- o .: " actor "
fmap ( a , ) $
2019-06-19 17:53:31 +09:00
Activity
2019-07-23 22:59:48 +09:00
<$> withAuthorityMaybeO a ( o .:? " id " )
2019-06-19 17:53:31 +09:00
<*> pure actor
2022-06-22 16:39:38 +09:00
<*> o .:? " capability "
2022-09-21 21:50:26 +09:00
<*> o .:? " summary "
2019-06-16 01:24:34 +09:00
<*> parseAudience o
2022-08-16 23:55:21 +09:00
<*> o .:? " fulfills " .!= []
2023-05-30 15:48:21 +09:00
<*> ( do mp <- o .:? " proof "
for mp $ withAuthorityT a . parseObject
)
2019-03-14 11:30:36 +09:00
<*> do
typ <- o .: " type "
case typ of
2020-07-23 23:27:11 +09:00
" Accept " -> AcceptActivity <$> parseAccept a o
2020-09-10 19:57:02 +09:00
" Add " -> AddActivity <$> parseAdd o a
2022-06-23 18:09:02 +09:00
" Apply " -> ApplyActivity <$> parseApply o
2020-07-23 23:27:11 +09:00
" Create " -> CreateActivity <$> parseCreate o a actor
" Follow " -> FollowActivity <$> parseFollow o
2023-05-29 15:50:17 +09:00
" Grant " -> GrantActivity <$> parseGrant a o
2022-09-06 01:19:52 +09:00
" Invite " -> InviteActivity <$> parseInvite o
2022-11-15 00:11:25 +09:00
" Join " -> JoinActivity <$> parseJoin o
2020-07-23 23:27:11 +09:00
" Offer " -> OfferActivity <$> parseOffer o a actor
" Push " -> PushActivity <$> parsePush a o
" Reject " -> RejectActivity <$> parseReject o
" Resolve " -> ResolveActivity <$> parseResolve o
" Undo " -> UndoActivity <$> parseUndo a o
2019-03-14 11:30:36 +09:00
_ ->
fail $
" Unrecognized activity type: " ++ T . unpack typ
2023-05-30 15:48:21 +09:00
toSeries authority ( Activity id_ actor mcap summary audience fulfills mproof specific )
2022-06-22 16:39:38 +09:00
= " type " .= activityType specific
<> " id " .=? ( ObjURI authority <$> id_ )
<> " actor " .= ObjURI authority actor
<> " capability " .=? mcap
<> " summary " .=? summary
2019-03-14 11:30:36 +09:00
<> encodeAudience audience
2022-08-16 23:55:21 +09:00
<> " fulfills " .=% fulfills
2023-05-30 15:48:21 +09:00
<> " proof " .=? ( Doc authority <$> mproof )
2019-07-23 22:59:48 +09:00
<> encodeSpecific authority actor specific
2019-03-14 08:37:58 +09:00
where
2020-07-23 23:27:11 +09:00
encodeSpecific h _ ( AcceptActivity a ) = encodeAccept h a
2020-09-10 19:57:02 +09:00
encodeSpecific h _ ( AddActivity a ) = encodeAdd h a
2022-06-23 18:09:02 +09:00
encodeSpecific _ _ ( ApplyActivity a ) = encodeApply a
2022-07-25 01:52:28 +09:00
encodeSpecific _ _ ( CreateActivity a ) = encodeCreate a
2020-07-23 23:27:11 +09:00
encodeSpecific _ _ ( FollowActivity a ) = encodeFollow a
2023-05-29 15:50:17 +09:00
encodeSpecific h _ ( GrantActivity a ) = encodeGrant h a
2022-09-06 01:19:52 +09:00
encodeSpecific _ _ ( InviteActivity a ) = encodeInvite a
2022-11-15 00:11:25 +09:00
encodeSpecific _ _ ( JoinActivity a ) = encodeJoin a
2020-07-23 23:27:11 +09:00
encodeSpecific h u ( OfferActivity a ) = encodeOffer h u a
encodeSpecific h _ ( PushActivity a ) = encodePush h a
encodeSpecific _ _ ( RejectActivity a ) = encodeReject a
encodeSpecific _ _ ( ResolveActivity a ) = encodeResolve a
encodeSpecific h _ ( UndoActivity a ) = encodeUndo h a
2019-01-22 00:54:57 +09:00
2020-05-02 02:48:01 +09:00
emptyAudience :: Audience u
emptyAudience = Audience [] [] [] [] [] []
2020-02-03 23:53:12 +09:00
emptyActivity :: Activity u
emptyActivity = Activity
2022-06-22 16:39:38 +09:00
{ activityId = Nothing
, activityActor = topLocalURI
, activityCapability = Nothing
, activitySummary = Nothing
, activityAudience = emptyAudience
2022-08-16 23:55:21 +09:00
, activityFulfills = []
2023-05-30 15:48:21 +09:00
, activityProof = Nothing
2022-06-22 16:39:38 +09:00
, activitySpecific =
2020-02-03 23:53:12 +09:00
RejectActivity $ Reject $ ObjURI ( Authority " " Nothing ) topLocalURI
}
2019-01-22 00:54:57 +09:00
typeActivityStreams2 :: ContentType
typeActivityStreams2 = " application/activity+json "
typeActivityStreams2LD :: ContentType
typeActivityStreams2LD =
" application/ld+json; profile= \ " https://www.w3.org/ns/activitystreams \ " "
2019-02-07 19:34:33 +09:00
hActivityPubActor :: HeaderName
hActivityPubActor = " ActivityPub-Actor "
2019-03-20 21:01:10 +09:00
provideAP :: ( Monad m , ToJSON a ) => m a -> Writer ( Endo [ ProvidedRep m ] ) ()
provideAP mk =
-- let enc = toEncoding v
2019-01-22 00:54:57 +09:00
-- provideRepType typeActivityStreams2 $ return enc
2019-03-20 21:01:10 +09:00
provideRepType typeActivityStreams2LD $ toEncoding <$> mk
2019-01-22 00:54:57 +09:00
2019-07-01 01:53:53 +09:00
provideAP' :: Monad m => m ByteString -> Writer ( Endo [ ProvidedRep m ] ) ()
2019-06-29 08:15:08 +09:00
provideAP' = provideRepType typeActivityStreams2LD
2019-01-22 00:54:57 +09:00
data APGetError
= APGetErrorHTTP HttpException
| APGetErrorJSON JSONException
2019-01-22 07:24:09 +09:00
| APGetErrorContentType Text
2019-01-22 00:54:57 +09:00
deriving Show
instance Exception APGetError
-- | Perform an HTTP GET request to fetch an ActivityPub object.
--
-- * Verify the URI scheme is _https:_ and authority part is present
-- * Set _Accept_ request header
-- * Perform the GET request
-- * Verify the _Content-Type_ response header
-- * Parse the JSON response body
httpGetAP
2019-07-23 22:59:48 +09:00
:: ( MonadIO m , UriMode u , FromJSON a )
2019-01-22 00:54:57 +09:00
=> Manager
2019-07-23 22:59:48 +09:00
-> Either ( ObjURI u ) ( SubURI u )
2019-01-22 00:54:57 +09:00
-> m ( Either APGetError ( Response a ) )
httpGetAP manager uri =
2019-02-08 08:08:28 +09:00
liftIO $
mkResult <$> try ( httpAPEither manager =<< requestFromURI ( toURI uri ) )
2019-01-22 00:54:57 +09:00
where
2019-07-23 22:59:48 +09:00
toURI = either uriFromObjURI uriFromSubURI
2019-01-22 00:54:57 +09:00
lookup' x = map snd . filter ( ( == x ) . fst )
mkResult ( Left e ) = Left $ APGetErrorHTTP e
mkResult ( Right r ) =
case lookup' hContentType $ responseHeaders r of
[] -> Left $ APGetErrorContentType " No Content-Type "
[ b ] -> if b == typeActivityStreams2LD || b == typeActivityStreams2
then case responseBody r of
Left e -> Left $ APGetErrorJSON e
Right v -> Right $ v <$ r
2019-01-22 07:24:09 +09:00
else Left $ APGetErrorContentType $ " Non-AP Content-Type: " <> decodeUtf8 b
2019-01-22 00:54:57 +09:00
_ -> Left $ APGetErrorContentType " Multiple Content-Type "
2019-03-05 17:26:41 +09:00
data APPostError
2019-03-11 08:15:42 +09:00
= APPostErrorSig S . HttpSigGenError
2019-03-05 17:26:41 +09:00
| APPostErrorHTTP HttpException
deriving Show
instance Exception APPostError
2019-04-28 19:18:50 +09:00
hActivityPubForwarder :: HeaderName
hActivityPubForwarder = " ActivityPub-Forwarder "
2019-04-26 09:25:50 +09:00
2019-04-28 19:18:50 +09:00
hForwardingSignature :: HeaderName
hForwardingSignature = " Forwarding-Signature "
2019-04-26 09:25:50 +09:00
2019-04-28 19:18:50 +09:00
hForwardedSignature :: HeaderName
hForwardedSignature = " Forwarded-Signature "
2019-04-26 09:25:50 +09:00
2019-01-22 00:54:57 +09:00
-- | Perform an HTTP POST request to submit an ActivityPub object.
--
-- * Verify the URI scheme is _https:_ and authority part is present
-- * Set _Content-Type_ request header
2019-02-07 19:34:33 +09:00
-- * Set _ActivityPub-Actor_ request header
2019-04-26 00:49:15 +09:00
-- * Set _Digest_ request header using SHA-256 hash
2019-04-28 19:18:50 +09:00
-- * If recipient is given, set _ActivityPub-Forwarder_ header and compute
-- _Forwarding-Signature_ header
-- * If forwarded signature is given, set set _ActivityPub-Forwarder_ and
-- _Forwarded-Signature_ headers
2019-01-22 00:54:57 +09:00
-- * Compute HTTP signature and add _Signature_ request header
-- * Perform the POST request
-- * Verify the response status is 2xx
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
{-
2019-01-22 00:54:57 +09:00
httpPostAP
2019-07-23 22:59:48 +09:00
:: ( MonadIO m , UriMode u , ToJSON a )
2019-01-22 00:54:57 +09:00
=> Manager
-> NonEmpty HeaderName
2019-04-26 09:25:50 +09:00
-> S . KeyId
-> ( ByteString -> S . Signature )
2019-02-07 19:34:33 +09:00
-> Text
2019-01-22 00:54:57 +09:00
-> a
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
-> ObjURI u
-> Maybe ( Either ( ObjURI u ) ByteString )
2019-03-05 17:26:41 +09:00
-> m ( Either APPostError ( Response () ) )
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
httpPostAP manager headers keyid sign uSender value =
httpPostAPBytes manager headers keyid sign uSender $ encode value
- }
data ForwardMode u
= SendNoForward
| SendAllowForward LocalURI
| ForwardBy ( ObjURI u ) ByteString
data Envelope u = Envelope
{ envelopeKey :: RefURI u
, envelopeSign :: ByteString -> S . Signature
, envelopeHolder :: Maybe LocalURI
, envelopeBody :: BL . ByteString
}
data Errand u = Errand
{ errandKey :: RefURI u
, errandSign :: ByteString -> S . Signature
, errandHolder :: Bool
, errandFwder :: LocalURI
, errandBody :: BL . ByteString
, errandProof :: ByteString
}
2019-05-04 06:04:53 +09:00
-- | Like 'httpPostAP', except it takes the object as a raw lazy
-- 'BL.ByteString'. It's your responsibility to make sure it's valid JSON.
httpPostAPBytes
2019-07-23 22:59:48 +09:00
:: ( MonadIO m , UriMode u )
2019-05-04 06:04:53 +09:00
=> Manager
-> NonEmpty HeaderName
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
-> RefURI u
2019-05-04 06:04:53 +09:00
-> ( ByteString -> S . Signature )
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
-> Maybe LocalURI
2019-05-04 06:04:53 +09:00
-> BL . ByteString
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
-> ForwardMode u
-> ObjURI u
2019-05-04 06:04:53 +09:00
-> m ( Either APPostError ( Response () ) )
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
httpPostAPBytes manager headers ruKey @ ( RefURI hKey _ ) sign mluHolder body fwd uInbox @ ( ObjURI hInbox _ ) =
2019-04-28 19:18:50 +09:00
liftIO $ runExceptT $ do
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
req <- requestFromURI $ uriFromObjURI uInbox
2019-05-04 06:04:53 +09:00
let digest = formatHttpBodyDigest SHA256 " SHA-256 " $ hashlazy body
2019-04-28 19:18:50 +09:00
req' =
setRequestCheckStatus $
consHeader hContentType typeActivityStreams2LD $
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
maybe id ( consHeader hActivityPubActor . TE . encodeUtf8 . renderObjURI . ObjURI hKey ) mluHolder $
2019-04-28 19:18:50 +09:00
consHeader hDigest digest $
req { method = " POST "
, requestBody = RequestBodyLBS body
}
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
keyid = S . KeyId $ TE . encodeUtf8 $ renderRefURI ruKey
now <- lift getCurrentTime
req'' <- except $ first APPostErrorSig $ signRequest headers Nothing keyid sign now req'
2019-04-28 19:18:50 +09:00
req''' <-
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
case fwd of
SendNoForward -> return req''
SendAllowForward luRecip ->
except $ first APPostErrorSig $
signRequestInto hForwardingSignature ( hDigest :| [ hActivityPubForwarder ] ) Nothing keyid sign now $
consHeader hActivityPubForwarder ( encodeUtf8 $ renderObjURI $ ObjURI hInbox luRecip ) req''
ForwardBy uSender sig ->
2019-04-28 19:18:50 +09:00
return $
consHeader hForwardedSignature sig $
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
consHeader hActivityPubForwarder ( encodeUtf8 $ renderObjURI uSender )
2019-04-28 19:18:50 +09:00
req''
tryExceptT APPostErrorHTTP $ httpNoBody req''' manager
2019-01-22 00:54:57 +09:00
where
consHeader n b r = r { requestHeaders = ( n , b ) : requestHeaders r }
2019-04-28 19:18:50 +09:00
tryExceptT adapt action = ExceptT $ first adapt <$> try action
2019-02-04 08:39:56 +09:00
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
sending
:: UriMode u
=> LocalRefURI
-> ( ByteString -> S . Signature )
2023-05-30 15:48:21 +09:00
-> Maybe ( ProofConfig u , ByteString -> ByteString )
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
-> Bool
-> ObjURI u
-> LocalURI
-> Action u
-> Envelope u
2023-05-30 15:48:21 +09:00
sending lruKey sign mprove holder uActor @ ( ObjURI hActor luActor ) luId action =
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
Envelope
{ envelopeKey = RefURI hActor lruKey
, envelopeSign = sign
, envelopeHolder = guard holder >> Just luActor
2023-05-30 15:48:21 +09:00
, envelopeBody =
let act = makeActivity luId luActor action
lb = encode $ Doc hActor act
in case mprove of
Nothing -> lb
Just ( config , prove ) ->
let configLB = encode $ Doc hActor config
configHash = hashWith SHA256 $ BL . toStrict configLB
bodyHash = hashWith SHA256 $ BL . toStrict lb
input = BA . convert configHash ` B . append ` BA . convert bodyHash
proof = Proof config ( prove input )
actWithProof = act { activityProof = Just proof }
in encode $ Doc hActor actWithProof
Improve the AP async HTTP delivery API and per-actor key support
New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:
* When sending a ByteString body, the sender is explicitly passed as a
parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
handler code more concise and clear
Also added in this patch:
* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
builder functions
Caveats:
* Actor documents still don't link to their per-actor keys; that should be the
last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
activity is separately encoded into bytestring for DB and for HTTP; this will
be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
simplifies the code, but may be changed for efficiency/robustness in the next
iterations
* Periodic delivery collects per-actor keys in a
1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
big Esqueleto query (or keeping the signed output in the DB; this isn't done
currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
fetched, rather than doing a DB transaction to grab it; such support would be
just an optimization, so it's low-priority, but will be added in later
iterations
2022-10-13 01:50:11 +09:00
}
retrying
:: RefURI u
-> ( ByteString -> S . Signature )
-> Maybe LocalURI
-> BL . ByteString
-> Envelope u
retrying = Envelope
forwarding
:: LocalRefURI
-> ( ByteString -> S . Signature )
-> Bool
-> ObjURI u
-> BL . ByteString
-> ByteString
-> Errand u
forwarding lruKey sign holder ( ObjURI hFwder luFwder ) body sig =
Errand
{ errandKey = RefURI hFwder lruKey
, errandSign = sign
, errandHolder = holder
, errandFwder = luFwder
, errandBody = body
, errandProof = sig
}
deliver
:: ( MonadIO m , UriMode u )
=> Manager
-> NonEmpty HeaderName
-> Envelope u
-> Maybe LocalURI
-> ObjURI u
-> m ( Either APPostError ( Response () ) )
deliver manager headers ( Envelope ruKey sign mluHolder body ) mluFwd uInbox =
httpPostAPBytes
manager
headers
ruKey
sign
mluHolder
body
( maybe SendNoForward SendAllowForward mluFwd )
uInbox
forward
:: ( MonadIO m , UriMode u )
=> Manager
-> NonEmpty HeaderName
-> Errand u
-> ObjURI u
-> m ( Either APPostError ( Response () ) )
forward manager headers ( Errand ruKey @ ( RefURI hKey _ ) sign holder luFwder body sig ) uInbox =
httpPostAPBytes
manager
headers
ruKey
sign
( guard holder >> Just luFwder )
body
( ForwardBy ( ObjURI hKey luFwder ) sig )
uInbox
2019-02-06 11:48:23 +09:00
-- | Result of GETing the keyId URI and processing the JSON document.
data Fetched = Fetched
2020-04-11 23:58:38 +09:00
{ fetchedPublicKey :: PublicVerifKey
2019-03-11 08:15:42 +09:00
-- ^ The Ed25519 or RSA public key corresponding to the URI we requested.
2020-04-11 23:58:38 +09:00
, fetchedKeyExpires :: Maybe UTCTime
2019-02-06 11:48:23 +09:00
-- ^ Optional expiration time declared for the key we received.
2020-04-11 23:58:38 +09:00
, fetchedActorId :: LocalURI
2019-02-06 11:48:23 +09:00
-- ^ The @id URI of the actor for whom the key's signature applies.
2020-04-11 23:58:38 +09:00
, fetchedActorName :: Maybe Text
2019-05-21 17:44:11 +09:00
-- ^ Name of the actor for whom the key's signature applies.
2020-04-11 23:58:38 +09:00
, fetchedActorInbox :: LocalURI
2019-02-15 08:27:40 +09:00
-- ^ The inbox URI of the actor for whom the key's signature applies.
2020-04-11 23:58:38 +09:00
, fetchedActorFollowers :: Maybe LocalURI
-- ^ The follower collection URI of the actor for whom the key's
-- signature applies.
, fetchedKeyShared :: Bool
2019-02-06 11:48:23 +09:00
-- ^ Whether the key we received is shared. A shared key can sign
-- requests for any actor on the same instance, while a personal key is
-- only for one actor. Knowing whether the key is shared will allow us
-- when receiving more requests, whether to accept signatures made on
-- different actors, or allow only a single permanent actor for the key
-- we received.
}
2019-07-23 22:59:48 +09:00
fetchAP' :: ( MonadIO m , UriMode u , FromJSON a ) => Manager -> Either ( ObjURI u ) ( SubURI u ) -> ExceptT APGetError m a
2019-04-16 23:27:50 +09:00
fetchAP' m u = ExceptT $ second responseBody <$> httpGetAP m u
2019-07-23 22:59:48 +09:00
fetchAP :: ( MonadIO m , UriMode u , FromJSON a ) => Manager -> Either ( ObjURI u ) ( SubURI u ) -> ExceptT String m a
2019-04-16 23:27:50 +09:00
fetchAP m u = withExceptT displayException $ fetchAP' m u
2019-02-22 08:59:53 +09:00
2022-09-25 06:15:40 +09:00
fetchAP_T :: ( MonadIO m , UriMode u , FromJSON a ) => Manager -> Either ( ObjURI u ) ( SubURI u ) -> ExceptT Text m a
fetchAP_T m u = withExceptT T . pack $ fetchAP m u
2019-02-24 02:17:52 +09:00
{-
2019-02-22 08:59:53 +09:00
fetchAPH :: ( MonadIO m , ActivityPub a ) => Manager -> Text -> LocalURI -> ExceptT String m a
fetchAPH m h lu = do
Doc h' v <- fetchAP m $ l2f h lu
if h == h'
then return v
else throwE " Object @id URI's host doesn't match the URI we fetched "
2019-02-24 02:17:52 +09:00
- }
2019-02-22 08:59:53 +09:00
2019-07-23 22:59:48 +09:00
fetchAPID' :: ( MonadIO m , UriMode u , ActivityPub a ) => Manager -> ( a u -> LocalURI ) -> Authority u -> LocalURI -> m ( Either ( Maybe APGetError ) ( a u ) )
2019-04-16 23:27:50 +09:00
fetchAPID' m getId h lu = runExceptT $ do
2019-07-23 22:59:48 +09:00
Doc h' v <- withExceptT Just $ fetchAP' m $ Left $ ObjURI h lu
2019-02-22 08:59:53 +09:00
if h == h' && getId v == lu
then return v
2019-04-16 23:27:50 +09:00
else throwE Nothing
2022-09-21 21:50:26 +09:00
fetchTip :: ( MonadIO m , UriMode u ) => Manager -> Authority u -> LocalURI -> m ( Either ( Maybe APGetError ) ( Either ( Repo u ) ( Branch u ) ) )
fetchTip m h lu = runExceptT $ do
tip <- fmap toEither $ withExceptT Just $ fetchAP' m $ Left $ ObjURI h lu
bitraverse
( \ ( Doc h' repo ) ->
if h == h' && actorId ( actorLocal $ repoActor repo ) == lu
then return repo
else throwE Nothing
)
( \ ( Doc _ branch ) -> pure branch )
tip
2019-07-23 22:59:48 +09:00
fetchRecipient :: ( MonadIO m , UriMode u ) => Manager -> Authority u -> LocalURI -> m ( Either ( Maybe APGetError ) ( Recipient u ) )
2019-05-18 07:42:01 +09:00
fetchRecipient m = fetchAPID' m getId
where
2022-07-25 01:52:28 +09:00
getId ( RecipientActor a ) = actorId $ actorLocal a
2019-05-18 07:42:01 +09:00
getId ( RecipientCollection c ) = collectionId c
2022-08-28 22:51:43 +09:00
fetchResource :: ( MonadIO m , UriMode u ) => Manager -> Authority u -> LocalURI -> m ( Either ( Maybe APGetError ) ( Resource u ) )
fetchResource m = fetchAPID' m getId
where
getId ( ResourceActor a ) = actorId $ actorLocal a
getId ( ResourceChild luId _ ) = luId
2019-07-23 22:59:48 +09:00
fetchAPID :: ( MonadIO m , UriMode u , ActivityPub a ) => Manager -> ( a u -> LocalURI ) -> Authority u -> LocalURI -> m ( Either String ( a u ) )
2019-04-16 23:27:50 +09:00
fetchAPID m getId h lu = first showError <$> fetchAPID' m getId h lu
where
showError Nothing = " Object @id doesn't match the URI we fetched "
showError ( Just e ) = displayException e
2019-02-22 08:59:53 +09:00
2019-04-17 01:10:17 +09:00
data FetchAPError
= FetchAPErrorGet APGetError
-- Object @id doesn't match the URI we fetched
| FetchAPErrorIdMismatch
-- Object @id URI's host doesn't match the URI we fetched
| FetchAPErrorHostMismatch
deriving Show
fetchAPIDOrH'
2019-07-23 22:59:48 +09:00
:: ( MonadIO m , UriMode u , ActivityPub a , ActivityPub b )
2019-02-22 08:59:53 +09:00
=> Manager
2019-07-23 22:59:48 +09:00
-> ( a u -> LocalRefURI )
-> Authority u
-> LocalRefURI
-> ExceptT FetchAPError m ( Either ( a u ) ( b u ) )
fetchAPIDOrH' m getId h ( LocalRefURI lu ) = do
e <- withExceptT FetchAPErrorGet $ fetchAP' m $ bimap ( ObjURI h ) ( SubURI h ) lu
2019-02-22 08:59:53 +09:00
case e of
Left' ( Doc h' x ) ->
2019-07-23 22:59:48 +09:00
if h == h' && getId x == LocalRefURI lu
2019-02-22 08:59:53 +09:00
then return $ Left x
2019-04-17 01:10:17 +09:00
else throwE FetchAPErrorIdMismatch
2019-02-22 08:59:53 +09:00
Right' ( Doc h' y ) ->
if h == h'
then return $ Right y
2019-04-17 01:10:17 +09:00
else throwE FetchAPErrorHostMismatch
fetchAPIDOrH
2019-07-23 22:59:48 +09:00
:: ( MonadIO m , UriMode u , ActivityPub a , ActivityPub b )
2019-04-17 01:10:17 +09:00
=> Manager
2019-07-23 22:59:48 +09:00
-> ( a u -> LocalRefURI )
-> Authority u
-> LocalRefURI
-> ExceptT String m ( Either ( a u ) ( b u ) )
2019-04-17 01:10:17 +09:00
fetchAPIDOrH m getId h lu = withExceptT show $ fetchAPIDOrH' m getId h lu
2019-02-22 08:59:53 +09:00
When verifying HTTP sig with known shared key, verify actor lists the key
Previously, when verifying an HTTP signature and we fetched the key and
discovered it's shared, we'd fetch the actor and make sure it lists the key URI
in the `publicKey` field. But if we already knew the key, had it cached in our
DB, we wouldn't check the actor at all, despite not knowing whether it lists
the key.
With this patch, we now always GET the actor when the key is shared,
determining the actor URI from the `ActivityPub-Actor` request header, and we
verify that the actor lists the key URI. We do that regardless of whether or
not we have the key in the DB, although these two cases and handled in
different parts of the code right now (for a new key, it's in Web.ActivityPub
fetchKey; for a known key, it's in Vervis.Foundation httpVerifySig).
2019-02-18 18:20:13 +09:00
-- | Fetches the given actor and checks whether it lists the given key (as a
-- URI, not as an embedded object). If it does, returns 'Right' the fetched
-- actor. Otherwise, or if an error occurs during fetching, returns 'Left' an
-- error message.
2019-07-23 22:59:48 +09:00
keyListedByActor
:: ( MonadIO m , UriMode u )
=> Manager
-> Authority u
-> LocalRefURI
-> LocalURI
-> m ( Either String ( Actor u ) )
2019-02-22 08:59:53 +09:00
keyListedByActor manager host luKey luActor = runExceptT $ do
2022-07-25 01:52:28 +09:00
actor <- ExceptT $ fetchAPID manager ( actorId . actorLocal ) host luActor
2019-02-22 08:59:53 +09:00
if keyUriListed luKey actor
When verifying HTTP sig with known shared key, verify actor lists the key
Previously, when verifying an HTTP signature and we fetched the key and
discovered it's shared, we'd fetch the actor and make sure it lists the key URI
in the `publicKey` field. But if we already knew the key, had it cached in our
DB, we wouldn't check the actor at all, despite not knowing whether it lists
the key.
With this patch, we now always GET the actor when the key is shared,
determining the actor URI from the `ActivityPub-Actor` request header, and we
verify that the actor lists the key URI. We do that regardless of whether or
not we have the key in the DB, although these two cases and handled in
different parts of the code right now (for a new key, it's in Web.ActivityPub
fetchKey; for a known key, it's in Vervis.Foundation httpVerifySig).
2019-02-18 18:20:13 +09:00
then return actor
else throwE " Actor publicKey has no URI matching pkey @id "
where
2019-07-23 22:59:48 +09:00
keyUriListed ( LocalRefURI uk ) a =
let match ( Left uri ) = Left uri == uk
When verifying HTTP sig with known shared key, verify actor lists the key
Previously, when verifying an HTTP signature and we fetched the key and
discovered it's shared, we'd fetch the actor and make sure it lists the key URI
in the `publicKey` field. But if we already knew the key, had it cached in our
DB, we wouldn't check the actor at all, despite not knowing whether it lists
the key.
With this patch, we now always GET the actor when the key is shared,
determining the actor URI from the `ActivityPub-Actor` request header, and we
verify that the actor lists the key URI. We do that regardless of whether or
not we have the key in the DB, although these two cases and handled in
different parts of the code right now (for a new key, it's in Web.ActivityPub
fetchKey; for a known key, it's in Vervis.Foundation httpVerifySig).
2019-02-18 18:20:13 +09:00
match ( Right _ ) = False
2022-07-25 01:52:28 +09:00
in any match $ actorPublicKeys $ actorLocal a
When verifying HTTP sig with known shared key, verify actor lists the key
Previously, when verifying an HTTP signature and we fetched the key and
discovered it's shared, we'd fetch the actor and make sure it lists the key URI
in the `publicKey` field. But if we already knew the key, had it cached in our
DB, we wouldn't check the actor at all, despite not knowing whether it lists
the key.
With this patch, we now always GET the actor when the key is shared,
determining the actor URI from the `ActivityPub-Actor` request header, and we
verify that the actor lists the key URI. We do that regardless of whether or
not we have the key in the DB, although these two cases and handled in
different parts of the code right now (for a new key, it's in Web.ActivityPub
fetchKey; for a known key, it's in Vervis.Foundation httpVerifySig).
2019-02-18 18:20:13 +09:00
2019-07-23 22:59:48 +09:00
matchKeyObj
:: ( Foldable f , Monad m , UriMode u )
=> LocalRefURI
-> f ( Either LocalURI ( PublicKey u ) )
-> ExceptT String m ( PublicKey u )
2019-02-24 10:21:42 +09:00
matchKeyObj luKey es =
case find' ( match luKey ) es of
Nothing -> throwE " keyId resolved to actor which doesn't have a key object with that ID "
Just pk -> return pk
2019-02-24 02:17:52 +09:00
where
2019-02-24 10:21:42 +09:00
find' :: Foldable f => ( a -> Maybe b ) -> f a -> Maybe b
find' p = join . fmap getFirst . foldMap ( Just . First . p )
2019-02-24 02:17:52 +09:00
match _ ( Left _ ) = Nothing
match luk ( Right pk ) =
if publicKeyId pk == luk
then Just pk
else Nothing
2019-03-11 08:15:42 +09:00
verifyAlgo :: Maybe S . Algorithm -> PublicVerifKey -> Either String ()
verifyAlgo Nothing _ = Right ()
verifyAlgo ( Just a ) k =
case a of
S . AlgorithmEd25519 ->
case k of
PublicVerifKeyEd25519 _ -> Right ()
PublicVerifKeyRSA _ ->
Left " Algo mismatch, algo is Ed25519 but actual key is RSA "
S . AlgorithmRsaSha256 ->
case k of
PublicVerifKeyEd25519 _ ->
Left
" Algo mismatch, algo is RSA-SHA256 but actual key is \
\ Ed25519 "
PublicVerifKeyRSA _ -> Right ()
S . AlgorithmOther b -> Left $ concat
[ " Unrecognized algo "
, BC . unpack b
, " , actual key is "
, case k of
PublicVerifKeyEd25519 _ -> " Ed25519 "
PublicVerifKeyRSA _ -> " RSA "
]
2019-02-24 02:17:52 +09:00
-- | Fetch a key we don't have cached locally.
fetchUnknownKey
2019-07-23 22:59:48 +09:00
:: ( MonadIO m , UriMode u )
2019-02-04 08:39:56 +09:00
=> Manager
2019-02-24 02:17:52 +09:00
-- ^ Manager for making HTTP requests
2019-03-11 08:15:42 +09:00
-> Maybe S . Algorithm
-- ^ Signature algorithm possibly specified in the HTTP signature header
2019-07-23 22:59:48 +09:00
-> Authority u
2019-02-24 02:17:52 +09:00
-- ^ Instance host
2019-02-22 08:59:53 +09:00
-> Maybe LocalURI
2019-02-24 02:17:52 +09:00
-- ^ Actor URI possibly provided in the HTTP request's actor header
2019-07-23 22:59:48 +09:00
-> LocalRefURI
2019-02-24 02:17:52 +09:00
-- ^ Key URI provided in HTTP signature header
-> ExceptT String m Fetched
2019-03-11 08:15:42 +09:00
fetchUnknownKey manager malgo host mluActor luKey = do
2019-02-22 08:59:53 +09:00
obj <- fetchAPIDOrH manager publicKeyId host luKey
2019-03-11 08:15:42 +09:00
fetched <-
2019-02-04 08:39:56 +09:00
case obj of
2019-02-22 08:59:53 +09:00
Left pkey -> do
2019-02-22 16:20:19 +09:00
( oi , luActor ) <-
2019-02-22 08:59:53 +09:00
case publicKeyOwner pkey of
OwnerInstance ->
case mluActor of
Nothing -> throwE " Key is shared but actor header not specified! "
2019-02-22 16:20:19 +09:00
Just u -> return ( True , u )
2019-02-22 08:59:53 +09:00
OwnerActor owner -> do
for_ mluActor $ \ lu ->
if owner == lu
2019-02-17 09:14:05 +09:00
then return ()
else throwE " Key's owner doesn't match actor header "
2019-02-22 16:20:19 +09:00
return ( False , owner )
2022-07-25 01:52:28 +09:00
Actor local detail <- ExceptT $ keyListedByActor manager host luKey luActor
2019-03-11 08:15:42 +09:00
return Fetched
2020-04-11 23:58:38 +09:00
{ fetchedPublicKey = publicKeyMaterial pkey
, fetchedKeyExpires = publicKeyExpires pkey
, fetchedActorId = luActor
2022-07-25 01:52:28 +09:00
, fetchedActorName = actorName detail <|> actorUsername detail
, fetchedActorInbox = actorInbox local
, fetchedActorFollowers = actorFollowers local
2020-04-11 23:58:38 +09:00
, fetchedKeyShared = oi
2019-03-11 08:15:42 +09:00
}
2022-07-25 01:52:28 +09:00
Right ( Actor local detail ) -> do
2019-07-23 22:59:48 +09:00
case luKey of
LocalRefURI ( Right lsu ) |
2022-07-25 01:52:28 +09:00
actorId local == localSubUriResource lsu -> return ()
2019-07-23 22:59:48 +09:00
_ -> throwE " Actor ID doesn't match the keyid URI we fetched "
2019-02-22 08:59:53 +09:00
for_ mluActor $ \ lu ->
2022-07-25 01:52:28 +09:00
if actorId local == lu
2019-02-17 09:14:05 +09:00
then return ()
else throwE " Key's owner doesn't match actor header "
2022-07-25 01:52:28 +09:00
pk <- matchKeyObj luKey $ actorPublicKeys local
2019-02-22 17:30:43 +09:00
owner <- case publicKeyOwner pk of
OwnerInstance -> throwE " Actor's publicKey is shared, but embedded in actor document! We allow shared keys only if they're in a separate document "
2019-02-24 10:21:42 +09:00
OwnerActor owner ->
2022-07-25 01:52:28 +09:00
if owner == actorId local
2019-02-22 17:30:43 +09:00
then return owner
else throwE " Actor's publicKey's owner doesn't match the actor's ID "
2019-03-11 08:15:42 +09:00
return Fetched
2020-04-11 23:58:38 +09:00
{ fetchedPublicKey = publicKeyMaterial pk
, fetchedKeyExpires = publicKeyExpires pk
, fetchedActorId = owner
2022-07-25 01:52:28 +09:00
, fetchedActorName = actorName detail <|> actorUsername detail
, fetchedActorInbox = actorInbox local
, fetchedActorFollowers = actorFollowers local
2020-04-11 23:58:38 +09:00
, fetchedKeyShared = False
2019-03-11 08:15:42 +09:00
}
ExceptT . pure $ verifyAlgo malgo $ fetchedPublicKey fetched
return fetched
keyDetail pk = ( publicKeyMaterial pk , publicKeyExpires pk )
2019-02-24 02:17:52 +09:00
-- | Fetch a personal key we already have cached locally, but we'd like to
-- refresh the local copy by fetching the key again from the server.
fetchKnownPersonalKey
2019-07-23 22:59:48 +09:00
:: ( MonadIO m , UriMode u )
2019-02-24 02:17:52 +09:00
=> Manager
-- ^ Manager for making HTTP requests
2019-03-11 08:15:42 +09:00
-> Maybe S . Algorithm
-- ^ Signature algorithm possibly specified in the HTTP signature header
2019-07-23 22:59:48 +09:00
-> Authority u
2019-02-24 02:17:52 +09:00
-- ^ Instance host
-> LocalURI
-- ^ Key owner actor ID URI
2019-07-23 22:59:48 +09:00
-> LocalRefURI
2019-02-24 02:17:52 +09:00
-- ^ Key URI
2019-03-11 08:15:42 +09:00
-> ExceptT String m ( PublicVerifKey , Maybe UTCTime )
2019-07-23 22:59:48 +09:00
fetchKnownPersonalKey manager malgo host luOwner luKey @ ( LocalRefURI ek ) = do
2019-02-24 02:17:52 +09:00
obj <- fetchAPIDOrH manager publicKeyId host luKey
2019-03-11 08:15:42 +09:00
( material , mexpires ) <-
2019-02-24 02:17:52 +09:00
case obj of
Left pkey -> do
case publicKeyOwner pkey of
OwnerInstance -> throwE " Personal key became shared "
OwnerActor owner ->
when ( luOwner /= owner ) $ throwE " Key owner changed "
return $ keyDetail pkey
2022-07-25 01:52:28 +09:00
Right ( Actor local detail ) -> do
unless ( Right ( actorId local ) == second localSubUriResource ek ) $
2019-02-24 02:17:52 +09:00
throwE " Actor ID doesn't match the keyid URI we fetched "
2022-07-25 01:52:28 +09:00
unless ( actorId local == luOwner ) $
2019-02-24 02:17:52 +09:00
throwE " Key owner changed "
2022-07-25 01:52:28 +09:00
pk <- matchKeyObj luKey $ actorPublicKeys local
2019-02-24 02:17:52 +09:00
case publicKeyOwner pk of
OwnerInstance -> throwE " Personal key became shared "
OwnerActor owner ->
when ( owner /= luOwner ) $
throwE " Actor's publicKey's owner doesn't match the actor's ID "
return $ keyDetail pk
2019-03-11 08:15:42 +09:00
ExceptT . pure $ verifyAlgo malgo material
return ( material , mexpires )
2019-02-24 02:17:52 +09:00
-- | Fetch a shared key we already have cached locally, but we'd like to
-- refresh the local copy by fetching the key again from the server.
fetchKnownSharedKey
2019-07-23 22:59:48 +09:00
:: ( MonadIO m , UriMode u )
2019-02-24 02:17:52 +09:00
=> Manager
-- ^ Manager for making HTTP requests
2019-03-11 08:15:42 +09:00
-> Maybe S . Algorithm
-- ^ Signature algorithm possibly specified in the HTTP signature header
2019-07-23 22:59:48 +09:00
-> Authority u
2019-02-24 02:17:52 +09:00
-- ^ Instance host
-> LocalURI
-- ^ Actor ID from HTTP actor header
2019-07-23 22:59:48 +09:00
-> LocalRefURI
2019-02-24 02:17:52 +09:00
-- ^ Key URI
2019-03-11 08:15:42 +09:00
-> ExceptT String m ( PublicVerifKey , Maybe UTCTime )
2019-03-22 06:38:59 +09:00
fetchKnownSharedKey manager malgo host luActor luKey = do
2019-02-24 02:17:52 +09:00
obj <- fetchAPIDOrH manager publicKeyId host luKey
pkey <-
2019-07-23 22:59:48 +09:00
case asKeyOrActor host obj of
2019-02-24 02:17:52 +09:00
Left pk -> return pk
Right _actor -> throwE " Expected stand-alone key, got embedded key "
case publicKeyOwner pkey of
OwnerInstance -> return ()
OwnerActor _owner -> throwE " Shared key became personal "
2019-03-11 08:15:42 +09:00
let ( material , mexpires ) = keyDetail pkey
ExceptT . pure $ verifyAlgo malgo material
return ( material , mexpires )
2019-07-23 22:59:48 +09:00
where
asKeyOrActor
:: Authority u
-> Either ( PublicKey u ) ( Actor u )
-> Either ( PublicKey u ) ( Actor u )
asKeyOrActor _ = id
2020-06-18 19:38:04 +09:00
data Obj u = Obj
{ objId :: ObjURI u
, objType :: Text
, objContext :: Maybe ( ObjURI u )
, objFollowers :: Maybe LocalURI
, objInbox :: Maybe LocalURI
, objTeam :: Maybe LocalURI
}
instance UriMode u => FromJSON ( Obj u ) where
parseJSON = withObject " Obj " $ \ o -> do
id_ @ ( ObjURI h _ ) <- o .: " id " <|> o .: " @id "
Obj id_
<$> ( o .: " type " <|> o .: " @type " )
<*> o .:? " context "
<*> withAuthorityMaybeO h ( o .:? " followers " )
<*> withAuthorityMaybeO h ( o .:? " inbox " )
<*> withAuthorityMaybeO h ( o .:? " team " )