actor.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. package apmodels
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/url"
  6. "time"
  7. "github.com/go-fed/activity/streams"
  8. "github.com/go-fed/activity/streams/vocab"
  9. "github.com/owncast/owncast/activitypub/crypto"
  10. "github.com/owncast/owncast/core/data"
  11. "github.com/owncast/owncast/models"
  12. log "github.com/sirupsen/logrus"
  13. )
  14. // ActivityPubActor represents a single actor in handling ActivityPub activity.
  15. type ActivityPubActor struct {
  16. // RequestObject is the actual follow request object.
  17. RequestObject vocab.ActivityStreamsFollow
  18. // W3IDSecurityV1PublicKey is the public key of the actor.
  19. W3IDSecurityV1PublicKey vocab.W3IDSecurityV1PublicKeyProperty
  20. // ActorIRI is the IRI of the remote actor.
  21. ActorIri *url.URL
  22. // FollowRequestIRI is the unique identifier of the follow request.
  23. FollowRequestIri *url.URL
  24. // Inbox is the inbox URL of the remote follower
  25. Inbox *url.URL
  26. // Image is the avatar image of the Actor.
  27. Image *url.URL
  28. // DisabledAt is the time, if any, this follower was blocked/removed.
  29. DisabledAt *time.Time
  30. // Name is the display name of the follower.
  31. Name string
  32. // Username is the account username of the remote actor.
  33. Username string
  34. // FullUsername is the username@account.tld representation of the user.
  35. FullUsername string
  36. }
  37. // DeleteRequest represents a request for delete.
  38. type DeleteRequest struct {
  39. ActorIri string
  40. }
  41. // ExternalEntity represents an ActivityPub Person, Service or Application.
  42. type ExternalEntity interface {
  43. GetJSONLDId() vocab.JSONLDIdProperty
  44. GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty
  45. GetActivityStreamsName() vocab.ActivityStreamsNameProperty
  46. GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty
  47. GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty
  48. GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty
  49. }
  50. // MakeActorFromExernalAPEntity takes a full ActivityPub entity and returns our
  51. // internal representation of an actor.
  52. func MakeActorFromExernalAPEntity(entity ExternalEntity) (*ActivityPubActor, error) {
  53. // Username is required (but not a part of the official ActivityPub spec)
  54. if entity.GetActivityStreamsPreferredUsername() == nil || entity.GetActivityStreamsPreferredUsername().GetXMLSchemaString() == "" {
  55. return nil, errors.New("remote activitypub entity does not have a preferred username set, rejecting")
  56. }
  57. username := GetFullUsernameFromExternalEntity(entity)
  58. // Key is required
  59. if entity.GetW3IDSecurityV1PublicKey() == nil {
  60. return nil, errors.New("remote activitypub entity does not have a public key set, rejecting")
  61. }
  62. // Name is optional
  63. var name string
  64. if entity.GetActivityStreamsName() != nil && !entity.GetActivityStreamsName().Empty() {
  65. name = entity.GetActivityStreamsName().At(0).GetXMLSchemaString()
  66. }
  67. // Image is optional
  68. var image *url.URL
  69. if entity.GetActivityStreamsIcon() != nil && !entity.GetActivityStreamsIcon().Empty() && entity.GetActivityStreamsIcon().At(0).GetActivityStreamsImage() != nil {
  70. image = entity.GetActivityStreamsIcon().At(0).GetActivityStreamsImage().GetActivityStreamsUrl().Begin().GetIRI()
  71. }
  72. apActor := ActivityPubActor{
  73. ActorIri: entity.GetJSONLDId().Get(),
  74. Inbox: entity.GetActivityStreamsInbox().GetIRI(),
  75. Name: name,
  76. Username: entity.GetActivityStreamsPreferredUsername().GetXMLSchemaString(),
  77. FullUsername: username,
  78. W3IDSecurityV1PublicKey: entity.GetW3IDSecurityV1PublicKey(),
  79. Image: image,
  80. }
  81. return &apActor, nil
  82. }
  83. // MakeActorPropertyWithID will return an actor property filled with the provided IRI.
  84. func MakeActorPropertyWithID(idIRI *url.URL) vocab.ActivityStreamsActorProperty {
  85. actor := streams.NewActivityStreamsActorProperty()
  86. actor.AppendIRI(idIRI)
  87. return actor
  88. }
  89. // MakeServiceForAccount will create a new local actor service with the the provided username.
  90. func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
  91. actorIRI := MakeLocalIRIForAccount(accountName)
  92. person := streams.NewActivityStreamsService()
  93. nameProperty := streams.NewActivityStreamsNameProperty()
  94. nameProperty.AppendXMLSchemaString(data.GetServerName())
  95. person.SetActivityStreamsName(nameProperty)
  96. preferredUsernameProperty := streams.NewActivityStreamsPreferredUsernameProperty()
  97. preferredUsernameProperty.SetXMLSchemaString(accountName)
  98. person.SetActivityStreamsPreferredUsername(preferredUsernameProperty)
  99. inboxIRI := MakeLocalIRIForResource("/user/" + accountName + "/inbox")
  100. inboxProp := streams.NewActivityStreamsInboxProperty()
  101. inboxProp.SetIRI(inboxIRI)
  102. person.SetActivityStreamsInbox(inboxProp)
  103. needsFollowApprovalProperty := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
  104. needsFollowApprovalProperty.Set(data.GetFederationIsPrivate())
  105. person.SetActivityStreamsManuallyApprovesFollowers(needsFollowApprovalProperty)
  106. outboxIRI := MakeLocalIRIForResource("/user/" + accountName + "/outbox")
  107. outboxProp := streams.NewActivityStreamsOutboxProperty()
  108. outboxProp.SetIRI(outboxIRI)
  109. person.SetActivityStreamsOutbox(outboxProp)
  110. id := streams.NewJSONLDIdProperty()
  111. id.Set(actorIRI)
  112. person.SetJSONLDId(id)
  113. publicKey := crypto.GetPublicKey(actorIRI)
  114. publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty()
  115. publicKeyType := streams.NewW3IDSecurityV1PublicKey()
  116. pubKeyIDProp := streams.NewJSONLDIdProperty()
  117. pubKeyIDProp.Set(publicKey.ID)
  118. publicKeyType.SetJSONLDId(pubKeyIDProp)
  119. ownerProp := streams.NewW3IDSecurityV1OwnerProperty()
  120. ownerProp.SetIRI(publicKey.Owner)
  121. publicKeyType.SetW3IDSecurityV1Owner(ownerProp)
  122. publicKeyPemProp := streams.NewW3IDSecurityV1PublicKeyPemProperty()
  123. publicKeyPemProp.Set(publicKey.PublicKeyPem)
  124. publicKeyType.SetW3IDSecurityV1PublicKeyPem(publicKeyPemProp)
  125. publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKeyType)
  126. person.SetW3IDSecurityV1PublicKey(publicKeyProp)
  127. if t, err := data.GetServerInitTime(); t != nil {
  128. publishedDateProp := streams.NewActivityStreamsPublishedProperty()
  129. publishedDateProp.Set(t.Time)
  130. person.SetActivityStreamsPublished(publishedDateProp)
  131. } else {
  132. log.Errorln("unable to fetch server init time", err)
  133. }
  134. // Profile properties
  135. // Avatar
  136. uniquenessString := data.GetLogoUniquenessString()
  137. userAvatarURLString := data.GetServerURL() + "/logo/external"
  138. userAvatarURL, err := url.Parse(userAvatarURLString)
  139. userAvatarURL.RawQuery = "uc=" + uniquenessString
  140. if err != nil {
  141. log.Errorln("unable to parse user avatar url", userAvatarURLString, err)
  142. }
  143. image := streams.NewActivityStreamsImage()
  144. imgProp := streams.NewActivityStreamsUrlProperty()
  145. imgProp.AppendIRI(userAvatarURL)
  146. image.SetActivityStreamsUrl(imgProp)
  147. icon := streams.NewActivityStreamsIconProperty()
  148. icon.AppendActivityStreamsImage(image)
  149. person.SetActivityStreamsIcon(icon)
  150. // Actor URL
  151. urlProperty := streams.NewActivityStreamsUrlProperty()
  152. urlProperty.AppendIRI(actorIRI)
  153. person.SetActivityStreamsUrl(urlProperty)
  154. // Profile header
  155. headerImage := streams.NewActivityStreamsImage()
  156. headerImgPropURL := streams.NewActivityStreamsUrlProperty()
  157. headerImgPropURL.AppendIRI(userAvatarURL)
  158. headerImage.SetActivityStreamsUrl(headerImgPropURL)
  159. headerImageProp := streams.NewActivityStreamsImageProperty()
  160. headerImageProp.AppendActivityStreamsImage(headerImage)
  161. person.SetActivityStreamsImage(headerImageProp)
  162. // Profile bio
  163. summaryProperty := streams.NewActivityStreamsSummaryProperty()
  164. summaryProperty.AppendXMLSchemaString(data.GetServerSummary())
  165. person.SetActivityStreamsSummary(summaryProperty)
  166. // Links
  167. if serverURL := data.GetServerURL(); serverURL != "" {
  168. addMetadataLinkToProfile(person, "Stream", serverURL)
  169. }
  170. for _, link := range data.GetSocialHandles() {
  171. addMetadataLinkToProfile(person, link.Platform, link.URL)
  172. }
  173. // Discoverable
  174. discoverableProperty := streams.NewTootDiscoverableProperty()
  175. discoverableProperty.Set(true)
  176. person.SetTootDiscoverable(discoverableProperty)
  177. // Followers
  178. followersProperty := streams.NewActivityStreamsFollowersProperty()
  179. followersURL := *actorIRI
  180. followersURL.Path = actorIRI.Path + "/followers"
  181. followersProperty.SetIRI(&followersURL)
  182. person.SetActivityStreamsFollowers(followersProperty)
  183. // Tags
  184. tagProp := streams.NewActivityStreamsTagProperty()
  185. for _, tagString := range data.GetServerMetadataTags() {
  186. hashtag := MakeHashtag(tagString)
  187. tagProp.AppendTootHashtag(hashtag)
  188. }
  189. person.SetActivityStreamsTag(tagProp)
  190. // Work around an issue where a single attachment will not serialize
  191. // as an array, so add another item to the mix.
  192. if len(data.GetSocialHandles()) == 1 {
  193. addMetadataLinkToProfile(person, "Owncast", "https://owncast.online")
  194. }
  195. return person
  196. }
  197. // GetFullUsernameFromExternalEntity will return the full username from an
  198. // internal representation of an ExternalEntity. Returns user@host.tld.
  199. func GetFullUsernameFromExternalEntity(entity ExternalEntity) string {
  200. hostname := entity.GetJSONLDId().GetIRI().Hostname()
  201. username := entity.GetActivityStreamsPreferredUsername().GetXMLSchemaString()
  202. fullUsername := fmt.Sprintf("%s@%s", username, hostname)
  203. return fullUsername
  204. }
  205. func addMetadataLinkToProfile(profile vocab.ActivityStreamsService, name string, url string) {
  206. attachments := profile.GetActivityStreamsAttachment()
  207. if attachments == nil {
  208. attachments = streams.NewActivityStreamsAttachmentProperty()
  209. }
  210. displayName := name
  211. socialHandle := models.GetSocialHandle(name)
  212. if socialHandle != nil {
  213. displayName = socialHandle.Platform
  214. }
  215. linkValue := fmt.Sprintf("<a href=\"%s\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\">%s</a>", url, url)
  216. attachment := streams.NewActivityStreamsObject()
  217. attachmentProp := streams.NewJSONLDTypeProperty()
  218. attachmentProp.AppendXMLSchemaString("PropertyValue")
  219. attachment.SetJSONLDType(attachmentProp)
  220. attachmentName := streams.NewActivityStreamsNameProperty()
  221. attachmentName.AppendXMLSchemaString(displayName)
  222. attachment.SetActivityStreamsName(attachmentName)
  223. attachment.GetUnknownProperties()["value"] = linkValue
  224. attachments.AppendActivityStreamsObject(attachment)
  225. profile.SetActivityStreamsAttachment(attachments)
  226. }