object_validator_test.exs (21845B)
1 defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do 2 use Pleroma.DataCase 3 4 alias Pleroma.Object 5 alias Pleroma.Web.ActivityPub.ActivityPub 6 alias Pleroma.Web.ActivityPub.Builder 7 alias Pleroma.Web.ActivityPub.ObjectValidator 8 alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator 9 alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator 10 alias Pleroma.Web.ActivityPub.Utils 11 alias Pleroma.Web.CommonAPI 12 13 import Pleroma.Factory 14 15 describe "attachments" do 16 test "works with honkerific attachments" do 17 attachment = %{ 18 "mediaType" => "", 19 "name" => "", 20 "summary" => "298p3RG7j27tfsZ9RQ.jpg", 21 "type" => "Document", 22 "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg" 23 } 24 25 assert {:ok, attachment} = 26 AttachmentValidator.cast_and_validate(attachment) 27 |> Ecto.Changeset.apply_action(:insert) 28 29 assert attachment.mediaType == "application/octet-stream" 30 end 31 32 test "it turns mastodon attachments into our attachments" do 33 attachment = %{ 34 "url" => 35 "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg", 36 "type" => "Document", 37 "name" => nil, 38 "mediaType" => "image/jpeg" 39 } 40 41 {:ok, attachment} = 42 AttachmentValidator.cast_and_validate(attachment) 43 |> Ecto.Changeset.apply_action(:insert) 44 45 assert [ 46 %{ 47 href: 48 "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg", 49 type: "Link", 50 mediaType: "image/jpeg" 51 } 52 ] = attachment.url 53 54 assert attachment.mediaType == "image/jpeg" 55 end 56 57 test "it handles our own uploads" do 58 user = insert(:user) 59 60 file = %Plug.Upload{ 61 content_type: "image/jpg", 62 path: Path.absname("test/fixtures/image.jpg"), 63 filename: "an_image.jpg" 64 } 65 66 {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id) 67 68 {:ok, attachment} = 69 attachment.data 70 |> AttachmentValidator.cast_and_validate() 71 |> Ecto.Changeset.apply_action(:insert) 72 73 assert attachment.mediaType == "image/jpeg" 74 end 75 end 76 77 describe "chat message create activities" do 78 test "it is invalid if the object already exists" do 79 user = insert(:user) 80 recipient = insert(:user) 81 {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey") 82 object = Object.normalize(activity, false) 83 84 {:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id]) 85 86 {:error, cng} = ObjectValidator.validate(create_data, []) 87 88 assert {:object, {"The object to create already exists", []}} in cng.errors 89 end 90 91 test "it is invalid if the object data has a different `to` or `actor` field" do 92 user = insert(:user) 93 recipient = insert(:user) 94 {:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey") 95 96 {:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id]) 97 98 {:error, cng} = ObjectValidator.validate(create_data, []) 99 100 assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors 101 assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors 102 end 103 end 104 105 describe "chat messages" do 106 setup do 107 clear_config([:instance, :remote_limit]) 108 user = insert(:user) 109 recipient = insert(:user, local: false) 110 111 {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:") 112 113 %{user: user, recipient: recipient, valid_chat_message: valid_chat_message} 114 end 115 116 test "let's through some basic html", %{user: user, recipient: recipient} do 117 {:ok, valid_chat_message, _} = 118 Builder.chat_message( 119 user, 120 recipient.ap_id, 121 "hey <a href='https://example.org'>example</a> <script>alert('uguu')</script>" 122 ) 123 124 assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) 125 126 assert object["content"] == 127 "hey <a href=\"https://example.org\">example</a> alert('uguu')" 128 end 129 130 test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do 131 assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) 132 133 assert Map.put(valid_chat_message, "attachment", nil) == object 134 end 135 136 test "validates for a basic object with an attachment", %{ 137 valid_chat_message: valid_chat_message, 138 user: user 139 } do 140 file = %Plug.Upload{ 141 content_type: "image/jpg", 142 path: Path.absname("test/fixtures/image.jpg"), 143 filename: "an_image.jpg" 144 } 145 146 {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id) 147 148 valid_chat_message = 149 valid_chat_message 150 |> Map.put("attachment", attachment.data) 151 152 assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) 153 154 assert object["attachment"] 155 end 156 157 test "validates for a basic object with an attachment in an array", %{ 158 valid_chat_message: valid_chat_message, 159 user: user 160 } do 161 file = %Plug.Upload{ 162 content_type: "image/jpg", 163 path: Path.absname("test/fixtures/image.jpg"), 164 filename: "an_image.jpg" 165 } 166 167 {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id) 168 169 valid_chat_message = 170 valid_chat_message 171 |> Map.put("attachment", [attachment.data]) 172 173 assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) 174 175 assert object["attachment"] 176 end 177 178 test "validates for a basic object with an attachment but without content", %{ 179 valid_chat_message: valid_chat_message, 180 user: user 181 } do 182 file = %Plug.Upload{ 183 content_type: "image/jpg", 184 path: Path.absname("test/fixtures/image.jpg"), 185 filename: "an_image.jpg" 186 } 187 188 {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id) 189 190 valid_chat_message = 191 valid_chat_message 192 |> Map.put("attachment", attachment.data) 193 |> Map.delete("content") 194 195 assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) 196 197 assert object["attachment"] 198 end 199 200 test "does not validate if the message has no content", %{ 201 valid_chat_message: valid_chat_message 202 } do 203 contentless = 204 valid_chat_message 205 |> Map.delete("content") 206 207 refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, [])) 208 end 209 210 test "does not validate if the message is longer than the remote_limit", %{ 211 valid_chat_message: valid_chat_message 212 } do 213 Pleroma.Config.put([:instance, :remote_limit], 2) 214 refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, [])) 215 end 216 217 test "does not validate if the recipient is blocking the actor", %{ 218 valid_chat_message: valid_chat_message, 219 user: user, 220 recipient: recipient 221 } do 222 Pleroma.User.block(recipient, user) 223 refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, [])) 224 end 225 226 test "does not validate if the actor or the recipient is not in our system", %{ 227 valid_chat_message: valid_chat_message 228 } do 229 chat_message = 230 valid_chat_message 231 |> Map.put("actor", "https://raymoo.com/raymoo") 232 233 {:error, _} = ObjectValidator.validate(chat_message, []) 234 235 chat_message = 236 valid_chat_message 237 |> Map.put("to", ["https://raymoo.com/raymoo"]) 238 239 {:error, _} = ObjectValidator.validate(chat_message, []) 240 end 241 242 test "does not validate for a message with multiple recipients", %{ 243 valid_chat_message: valid_chat_message, 244 user: user, 245 recipient: recipient 246 } do 247 chat_message = 248 valid_chat_message 249 |> Map.put("to", [user.ap_id, recipient.ap_id]) 250 251 assert {:error, _} = ObjectValidator.validate(chat_message, []) 252 end 253 254 test "does not validate if it doesn't concern local users" do 255 user = insert(:user, local: false) 256 recipient = insert(:user, local: false) 257 258 {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey") 259 assert {:error, _} = ObjectValidator.validate(valid_chat_message, []) 260 end 261 end 262 263 describe "EmojiReacts" do 264 setup do 265 user = insert(:user) 266 {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"}) 267 268 object = Pleroma.Object.get_by_ap_id(post_activity.data["object"]) 269 270 {:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌") 271 272 %{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react} 273 end 274 275 test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do 276 assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, []) 277 end 278 279 test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do 280 without_content = 281 valid_emoji_react 282 |> Map.delete("content") 283 284 {:error, cng} = ObjectValidator.validate(without_content, []) 285 286 refute cng.valid? 287 assert {:content, {"can't be blank", [validation: :required]}} in cng.errors 288 end 289 290 test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do 291 without_emoji_content = 292 valid_emoji_react 293 |> Map.put("content", "x") 294 295 {:error, cng} = ObjectValidator.validate(without_emoji_content, []) 296 297 refute cng.valid? 298 299 assert {:content, {"must be a single character emoji", []}} in cng.errors 300 end 301 end 302 303 describe "Undos" do 304 setup do 305 user = insert(:user) 306 {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"}) 307 {:ok, like} = CommonAPI.favorite(user, post_activity.id) 308 {:ok, valid_like_undo, []} = Builder.undo(user, like) 309 310 %{user: user, like: like, valid_like_undo: valid_like_undo} 311 end 312 313 test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do 314 assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, []) 315 end 316 317 test "it does not validate if the actor of the undo is not the actor of the object", %{ 318 valid_like_undo: valid_like_undo 319 } do 320 other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo") 321 322 bad_actor = 323 valid_like_undo 324 |> Map.put("actor", other_user.ap_id) 325 326 {:error, cng} = ObjectValidator.validate(bad_actor, []) 327 328 assert {:actor, {"not the same as object actor", []}} in cng.errors 329 end 330 331 test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do 332 missing_object = 333 valid_like_undo 334 |> Map.put("object", "https://gensokyo.2hu/objects/1") 335 336 {:error, cng} = ObjectValidator.validate(missing_object, []) 337 338 assert {:object, {"can't find object", []}} in cng.errors 339 assert length(cng.errors) == 1 340 end 341 end 342 343 describe "deletes" do 344 setup do 345 user = insert(:user) 346 {:ok, post_activity} = CommonAPI.post(user, %{status: "cancel me daddy"}) 347 348 {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"]) 349 {:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id) 350 351 %{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete} 352 end 353 354 test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do 355 {:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, []) 356 357 assert valid_post_delete["deleted_activity_id"] 358 end 359 360 test "it is invalid if the object isn't in a list of certain types", %{ 361 valid_post_delete: valid_post_delete 362 } do 363 object = Object.get_by_ap_id(valid_post_delete["object"]) 364 365 data = 366 object.data 367 |> Map.put("type", "Like") 368 369 {:ok, _object} = 370 object 371 |> Ecto.Changeset.change(%{data: data}) 372 |> Object.update_and_set_cache() 373 374 {:error, cng} = ObjectValidator.validate(valid_post_delete, []) 375 assert {:object, {"object not in allowed types", []}} in cng.errors 376 end 377 378 test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do 379 assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, [])) 380 end 381 382 test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do 383 no_id = 384 valid_post_delete 385 |> Map.delete("id") 386 387 {:error, cng} = ObjectValidator.validate(no_id, []) 388 389 assert {:id, {"can't be blank", [validation: :required]}} in cng.errors 390 end 391 392 test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do 393 missing_object = 394 valid_post_delete 395 |> Map.put("object", "http://does.not/exist") 396 397 {:error, cng} = ObjectValidator.validate(missing_object, []) 398 399 assert {:object, {"can't find object", []}} in cng.errors 400 end 401 402 test "it's invalid if the actor of the object and the actor of delete are from different domains", 403 %{valid_post_delete: valid_post_delete} do 404 valid_user = insert(:user) 405 406 valid_other_actor = 407 valid_post_delete 408 |> Map.put("actor", valid_user.ap_id) 409 410 assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, [])) 411 412 invalid_other_actor = 413 valid_post_delete 414 |> Map.put("actor", "https://gensokyo.2hu/users/raymoo") 415 416 {:error, cng} = ObjectValidator.validate(invalid_other_actor, []) 417 418 assert {:actor, {"is not allowed to delete object", []}} in cng.errors 419 end 420 421 test "it's valid if the actor of the object is a local superuser", 422 %{valid_post_delete: valid_post_delete} do 423 user = 424 insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo") 425 426 valid_other_actor = 427 valid_post_delete 428 |> Map.put("actor", user.ap_id) 429 430 {:ok, _, meta} = ObjectValidator.validate(valid_other_actor, []) 431 assert meta[:do_not_federate] 432 end 433 end 434 435 describe "likes" do 436 setup do 437 user = insert(:user) 438 {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"}) 439 440 valid_like = %{ 441 "to" => [user.ap_id], 442 "cc" => [], 443 "type" => "Like", 444 "id" => Utils.generate_activity_id(), 445 "object" => post_activity.data["object"], 446 "actor" => user.ap_id, 447 "context" => "a context" 448 } 449 450 %{valid_like: valid_like, user: user, post_activity: post_activity} 451 end 452 453 test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do 454 {:ok, object, _meta} = ObjectValidator.validate(valid_like, []) 455 456 assert "id" in Map.keys(object) 457 end 458 459 test "is valid for a valid object", %{valid_like: valid_like} do 460 assert LikeValidator.cast_and_validate(valid_like).valid? 461 end 462 463 test "sets the 'to' field to the object actor if no recipients are given", %{ 464 valid_like: valid_like, 465 user: user 466 } do 467 without_recipients = 468 valid_like 469 |> Map.delete("to") 470 471 {:ok, object, _meta} = ObjectValidator.validate(without_recipients, []) 472 473 assert object["to"] == [user.ap_id] 474 end 475 476 test "sets the context field to the context of the object if no context is given", %{ 477 valid_like: valid_like, 478 post_activity: post_activity 479 } do 480 without_context = 481 valid_like 482 |> Map.delete("context") 483 484 {:ok, object, _meta} = ObjectValidator.validate(without_context, []) 485 486 assert object["context"] == post_activity.data["context"] 487 end 488 489 test "it errors when the actor is missing or not known", %{valid_like: valid_like} do 490 without_actor = Map.delete(valid_like, "actor") 491 492 refute LikeValidator.cast_and_validate(without_actor).valid? 493 494 with_invalid_actor = Map.put(valid_like, "actor", "invalidactor") 495 496 refute LikeValidator.cast_and_validate(with_invalid_actor).valid? 497 end 498 499 test "it errors when the object is missing or not known", %{valid_like: valid_like} do 500 without_object = Map.delete(valid_like, "object") 501 502 refute LikeValidator.cast_and_validate(without_object).valid? 503 504 with_invalid_object = Map.put(valid_like, "object", "invalidobject") 505 506 refute LikeValidator.cast_and_validate(with_invalid_object).valid? 507 end 508 509 test "it errors when the actor has already like the object", %{ 510 valid_like: valid_like, 511 user: user, 512 post_activity: post_activity 513 } do 514 _like = CommonAPI.favorite(user, post_activity.id) 515 516 refute LikeValidator.cast_and_validate(valid_like).valid? 517 end 518 519 test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do 520 wrapped_like = 521 valid_like 522 |> Map.put("actor", %{"id" => valid_like["actor"]}) 523 |> Map.put("object", %{"id" => valid_like["object"]}) 524 525 validated = LikeValidator.cast_and_validate(wrapped_like) 526 527 assert validated.valid? 528 529 assert {:actor, valid_like["actor"]} in validated.changes 530 assert {:object, valid_like["object"]} in validated.changes 531 end 532 end 533 534 describe "announces" do 535 setup do 536 user = insert(:user) 537 announcer = insert(:user) 538 {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"}) 539 540 object = Object.normalize(post_activity, false) 541 {:ok, valid_announce, []} = Builder.announce(announcer, object) 542 543 %{ 544 valid_announce: valid_announce, 545 user: user, 546 post_activity: post_activity, 547 announcer: announcer 548 } 549 end 550 551 test "returns ok for a valid announce", %{valid_announce: valid_announce} do 552 assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, []) 553 end 554 555 test "returns an error if the object can't be found", %{valid_announce: valid_announce} do 556 without_object = 557 valid_announce 558 |> Map.delete("object") 559 560 {:error, cng} = ObjectValidator.validate(without_object, []) 561 562 assert {:object, {"can't be blank", [validation: :required]}} in cng.errors 563 564 nonexisting_object = 565 valid_announce 566 |> Map.put("object", "https://gensokyo.2hu/objects/99999999") 567 568 {:error, cng} = ObjectValidator.validate(nonexisting_object, []) 569 570 assert {:object, {"can't find object", []}} in cng.errors 571 end 572 573 test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do 574 nonexisting_actor = 575 valid_announce 576 |> Map.put("actor", "https://gensokyo.2hu/users/raymoo") 577 578 {:error, cng} = ObjectValidator.validate(nonexisting_actor, []) 579 580 assert {:actor, {"can't find user", []}} in cng.errors 581 end 582 583 test "returns an error if the actor already announced the object", %{ 584 valid_announce: valid_announce, 585 announcer: announcer, 586 post_activity: post_activity 587 } do 588 _announce = CommonAPI.repeat(post_activity.id, announcer) 589 590 {:error, cng} = ObjectValidator.validate(valid_announce, []) 591 592 assert {:actor, {"already announced this object", []}} in cng.errors 593 assert {:object, {"already announced by this actor", []}} in cng.errors 594 end 595 596 test "returns an error if the actor can't announce the object", %{ 597 announcer: announcer, 598 user: user 599 } do 600 {:ok, post_activity} = 601 CommonAPI.post(user, %{status: "a secret post", visibility: "private"}) 602 603 object = Object.normalize(post_activity, false) 604 605 # Another user can't announce it 606 {:ok, announce, []} = Builder.announce(announcer, object, public: false) 607 608 {:error, cng} = ObjectValidator.validate(announce, []) 609 610 assert {:actor, {"can not announce this object", []}} in cng.errors 611 612 # The actor of the object can announce it 613 {:ok, announce, []} = Builder.announce(user, object, public: false) 614 615 assert {:ok, _, _} = ObjectValidator.validate(announce, []) 616 617 # The actor of the object can not announce it publicly 618 {:ok, announce, []} = Builder.announce(user, object, public: true) 619 620 {:error, cng} = ObjectValidator.validate(announce, []) 621 622 assert {:actor, {"can not announce this object publicly", []}} in cng.errors 623 end 624 end 625 626 describe "updates" do 627 setup do 628 user = insert(:user) 629 630 object = %{ 631 "id" => user.ap_id, 632 "name" => "A new name", 633 "summary" => "A new bio" 634 } 635 636 {:ok, valid_update, []} = Builder.update(user, object) 637 638 %{user: user, valid_update: valid_update} 639 end 640 641 test "validates a basic object", %{valid_update: valid_update} do 642 assert {:ok, _update, []} = ObjectValidator.validate(valid_update, []) 643 end 644 645 test "returns an error if the object can't be updated by the actor", %{ 646 valid_update: valid_update 647 } do 648 other_user = insert(:user) 649 650 update = 651 valid_update 652 |> Map.put("actor", other_user.ap_id) 653 654 assert {:error, _cng} = ObjectValidator.validate(update, []) 655 end 656 end 657 658 describe "blocks" do 659 setup do 660 user = insert(:user, local: false) 661 blocked = insert(:user) 662 663 {:ok, valid_block, []} = Builder.block(user, blocked) 664 665 %{user: user, valid_block: valid_block} 666 end 667 668 test "validates a basic object", %{ 669 valid_block: valid_block 670 } do 671 assert {:ok, _block, []} = ObjectValidator.validate(valid_block, []) 672 end 673 674 test "returns an error if we don't know the blocked user", %{ 675 valid_block: valid_block 676 } do 677 block = 678 valid_block 679 |> Map.put("object", "https://gensokyo.2hu/users/raymoo") 680 681 assert {:error, _cng} = ObjectValidator.validate(block, []) 682 end 683 end 684 end