diff --git a/.gitignore b/.gitignore index 4f7f1202..4c08c995 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ erl_crash.dump .env.test /.env .env.2 +.env.1 /setup_db.psql diff --git a/config/config.exs b/config/config.exs index 8899ceae..2780bfc9 100644 --- a/config/config.exs +++ b/config/config.exs @@ -22,7 +22,7 @@ config :mobilizon, :instance, repository: Mix.Project.config()[:source_url], allow_relay: true, # Federation is to be activated with Mobilizon 1.0.0-beta.2 - federating: false, + federating: true, remote_limit: 100_000, upload_limit: 10_000_000, avatar_upload_limit: 2_000_000, @@ -63,7 +63,7 @@ config :mobilizon, MobilizonWeb.Upload, config :mobilizon, MobilizonWeb.Uploaders.Local, uploads: "uploads" config :mobilizon, :media_proxy, - enabled: false, + enabled: true, proxy_opts: [ redirect_on_failure: false, max_body_length: 25 * 1_048_576, @@ -107,7 +107,9 @@ config :auto_linker, # TODO: Set to :no_scheme when it works properly validate_tld: true, class: false, - strip_prefix: false + strip_prefix: false, + new_window: true, + rel: "noopener noreferrer ugc" ] config :phoenix, :format_encoders, json: Jason, "activity-json": Jason @@ -120,6 +122,8 @@ config :ex_cldr, config :http_signatures, adapter: Mobilizon.Service.HTTPSignatures.Signature +config :mobilizon, :activitypub, sign_object_fetches: true + config :mobilizon, Mobilizon.Service.Geospatial.Nominatim, endpoint: System.get_env("GEOSPATIAL_NOMINATIM_ENDPOINT") || "https://nominatim.openstreetmap.org", @@ -155,7 +159,7 @@ config :mobilizon, :maps, config :mobilizon, Oban, repo: Mobilizon.Storage.Repo, prune: {:maxlen, 10_000}, - queues: [default: 10, search: 20] + queues: [default: 10, search: 20, background: 5] # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/docs/contribute/activity_pub.md b/docs/contribute/activity_pub.md new file mode 100644 index 00000000..47986abe --- /dev/null +++ b/docs/contribute/activity_pub.md @@ -0,0 +1,104 @@ +# Federation + +## ActivityPub + +Mobilizon uses [ActivityPub](http://activitypub.rocks/) to federate content between instances. It only supports the server-to-server part of [the ActivityPub spec](https://www.w3.org/TR/activitypub/). + +It implements the [HTTP signatures spec](https://tools.ietf.org/html/draft-cavage-http-signatures-12) for authentication of inbox deliveries, but doesn't implement Linked Data Signatures for forwarded payloads, and instead fetches content when needed. + +To match usernames to actors, Mobilizon uses [WebFinger](https://tools.ietf.org/html/rfc7033). + +## Instance subscriptions + +Instances subscribe to each other through an internal actor named `relay@instance.tld` that publishes (through `Announce`) every created content to it's followers. Each content creation share is saved so that updates and deletes are correctly sent to every + +## Activities + +Supported Activity | Supported Object +------------ | ------------- +`Accept` | `Follow`, `Join` +`Announce` | `Object` +`Create` | `Note`, `Event` +`Delete` | `Object` +`Flag` | `Object` +`Follow` | `Object` +`Reject` | `Follow`, `Join` +`Remove` | `Note`, `Event` +`Undo` | `Announce`, `Follow` +`Update` | `Object` + +## Extensions + +### Event + +The vocabulary for Event is based on [the Event object in ActivityStreams](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event), extended with : + +* the [Event Schema](https://schema.org/Event) from Schema.org +* some properties from [iCalendar](https://tools.ietf.org/html/rfc5545), such as `ical:status` (see [this issue](https://framagit.org/framasoft/mobilizon/issues/320)) + +The following properties are added. + +#### repliesModeration + +Disabling replies is [an ongoing issue with ActivityPub](https://github.com/w3c/activitypub/issues/319) so we use a temporary property. + +See [the corresponding issue](https://framagit.org/framasoft/mobilizon/issues/321). + +Accepted values: `allow_all`, `closed`, `moderated` (not used at the moment) + +Example: +```json +{ + "@context": [ + "...", + { + "mz": "https://joinmobilizon.org/ns#", + "repliesModerationOption": { + "@id": "mz:repliesModerationOption", + "@type": "mz:repliesModerationOptionType" + }, + "repliesModerationOptionType": { + "@id": "mz:repliesModerationOptionType", + "@type": "rdfs:Class" + } + } + ], + "...": "...", + "repliesModerationOption": "allow_all", + "type": "Event", + "url": "http://mobilizon1.com/events/8cf76e9f-c426-4912-9cd6-c7030b969611" +} +``` + + +#### joinMode + +Indicator of how new members may be able to join. + +See [the corresponding issue](https://framagit.org/framasoft/mobilizon/issues/321). + +Accepted values: `free`, `restricted`, `invite` (not used at the moment) + +Example: +```json +{ + "@context": [ + "...", + { + "mz": "https://joinmobilizon.org/ns#", + "joinMode": { + "@id": "mz:joinMode", + "@type": "mz:joinModeType" + }, + "joinModeType": { + "@id": "mz:joinModeType", + "@type": "rdfs:Class" + } + } + ], + "...": "...", + "joinMode": "restricted", + "type": "Event", + "url": "http://mobilizon1.com/events/8cf76e9f-c426-4912-9cd6-c7030b969611" +} +``` diff --git a/js/package.json b/js/package.json index 583f38f9..4398dc80 100644 --- a/js/package.json +++ b/js/package.json @@ -12,15 +12,21 @@ "dev": "vue-cli-service build --watch", "styleguide": "vue-cli-service styleguidist", "styleguide:build": "vue-cli-service styleguidist:build", - "vue-i18n-extract": "vue-i18n-extract" + "vue-i18n-extract": "vue-i18n-extract", + "graphql:get-schema": "graphql get-schema", + "i18n-extract": "vue-i18n-extract report -v './src/**/*.?(ts|vue)' -l './src/i18n/en_US.json' -o output.json" }, "dependencies": { + "@absinthe/socket": "^0.2.1", + "@absinthe/socket-apollo-link": "^0.2.1", "@mdi/font": "^4.5.95", "apollo-absinthe-upload-link": "^1.5.0", "apollo-cache-inmemory": "^1.5.1", "apollo-client": "^2.5.1", "apollo-link": "^1.2.11", "apollo-link-http": "^1.5.16", + "apollo-link-ws": "^1.0.19", + "apollo-utilities": "^1.3.2", "buefy": "^0.8.2", "graphql": "^14.5.8", "graphql-tag": "^2.10.1", @@ -30,6 +36,7 @@ "leaflet.locatecontrol": "^0.68.0", "lodash": "^4.17.11", "ngeohash": "^0.6.3", + "phoenix": "^1.4.11", "register-service-worker": "^1.6.2", "tippy.js": "4.3.5", "tiptap": "^1.26.0", @@ -58,7 +65,7 @@ "@vue/cli-plugin-unit-mocha": "^4.0.3", "@vue/cli-service": "^4.0.3", "@vue/eslint-config-typescript": "^5.0.0", - "@vue/test-utils": "^1.0.0-beta.29", + "@vue/test-utils": "^1.0.0-beta.30", "apollo-link-error": "^1.1.12", "chai": "^4.2.0", "dotenv-webpack": "^1.7.0", diff --git a/js/src/components/Account/ParticipantCard.vue b/js/src/components/Account/ParticipantCard.vue index 59456fdb..cafac377 100644 --- a/js/src/components/Account/ParticipantCard.vue +++ b/js/src/components/Account/ParticipantCard.vue @@ -26,7 +26,8 @@
{{ actorDisplayName }}
- @{{ participant.actor.preferredUsername }} + @{{ participant.actor.preferredUsername }}@{{ participant.actor.domain }} + @{{ participant.actor.preferredUsername }}
@@ -41,7 +42,7 @@ \ No newline at end of file diff --git a/js/src/components/Admin/Followings.vue b/js/src/components/Admin/Followings.vue new file mode 100644 index 00000000..97a58dde --- /dev/null +++ b/js/src/components/Admin/Followings.vue @@ -0,0 +1,142 @@ + + \ No newline at end of file diff --git a/js/src/components/Comment/Comment.vue b/js/src/components/Comment/Comment.vue index ce31e8ff..d9ee1207 100644 --- a/js/src/components/Comment/Comment.vue +++ b/js/src/components/Comment/Comment.vue @@ -11,7 +11,8 @@
{{ comment.actor.name }} - @{{ comment.actor.preferredUsername }} + @{{ comment.actor.preferredUsername }}@{{ comment.actor.domain }} + @{{ comment.actor.preferredUsername }} {{ timeago(new Date(comment.updatedAt)) }} @@ -202,7 +203,7 @@ export default class Comment extends Vue { timeago(dateTime): String { if (this.timeAgoInstance != null) { - // @ts-ignore + // @ts-ignore return this.timeAgoInstance.format(dateTime); } return ''; @@ -213,7 +214,7 @@ export default class Comment extends Vue { } get commentFromOrganizer(): boolean { - return this.event.organizerActor !== undefined && this.comment.actor.id === this.event.organizerActor.id; + return this.event.organizerActor !== undefined && this.comment.actor && this.comment.actor.id === this.event.organizerActor.id; } get commentId(): String { @@ -230,6 +231,7 @@ export default class Comment extends Vue { title: this.$t('Report this comment'), comment: this.comment, onConfirm: this.reportComment, + outsideDomain: this.comment.actor.domain, }, }); } @@ -244,6 +246,7 @@ export default class Comment extends Vue { reportedId: this.comment.actor.id, commentsIds: [this.comment.id], content, + forward, }, }); this.$buefy.notification.open({ diff --git a/js/src/components/Comment/CommentTree.vue b/js/src/components/Comment/CommentTree.vue index df20bfec..cf8b7d26 100644 --- a/js/src/components/Comment/CommentTree.vue +++ b/js/src/components/Comment/CommentTree.vue @@ -221,7 +221,7 @@ export default class CommentTree extends Vue { data: { thread: replies }, }); - // @ts-ignore + // @ts-ignore const parentCommentIndex = oldComments.findIndex(oldComment => oldComment.id === comment.originComment.id); const parentComment = oldComments[parentCommentIndex]; parentComment.replies = replies; diff --git a/js/src/components/Editor.vue b/js/src/components/Editor.vue index 3bc2ca9c..8d21fe6a 100644 --- a/js/src/components/Editor.vue +++ b/js/src/components/Editor.vue @@ -409,9 +409,9 @@ export default class EditorComponent extends Vue { } replyToComment(comment: IComment) { - console.log('called replyToComment', comment); const actorModel = new Actor(comment.actor); if (!this.editor) return; + console.log(this.editor.commands); this.editor.commands.mention({ id: actorModel.id, label: actorModel.usernameWithDomain().substring(1) }); this.editor.focus(); } diff --git a/js/src/components/Event/AddressAutoComplete.vue b/js/src/components/Event/AddressAutoComplete.vue index 2fd40f7d..4de773b4 100644 --- a/js/src/components/Event/AddressAutoComplete.vue +++ b/js/src/components/Event/AddressAutoComplete.vue @@ -112,7 +112,7 @@ export default class AddressAutoComplete extends Vue { addressData: IAddress[] = []; selected: IAddress = new Address(); isFetching: boolean = false; - queryText: string = this.value && (new Address(this.value)).fullName || ''; + queryText: string = (this.value && (new Address(this.value)).fullName) || ''; addressModalActive: boolean = false; private gettingLocation: boolean = false; private location!: Position; @@ -164,6 +164,7 @@ export default class AddressAutoComplete extends Vue { @Watch('value') updateEditing() { + if (!(this.value && this.value.id)) return; this.selected = this.value; const address = new Address(this.selected); this.queryText = `${address.poiInfos.name} ${address.poiInfos.alternativeName}`; diff --git a/js/src/components/Event/ParticipationButton.vue b/js/src/components/Event/ParticipationButton.vue index 401fa746..f48a1e2b 100644 --- a/js/src/components/Event/ParticipationButton.vue +++ b/js/src/components/Event/ParticipationButton.vue @@ -26,11 +26,11 @@ A button to set your participation
@@ -45,11 +45,11 @@ A button to set your participation
@@ -73,7 +73,7 @@ A button to set your participation - + @@ -84,12 +84,12 @@ A button to set your participation
- {{ $t('with {identity}', {identity: currentActor.preferredUsername }) }} + {{ $t('as {identity}', {identity: currentActor.preferredUsername }) }}
- + {{ $t('with another identity…')}} @@ -99,14 +99,32 @@ A button to set your participation \ No newline at end of file diff --git a/js/src/views/Event/Edit.vue b/js/src/views/Event/Edit.vue index 20909167..fe1c0c48 100644 --- a/js/src/views/Event/Edit.vue +++ b/js/src/views/Event/Edit.vue @@ -29,7 +29,7 @@ - +
@@ -92,7 +92,7 @@
- + + + + + + {{ $t('Delete') }} @@ -74,24 +77,24 @@
-

+

{{ $t('No comment') }}

{{ report.event.title }}

-

+

- {{ $t('Edit') }} + + + + + + {{ $t('Delete') }}

@@ -101,17 +104,25 @@
-
+
Image
- {{ comment.actor.name }} @{{ comment.actor.preferredUsername }} + + {{ comment.actor.name }} @{{ comment.actor.preferredUsername }} + + {{ $t('Unknown actor') }}
-

+

+ {{ $t('Delete') }}
@@ -131,21 +142,23 @@ - {{ $t('Ajouter une note') }} + {{ $t('Add a note') }}
- \ No newline at end of file diff --git a/js/src/vue-apollo.ts b/js/src/vue-apollo.ts index 10a045cf..f95e8f43 100644 --- a/js/src/vue-apollo.ts +++ b/js/src/vue-apollo.ts @@ -1,10 +1,10 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import { ApolloLink, Observable } from 'apollo-link'; +import { ApolloLink, Observable, split } from 'apollo-link'; import { defaultDataIdFromObject, InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import { onError } from 'apollo-link-error'; import { createLink } from 'apollo-absinthe-upload-link'; -import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH } from './api/_entrypoint'; +import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH, MOBILIZON_INSTANCE_HOST } from './api/_entrypoint'; import { ApolloClient } from 'apollo-client'; import { buildCurrentUserResolver } from '@/apollo/user'; import { isServerError } from '@/types/apollo'; @@ -13,13 +13,18 @@ import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN } from '@/constants'; import { logout, saveTokenData } from '@/utils/auth'; import { SnackbarProgrammatic as Snackbar } from 'buefy'; import { defaultError, errors, IError, refreshSuggestion } from '@/utils/errors'; +import { Socket as PhoenixSocket } from 'phoenix'; +import * as AbsintheSocket from '@absinthe/socket'; +import { createAbsintheSocketLink } from '@absinthe/socket-apollo-link'; +import { getMainDefinition } from 'apollo-utilities'; // Install the vue plugin Vue.use(VueApollo); -// Http endpoint +// Endpoints const httpServer = GRAPHQL_API_ENDPOINT || 'http://localhost:4000'; const httpEndpoint = GRAPHQL_API_FULL_PATH || `${httpServer}/api`; +const wsEndpoint = `ws${httpServer.substring(httpServer.indexOf(':'))}/graphql_socket`; const fragmentMatcher = new IntrospectionFragmentMatcher({ introspectionQueryResultData: { @@ -60,10 +65,6 @@ const authMiddleware = new ApolloLink((operation, forward) => { return null; }); -const uploadLink = createLink({ - uri: httpEndpoint, -}); - let refreshingTokenPromise: Promise | undefined; let alreadyRefreshedToken = false; const errorLink = onError(({ graphQLErrors, networkError, forward, operation }) => { @@ -126,9 +127,38 @@ const computeErrorMessage = (message) => { return error.suggestRefresh === false ? error.value : `${error.value}
${refreshSuggestion}`; }; -const link = authMiddleware +const uploadLink = createLink({ + uri: httpEndpoint, +}); + +const phoenixSocket = new PhoenixSocket(wsEndpoint, { + params: () => { + const token = localStorage.getItem(AUTH_ACCESS_TOKEN); + if (token) { + return { token }; + } + return {}; + + }, +}); + +const absintheSocket = AbsintheSocket.create(phoenixSocket); +const wsLink = createAbsintheSocketLink(absintheSocket); + +const link = split( + // split based on operation type + ({ query }) => { + const definition = getMainDefinition(query); + return definition.kind === 'OperationDefinition' && + definition.operation === 'subscription'; + }, + wsLink, + uploadLink, +); + +const fullLink = authMiddleware .concat(errorLink) - .concat(uploadLink); + .concat(link); const cache = new InMemoryCache({ fragmentMatcher, @@ -143,7 +173,7 @@ const cache = new InMemoryCache({ const apolloClient = new ApolloClient({ cache, - link, + link: fullLink, connectToDevTools: true, resolvers: buildCurrentUserResolver(cache), }); diff --git a/js/yarn.lock b/js/yarn.lock index a354fa3d..f50f0a8b 100644 --- a/js/yarn.lock +++ b/js/yarn.lock @@ -2,6 +2,31 @@ # yarn lockfile v1 +"@absinthe/socket-apollo-link@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@absinthe/socket-apollo-link/-/socket-apollo-link-0.2.1.tgz#449c93109c903403b948abb8e911d9847cca9125" + integrity sha512-QxEazdjUXth+XMTAdlODZwS5h7fUAq9LEIH5O/EN0c/pS7Q3dFrTM1ZiP6n/0VdSEc+xBZyTisN63N2cPgE8ZQ== + dependencies: + "@absinthe/socket" "0.2.1" + "@babel/runtime" "7.2.0" + apollo-link "1.2.5" + core-js "2.6.0" + flow-static-land "0.2.8" + graphql "14.0.2" + zen-observable "0.8.11" + +"@absinthe/socket@0.2.1", "@absinthe/socket@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@absinthe/socket/-/socket-0.2.1.tgz#dd0d7bfc8e149f8376429c7fc2e87ac958578b91" + integrity sha512-rCuMRG4WndooGR+QfU5v+xL6U8YKEXFyvjqYt0qTHupAh+k+tpD6a5dlxcLO0g38p/hb1I12OzKvl+0G1XYCkA== + dependencies: + "@babel/runtime" "7.2.0" + "@jumpn/utils-array" "0.3.4" + "@jumpn/utils-composite" "0.7.0" + "@jumpn/utils-graphql" "0.6.0" + core-js "2.6.0" + zen-observable "0.8.11" + "@babel/code-frame@7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" @@ -16,15 +41,15 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.0.0", "@babel/core@^7.6.4": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.4.tgz#37e864532200cb6b50ee9a4045f5f817840166ab" - integrity sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ== +"@babel/core@^7.0.0", "@babel/core@^7.7.4": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.5.tgz#ae1323cd035b5160293307f50647e83f8ba62f7e" + integrity sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw== dependencies: "@babel/code-frame" "^7.5.5" "@babel/generator" "^7.7.4" "@babel/helpers" "^7.7.4" - "@babel/parser" "^7.7.4" + "@babel/parser" "^7.7.5" "@babel/template" "^7.7.4" "@babel/traverse" "^7.7.4" "@babel/types" "^7.7.4" @@ -155,10 +180,10 @@ dependencies: "@babel/types" "^7.7.4" -"@babel/helper-module-transforms@^7.7.4": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.4.tgz#8d7cdb1e1f8ea3d8c38b067345924ac4f8e0879a" - integrity sha512-ehGBu4mXrhs0FxAqN8tWkzF8GSIGAiEumu4ONZ/hD9M88uHcD+Yu2ttKfOCgwzoesJOJrtQh7trI5YPbRtMmnA== +"@babel/helper-module-transforms@^7.7.4", "@babel/helper-module-transforms@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz#d044da7ffd91ec967db25cd6748f704b6b244835" + integrity sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw== dependencies: "@babel/helper-module-imports" "^7.7.4" "@babel/helper-simple-access" "^7.7.4" @@ -250,10 +275,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.2.3", "@babel/parser@^7.7.4": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.4.tgz#75ab2d7110c2cf2fa949959afb05fa346d2231bb" - integrity sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g== +"@babel/parser@^7.2.3", "@babel/parser@^7.7.4", "@babel/parser@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71" + integrity sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig== "@babel/plugin-proposal-async-generator-functions@^7.7.4": version "7.7.4" @@ -264,7 +289,7 @@ "@babel/helper-remap-async-to-generator" "^7.7.4" "@babel/plugin-syntax-async-generators" "^7.7.4" -"@babel/plugin-proposal-class-properties@^7.4.4": +"@babel/plugin-proposal-class-properties@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.7.4.tgz#2f964f0cb18b948450362742e33e15211e77c2ba" integrity sha512-EcuXeV4Hv1X3+Q1TsuOmyyxeTRiSqurGJ26+I/FW1WbymmRRapVORm6x1Zl3iDIHyRxEs+VXWp6qnlcfcJSbbw== @@ -272,7 +297,7 @@ "@babel/helper-create-class-features-plugin" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-proposal-decorators@^7.6.0": +"@babel/plugin-proposal-decorators@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.7.4.tgz#58c1e21d21ea12f9f5f0a757e46e687b94a7ab2b" integrity sha512-GftcVDcLCwVdzKmwOBDjATd548+IE+mBo7ttgatqNDR7VG7GqIuZPtRWlMLHbhTXhcnFZiGER8iIYl1n/imtsg== @@ -335,7 +360,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-dynamic-import@^7.0.0", "@babel/plugin-syntax-dynamic-import@^7.7.4": +"@babel/plugin-syntax-dynamic-import@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz#29ca3b4415abfe4a5ec381e903862ad1a54c3aec" integrity sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg== @@ -349,7 +374,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.2.0": +"@babel/plugin-syntax-jsx@^7.2.0", "@babel/plugin-syntax-jsx@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.7.4.tgz#dab2b56a36fb6c3c222a1fbc71f7bf97f327a9ec" integrity sha512-wuy6fiMe9y7HeZBWXYCGt2RGxZOj0BImZ9EyXJVnVGBKO/Br592rbR3rtIQn0eQhAk9vqaKP5n8tVqEFBQMfLg== @@ -488,21 +513,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-amd@^7.7.4": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.4.tgz#276b3845ca2b228f2995e453adc2e6f54d72fb71" - integrity sha512-/542/5LNA18YDtg1F+QHvvUSlxdvjZoD/aldQwkq+E3WCkbEjNSN9zdrOXaSlfg3IfGi22ijzecklF/A7kVZFQ== +"@babel/plugin-transform-modules-amd@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz#39e0fb717224b59475b306402bb8eedab01e729c" + integrity sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ== dependencies: - "@babel/helper-module-transforms" "^7.7.4" + "@babel/helper-module-transforms" "^7.7.5" "@babel/helper-plugin-utils" "^7.0.0" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-commonjs@^7.7.4": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.4.tgz#bee4386e550446343dd52a571eda47851ff857a3" - integrity sha512-k8iVS7Jhc367IcNF53KCwIXtKAH7czev866ThsTgy8CwlXjnKZna2VHwChglzLleYrcHz1eQEIJlGRQxB53nqA== +"@babel/plugin-transform-modules-commonjs@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz#1d27f5eb0bcf7543e774950e5b2fa782e637b345" + integrity sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q== dependencies: - "@babel/helper-module-transforms" "^7.7.4" + "@babel/helper-module-transforms" "^7.7.5" "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-simple-access" "^7.7.4" babel-plugin-dynamic-import-node "^2.3.0" @@ -562,10 +587,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-regenerator@^7.7.4": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.4.tgz#d18eac0312a70152d7d914cbed2dc3999601cfc0" - integrity sha512-e7MWl5UJvmPEwFJTwkBlPmqixCtr9yAASBqff4ggXTNicZiwbF8Eefzm6NVgfiBp7JdAGItecnctKTgH44q2Jw== +"@babel/plugin-transform-regenerator@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz#3a8757ee1a2780f390e89f246065ecf59c26fce9" + integrity sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw== dependencies: regenerator-transform "^0.14.0" @@ -576,10 +601,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-runtime@^7.6.2": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.4.tgz#51fe458c1c1fa98a8b07934f4ed38b6cd62177a6" - integrity sha512-O8kSkS5fP74Ad/8pfsCMGa8sBRdLxYoSReaARRNSz3FbFQj3z/QUvoUmJ28gn9BO93YfnXc3j+Xyaqe8cKDNBQ== +"@babel/plugin-transform-runtime@^7.7.4": + version "7.7.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.6.tgz#4f2b548c88922fb98ec1c242afd4733ee3e12f61" + integrity sha512-tajQY+YmXR7JjTwRvwL4HePqoL3DYxpYXIHKVvrOIvJmeHe2y1w4tz5qz9ObUDC9m76rCzIMPyn4eERuwA4a4A== dependencies: "@babel/helper-module-imports" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" @@ -631,7 +656,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/polyfill@^7.6.0": +"@babel/polyfill@^7.7.0": version "7.7.0" resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.7.0.tgz#e1066e251e17606ec7908b05617f9b7f8180d8f3" integrity sha512-/TS23MVvo34dFmf8mwCisCbWGrfhbiWZSwBo6HkADTBhUa2Q/jWltyY/tpofz/b6/RIhqaqQcquptCirqIhOaQ== @@ -639,10 +664,10 @@ core-js "^2.6.5" regenerator-runtime "^0.13.2" -"@babel/preset-env@^7.6.3": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.4.tgz#ccaf309ae8d1ee2409c85a4e2b5e280ceee830f8" - integrity sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g== +"@babel/preset-env@^7.7.4": + version "7.7.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.6.tgz#39ac600427bbb94eec6b27953f1dfa1d64d457b2" + integrity sha512-k5hO17iF/Q7tR9Jv8PdNBZWYW6RofxhnxKjBMc0nG4JTaWvOTiPoO/RLFwAKcA4FpmuBFm6jkoqaRJLGi0zdaQ== dependencies: "@babel/helper-module-imports" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" @@ -672,8 +697,8 @@ "@babel/plugin-transform-function-name" "^7.7.4" "@babel/plugin-transform-literals" "^7.7.4" "@babel/plugin-transform-member-expression-literals" "^7.7.4" - "@babel/plugin-transform-modules-amd" "^7.7.4" - "@babel/plugin-transform-modules-commonjs" "^7.7.4" + "@babel/plugin-transform-modules-amd" "^7.7.5" + "@babel/plugin-transform-modules-commonjs" "^7.7.5" "@babel/plugin-transform-modules-systemjs" "^7.7.4" "@babel/plugin-transform-modules-umd" "^7.7.4" "@babel/plugin-transform-named-capturing-groups-regex" "^7.7.4" @@ -681,7 +706,7 @@ "@babel/plugin-transform-object-super" "^7.7.4" "@babel/plugin-transform-parameters" "^7.7.4" "@babel/plugin-transform-property-literals" "^7.7.4" - "@babel/plugin-transform-regenerator" "^7.7.4" + "@babel/plugin-transform-regenerator" "^7.7.5" "@babel/plugin-transform-reserved-words" "^7.7.4" "@babel/plugin-transform-shorthand-properties" "^7.7.4" "@babel/plugin-transform-spread" "^7.7.4" @@ -691,23 +716,22 @@ "@babel/plugin-transform-unicode-regex" "^7.7.4" "@babel/types" "^7.7.4" browserslist "^4.6.0" - core-js-compat "^3.1.1" + core-js-compat "^3.4.7" invariant "^2.2.2" js-levenshtein "^1.1.3" semver "^5.5.0" -"@babel/runtime-corejs3@^7.6.3": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.7.4.tgz#f861adc1cecb9903dfd66ea97917f02ff8d79888" - integrity sha512-BBIEhzk8McXDcB3IbOi8zQPzzINUp4zcLesVlBSOcyGhzPUU8Xezk5GAG7Sy5GVhGmAO0zGd2qRSeY2g4Obqxw== +"@babel/runtime@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f" + integrity sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg== dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.2" + regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.6.3": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.4.tgz#b23a856751e4bf099262f867767889c0e3fe175b" - integrity sha512-r24eVUUr0QqNZa+qrImUk8fn5SPhHq+IfYvIoIMg0do3GdK9sMdiLKP3GYVVaxpPKORgm8KRKaNTEhAjgIpLMw== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.7.4": + version "7.7.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f" + integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw== dependencies: regenerator-runtime "^0.13.2" @@ -831,6 +855,35 @@ cssnano-preset-default "^4.0.0" postcss "^7.0.0" +"@jumpn/utils-array@0.3.4": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@jumpn/utils-array/-/utils-array-0.3.4.tgz#fb4310120108f659dab54075ef93abc56137de5e" + integrity sha1-+0MQEgEI9lnatUB175OrxWE33l4= + dependencies: + babel-polyfill "6.26.0" + babel-runtime "6.26.0" + flow-static-land "0.2.7" + +"@jumpn/utils-composite@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@jumpn/utils-composite/-/utils-composite-0.7.0.tgz#1979db00dd9465ebc33826adab17f1dd48e6fd0c" + integrity sha512-kamRVYJLNvjMrnKKeu2RSFQHLUO/IYFo05gLI7GQcCk063mJzsjCCfRycCievIBI+5Sg8C7A5gwRYxkBA5jY8w== + dependencies: + "@jumpn/utils-array" "0.3.4" + babel-polyfill "6.26.0" + babel-runtime "6.26.0" + fast-deep-equal "1.0.0" + flow-static-land "0.2.8" + +"@jumpn/utils-graphql@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@jumpn/utils-graphql/-/utils-graphql-0.6.0.tgz#9afd384c14e3f4caf68fa3ebeb3270218b18e931" + integrity sha512-I5OSEh8Ed4FdLIcUTYzWdpO9noQOoWptdgF8yOZ0xhDD7h7E9IgPYxfy36qbC6v9xlpGTwQMu3Wn8ulkinG/MQ== + dependencies: + "@babel/runtime" "7.2.0" + core-js "2.6.0" + graphql "14.0.2" + "@kbrandwijk/swagger-to-graphql@2.4.3": version "2.4.3" resolved "https://registry.yarnpkg.com/@kbrandwijk/swagger-to-graphql/-/swagger-to-graphql-2.4.3.tgz#7c0fb2410eb0b6b9cc81fad28cc20f9386153cf1" @@ -847,9 +900,9 @@ yargs "^8.0.2" "@mdi/font@^4.5.95": - version "4.6.95" - resolved "https://registry.yarnpkg.com/@mdi/font/-/font-4.6.95.tgz#f97ecbdfd59c183d19f3a24a6fe4f84f579cf287" - integrity sha512-m5AjVnVS9Xi4xbQQt2rh+ClVcWT9oFqSmo8mZs7teOGbAmUrZ9km1RuNRNKIr7LV3vy/zCeEfMc154Z9l+ab0g== + version "4.7.95" + resolved "https://registry.yarnpkg.com/@mdi/font/-/font-4.7.95.tgz#46fddf35aad64dd623a8b1837f78ca4ed7bc48b1" + integrity sha512-/SWooHIFz2dXkQJk3VhEXSbBplOU1lIkGSELAmw0peFEgR8KPqyM//M3vD8WDZETuEOSRVhVqLevP3okrsM5dw== "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" @@ -924,9 +977,9 @@ "@types/babel-types" "*" "@types/chai@^4.2.3": - version "4.2.5" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.5.tgz#f8da153ebbe30babb0adc9a528b9ad32be3175a2" - integrity sha512-YvbLiIc0DbbhiANrfVObdkLEHJksQZVq0Uvfg550SRAKVYaEJy+V70j65BVe2WNp6E3HtKsUczeijHFCjba3og== + version "4.2.7" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.7.tgz#1c8c25cbf6e59ffa7d6b9652c78e547d9a41692d" + integrity sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g== "@types/color-name@^1.1.1": version "1.1.1" @@ -960,9 +1013,9 @@ "@types/leaflet" "*" "@types/leaflet@*", "@types/leaflet@^1.5.2": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.5.5.tgz#006c0aa89c4b5e62941717fa71a09e846423536c" - integrity sha512-Eyh1LMmW4OFgafL6rjLyGkMqFS5IzgwWHMSgTKbrsvwLjLaWH8Ae8CV5liRe8HSM731oOVDwAMIZgg9P0SO9tg== + version "1.5.6" + resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.5.6.tgz#cdad1f32328331b32ee4f63c24c59cba055c9b0c" + integrity sha512-a9gVDwmNNalKrsU124kS7Lv9eo0z95CCMJu1Fp7l+A+EQ7Vv0UJ7LFkjaxu176ebUOBDEqvjn7A2vrlq5kLtkw== dependencies: "@types/geojson" "*" @@ -982,9 +1035,9 @@ integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== "@types/node@*", "@types/node@>=6": - version "12.12.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.12.tgz#529bc3e73dbb35dd9e90b0a1c83606a9d3264bdb" - integrity sha512-MGuvYJrPU0HUwqF7LqvIj50RZUX23Z+m583KBygKYUZLlZ88n6w28XRNJRJgsHukLEnLz6w6SvxZoLgbr5wLqQ== + version "12.12.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.17.tgz#191b71e7f4c325ee0fb23bc4a996477d92b8c39b" + integrity sha512-Is+l3mcHvs47sKy+afn2O1rV4ldZFU7W8101cNlOd+MRbjM4Onida8jSZnJdTe/0Pcf25g9BNIUsuugmE6puHA== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -1071,28 +1124,26 @@ lodash.kebabcase "^4.1.1" svg-tags "^1.0.0" -"@vue/babel-preset-app@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.0.5.tgz#17cfe7cfe273dc4cca8d988d5b8ae15a42887fb3" - integrity sha512-EXq/eqqw0rpQjVNOz1AIC/K6c4/6VNva7PenMK+MmmE/n9wNHn3BFI5t8Dz3tkuKU57Zlln/HUKjfdm29cvrcw== +"@vue/babel-preset-app@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.1.1.tgz#a3982aca2e1a84d37457fdfdfc8da904d2b33b10" + integrity sha512-nmt+7q0/e1CmoSWmrP3tgAXDbKdLfWh7O7VeMYk0i1bMHBYqjACmk13AxRwlby+fet/9JOicl0ubZq/bEs81Hg== dependencies: - "@babel/core" "^7.6.4" - "@babel/helper-module-imports" "^7.0.0" - "@babel/plugin-proposal-class-properties" "^7.4.4" - "@babel/plugin-proposal-decorators" "^7.6.0" - "@babel/plugin-syntax-dynamic-import" "^7.0.0" - "@babel/plugin-syntax-jsx" "^7.0.0" - "@babel/plugin-transform-runtime" "^7.6.2" - "@babel/preset-env" "^7.6.3" - "@babel/runtime" "^7.6.3" - "@babel/runtime-corejs3" "^7.6.3" - "@vue/babel-preset-jsx" "^1.1.1" + "@babel/core" "^7.7.4" + "@babel/helper-module-imports" "^7.7.4" + "@babel/plugin-proposal-class-properties" "^7.7.4" + "@babel/plugin-proposal-decorators" "^7.7.4" + "@babel/plugin-syntax-dynamic-import" "^7.7.4" + "@babel/plugin-syntax-jsx" "^7.7.4" + "@babel/plugin-transform-runtime" "^7.7.4" + "@babel/preset-env" "^7.7.4" + "@babel/runtime" "^7.7.4" + "@vue/babel-preset-jsx" "^1.1.2" babel-plugin-dynamic-import-node "^2.2.0" - babel-plugin-module-resolver "^3.2.0" - core-js "^3.3.2" - core-js-compat "^3.3.2" + core-js "^3.4.3" + core-js-compat "^3.4.3" -"@vue/babel-preset-jsx@^1.1.1": +"@vue/babel-preset-jsx@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@vue/babel-preset-jsx/-/babel-preset-jsx-1.1.2.tgz#2e169eb4c204ea37ca66c2ea85a880bfc99d4f20" integrity sha512-zDpVnFpeC9YXmvGIDSsKNdL7qCG2rA3gjywLYHPCKDT10erjxF4U+6ay9X6TW5fl4GsDlJp9bVfAVQAAVzxxvQ== @@ -1139,108 +1190,112 @@ "@vue/babel-plugin-transform-vue-jsx" "^1.1.2" camelcase "^5.0.0" -"@vue/cli-overlay@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.0.5.tgz#9fa5936a2e26f94b0d737b475c6d62e1c5813aaa" - integrity sha512-guVLEZoV1QtCEjByutSizgBQin/L0Pvz2siQqU+eOFXzXs7P/MtyUYhbKh07AUHHEQEbqGJOvxSIks/fLfrp4w== +"@vue/cli-overlay@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.1.1.tgz#d4559c12e50075a4817ac977cc0bde504520d9de" + integrity sha512-y5fBtw/aXUem3B/xVb37xB71gq2hNAZsbhW0t4DIGuNConS+Tps41MKWb7dbxq4TLyH7MWX3aJbDzuUGanBMqQ== "@vue/cli-plugin-babel@^4.0.3": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.0.5.tgz#e01b615952fce444d10a156608a69db9d850909f" - integrity sha512-2B/DDgdWvE6mBRhpUu9tNkaoFLopxr5/2tzXbGLH8Lkr8HToNERZ4RoGSSV1akTsosAxXSER9wGSa9jXhZ41iA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.1.1.tgz#aa27100a25a385f54e2a7a2bc97af47dfdd57b25" + integrity sha512-1TyuKEFFlEQwXvVohhUTJEa85o29Z4F62p1nzR+EIMOudo9tHaO1WWPqShZ2Trehrl7cpIjul9dhRUuyhwKiaQ== dependencies: - "@babel/core" "^7.6.4" - "@vue/babel-preset-app" "^4.0.5" - "@vue/cli-shared-utils" "^4.0.5" + "@babel/core" "^7.7.4" + "@vue/babel-preset-app" "^4.1.1" + "@vue/cli-shared-utils" "^4.1.1" babel-loader "^8.0.6" + cache-loader "^4.1.0" + thread-loader "^2.1.3" webpack "^4.0.0" "@vue/cli-plugin-e2e-cypress@^4.0.3": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-plugin-e2e-cypress/-/cli-plugin-e2e-cypress-4.0.5.tgz#81bff4881b306214f46c608f236073358c97589a" - integrity sha512-OUIxVV6Sp5FyfJVLV5zzLAm8lC6pP5LFqBVa6akM3OP4mzxWkdIbhJ3/7mZGCDZoHX5vsZkp6eSE3uAWp6pA8A== + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-plugin-e2e-cypress/-/cli-plugin-e2e-cypress-4.1.1.tgz#22f3568655550d031b5b4a44ba796013e1e19c97" + integrity sha512-+wxi66LEhO1QycSaoUX2pbBog1MSU8hFaKyqb4v4ob9DuPLyHcGVHoo5tdgb+RkTXIQjSKVptMqgb/OMEivPbg== dependencies: - "@vue/cli-shared-utils" "^4.0.5" + "@vue/cli-shared-utils" "^4.1.1" cypress "^3.3.1" eslint-plugin-cypress "^2.7.0" "@vue/cli-plugin-pwa@^4.0.3": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.0.5.tgz#e5b65fa453a52af1a06f0d71e782625d52e260e1" - integrity sha512-0dzN1K6khVOQ9V3DJrLxx/82snaTfoHtl7kZd7lc92bP8dFkxuU7qE12hlO0Glbja32K0QCZEVljyjDALYMvTA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.1.1.tgz#1fde8743d8039cc655f96c9a81112ef46db109ca" + integrity sha512-fmyMfW8S7/PjeCPHCQwoRI6JbzAdKIBczcUJkhoyfTZV4iqrCn7mpI0vRhdBYLKplloZxVKqjRUu5yKyiVerXg== dependencies: - "@vue/cli-shared-utils" "^4.0.5" + "@vue/cli-shared-utils" "^4.1.1" webpack "^4.0.0" workbox-webpack-plugin "^4.3.1" -"@vue/cli-plugin-router@^4.0.3", "@vue/cli-plugin-router@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.0.5.tgz#2891520a6293bbd6d7784fa14e1041a41ba6d4fd" - integrity sha512-pSbw7CZZd6fQHomwIsxX/qyMBFeXsxhUOrwjmp1s03qe/VjsyREIsLW+L5BiXoHZQFdqfH2NaOF9Uivxiv2cvQ== +"@vue/cli-plugin-router@^4.0.3", "@vue/cli-plugin-router@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.1.1.tgz#85c7f2d34f9217ad70b49a71dae6bf7067042759" + integrity sha512-n2L2LPLnNcCeeVwJUrbRexi/coBnexIZorRTihinIkUzv3J+Qiw0KPsGjg1RF6UrieFlRhqcY3a5GloC+L0SBQ== dependencies: - "@vue/cli-shared-utils" "^4.0.5" + "@vue/cli-shared-utils" "^4.1.1" "@vue/cli-plugin-typescript@^4.0.3": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-plugin-typescript/-/cli-plugin-typescript-4.0.5.tgz#9c04e8bda83fb1957008d8878035a133b6a318b7" - integrity sha512-90F2UvbJxzf9YSeGg+k8jfd8ALGNx4RChKAhko/6JR+A5GW1nYwN5Vcz174aj0obxra2etFgmY+B65AhoGD7Xg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-plugin-typescript/-/cli-plugin-typescript-4.1.1.tgz#cc43cb82efb0b4e504c5de0e0e0cd21665dc158c" + integrity sha512-HsRughkv/BJ3Q6VytnmOUkJGHGTNJduLRVnBdMC7CkHFn1S72Vxn2fOecWxPkJpFqhujf6butisd/ErT354zuw== dependencies: "@types/webpack-env" "^1.13.9" - "@vue/cli-shared-utils" "^4.0.5" + "@vue/cli-shared-utils" "^4.1.1" + cache-loader "^4.1.0" fork-ts-checker-webpack-plugin "^1.5.1" globby "^9.2.0" - ts-loader "^6.2.0" - tslint "^5.16.0" + thread-loader "^2.1.3" + ts-loader "^6.2.1" + tslint "^5.20.1" webpack "^4.0.0" yorkie "^2.0.0" "@vue/cli-plugin-unit-mocha@^4.0.3": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-plugin-unit-mocha/-/cli-plugin-unit-mocha-4.0.5.tgz#c7a599307b044828f8b4c2572f6d83fa04b48ba6" - integrity sha512-DVpl73eWDt6rcebzzAzWXeacGxkPVbVWA5P63sBpiS0NIBz8Byt3OOS90JeBVF8NLKxtA3/2Ub8sxwM3aFiZsA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-plugin-unit-mocha/-/cli-plugin-unit-mocha-4.1.1.tgz#0d3d435d9d200ba6893f0a46aa8825e60e19d2f2" + integrity sha512-RbWrkiDrstvAgmSZ0GLcNg5rErGLQEFRtxAXBDiJEcQHwB90jStPxutMCyb2lynCjhWh1VWTDHrjkmQejOKQZw== dependencies: - "@vue/cli-shared-utils" "^4.0.5" - jsdom "^15.2.0" + "@vue/cli-shared-utils" "^4.1.1" + jsdom "^15.2.1" jsdom-global "^3.0.2" mocha "^6.2.2" - mochapack "^1.1.5" + mochapack "^1.1.12" -"@vue/cli-plugin-vuex@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.0.5.tgz#7565ce75a0b6bb6c68a0ad216be54a583c9795bc" - integrity sha512-stppb+Fw5J84EA9EPs2jpclCr1lJbYtJClmEIP8RZZzGm0xGGdwMEK+VUOYjaFo4kMrReteSiMww8jxdRCeijg== +"@vue/cli-plugin-vuex@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.1.1.tgz#81908ee66370dda162b5517afc869f91d3abe2bf" + integrity sha512-AkK+FCrghjcyxUgfJyxpSuyJ0w9FSlwQEZv7+aRhs9j+YguROdjKA8DDTp8Ve1yboALeNMRv8eXApQEVC3xFQA== "@vue/cli-service@^4.0.3": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.0.5.tgz#1bfc19be4d2b8dd4bba163711312d01924b31a17" - integrity sha512-ScVaGzbLbtiTqlzFBBpGoYEdw6kZTSsQwgBJ2UjO5GZwVhx6Tbcwusw+pUC2zxUPoFki5FrTdbBZO6lrVkwATw== + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.1.1.tgz#a14e9d455752f1a8e4e87ac436ebde88cad82276" + integrity sha512-woEIXXc22DXKrSO+FAFnrzhnysJcAB1UTF2t0NIPrxnngm0O2YSO0idmy01a2H/q3auMCVNQdzggQ4JWUeV7Gg== dependencies: "@intervolga/optimize-cssnano-plugin" "^1.0.5" "@soda/friendly-errors-webpack-plugin" "^1.7.1" - "@vue/cli-overlay" "^4.0.5" - "@vue/cli-plugin-router" "^4.0.5" - "@vue/cli-plugin-vuex" "^4.0.5" - "@vue/cli-shared-utils" "^4.0.5" - "@vue/component-compiler-utils" "^3.0.0" + "@vue/cli-overlay" "^4.1.1" + "@vue/cli-plugin-router" "^4.1.1" + "@vue/cli-plugin-vuex" "^4.1.1" + "@vue/cli-shared-utils" "^4.1.1" + "@vue/component-compiler-utils" "^3.0.2" "@vue/preload-webpack-plugin" "^1.1.0" "@vue/web-component-wrapper" "^1.2.0" acorn "^6.1.1" acorn-walk "^6.1.1" address "^1.1.2" - autoprefixer "^9.5.1" - browserslist "^4.7.1" + autoprefixer "^9.7.2" + browserslist "^4.7.3" cache-loader "^4.1.0" case-sensitive-paths-webpack-plugin "^2.2.0" chalk "^2.4.2" - cli-highlight "^2.1.1" + cli-highlight "^2.1.4" clipboardy "^2.0.0" cliui "^5.0.0" - copy-webpack-plugin "^5.0.3" + copy-webpack-plugin "^5.0.5" css-loader "^3.1.0" cssnano "^4.1.10" current-script-polyfill "^1.0.0" debug "^4.1.1" - default-gateway "^5.0.2" + default-gateway "^5.0.5" dotenv "^8.2.0" dotenv-expand "^5.1.0" file-loader "^4.2.0" @@ -1263,20 +1318,21 @@ source-map-url "^0.4.0" ssri "^6.0.1" string.prototype.padend "^3.0.0" - terser-webpack-plugin "^2.1.2" + terser-webpack-plugin "^2.2.1" thread-loader "^2.1.3" url-loader "^2.2.0" - vue-loader "^15.7.0" + vue-loader "^15.7.2" + vue-style-loader "^4.1.0" webpack "^4.0.0" webpack-bundle-analyzer "^3.6.0" webpack-chain "^6.0.0" - webpack-dev-server "^3.8.2" + webpack-dev-server "^3.9.0" webpack-merge "^4.2.2" -"@vue/cli-shared-utils@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.0.5.tgz#dd263fa3b3a75c11cdc64376d4c45470fba6b270" - integrity sha512-NlNZ4Dx5QcP5uO5fCOLgkN2tbhNan5EcptPvXawW/md18cIpMlKbph6L6lEfJj8vrSvTUf2i/FyoFSh1rV53hw== +"@vue/cli-shared-utils@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.1.1.tgz#79e26b56fda185fda00e5787a8f4aac56757b123" + integrity sha512-nsxNW8Sy9y2yx/r9DqgZoYg/DoygvASIQl0XXG+imQUDWEXKmD6UZA6y5ANfStCljzZ/wd7WgWP+txmjy6exOw== dependencies: "@hapi/joi" "^15.0.1" chalk "^2.4.1" @@ -1287,14 +1343,15 @@ open "^6.3.0" ora "^3.4.0" request "^2.87.0" - request-promise-native "^1.0.7" + request-promise-native "^1.0.8" semver "^6.1.0" string.prototype.padstart "^3.0.0" + strip-ansi "^6.0.0" -"@vue/component-compiler-utils@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.0.2.tgz#7daf8aaf0d5faa66e7c8a1f6fea315630e45fbc9" - integrity sha512-BSnY2PmW4QwU1AOcGSNYAmEPLjdQ9itl1YpLCWtpwMA5Jy/aqWNuzZ9+ZZ8h6yZJ53W95tVkEP6yrXJ/zUHdEA== +"@vue/component-compiler-utils@^3.0.0", "@vue/component-compiler-utils@^3.0.2": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.1.0.tgz#64cd394925f5af1f9c3228c66e954536f5311857" + integrity sha512-OJ7swvl8LtKtX5aYP8jHhO6fQBIRIGkU6rvWzK+CGJiNOnvg16nzcBkd9qMZzW8trI2AsqAKx263nv7kb5rhZw== dependencies: consolidate "^0.15.1" hash-sum "^1.0.2" @@ -1307,22 +1364,23 @@ vue-template-es2015-compiler "^1.9.0" "@vue/eslint-config-typescript@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@vue/eslint-config-typescript/-/eslint-config-typescript-5.0.0.tgz#6e8452f1fda460e935449b8c0b72372106ac215f" - integrity sha512-YDeZyirzMDI4sgrYvjJ1kCIMBMF36b56BxNeqyVVvOsd8F7/cggdNdqVsa3PVvMJGFX9bSn7KEUvqbUxphMCuQ== + version "5.0.1" + resolved "https://registry.yarnpkg.com/@vue/eslint-config-typescript/-/eslint-config-typescript-5.0.1.tgz#06d986dd91cadc60583158dadc7c9c4f0372af31" + integrity sha512-gpP8zQA0rJ93ROkAW5fbOJB3EG7p6U70Jb0/CVOjhs5zuEXf1WgLk4gP+zUZGwiRpLoXBa5oIRH4hLQDbS1/eg== "@vue/preload-webpack-plugin@^1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.1.tgz#18723530d304f443021da2292d6ec9502826104a" integrity sha512-8VCoJeeH8tCkzhkpfOkt+abALQkS11OIHhte5MBzYaKMTqK0A3ZAKEUVAffsOklhEv7t0yrQt696Opnu9oAx+w== -"@vue/test-utils@^1.0.0-beta.29": - version "1.0.0-beta.29" - resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.29.tgz#c942cf25e891cf081b6a03332b4ae1ef430726f0" - integrity sha512-yX4sxEIHh4M9yAbLA/ikpEnGKMNBCnoX98xE1RwxfhQVcn0MaXNSj1Qmac+ZydTj6VBSEVukchBogXBTwc+9iA== +"@vue/test-utils@^1.0.0-beta.30": + version "1.0.0-beta.30" + resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.30.tgz#d5f26d1e2411fdb7fa7fdedb61b4b4ea4194c49d" + integrity sha512-Wyvcha9fNk8+kzTDwb3xWGjPkCPzHSYSwKP6MplrPTG/auhqoad7JqUEceZLc6u7AU4km2pPQ8/m9s0RgCZ0NA== dependencies: dom-event-types "^1.0.0" - lodash "^4.17.4" + lodash "^4.17.15" + pretty "^2.0.0" "@vue/web-component-wrapper@^1.2.0": version "1.2.0" @@ -1565,10 +1623,10 @@ acorn@^4.0.4, acorn@~4.0.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c= -acorn@^6.0.1, acorn@^6.0.7, acorn@^6.1.1, acorn@^6.2.1, acorn@~6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" - integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== +acorn@^6.0.1, acorn@^6.0.7, acorn@^6.1.1, acorn@^6.2.1, acorn@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784" + integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw== acorn@^7.1.0: version "7.1.0" @@ -1900,6 +1958,22 @@ apollo-link-http@^1.3.2, apollo-link-http@^1.5.16: apollo-link-http-common "^0.2.15" tslib "^1.9.3" +apollo-link-ws@^1.0.19: + version "1.0.19" + resolved "https://registry.yarnpkg.com/apollo-link-ws/-/apollo-link-ws-1.0.19.tgz#dfa871d4df883a8777c9556c872fc892e103daa5" + integrity sha512-mRXmeUkc55ixOdYRtfq5rq3o9sboKghKABKroDVhJnkdS56zthBEWMAD+phajujOUbqByxjok0te8ABqByBdeQ== + dependencies: + apollo-link "^1.2.13" + tslib "^1.9.3" + +apollo-link@1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.5.tgz#f54932d6b8f1412a35e088bc199a116bce3f1f16" + integrity sha512-GJHEE4B06oEB58mpRRwW6ISyvgX2aCqCLjpcE3M/6/4e+ZVeX7fRGpMJJDq2zZ8n7qWdrEuY315JfxzpsJmUhA== + dependencies: + apollo-utilities "^1.0.0" + zen-observable-ts "^0.8.12" + apollo-link@^1.0.0, apollo-link@^1.0.7, apollo-link@^1.2.11, apollo-link@^1.2.13: version "1.2.13" resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.13.tgz#dff00fbf19dfcd90fddbc14b6a3f9a771acac6c4" @@ -1910,7 +1984,7 @@ apollo-link@^1.0.0, apollo-link@^1.0.7, apollo-link@^1.2.11, apollo-link@^1.2.13 tslib "^1.9.3" zen-observable-ts "^0.8.20" -apollo-utilities@1.3.2, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2: +apollo-utilities@1.3.2, apollo-utilities@^1.0.0, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9" integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg== @@ -2125,13 +2199,13 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@^9.5.1: - version "9.7.2" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.2.tgz#26cf729fbb709323b40171a874304884dcceffed" - integrity sha512-LCAfcdej1182uVvPOZnytbq61AhnOZ/4JelDaJGDeNwewyU1AMaNthcHsyz1NRjTmd2FkurMckLWfkHg3Z//KA== +autoprefixer@^9.7.2: + version "9.7.3" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.3.tgz#fd42ed03f53de9beb4ca0d61fb4f7268a9bb50b4" + integrity sha512-8T5Y1C5Iyj6PgkPSFd0ODvK9DIleuPKUPYniNxybS47g2k2wFgLZ46lGQHlBuGKIAEV8fbCDfKCCRS1tvOgc3Q== dependencies: - browserslist "^4.7.3" - caniuse-lite "^1.0.30001010" + browserslist "^4.8.0" + caniuse-lite "^1.0.30001012" chalk "^2.4.2" normalize-range "^0.1.2" num2fraction "^1.2.2" @@ -2144,9 +2218,9 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + version "1.9.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c" + integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A== babel-code-frame@^6.22.0: version "6.26.0" @@ -2181,17 +2255,6 @@ babel-plugin-dynamic-import-node@^2.2.0, babel-plugin-dynamic-import-node@^2.3.0 dependencies: object.assign "^4.1.0" -babel-plugin-module-resolver@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-3.2.0.tgz#ddfa5e301e3b9aa12d852a9979f18b37881ff5a7" - integrity sha512-tjR0GvSndzPew/Iayf4uICWZqjBwnlMWjSx6brryfQ81F9rxBVqwDJtFCV8oOs0+vJeefK9TmdZtkIFdFe1UnA== - dependencies: - find-babel-config "^1.1.0" - glob "^7.1.2" - pkg-up "^2.0.0" - reselect "^3.0.1" - resolve "^1.4.0" - babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -2205,7 +2268,16 @@ babel-plugin-transform-object-rest-spread@^6.26.0: babel-plugin-syntax-object-rest-spread "^6.8.0" babel-runtime "^6.26.0" -babel-runtime@^6.25.0, babel-runtime@^6.26.0: +babel-polyfill@6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" + integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM= + dependencies: + babel-runtime "^6.26.0" + core-js "^2.5.0" + regenerator-runtime "^0.10.5" + +babel-runtime@6.26.0, babel-runtime@^6.25.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= @@ -2311,9 +2383,9 @@ bluebird@3.5.0: integrity sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw= bluebird@^3.1.1, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.5: - version "3.7.1" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de" - integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg== + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" @@ -2503,14 +2575,14 @@ browserslist@4.7.0: electron-to-chromium "^1.3.247" node-releases "^1.1.29" -browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.7.1, browserslist@^4.7.3: - version "4.7.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.3.tgz#02341f162b6bcc1e1028e30624815d4924442dc3" - integrity sha512-jWvmhqYpx+9EZm/FxcZSbUZyDEvDTLDi3nSAKbzEkyWvtI0mNSmUosey+5awDW1RUlrgXbQb5A6qY1xQH9U6MQ== +browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.7.3, browserslist@^4.8.0, browserslist@^4.8.2: + version "4.8.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.2.tgz#b45720ad5fbc8713b7253c20766f701c9a694289" + integrity sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA== dependencies: - caniuse-lite "^1.0.30001010" - electron-to-chromium "^1.3.306" - node-releases "^1.1.40" + caniuse-lite "^1.0.30001015" + electron-to-chromium "^1.3.322" + node-releases "^1.1.42" buble@0.19.8, buble@^0.19.7: version "0.19.8" @@ -2769,10 +2841,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30001010: - version "1.0.30001012" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001012.tgz#653ec635e815b9e0fb801890923b0c2079eb34ec" - integrity sha512-7RR4Uh04t9K1uYRWzOJmzplgEOAXbfK72oVNokCdMzA67trrhPzy93ahKk1AWHiA0c58tD2P+NHqxrA8FZ+Trg== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30001012, caniuse-lite@^1.0.30001015: + version "1.0.30001015" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz#15a7ddf66aba786a71d99626bc8f2b91c6f0f5f0" + integrity sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ== capture-stack-trace@^1.0.0: version "1.0.1" @@ -3037,7 +3109,7 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-highlight@^2.1.1: +cli-highlight@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.4.tgz#098cb642cf17f42adc1c1145e07f960ec4d7522b" integrity sha512-s7Zofobm20qriqDoU9sXptQx0t2R9PEgac92mENNm7xaEe1hn71IIMsXMK+6encA6WRCWWxIGQbipr3q998tlQ== @@ -3300,7 +3372,7 @@ commander@2.17.x: resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== -commander@^2.11.0, commander@^2.12.1, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.7.1, commander@^2.9.0: +commander@^2.11.0, commander@^2.12.1, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.7.1, commander@^2.9.0, commander@~2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -3389,6 +3461,23 @@ concat-stream@1.6.2, concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" +condense-newlines@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f" + integrity sha1-PemFVTE5R10yUCyDsC9gaE0kxV8= + dependencies: + extend-shallow "^2.0.1" + is-whitespace "^0.3.0" + kind-of "^3.0.2" + +config-chain@^1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" + integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + configstore@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" @@ -3504,10 +3593,10 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -copy-webpack-plugin@^5.0.3, copy-webpack-plugin@^5.0.4: - version "5.0.5" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.5.tgz#731df6a837a2ef0f8f8e2345bdfe9b7c62a2da68" - integrity sha512-7N68eIoQTyudAuxkfPT7HzGoQ+TsmArN/I3HFwG+lVE3FNzqvZKIiaxtYh4o3BIznioxUvx9j26+Rtsc9htQUQ== +copy-webpack-plugin@^5.0.5, copy-webpack-plugin@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz#5481a03dea1123d88a988c6ff8b78247214f0b88" + integrity sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg== dependencies: cacache "^12.0.3" find-cache-dir "^2.1.0" @@ -3519,31 +3608,31 @@ copy-webpack-plugin@^5.0.3, copy-webpack-plugin@^5.0.4: normalize-path "^3.0.0" p-limit "^2.2.1" schema-utils "^1.0.0" - serialize-javascript "^2.1.0" + serialize-javascript "^2.1.2" webpack-log "^2.0.0" -core-js-compat@^3.1.1, core-js-compat@^3.3.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.4.2.tgz#652fa7c54652b7f6586a893e37001df55ea2ac37" - integrity sha512-W0Aj+LM3EAxxjD0Kp2o4be8UlnxIZHNupBv2znqrheR4aY2nOn91794k/xoSp+SxqqriiZpTsSwBtZr60cbkwQ== +core-js-compat@^3.4.3, core-js-compat@^3.4.7: + version "3.5.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.5.0.tgz#5a11a619a9e9dd2dcf1c742b2060bc4a2143e5b6" + integrity sha512-E7iJB72svRjJTnm9HDvujzNVMCm3ZcDYEedkJ/sDTNsy/0yooCd9Cg7GSzE7b4e0LfIkjijdB1tqg0pGwxWeWg== dependencies: - browserslist "^4.7.3" + browserslist "^4.8.2" semver "^6.3.0" -core-js-pure@^3.0.0: - version "3.4.2" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.4.2.tgz#ffd4ea4dc1f8517f75d4a929986a214629477417" - integrity sha512-6+iSif/3zO0bSkhjVY9o4MTdv36X+rO6rqs/UxQ+uxBevmC4fsfwyQwFVdZXXONmLlKVLiXCG8PDvQ2Gn/iteA== +core-js@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.0.tgz#1e30793e9ee5782b307e37ffa22da0eacddd84d4" + integrity sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw== -core-js@^2.4.0, core-js@^2.5.3, core-js@^2.5.7, core-js@^2.6.10, core-js@^2.6.5: - version "2.6.10" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.10.tgz#8a5b8391f8cc7013da703411ce5b585706300d7f" - integrity sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA== +core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.3, core-js@^2.5.7, core-js@^2.6.10, core-js@^2.6.5: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== -core-js@^3.3.2, core-js@^3.3.5: - version "3.4.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.4.2.tgz#ee2b1a60b50388d8ddcda8cdb44a92c7a9ea76df" - integrity sha512-bUTfqFWtNKWp73oNIfRkqwYZJeNT3lstzZcAkhhiuvDraRSgOH1/+F9ZklbpR4zpdKuo4cpXN8tKP7s61yjX+g== +core-js@^3.3.5, core-js@^3.4.3: + version "3.5.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.5.0.tgz#66df8e49be4bd775e6f952a9d083b756ad41c1ed" + integrity sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -3716,22 +3805,22 @@ css-loader@^2.1.1: schema-utils "^1.0.0" css-loader@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.2.0.tgz#bb570d89c194f763627fcf1f80059c6832d009b2" - integrity sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ== + version "3.3.2" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.3.2.tgz#41b2086528aa4fbf8c0692e874bc14f081129b21" + integrity sha512-4XSiURS+YEK2fQhmSaM1onnUm0VKWNf6WWBYjkp9YbSDGCBTVZ5XOM6Gkxo8tLgQlzkZOBJvk9trHlDk4gjEYg== dependencies: camelcase "^5.3.1" cssesc "^3.0.0" icss-utils "^4.1.1" loader-utils "^1.2.3" normalize-path "^3.0.0" - postcss "^7.0.17" + postcss "^7.0.23" postcss-modules-extract-imports "^2.0.0" postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.1.0" + postcss-modules-scope "^2.1.1" postcss-modules-values "^3.0.0" - postcss-value-parser "^4.0.0" - schema-utils "^2.0.0" + postcss-value-parser "^4.0.2" + schema-utils "^2.6.0" css-select-base-adapter@^0.1.1: version "0.1.1" @@ -3918,9 +4007,9 @@ cyclist@^1.0.1: integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= cypress@^3.3.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.6.1.tgz#4420957923879f60b7a5146ccbf81841a149b653" - integrity sha512-6n0oqENdz/oQ7EJ6IgESNb2M7Bo/70qX9jSJsAziJTC3kICfEMmJUlrAnP9bn+ut24MlXQST5nRXhUP5nRIx6A== + version "3.8.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.8.0.tgz#7d4cd08f81f9048ee36760cc9ee3b9014f9e84ab" + integrity sha512-gtEbqCgKETRc3pQFMsELRgIBNgiQg7vbOWTrCi7WE7bgOwNCaW9PEX8Jb3UN8z/maIp9WwzoFfeySfelYY7nRA== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/xvfb" "1.2.4" @@ -4073,7 +4162,7 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" -default-gateway@^5.0.2: +default-gateway@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-5.0.5.tgz#4fd6bd5d2855d39b34cc5a59505486e9aafc9b10" integrity sha512-z2RnruVmj8hVMmAnEJMTIJNijhKCDiGjbLP+BHJFOT7ld3Bo5qcIBpVYDniqhbMIIf+jZDlkP2MkPXiQy/DBLA== @@ -4088,9 +4177,9 @@ defaults@^1.0.3: clone "^1.0.2" defer-to-connect@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.0.tgz#b41bd7efa8508cef13f8456975f7a278c72833fd" - integrity sha512-WE2sZoctWm/v4smfCAdjYbrfS55JiMRdlY9ZubFhsYbteCK9+BvAx4YV7nPjYM6ZnX5BcoVKwfmyx9sIFTgQMQ== + version "1.1.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f" + integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ== define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" @@ -4440,6 +4529,16 @@ ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer "^5.0.1" +editorconfig@^0.15.3: + version "0.15.3" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" + integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g== + dependencies: + commander "^2.19.0" + lru-cache "^4.1.5" + semver "^5.6.0" + sigmund "^1.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -4450,10 +4549,10 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== -electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.306: - version "1.3.314" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.314.tgz#c186a499ed2c9057bce9eb8dca294d6d5450facc" - integrity sha512-IKDR/xCxKFhPts7h+VaSXS02Z1mznP3fli1BbXWXeN89i2gCzKraU8qLpEid8YzKcmZdZD3Mly3cn5/lY9xsBQ== +electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.322: + version "1.3.322" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8" + integrity sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA== elegant-spinner@^1.0.1: version "1.0.1" @@ -4552,10 +4651,27 @@ error-stack-parser@^2.0.0: dependencies: stackframe "^1.1.0" -es-abstract@^1.12.0, es-abstract@^1.4.3, es-abstract@^1.5.1: - version "1.16.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.2.tgz#4e874331645e9925edef141e74fc4bd144669d34" - integrity sha512-jYo/J8XU2emLXl3OLwfwtuFfuF2w6DYPs+xy9ZfVyPkDcrauu6LYrw/q2TyCtrbc/KUdCiC5e9UajRhgNkVopA== +es-abstract@^1.17.0-next.1: + version "1.17.0-next.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.0-next.1.tgz#94acc93e20b05a6e96dacb5ab2f1cb3a81fc2172" + integrity sha512-7MmGr03N7Rnuid6+wyhD9sHNE2n4tFSwExnU2lQl3lIo2ShXWGePY80zYaoMOmILWv57H0amMjZGHNzzGG70Rw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.0" + string.prototype.trimright "^2.1.0" + +es-abstract@^1.4.3: + version "1.16.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.3.tgz#52490d978f96ff9f89ec15b5cf244304a5bca161" + integrity sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw== dependencies: es-to-primitive "^1.2.1" function-bind "^1.1.1" @@ -4652,9 +4768,9 @@ eslint-visitor-keys@^1.1.0: integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== eslint@^6.5.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.7.1.tgz#269ccccec3ef60ab32358a44d147ac209154b919" - integrity sha512-UWzBS79pNcsDSxgxbdjkmzn/B6BhsXMfUaOHnNwyE8nD+Q6pyT96ow2MccVayUTV4yMid4qLhMiQaywctRkBLA== + version "6.7.2" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.7.2.tgz#c17707ca4ad7b2d8af986a33feba71e18a9fecd1" + integrity sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -5009,6 +5125,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= +fast-deep-equal@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + integrity sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8= + fast-deep-equal@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" @@ -5032,9 +5153,9 @@ fast-glob@^2.0.2, fast-glob@^2.2.6: micromatch "^3.1.10" fast-glob@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.0.tgz#77375a7e3e6f6fc9b18f061cddd28b8d1eec75ae" - integrity sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw== + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.1.tgz#87ee30e9e9f3eb40d6f254a7997655da753d7c82" + integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -5171,14 +5292,6 @@ finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -find-babel-config@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2" - integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA== - dependencies: - json5 "^0.5.1" - path-exists "^3.0.0" - find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -5188,10 +5301,10 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" -find-cache-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.1.0.tgz#9935894999debef4cf9f677fdf646d002c4cdecb" - integrity sha512-zw+EFiNBNPgI2NTrKkDd1xd7q0cs6wr/iWnr/oUkI0yF9K9GqQ+riIt4aiyFaaqpaWbxPrJXHI+QvmNUQbX+0Q== +find-cache-dir@^3.0.0, find-cache-dir@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.2.0.tgz#e7fe44c1abc1299f516146e563108fd1006c1874" + integrity sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg== dependencies: commondir "^1.0.1" make-dir "^3.0.0" @@ -5263,6 +5376,16 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== +flow-static-land@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/flow-static-land/-/flow-static-land-0.2.7.tgz#937f9dcb2780889a609155e5d1a55a993bc2ffb3" + integrity sha1-k3+dyyeAiJpgkVXl0aVamTvC/7M= + +flow-static-land@0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/flow-static-land/-/flow-static-land-0.2.8.tgz#49617e531396928bae6eb5d8ba32e7071637e5b9" + integrity sha512-pOZFExu2rbscCgcEo7nL7FNhBubMi18dn1Un4lm8LOmQkYhgsHLsrBGMWmuJXRWcYMrOC7I/bPsiqqVjdD3K1g== + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -5495,9 +5618,9 @@ get-func-name@^2.0.0: integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= get-own-enumerable-property-symbols@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.1.tgz#6f7764f88ea11e0b514bd9bd860a132259992ca4" - integrity sha512-09/VS4iek66Dh2bctjRkowueRJbY1JDGR1L/zRxO1Qk8Uxs6PnqaNSqalpizPT+CDjre3hnEsuzvhgomz9qYrA== + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== get-stdin@^4.0.1: version "4.0.1" @@ -5939,6 +6062,13 @@ graphql@0.11.3: dependencies: iterall "^1.1.0" +graphql@14.0.2: + version "14.0.2" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.0.2.tgz#7dded337a4c3fd2d075692323384034b357f5650" + integrity sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw== + dependencies: + iterall "^1.2.2" + graphql@^0.13.1: version "0.13.2" resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270" @@ -5979,6 +6109,17 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== +handlebars@^4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482" + integrity sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA== + dependencies: + neo-async "^2.6.0" + optimist "^0.6.1" + source-map "^0.6.1" + optionalDependencies: + uglify-js "^3.1.4" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -6101,7 +6242,14 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -highlight.js@^9.6.0, highlight.js@~9.16.0: +highlight.js@^9.6.0: + version "9.17.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.17.1.tgz#14a4eded23fd314b05886758bb906e39dd627f9a" + integrity sha512-TA2/doAur5Ol8+iM3Ov7qy3jYcr/QiJ2eDTdRF4dfbjG7AaaB99J5G+zSl11ljbl6cIcahgPY6SKb3sC3EJ0fw== + dependencies: + handlebars "^4.5.3" + +highlight.js@~9.16.0: version "9.16.2" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.16.2.tgz#68368d039ffe1c6211bcc07e483daf95de3e403e" integrity sha512-feMUrVLZvjy0oC7FVJQcSQRqbBq9kwqnYE4+Kj9ZjbHh3g+BisiPgF49NyQbVLNdrL/qqZr3Ca9yOKwgn2i/tw== @@ -7043,6 +7191,11 @@ is-whitespace-character@^1.0.0: resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz#b3ad9546d916d7d3ffa78204bca0c26b56257fac" integrity sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ== +is-whitespace@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f" + integrity sha1-Fjnssb4DauxppUy7QBz77XEUq38= + is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -7138,6 +7291,17 @@ js-base64@^2.1.8, js-base64@^2.3.2: resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== +js-beautify@^1.6.12: + version "1.10.2" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.2.tgz#88c9099cd6559402b124cfab18754936f8a7b178" + integrity sha512-ZtBYyNUYJIsBWERnQP0rPN9KjkrDfJcMjuVGcvXOUJrD1zmOGwhRwQ4msG+HJ+Ni/FA7+sRQEMYVzdTQDvnzvQ== + dependencies: + config-chain "^1.1.12" + editorconfig "^0.15.3" + glob "^7.1.3" + mkdirp "~0.5.1" + nopt "~4.0.1" + js-levenshtein@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" @@ -7188,7 +7352,7 @@ jsdom-global@^3.0.2: resolved "https://registry.yarnpkg.com/jsdom-global/-/jsdom-global-3.0.2.tgz#6bd299c13b0c4626b2da2c0393cd4385d606acb9" integrity sha1-a9KZwTsMRiay2iwDk81DhdYGrLk= -jsdom@^15.2.0: +jsdom@^15.2.1: version "15.2.1" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== @@ -7289,7 +7453,7 @@ json3@^3.3.2: resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== -json5@^0.5.0, json5@^0.5.1: +json5@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= @@ -7887,7 +8051,7 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== -lowlight@^1.12.1: +lowlight@1.13.0: version "1.13.0" resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.13.0.tgz#9b4fd00559985e40e11c916ccab14c7c0cf4320d" integrity sha512-bFXLa+UO1eM3zieFAcNqf6rTQ1D5ERFv64/euQbbH/LT3U9XXwH6tOrgUAGWDsQ1QgN3ZhgOcv8p3/S+qKGdTQ== @@ -8175,9 +8339,9 @@ mini-css-extract-plugin@^0.8.0: webpack-sources "^1.1.0" mini-html-webpack-plugin@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mini-html-webpack-plugin/-/mini-html-webpack-plugin-2.0.0.tgz#7105052a1d37a8d7c29530a06084dac9f7f77f4d" - integrity sha512-jHnbGBM3fwbAJVM1zr4xBghHFEyDKr3GLE/ThD+orjOqfkrW4Hx5KepraZe+iK4prgxAr+/0iSJAZS4Nz+fOgw== + version "2.1.0" + resolved "https://registry.yarnpkg.com/mini-html-webpack-plugin/-/mini-html-webpack-plugin-2.1.0.tgz#5bca4a1b6813efb66dbbe65397f6639c6b08a8b8" + integrity sha512-pSxQIDkCsGhE4SZE/ywZ7e5G+P/p2EDwQxpqBSpdmG9yGIr4/JMC5slCPuoZmNMZv13QQ7SKvo0itN0GBpeHTA== dependencies: webpack-sources "^1.3.0" @@ -8208,6 +8372,11 @@ minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= + minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" @@ -8319,10 +8488,10 @@ mocha@^6.2.2: yargs-parser "13.1.1" yargs-unparser "1.6.0" -mochapack@^1.1.5: - version "1.1.11" - resolved "https://registry.yarnpkg.com/mochapack/-/mochapack-1.1.11.tgz#9495305a105e133dd7463b29e5c45f9792bfc5fe" - integrity sha512-vRXu4lTFu222yw9CWRLhISxQr8G1hqEVZl/R5qDRVMLNVB8F6mZPKZRtnMgPoC13lKam1Whu9QTCqhZAWv08vQ== +mochapack@^1.1.12: + version "1.1.13" + resolved "https://registry.yarnpkg.com/mochapack/-/mochapack-1.1.13.tgz#7803cd2d0a5a635da30011cd61ec531dee89699c" + integrity sha512-SQQn/0hsX5E3+tFxjmAm9ruEqLRYpnINssmul69PxnRdqxnNjs4UQvOQz1zTsoE7RfJl6FAprVjvMznZFIzytQ== dependencies: babel-runtime "^6.26.0" chalk "^2.4.2" @@ -8584,10 +8753,10 @@ node-pre-gyp@^0.12.0: semver "^5.3.0" tar "^4" -node-releases@^1.1.29, node-releases@^1.1.3, node-releases@^1.1.40: - version "1.1.41" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.41.tgz#57674a82a37f812d18e3b26118aefaf53a00afed" - integrity sha512-+IctMa7wIs8Cfsa8iYzeaLTFwv5Y4r5jZud+4AnfymzeEXKBCavFX0KBgzVaPVqf0ywa6PrO8/b+bPqdwjGBSg== +node-releases@^1.1.29, node-releases@^1.1.3, node-releases@^1.1.42: + version "1.1.42" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.42.tgz#a999f6a62f8746981f6da90627a8d2fc090bbad7" + integrity sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA== dependencies: semver "^6.3.0" @@ -8631,7 +8800,7 @@ nodent-runtime@^3.2.1: dependencies: abbrev "1" -nopt@^4.0.1: +nopt@^4.0.1, nopt@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= @@ -8692,14 +8861,21 @@ normalize-url@^4.1.0: integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== + version "1.1.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" + integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== npm-packlist@^1.1.6: - version "1.4.6" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.6.tgz#53ba3ed11f8523079f1457376dd379ee4ea42ff4" - integrity sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg== + version "1.4.7" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.7.tgz#9e954365a06b80b18111ea900945af4f88ed4848" + integrity sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ== dependencies: ignore-walk "^3.0.1" npm-bundled "^1.0.1" @@ -8843,12 +9019,12 @@ object.assign@4.1.0, object.assign@^4.1.0: object-keys "^1.0.11" object.getownpropertydescriptors@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" - integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== dependencies: - define-properties "^1.1.2" - es-abstract "^1.5.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" object.pick@^1.3.0: version "1.3.0" @@ -8858,12 +9034,12 @@ object.pick@^1.3.0: isobject "^3.0.1" object.values@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9" - integrity sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg== + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== dependencies: define-properties "^1.1.3" - es-abstract "^1.12.0" + es-abstract "^1.17.0-next.1" function-bind "^1.1.1" has "^1.0.3" @@ -8955,6 +9131,14 @@ optimism@^0.10.0: dependencies: "@wry/context" "^0.4.0" +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + optionator@^0.8.1, optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -9429,6 +9613,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +phoenix@^1.4.11: + version "1.4.11" + resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.4.11.tgz#3e70cd2461600ef3b0d4308e8fef7d2005fe000a" + integrity sha512-UkuqKB/+Uy9LNt15v1PpPeoLizcYwF4cFTi1wiMZ2TVX5+2Pj7MbRDFmoUFPc+Z1jsOE/TdQ6kzZohErWMiYlQ== + picomatch@^2.0.5: version "2.1.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.1.tgz#ecdfbea7704adb5fe6fb47f9866c4c0e15e905c5" @@ -9475,7 +9664,7 @@ pkg-dir@^4.1.0: dependencies: find-up "^4.0.0" -pkg-up@2.0.0, pkg-up@^2.0.0: +pkg-up@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= @@ -9669,10 +9858,10 @@ postcss-modules-local-by-default@^3.0.2: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.0.0" -postcss-modules-scope@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz#ad3f5bf7856114f6fcab901b0502e2a2bc39d4eb" - integrity sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A== +postcss-modules-scope@^2.1.0, postcss-modules-scope@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.1.1.tgz#33d4fc946602eb5e9355c4165d68a10727689dba" + integrity sha512-OXRUPecnHCg8b9xWvldG/jUpRIGPNRka0r4D4j0ESUU2/5IOnpsjfPPmDprM3Ih8CgZ8FXjWqaniK5v4rWt3oQ== dependencies: postcss "^7.0.6" postcss-selector-parser "^6.0.0" @@ -9859,10 +10048,10 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.23, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.23" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.23.tgz#9f9759fad661b15964f3cfc3140f66f1e05eadc1" - integrity sha512-hOlMf3ouRIFXD+j2VJecwssTwbvsPGJVMzupptg+85WA+i7MwyrydmQAgY3R+m0Bc0exunhbJmijy8u8+vufuQ== +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.23, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.24" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.24.tgz#972c3c5be431b32e40caefe6c81b5a19117704c2" + integrity sha512-Xl0XvdNWg+CblAXzNvbSOUvgJXwSjmbAKORqyw9V2AlHrm1js2gFw9y3jibBAhpKZi8b5JzJCVh/FyzPsTtgTA== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -9901,6 +10090,15 @@ pretty-error@^2.0.2: renderkid "^2.0.1" utila "~0.4" +pretty@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pretty/-/pretty-2.0.0.tgz#adbc7960b7bbfe289a557dc5f737619a220d06a5" + integrity sha1-rbx5YLe7/iiaVX3F9zdhmiINBqU= + dependencies: + condense-newlines "^0.2.1" + extend-shallow "^2.0.1" + js-beautify "^1.6.12" + prisma-json-schema@0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/prisma-json-schema/-/prisma-json-schema-0.1.3.tgz#6c302db8f464f8b92e8694d3f7dd3f41ac9afcbe" @@ -9978,14 +10176,14 @@ prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -prosemirror-collab@^1.1.2: +prosemirror-collab@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.2.2.tgz#8d2c0e82779cfef5d051154bd0836428bd6d9c4a" integrity sha512-tBnHKMLgy5Qmx9MYVcLfs3pAyjtcqYYDd9kp3y+LSiQzkhMQDfZSV3NXWe4Gsly32adSef173BvObwfoSQL5MA== dependencies: prosemirror-state "^1.0.0" -prosemirror-commands@^1.0.8: +prosemirror-commands@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.1.2.tgz#6868cabc9f9112fba94c805139473527774b0dea" integrity sha512-JBa06kjgX67d9JVUVJbCkxwvSGtQnWAN/85nq9csOMS5Z9WZLEvVDtVvZranNlu8l/XNVBWrZxOOK+pB03eTfA== @@ -9994,7 +10192,7 @@ prosemirror-commands@^1.0.8: prosemirror-state "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-dropcursor@^1.2.0: +prosemirror-dropcursor@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.3.2.tgz#28738c4ed7102e814d7a8a26d70018523fc7cd6d" integrity sha512-4c94OUGyobGnwcQI70OXyMhE/9T4aTgjU+CHxkd5c7D+jH/J0mKM/lk+jneFVKt7+E4/M0D9HzRPifu8U28Thw== @@ -10003,7 +10201,7 @@ prosemirror-dropcursor@^1.2.0: prosemirror-transform "^1.1.0" prosemirror-view "^1.1.0" -prosemirror-gapcursor@^1.0.4: +prosemirror-gapcursor@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.1.2.tgz#a1400a86a51d4cccc065e68d5625a9fb5bc623e0" integrity sha512-Z+eqk6RysZVxidGWN5aWoSTbn5bTHf1XZ+nQJVwUSdwdBVkfQMFdTHgfrXA8W5MhHHdNg/EEEYG3z3Zi/vE2QQ== @@ -10013,7 +10211,7 @@ prosemirror-gapcursor@^1.0.4: prosemirror-state "^1.0.0" prosemirror-view "^1.0.0" -prosemirror-history@^1.0.4: +prosemirror-history@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.1.2.tgz#3e8f11efbd316e98322028be67549df1f94fc6da" integrity sha512-erhxYS5gm/6MiXP8jUoJBgc8IbaqjHDVPl9KGg5JrMZOSSOwHv85+4Fb0Q7sYtv2fYwAjOSw/kSA9vkxJ6wOwA== @@ -10022,7 +10220,7 @@ prosemirror-history@^1.0.4: prosemirror-transform "^1.0.0" rope-sequence "^1.3.0" -prosemirror-inputrules@^1.0.4: +prosemirror-inputrules@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.1.2.tgz#487e46c763e1212a4577397aba7706139084f012" integrity sha512-Ja5Z3BWestlHYGvtSGqyvxMeB8QEuBjlHM8YnKtLGUXMDp965qdDV4goV8lJb17kIWHk7e7JNj6Catuoa3302g== @@ -10030,7 +10228,7 @@ prosemirror-inputrules@^1.0.4: prosemirror-state "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.0.2: +prosemirror-keymap@1.1.3, prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.1.3.tgz#be22d6108df2521608e9216a87b1a810f0ed361e" integrity sha512-PRA4NzkUMzV/NFf5pyQ6tmlIHiW/qjQ1kGWUlV2rF/dvlOxtpGpTEjIMhWgLuMf+HiDEFnUEP7uhYXu+t+491g== @@ -10038,14 +10236,14 @@ prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.0.2: prosemirror-state "^1.0.0" w3c-keyname "^2.2.0" -prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.7.4: +prosemirror-model@1.8.2, prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.8.1: version "1.8.2" resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.8.2.tgz#c74eaacb0bbfea49b59a6d89fef5516181666a56" integrity sha512-piffokzW7opZVCjf/9YaoXvTC0g7zMRWKJib1hpphPfC+4x6ZXe5CiExgycoWZJe59VxxP7uHX8aFiwg2i9mUQ== dependencies: orderedmap "^1.1.0" -prosemirror-schema-list@^1.0.4: +prosemirror-schema-list@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.1.2.tgz#310809209094b03425da7f5c337105074913da6c" integrity sha512-dgM9PwtM4twa5WsgSYMB+J8bwjnR43DAD3L9MsR9rKm/nZR5Y85xcjB7gusVMSsbQ2NomMZF03RE6No6mTnclQ== @@ -10053,7 +10251,7 @@ prosemirror-schema-list@^1.0.4: prosemirror-model "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.2.4: +prosemirror-state@1.3.2, prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.3.2.tgz#1b910b0dc01c1f00926bb9ba1589f7b7ac0d658b" integrity sha512-t/JqE3aR0SV9QrzFVkAXsQwsgrQBNs/BDbcFH20RssW0xauqNNdjTXxy/J/kM7F+0zYi6+BRmz7cMMQQFU3mwQ== @@ -10061,30 +10259,37 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.2.4: prosemirror-model "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-tables@^0.9.5: - version "0.9.5" - resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-0.9.5.tgz#94d9881a46051e6fff3c51edffafa346da084def" - integrity sha512-RlAF/D7OvnDCOL8B6Qt6KuBkb0w3SedTdrou7wH7Nn2ml7+M5xUalW/h1f7dMD3wjsU47/Cn8zTbEkCDIpIggw== +prosemirror-tables@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.0.0.tgz#ec3d0b11e638c6a92dd14ae816d0a2efd1719b70" + integrity sha512-zFw5Us4G5Vdq0yIj8GiqZOGA6ud5UKpMKElux9O0HrfmhkuGa1jf1PCpz2R5pmIQJv+tIM24H1mox/ODBAX37Q== dependencies: - prosemirror-keymap "^1.0.0" - prosemirror-model "^1.0.0" - prosemirror-state "^1.0.0" - prosemirror-transform "^1.0.0" - prosemirror-view "^1.0.0" + prosemirror-keymap "^1.1.2" + prosemirror-model "^1.8.1" + prosemirror-state "^1.3.1" + prosemirror-transform "^1.2.1" + prosemirror-view "^1.13.3" -prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.1.5: +prosemirror-transform@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.2.2.tgz#4439ae7e88ea1395d9beed6a4cd852d72b16ed2f" integrity sha512-expO11jAsxaHk2RdZtzPsumc1bAAZi4UiXwTLQbftsdnIUWZE5Snyag595p1lx/B8QHUZ6tYWWOaOkzXKoJmYw== dependencies: prosemirror-model "^1.0.0" -prosemirror-utils@^0.9.6: +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.2.3.tgz#239d17591af24d39ef3f1999daa09e1f1c76b06a" + integrity sha512-PUfayeskQfuUBXktvL6207ZWRwHBFNPNPiek4fR+LgCPnBofuEb2+L0FfbNtrAwffHVs6M3DaFvJB1W2VQdV0A== + dependencies: + prosemirror-model "^1.0.0" + +prosemirror-utils@0.9.6: version "0.9.6" resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.9.6.tgz#3d97bd85897e3b535555867dc95a51399116a973" integrity sha512-UC+j9hQQ1POYfMc5p7UFxBTptRiGPR7Kkmbl3jVvU8VgQbkI89tR/GK+3QYC8n+VvBZrtAoCrJItNhWSxX3slA== -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.11.7: +prosemirror-view@1.13.4: version "1.13.4" resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.13.4.tgz#01d873db7731e0aacc410a9038447d1b7536fd07" integrity sha512-mtgWEK16uYQFk3kijRlkSpAmDuy7rxYuv0pgyEBDmLT1PCPY8380CoaYnP8znUT6BXIGlJ8oTveK3M50U+B0vw== @@ -10093,6 +10298,20 @@ prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.11.7: prosemirror-state "^1.0.0" prosemirror-transform "^1.1.0" +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3: + version "1.13.6" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.13.6.tgz#c1a70b50b5094e9c616543149befad404928651b" + integrity sha512-6Wg4Lxmbhlw5D3Db3JSxk4zProZdYywx6QX8yiEV+ReRXJz1IqtdXg+QEywQ5tEgrWhTYtpM8WmDhDYRguKNxA== + dependencies: + prosemirror-model "^1.1.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= + protochain@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/protochain/-/protochain-1.0.5.tgz#991c407e99de264aadf8f81504b5e7faf7bfa260" @@ -10117,9 +10336,9 @@ pseudomap@^1.0.2: integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= psl@^1.1.24, psl@^1.1.28: - version "1.4.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" - integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw== + version "1.6.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.6.0.tgz#60557582ee23b6c43719d9890fb4170ecd91e110" + integrity sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA== public-encrypt@^4.0.0: version "4.0.3" @@ -10482,9 +10701,9 @@ react-error-overlay@^5.1.4: integrity sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q== react-error-overlay@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.3.tgz#c378c4b0a21e88b2e159a3e62b2f531fd63bf60d" - integrity sha512-bOUvMWFQVk5oz8Ded9Xb7WVdEi3QGLC8tH7HmYP0Fdp4Bn3qw0tRFmr5TW6mvahzvmrK4a6bqWGfCevBflP+Xw== + version "6.0.4" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.4.tgz#0d165d6d27488e660bc08e57bdabaad741366f7a" + integrity sha512-ueZzLmHltszTshDMwyfELDq8zOA803wQ1ZuzCccXa1m57k1PxSHfflPD5W9YIiTXLs0JTLzoj6o1LuM5N6zzNA== react-group@^1.0.6: version "1.0.6" @@ -10527,13 +10746,13 @@ react-simple-code-editor@^0.9.4: resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.9.15.tgz#59e3583832e9e98992d3674b2d7673b4cd1c5709" integrity sha512-M8iKgjBTBZK92tZYgOEfMuR7c3zZ0q0v3QYllSxIPx3SU+w003VofH50txXQSBTu92pSOm2tidON1HbQ1l8BDA== -react-styleguidist@^10.0.0: - version "10.2.1" - resolved "https://registry.yarnpkg.com/react-styleguidist/-/react-styleguidist-10.2.1.tgz#de83739128532974858667c2a138d8a44348c6aa" - integrity sha512-Or3Ey8mCFGTgfwMY3sBYDnfZLnJO6JN+82xmT/QXaknbx9qYHtHWejUES7bslUXek1Ykmwllrn9x+lzTSHMDAg== +react-styleguidist@^10.2.1: + version "10.3.2" + resolved "https://registry.yarnpkg.com/react-styleguidist/-/react-styleguidist-10.3.2.tgz#702f97ef894496f2cd20891b22880441fd9fcf59" + integrity sha512-sD7/cVFABQ/FFLC9GtDQzQOhLnhiDcyIOHI/Yl3m8ZMK70T+CYiEOM4ca2XwY3RejgFrWzlWsgwto5YksGL8gA== dependencies: "@vxna/mini-html-webpack-template" "^1.0.0" - acorn "~6.3.0" + acorn "~6.4.0" acorn-jsx "^5.1.0" ast-types "~0.13.2" buble "0.19.8" @@ -10541,7 +10760,7 @@ react-styleguidist@^10.0.0: clipboard-copy "^3.1.0" clsx "^1.0.4" common-dir "^3.0.0" - copy-webpack-plugin "^5.0.4" + copy-webpack-plugin "^5.1.0" core-js "^3.3.5" doctrine "^3.0.0" es6-object-assign "~1.1.0" @@ -10729,11 +10948,21 @@ regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== +regenerator-runtime@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg= + regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== + regenerator-runtime@^0.13.2: version "0.13.3" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" @@ -10819,9 +11048,9 @@ regjsgen@^0.5.0: integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== regjsparser@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c" - integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ== + version "0.6.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.1.tgz#5b6b28c418f312ef42898dc6865ae2d4b9f0f7a2" + integrity sha512-7LutE94sz/NKSYegK+/4E77+8DipxF+Qn2Tmu362AcmsF2NYq/wx3+ObvU90TKEhjf7hQoFXo23ajjrXP7eUgg== dependencies: jsesc "~0.5.0" @@ -10942,7 +11171,7 @@ request-promise-core@1.1.3: dependencies: lodash "^4.17.15" -request-promise-native@^1.0.7: +request-promise-native@^1.0.7, request-promise-native@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== @@ -11007,11 +11236,6 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -reselect@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147" - integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc= - resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -11042,10 +11266,10 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.8.1: - version "1.12.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.2.tgz#08b12496d9aa8659c75f534a8f05f0d892fff594" - integrity sha512-cAVTI2VLHWYsGOirfeYVVQ7ZDejtQ9fp4YhYckWDEkFfqbVjaT11iM8k6xSAfGFMM+gDpZjMnFssPu8we+mqFw== +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.8.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" + integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w== dependencies: path-parse "^1.0.6" @@ -11256,10 +11480,10 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.1.0, schema-utils@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.5.0.tgz#8f254f618d402cc80257486213c8970edfd7c22f" - integrity sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ== +schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.1.0, schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.1.tgz#eb78f0b945c7bcfa2082b3565e8db3548011dc4f" + integrity sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg== dependencies: ajv "^6.10.2" ajv-keywords "^3.4.1" @@ -11343,15 +11567,10 @@ sentence-case@^2.1.0: no-case "^2.2.0" upper-case-first "^1.1.2" -serialize-javascript@^1.7.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb" - integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A== - -serialize-javascript@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.0.tgz#9310276819efd0eb128258bb341957f6eb2fc570" - integrity sha512-a/mxFfU00QT88umAJQsNWOnUKckhNCqOl028N48e7wFmo2/EHpTo9Wso+iJJCMrQnmFvcjto5RJdAHEvVhcyUQ== +serialize-javascript@^2.1.0, serialize-javascript@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" + integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== serializerr@^1.0.3: version "1.0.3" @@ -11467,6 +11686,11 @@ shell-quote@1.7.2, shell-quote@^1.6.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== +sigmund@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -11806,9 +12030,9 @@ stream-http@^2.7.2: xtend "^4.0.0" stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== stream-to-observable@^0.1.0: version "0.1.0" @@ -12016,9 +12240,9 @@ strip-json-comments@^3.0.1: integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== style-loader@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.0.0.tgz#1d5296f9165e8e2c85d24eee0b7caf9ec8ca1f82" - integrity sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw== + version "1.0.1" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.0.1.tgz#aec6d4c61d0ed8d0a442faed741d4dfc6573888a" + integrity sha512-CnpEkSR1C+REjudiTWCv4+ssP7SCiuaQZJTZDWBRwTJoS90mdqkB8uOGMHKgVeUzpaU7IfLWoyQbvvs5Joj3Xw== dependencies: loader-utils "^1.2.3" schema-utils "^2.0.1" @@ -12070,7 +12294,7 @@ svg-tags@^1.0.0: resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= -svgo@^1.0.0, svgo@^1.3.0: +svgo@^1.0.0, svgo@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== @@ -12173,39 +12397,39 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -terser-webpack-plugin@^1.3.0, terser-webpack-plugin@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" - integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg== +terser-webpack-plugin@^1.4.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" + integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== dependencies: cacache "^12.0.2" find-cache-dir "^2.1.0" is-wsl "^1.1.0" schema-utils "^1.0.0" - serialize-javascript "^1.7.0" + serialize-javascript "^2.1.2" source-map "^0.6.1" terser "^4.1.2" webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser-webpack-plugin@^2.1.2, terser-webpack-plugin@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.2.1.tgz#5569e6c7d8be79e5e43d6da23acc3b6ba77d22bd" - integrity sha512-jwdauV5Al7zopR6OAYvIIRcxXCSvLjZjr7uZE8l2tIWb/ryrGN48sJftqGf5k9z09tWhajx53ldp0XPI080YnA== +terser-webpack-plugin@^2.2.1, terser-webpack-plugin@^2.2.2: + version "2.3.0" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.0.tgz#00fd8f792a330dc572e2e2b468fd7cb5ffd7ea51" + integrity sha512-yez0HdpDf/iQVYGf+e/o8ZYWLb1g9d1nRRi5FIOZ4KfXbfSPT259UoqxPiSLhCnr0mlDoh+bucpYQSFbU0cEsQ== dependencies: cacache "^13.0.1" - find-cache-dir "^3.0.0" + find-cache-dir "^3.1.0" jest-worker "^24.9.0" - schema-utils "^2.5.0" - serialize-javascript "^2.1.0" + schema-utils "^2.6.1" + serialize-javascript "^2.1.2" source-map "^0.6.1" - terser "^4.3.9" + terser "^4.4.2" webpack-sources "^1.4.3" -terser@^4.1.2, terser@^4.3.9: - version "4.4.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.0.tgz#22c46b4817cf4c9565434bfe6ad47336af259ac3" - integrity sha512-oDG16n2WKm27JO8h4y/w3iqBGAOSCtq7k8dRmrn4Wf9NouL0b2WpMHGChFGZq4nFAQy1FsNJrVQHfurXOSTmOA== +terser@^4.1.2, terser@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.2.tgz#448fffad0245f4c8a277ce89788b458bfd7706e8" + integrity sha512-Uufrsvhj9O1ikwgITGsZ5EZS6qPokUOkCegS7fYOdGTv+OA90vndUbU6PEjr5ePqHfNUbGyMO7xyIZv2MhsALQ== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -12301,62 +12525,62 @@ tippy.js@4.3.5: dependencies: popper.js "^1.14.7" -tiptap-commands@^1.12.3: - version "1.12.3" - resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.12.3.tgz#604767878073e6344d1daf7a376fd89fc62e4742" - integrity sha512-Dck51lePBwuHmkvkJ6+8V3DbInxAhZwtS2mPvVwz74pDUIcy17tCFw1eHUN50JoXIAci7acuxPKO/weVO1JAyw== +tiptap-commands@^1.12.4: + version "1.12.4" + resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.12.4.tgz#03ef3eda290f0d2ed71a54d73d7619452b05dced" + integrity sha512-szgSZzd/5FHn3Hs02zxySLxNEwNqbyqPOmz5NEAkIJmcyatTkuL+RQDKHA/RNQRUT66SsUCrEXgL1osTqdOSyQ== dependencies: - prosemirror-commands "^1.0.8" - prosemirror-inputrules "^1.0.4" - prosemirror-model "^1.7.4" - prosemirror-schema-list "^1.0.4" - prosemirror-state "^1.2.4" - prosemirror-tables "^0.9.5" - prosemirror-utils "^0.9.6" - tiptap-utils "^1.8.2" + prosemirror-commands "1.1.2" + prosemirror-inputrules "1.1.2" + prosemirror-model "1.8.2" + prosemirror-schema-list "1.1.2" + prosemirror-state "1.3.2" + prosemirror-tables "1.0.0" + prosemirror-utils "0.9.6" + tiptap-utils "^1.8.3" tiptap-extensions@^1.28.0: - version "1.28.4" - resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.28.4.tgz#0e729d081a80105730101512e7eb5acdce8b9bde" - integrity sha512-UAtxngKifjrMtJFmi3D9RCNC5LJutq4yn1Np0cqJ4dTnvhWR49PqN6gKjlMYyzyutiLLQk+/3GM/E6EfVwmHOA== + version "1.28.5" + resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.28.5.tgz#6fba6f7c61abd82729f413f3afa68438b0ba8dd7" + integrity sha512-WvwRvznzgELeSA9JIFse4xNlDEcQ0JMN2PV2sybyPamKM1cvqrYBwF6fqf+EKGmrvwJzmr33CFZpMuzrMeAmWw== dependencies: - lowlight "^1.12.1" - prosemirror-collab "^1.1.2" - prosemirror-history "^1.0.4" - prosemirror-model "^1.7.4" - prosemirror-state "^1.2.4" - prosemirror-tables "^0.9.5" - prosemirror-transform "^1.1.5" - prosemirror-utils "^0.9.6" - prosemirror-view "^1.11.7" - tiptap "^1.26.4" - tiptap-commands "^1.12.3" + lowlight "1.13.0" + prosemirror-collab "1.2.2" + prosemirror-history "1.1.2" + prosemirror-model "1.8.2" + prosemirror-state "1.3.2" + prosemirror-tables "1.0.0" + prosemirror-transform "1.2.2" + prosemirror-utils "0.9.6" + prosemirror-view "1.13.4" + tiptap "^1.26.5" + tiptap-commands "^1.12.4" -tiptap-utils@^1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.8.2.tgz#f07a2053c6ac9fbbb4f02e0844b326d0e6c8b7fb" - integrity sha512-pyx+3p4fICGM7JU1mcsnRx5jXvLrCL8Nm/9yjeWEZXpAC85L/btY0eFo2Oz4+dKg39+1EGNHheodujx3ngw4lQ== +tiptap-utils@^1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.8.3.tgz#fdfc8f7888f6e9ed0dae081f5f66b9f5429608a9" + integrity sha512-SgqDTCA5ux17KKTpEV2YC54ugBWU2jzpiFlCmVckPjYl5BhmOwuJ1Q5H/8v/XGcnHDqP31Ui4lk31Vts4NmtTA== dependencies: - prosemirror-model "^1.7.4" - prosemirror-state "^1.2.4" - prosemirror-tables "^0.9.5" - prosemirror-utils "^0.9.6" + prosemirror-model "1.8.2" + prosemirror-state "1.3.2" + prosemirror-tables "1.0.0" + prosemirror-utils "0.9.6" -tiptap@^1.26.0, tiptap@^1.26.4: - version "1.26.4" - resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.26.4.tgz#bfa289841bc45c6401cbd1661a02b81c3d3f14f0" - integrity sha512-UCH0wufjGdKMuCUydL896sFYXEUWC3bE20h/oONABSf0gull+pqBEm7J1yCl7j50eYa9FiLgUBGPqPTzKLluxQ== +tiptap@^1.26.0, tiptap@^1.26.5: + version "1.26.5" + resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.26.5.tgz#d35f000e0bf93d97532357a29692fa7655e396da" + integrity sha512-PTm9w/UGDQTq6TEjyrNCpNBq9+ZbNU8aZrl+5KLLcmVyMpWCXRd/29b7nKqil8cmi0zUlLrQb9vHteExEgyyrg== dependencies: - prosemirror-commands "^1.0.8" - prosemirror-dropcursor "^1.2.0" - prosemirror-gapcursor "^1.0.4" - prosemirror-inputrules "^1.0.4" - prosemirror-keymap "^1.0.2" - prosemirror-model "^1.7.4" - prosemirror-state "^1.2.4" - prosemirror-view "^1.11.7" - tiptap-commands "^1.12.3" - tiptap-utils "^1.8.2" + prosemirror-commands "1.1.2" + prosemirror-dropcursor "1.3.2" + prosemirror-gapcursor "1.1.2" + prosemirror-inputrules "1.1.2" + prosemirror-keymap "1.1.3" + prosemirror-model "1.8.2" + prosemirror-state "1.3.2" + prosemirror-view "1.13.4" + tiptap-commands "^1.12.4" + tiptap-utils "^1.8.3" title-case@^2.1.0: version "2.1.1" @@ -12549,7 +12773,7 @@ ts-invariant@^0.4.0: dependencies: tslib "^1.9.3" -ts-loader@^6.2.0: +ts-loader@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef" integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g== @@ -12609,7 +12833,7 @@ tslint-microsoft-contrib@~5.2.1: dependencies: tsutils "^2.27.2 <2.29.0" -tslint@^5.16.0, tslint@^5.20.0: +tslint@^5.20.0, tslint@^5.20.1: version "5.20.1" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== @@ -12707,9 +12931,9 @@ typedarray@^0.0.6: integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= typescript@^3.6.3: - version "3.7.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" - integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== + version "3.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" + integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw== uglify-js@3.4.x: version "3.4.10" @@ -12729,6 +12953,14 @@ uglify-js@^2.6.1: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.1.4: + version "3.7.2" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.2.tgz#cb1a601e67536e9ed094a92dd1e333459643d3f9" + integrity sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA== + dependencies: + commander "~2.20.3" + source-map "~0.6.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -13138,9 +13370,9 @@ void-elements@^2.0.1: integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= vue-apollo@^3.0.0-rc.6: - version "3.0.0" - resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.0.tgz#e252130b18cbd7b0d060fc3dd9616813e4a65acf" - integrity sha512-ByeKajmgItICrOkUl2j/XzqWjv2FOdQYAPsuGyry4yrQBCU641gSoZZn1TjHiR9rAsR2aycGsY9vuV0sN14Mbg== + version "3.0.2" + resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.2.tgz#b198ecfa3765850a0b9f2b84ffaa7fbd8ec15f52" + integrity sha512-lrKyTT1L5mjDEp7nyqnTRJwD/kTpLDBIqFfZ+TGQVivjlUz6o5VA0pLYGCx5cGa1gEF/ERWc0AEdNSdKgs7Ygg== dependencies: chalk "^2.4.2" serialize-javascript "^2.1.0" @@ -13152,12 +13384,12 @@ vue-class-component@^7.0.2, vue-class-component@^7.1.0: integrity sha512-G9152NzUkz0i0xTfhk0Afc8vzdXxDR1pfN4dTwE72cskkgJtdXfrKBkMfGvDuxUh35U500g5Ve4xL8PEGdWeHg== vue-cli-plugin-styleguidist@^4.0.1: - version "4.0.6" - resolved "https://registry.yarnpkg.com/vue-cli-plugin-styleguidist/-/vue-cli-plugin-styleguidist-4.0.6.tgz#6e60ad57457b66b911d60518d1ef7c7980ae2622" - integrity sha512-wtHVu0vOFOdpIxvEF/JcEBEL5SZTiPAHzxR5aRGmrQBrWg18xtzODshWIpMuPwGmLAu4dN9oxjj1lP2IVe/Y2g== + version "4.2.1" + resolved "https://registry.yarnpkg.com/vue-cli-plugin-styleguidist/-/vue-cli-plugin-styleguidist-4.2.1.tgz#a937ee775b521b474c51c747a6db93a2e70e48c1" + integrity sha512-dYMd1TR4ZtWN3TvJCGZEk1GKI87d2R/67WM1QrD7sdNd0tGvNAdXRBUNo9lR8U+0N7qoXnkE5ayGPQCPvitr0A== dependencies: null-loader "^0.1.1" - vue-styleguidist "^4.0.5" + vue-styleguidist "^4.2.1" webpack-merge "^4.2.1" vue-cli-plugin-webpack-bundle-analyzer@^2.0.0: @@ -13167,10 +13399,10 @@ vue-cli-plugin-webpack-bundle-analyzer@^2.0.0: dependencies: webpack-bundle-analyzer "^3.6.0" -vue-docgen-api@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/vue-docgen-api/-/vue-docgen-api-4.0.5.tgz#1694e5d6766015fe84ca25c9e184d9eac7e757ce" - integrity sha512-/H2uqyAw8OKAqQslqn9HDFMuP2LvolegRwjlJPHnL3Tc21JWcQTnAByDLN7cybXohNyT2cQJ/1uEnyPvRBxZzQ== +vue-docgen-api@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vue-docgen-api/-/vue-docgen-api-4.2.0.tgz#c693e0133ca3f8d15154e766c777aa3109e692ad" + integrity sha512-8EbK+QYRHJzy4t6Lx89vqS2pAwbFp/+N2r51LE/8bPG4N4mYaA7Xuv0dV90hJED0wt5icRTkyUYrwkVnqXTaNA== dependencies: "@babel/parser" "^7.2.3" "@babel/types" "^7.0.0" @@ -13200,9 +13432,9 @@ vue-i18n-extract@^1.0.2: yargs "^13.2.2" vue-i18n@^8.14.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.15.0.tgz#9b11ef8e7a124f67cdf788c8c90a81f3606240ed" - integrity sha512-juJ/avAP39bOMycC+qQDLJ8U9z9LtLF/9PsRoJLBSfsYZo9bqYntyyX5QPicwlb1emJKjgxhZ3YofHiQcXBu0Q== + version "8.15.1" + resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.15.1.tgz#90097a08a1e932f645c6b9c404c780d24f6d6224" + integrity sha512-GBbz8qYCu0U2LNu4IcuFLZiuyninG4k26knvhL7GZG5Ncp4RR2VKDEH6g8gQ6I+UUBCvH2MBQVPSdxWe4DBkPw== vue-inbrowser-compiler-utils@^4.0.1: version "4.0.1" @@ -13223,7 +13455,7 @@ vue-inbrowser-compiler@^4.0.1: vue-inbrowser-compiler-utils "^4.0.1" walkes "^0.2.1" -vue-loader@^15.7.0: +vue-loader@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.2.tgz#cc89e2716df87f70fe656c9da9d7f8bec06c73d6" integrity sha512-H/P9xt/nkocyu4hZKg5TzPqyCT1oKOaCSk9zs0JCbJuy0Q8KtR0bjJpnT/5R5x/Ckd1GFkkLQnQ1C4x6xXeLZg== @@ -13268,10 +13500,10 @@ vue-style-loader@^4.1.0: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-styleguidist@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/vue-styleguidist/-/vue-styleguidist-4.0.5.tgz#6d3b85ebc14f2415ad6ff53a8ff4972b24fe2bd7" - integrity sha512-648cNdG9sK0UI1fvRzubPP2eHfdM6jhjiAgiuqHdO7MZQLtS78cuEVxZzAUGDeTrhBo93y1QZOSLBW0n2LJeAw== +vue-styleguidist@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vue-styleguidist/-/vue-styleguidist-4.2.1.tgz#e669fdb78506b9ed0b6a42d742444f9caabbf161" + integrity sha512-RenK+bkVqSmAmIDphdz2+NBtsQV9vQogWP1OWZwX96ergeXHv6eZTq2o1ch5aQeOknd+Pd3E0goPFaWIzY68Sg== dependencies: "@vxna/mini-html-webpack-template" "^1.0.0" ast-types "^0.12.2" @@ -13281,7 +13513,7 @@ vue-styleguidist@^4.0.5: clipboard-copy "^3.0.0" codemirror "^5.39.0" common-dir "^2.0.2" - copy-webpack-plugin "^5.0.4" + copy-webpack-plugin "^5.1.0" css-loader "^2.1.1" es6-object-assign "^1.1.0" es6-promise "^4.2.6" @@ -13313,27 +13545,27 @@ vue-styleguidist@^4.0.5: react-icons "^3.7.0" react-lifecycles-compat "^3.0.4" react-simple-code-editor "^0.9.4" - react-styleguidist "^10.0.0" + react-styleguidist "^10.2.1" rewrite-imports "^2.0.3" style-loader "^1.0.0" - terser-webpack-plugin "^1.3.0" + terser-webpack-plugin "^2.2.2" to-ast "^1.0.0" - vue-docgen-api "^4.0.5" + vue-docgen-api "^4.2.0" vue-inbrowser-compiler "^4.0.1" vue-inbrowser-compiler-utils "^4.0.1" webpack-dev-server "^3.7.1" webpack-merge "^4.0.0" vue-svg-inline-loader@^1.3.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/vue-svg-inline-loader/-/vue-svg-inline-loader-1.4.3.tgz#e2902c29c49c2c464661089fee92449ce76505d4" - integrity sha512-7wNZCDpHN8zkr8+eY6cuCUXBSxZcq11yuckHWH58ZiTq1lK0lt4TnxQE94SqYcM5v89j5TylXysgWcapdjSOVw== + version "1.4.4" + resolved "https://registry.yarnpkg.com/vue-svg-inline-loader/-/vue-svg-inline-loader-1.4.4.tgz#255cd58f5e8bb4ba13cc531cf0f07ee61881567d" + integrity sha512-iEuebrVvEM/XJ/TndBbndPgdINquEDwEf2hHBUeckgFJoiW0BEM7glgeOoOF3VdfkcmSkIRdrjeSMxDBldQsLw== dependencies: - "@babel/polyfill" "^7.6.0" - "@babel/runtime" "^7.6.3" + "@babel/polyfill" "^7.7.0" + "@babel/runtime" "^7.7.4" core-js "^2.6.10" loader-utils "^1.2.3" - svgo "^1.3.0" + svgo "^1.3.2" vue-template-compiler@^2.0.0, vue-template-compiler@^2.6.10: version "2.6.10" @@ -13450,7 +13682,7 @@ webpack-dev-middleware@^3.7.2: range-parser "^1.2.1" webpack-log "^2.0.0" -webpack-dev-server@^3.7.1, webpack-dev-server@^3.8.2, webpack-dev-server@^3.9.0: +webpack-dev-server@^3.7.1, webpack-dev-server@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.9.0.tgz#27c3b5d0f6b6677c4304465ac817623c8b27b89c" integrity sha512-E6uQ4kRrTX9URN9s/lIbqTAztwEPdvzVrcmHE8EQ9YnuT9J8Es5Wrd8n9BKg1a0oZ5EgEke/EQFgUsp18dSTBw== @@ -13647,6 +13879,11 @@ wordwrap@0.0.2: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= + workbox-background-sync@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz#26821b9bf16e9e37fd1d640289edddc08afd1950" @@ -14132,7 +14369,7 @@ z-schema@^3.18.2: optionalDependencies: commander "^2.7.1" -zen-observable-ts@^0.8.20: +zen-observable-ts@^0.8.12, zen-observable-ts@^0.8.20: version "0.8.20" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.20.tgz#44091e335d3fcbc97f6497e63e7f57d5b516b163" integrity sha512-2rkjiPALhOtRaDX6pWyNqK1fnP5KkJJybYebopNSn6wDG1lxBoFs2+nwwXKoA6glHIrtwrfBBy6da0stkKtTAA== @@ -14140,6 +14377,11 @@ zen-observable-ts@^0.8.20: tslib "^1.9.3" zen-observable "^0.8.0" +zen-observable@0.8.11: + version "0.8.11" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199" + integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ== + zen-observable@^0.8.0: version "0.8.15" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" diff --git a/lib/mix/tasks/mobilizon/relay.ex b/lib/mix/tasks/mobilizon/relay.ex index 989c7ea9..0e88ea60 100644 --- a/lib/mix/tasks/mobilizon/relay.ex +++ b/lib/mix/tasks/mobilizon/relay.ex @@ -30,7 +30,7 @@ defmodule Mix.Tasks.Mobilizon.Relay do Common.start_mobilizon() case Relay.follow(target) do - {:ok, _activity} -> + {:ok, _activity, _follow} -> # put this task to sleep to allow the genserver to push out the messages :timer.sleep(500) @@ -43,7 +43,7 @@ defmodule Mix.Tasks.Mobilizon.Relay do Common.start_mobilizon() case Relay.unfollow(target) do - {:ok, _activity} -> + {:ok, _activity, _follow} -> # put this task to sleep to allow the genserver to push out the messages :timer.sleep(500) diff --git a/lib/mobilizon.ex b/lib/mobilizon.ex index 6e18a6e2..49443f84 100644 --- a/lib/mobilizon.ex +++ b/lib/mobilizon.ex @@ -37,6 +37,7 @@ defmodule Mobilizon do # supervisors Mobilizon.Storage.Repo, MobilizonWeb.Endpoint, + {Absinthe.Subscription, [MobilizonWeb.Endpoint]}, {Oban, Application.get_env(:mobilizon, Oban)}, # workers Guardian.DB.Token.SweeperServer, @@ -44,7 +45,8 @@ defmodule Mobilizon do cachex_spec(:feed, 2500, 60, 60, &Feed.create_cache/1), cachex_spec(:ics, 2500, 60, 60, &ICalendar.create_cache/1), cachex_spec(:statistics, 10, 60, 60), - cachex_spec(:activity_pub, 2500, 3, 15) + cachex_spec(:activity_pub, 2500, 3, 15), + internal_actor() ] Supervisor.start_link(children, strategy: :one_for_one, name: Mobilizon.Supervisor) @@ -88,4 +90,12 @@ defmodule Mobilizon do @spec fallback_options(function | nil) :: keyword defp fallback_options(nil), do: [] defp fallback_options(fallback), do: [fallback: fallback(default: fallback)] + + defp internal_actor() do + %{ + id: :internal_actor_init, + start: {Task, :start_link, [&Mobilizon.Service.ActivityPub.Relay.init/0]}, + restart: :temporary + } + end end diff --git a/lib/mobilizon/actors/actor.ex b/lib/mobilizon/actors/actor.ex index f5623b41..50de3513 100644 --- a/lib/mobilizon/actors/actor.ex +++ b/lib/mobilizon/actors/actor.ex @@ -7,9 +7,9 @@ defmodule Mobilizon.Actors.Actor do import Ecto.Changeset - alias Mobilizon.{Actors, Config, Crypto} + alias Mobilizon.{Actors, Config, Crypto, Share} alias Mobilizon.Actors.{ActorOpenness, ActorType, ActorVisibility, Follower, Member} - alias Mobilizon.Events.{Event, FeedToken} + alias Mobilizon.Events.{Event, FeedToken, Comment} alias Mobilizon.Media.File alias Mobilizon.Reports.{Note, Report} alias Mobilizon.Users.User @@ -43,11 +43,14 @@ defmodule Mobilizon.Actors.Actor do followers: [Follower.t()], followings: [Follower.t()], organized_events: [Event.t()], + comments: [Comment.t()], feed_tokens: [FeedToken.t()], created_reports: [Report.t()], subject_reports: [Report.t()], report_notes: [Note.t()], mentions: [Mention.t()], + shares: [Share.t()], + owner_shares: [Share.t()], memberships: [t] } @@ -137,11 +140,14 @@ defmodule Mobilizon.Actors.Actor do has_many(:followers, Follower, foreign_key: :target_actor_id) has_many(:followings, Follower, foreign_key: :actor_id) has_many(:organized_events, Event, foreign_key: :organizer_actor_id) + has_many(:comments, Comment, foreign_key: :actor_id) has_many(:feed_tokens, FeedToken, foreign_key: :actor_id) has_many(:created_reports, Report, foreign_key: :reporter_id) has_many(:subject_reports, Report, foreign_key: :reported_id) has_many(:report_notes, Note, foreign_key: :moderator_id) has_many(:mentions, Mention) + has_many(:shares, Share, foreign_key: :actor_id) + has_many(:owner_shares, Share, foreign_key: :owner_actor_id) many_to_many(:memberships, __MODULE__, join_through: Member) timestamps() @@ -217,6 +223,19 @@ defmodule Mobilizon.Actors.Actor do |> validate_required(@update_required_attrs) end + @doc false + @spec delete_changeset(t) :: Ecto.Changeset.t() + def delete_changeset(%__MODULE__{} = actor) do + actor + |> change() + |> put_change(:name, nil) + |> put_change(:summary, nil) + |> put_change(:suspended, true) + |> put_change(:avatar, nil) + |> put_change(:banner, nil) + |> put_change(:user_id, nil) + end + @doc """ Changeset for person registration. """ diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex index a928b85e..05fc1c3d 100644 --- a/lib/mobilizon/actors/actors.ex +++ b/lib/mobilizon/actors/actors.ex @@ -12,6 +12,8 @@ defmodule Mobilizon.Actors do alias Mobilizon.{Crypto, Events} alias Mobilizon.Media.File alias Mobilizon.Storage.{Page, Repo} + alias Mobilizon.Service.Workers.BackgroundWorker + alias Mobilizon.Service.ActivityPub require Logger @@ -47,6 +49,7 @@ defmodule Mobilizon.Actors do @public_visibility [:public, :unlisted] @administrator_roles [:creator, :administrator] + @actor_preloads [:user, :organized_events, :comments] @doc """ Gets a single actor. @@ -224,16 +227,24 @@ defmodule Mobilizon.Actors do end end + def delete_actor(%Actor{} = actor) do + BackgroundWorker.enqueue("delete_actor", %{"actor_id" => actor.id}) + end + @doc """ Deletes an actor. """ - @spec delete_actor(Actor.t()) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()} - def delete_actor(%Actor{domain: nil} = actor) do + @spec perform(atom(), Actor.t()) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()} + def perform(:delete_actor, %Actor{} = actor) do + actor = Repo.preload(actor, @actor_preloads) + transaction = Multi.new() - |> Multi.delete(:actor, actor) - |> Multi.run(:remove_banner, fn _, %{actor: %Actor{}} -> remove_banner(actor) end) - |> Multi.run(:remove_avatar, fn _, %{actor: %Actor{}} -> remove_avatar(actor) end) + |> Multi.run(:delete_organized_events, fn _, _ -> delete_actor_organized_events(actor) end) + |> Multi.run(:empty_comments, fn _, _ -> delete_actor_empty_comments(actor) end) + |> Multi.run(:remove_banner, fn _, _ -> remove_banner(actor) end) + |> Multi.run(:remove_avatar, fn _, _ -> remove_avatar(actor) end) + |> Multi.update(:actor, Actor.delete_changeset(actor)) |> Repo.transaction() case transaction do @@ -245,8 +256,6 @@ defmodule Mobilizon.Actors do end end - def delete_actor(%Actor{} = actor), do: Repo.delete(actor) - @doc """ Returns the list of actors. """ @@ -486,9 +495,9 @@ defmodule Mobilizon.Actors do |> Repo.insert() end - @spec get_or_create_actor_by_url(String.t(), String.t()) :: + @spec get_or_create_instance_actor_by_url(String.t(), String.t()) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()} - def get_or_create_actor_by_url(url, preferred_username \\ "relay") do + def get_or_create_instance_actor_by_url(url, preferred_username \\ "relay") do case get_actor_by_url(url) do {:ok, %Actor{} = actor} -> {:ok, actor} @@ -571,9 +580,12 @@ defmodule Mobilizon.Actors do """ @spec update_follower(Follower.t(), map) :: {:ok, Follower.t()} | {:error, Ecto.Changeset.t()} def update_follower(%Follower{} = follower, attrs) do - follower - |> Follower.changeset(attrs) - |> Repo.update() + with {:ok, %Follower{} = follower} <- + follower + |> Follower.changeset(attrs) + |> Repo.update() do + {:ok, Repo.preload(follower, [:actor, :target_actor])} + end end @doc """ @@ -597,10 +609,10 @@ defmodule Mobilizon.Actors do Returns the list of followers for an actor. If actor A and C both follow actor B, actor B's followers are A and C. """ - @spec list_followers_for_actor(Actor.t()) :: [Follower.t()] - def list_followers_for_actor(%Actor{id: actor_id}) do + @spec list_followers_actors_for_actor(Actor.t()) :: [Actor.t()] + def list_followers_actors_for_actor(%Actor{id: actor_id}) do actor_id - |> followers_for_actor_query() + |> follower_actors_for_actor_query() |> Repo.all() end @@ -610,18 +622,28 @@ defmodule Mobilizon.Actors do @spec list_external_followers_for_actor(Actor.t()) :: [Follower.t()] def list_external_followers_for_actor(%Actor{id: actor_id}) do actor_id - |> followers_for_actor_query() - |> filter_external() + |> list_external_follower_actors_for_actor_query() |> Repo.all() end + @doc """ + Returns the paginated list of external followers for an actor. + """ + @spec list_external_followers_for_actor_paginated(Actor.t(), integer | nil, integer | nil) :: + Page.t() + def list_external_followers_for_actor_paginated(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do + actor_id + |> list_external_followers_for_actor_query() + |> Page.build_page(page, limit) + end + @doc """ Build a page struct for followers of an actor. """ @spec build_followers_for_actor(Actor.t(), integer | nil, integer | nil) :: Page.t() def build_followers_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do actor_id - |> followers_for_actor_query() + |> follower_actors_for_actor_query() |> Page.build_page(page, limit) end @@ -632,17 +654,32 @@ defmodule Mobilizon.Actors do @spec list_followings_for_actor(Actor.t()) :: [Follower.t()] def list_followings_for_actor(%Actor{id: actor_id}) do actor_id - |> followings_for_actor_query() + |> followings_actors_for_actor_query() |> Repo.all() end + @doc """ + Returns the list of external followings for an actor. + """ + @spec list_external_followings_for_actor_paginated(Actor.t(), integer | nil, integer | nil) :: + Page.t() + def list_external_followings_for_actor_paginated( + %Actor{id: actor_id}, + page \\ nil, + limit \\ nil + ) do + actor_id + |> list_external_followings_for_actor_query() + |> Page.build_page(page, limit) + end + @doc """ Build a page struct for followings of an actor. """ @spec build_followings_for_actor(Actor.t(), integer | nil, integer | nil) :: Page.t() def build_followings_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do actor_id - |> followings_for_actor_query() + |> followings_actors_for_actor_query() |> Page.build_page(page, limit) end @@ -747,7 +784,7 @@ defmodule Mobilizon.Actors do defp actor_with_preload_query(actor_id) do from( a in Actor, - where: a.id == ^actor_id, + where: a.id == ^actor_id and not a.suspended, preload: [:organized_events, :followers, :followings] ) end @@ -885,12 +922,13 @@ defmodule Mobilizon.Actors do defp follower_by_followed_and_following_query(followed_id, follower_id) do from( f in Follower, - where: f.target_actor_id == ^followed_id and f.actor_id == ^follower_id + where: f.target_actor_id == ^followed_id and f.actor_id == ^follower_id, + preload: [:actor, :target_actor] ) end - @spec followers_for_actor_query(integer | String.t()) :: Ecto.Query.t() - defp followers_for_actor_query(actor_id) do + @spec follower_actors_for_actor_query(integer | String.t()) :: Ecto.Query.t() + defp follower_actors_for_actor_query(actor_id) do from( a in Actor, join: f in Follower, @@ -899,8 +937,18 @@ defmodule Mobilizon.Actors do ) end - @spec followings_for_actor_query(integer | String.t()) :: Ecto.Query.t() - defp followings_for_actor_query(actor_id) do + @spec follower_for_actor_query(integer | String.t()) :: Ecto.Query.t() + defp follower_for_actor_query(actor_id) do + from( + f in Follower, + join: a in Actor, + on: a.id == f.actor_id, + where: f.target_actor_id == ^actor_id + ) + end + + @spec followings_actors_for_actor_query(integer | String.t()) :: Ecto.Query.t() + defp followings_actors_for_actor_query(actor_id) do from( a in Actor, join: f in Follower, @@ -909,6 +957,38 @@ defmodule Mobilizon.Actors do ) end + @spec followings_for_actor_query(integer | String.t()) :: Ecto.Query.t() + defp followings_for_actor_query(actor_id) do + from( + f in Follower, + join: a in Actor, + on: a.id == f.target_actor_id, + where: f.actor_id == ^actor_id + ) + end + + @spec list_external_follower_actors_for_actor_query(integer) :: Ecto.Query.t() + defp list_external_follower_actors_for_actor_query(actor_id) do + actor_id + |> follower_actors_for_actor_query() + |> filter_external() + end + + @spec list_external_followers_for_actor_query(integer) :: Ecto.Query.t() + defp list_external_followers_for_actor_query(actor_id) do + actor_id + |> follower_for_actor_query() + |> filter_follower_actors_external() + end + + @spec list_external_followings_for_actor_query(integer) :: Ecto.Query.t() + defp list_external_followings_for_actor_query(actor_id) do + actor_id + |> followings_for_actor_query() + |> filter_follower_actors_external() + |> order_by(desc: :updated_at) + end + @spec filter_local(Ecto.Query.t()) :: Ecto.Query.t() defp filter_local(query) do from(a in query, where: is_nil(a.domain)) @@ -919,8 +999,16 @@ defmodule Mobilizon.Actors do from(a in query, where: not is_nil(a.domain)) end + @spec filter_follower_actors_external(Ecto.Query.t()) :: Ecto.Query.t() + defp filter_follower_actors_external(query) do + query + |> where([_f, a], not is_nil(a.domain)) + |> preload([f, a], [:target_actor, :actor]) + end + @spec filter_by_type(Ecto.Query.t(), ActorType.t()) :: Ecto.Query.t() - defp filter_by_type(query, type) when type in [:Person, :Group] do + defp filter_by_type(query, type) + when type in [:Person, :Group, :Application, :Service, :Organisation] do from(a in query, where: a.type == ^type) end @@ -943,4 +1031,36 @@ defmodule Mobilizon.Actors do @spec preload_followers(Actor.t(), boolean) :: Actor.t() defp preload_followers(actor, true), do: Repo.preload(actor, [:followers]) defp preload_followers(actor, false), do: actor + + defp delete_actor_organized_events(%Actor{organized_events: organized_events}) do + res = + Enum.map(organized_events, fn event -> + event = + Repo.preload(event, [:organizer_actor, :participants, :picture, :mentions, :comments]) + + ActivityPub.delete(event, false) + end) + + if Enum.all?(res, fn {status, _, _} -> status == :ok end) do + {:ok, res} + else + {:error, res} + end + end + + defp delete_actor_empty_comments(%Actor{comments: comments}) do + res = + Enum.map(comments, fn comment -> + comment = + Repo.preload(comment, [:actor, :mentions, :event, :in_reply_to_comment, :origin_comment]) + + ActivityPub.delete(comment, false) + end) + + if Enum.all?(res, fn {status, _, _} -> status == :ok end) do + {:ok, res} + else + {:error, res} + end + end end diff --git a/lib/mobilizon/actors/follower.ex b/lib/mobilizon/actors/follower.ex index f38e00fd..9b29e4c9 100644 --- a/lib/mobilizon/actors/follower.ex +++ b/lib/mobilizon/actors/follower.ex @@ -19,11 +19,15 @@ defmodule Mobilizon.Actors.Follower do @required_attrs [:url, :approved, :target_actor_id, :actor_id] @attrs @required_attrs + @timestamps_opts [type: :utc_datetime] + @primary_key {:id, :binary_id, autogenerate: true} schema "followers" do field(:approved, :boolean, default: false) field(:url, :string) + timestamps() + belongs_to(:target_actor, Actor) belongs_to(:actor, Actor) end diff --git a/lib/mobilizon/events/comment.ex b/lib/mobilizon/events/comment.ex index 9e2efd69..8f18b0e7 100644 --- a/lib/mobilizon/events/comment.ex +++ b/lib/mobilizon/events/comment.ex @@ -32,7 +32,6 @@ defmodule Mobilizon.Events.Comment do # When deleting an event we only nihilify everything @required_attrs [:url] @creation_required_attrs @required_attrs ++ [:text, :actor_id] - @deletion_required_attrs @required_attrs ++ [:deleted_at] @optional_attrs [ :text, :actor_id, @@ -81,11 +80,13 @@ defmodule Mobilizon.Events.Comment do |> validate_required(@creation_required_attrs) end - @spec delete_changeset(t, map) :: Ecto.Changeset.t() - def delete_changeset(%__MODULE__{} = comment, attrs) do + @spec delete_changeset(t) :: Ecto.Changeset.t() + def delete_changeset(%__MODULE__{} = comment) do comment - |> common_changeset(attrs) - |> validate_required(@deletion_required_attrs) + |> change() + |> put_change(:text, nil) + |> put_change(:actor_id, nil) + |> put_change(:deleted_at, DateTime.utc_now() |> DateTime.truncate(:second)) end @doc """ diff --git a/lib/mobilizon/events/event.ex b/lib/mobilizon/events/event.ex index 4dd35c7d..603d858a 100644 --- a/lib/mobilizon/events/event.ex +++ b/lib/mobilizon/events/event.ex @@ -13,6 +13,8 @@ defmodule Mobilizon.Events.Event do alias Mobilizon.Addresses + alias Mobilizon.Events + alias Mobilizon.Events.{ Comment, EventOptions, @@ -73,6 +75,7 @@ defmodule Mobilizon.Events.Event do :category, :status, :draft, + :local, :visibility, :join_options, :publish_at, @@ -190,13 +193,16 @@ defmodule Mobilizon.Events.Event do def can_be_managed_by(_event, _actor), do: {:event_can_be_managed, false} @spec put_tags(Changeset.t(), map) :: Changeset.t() - defp put_tags(%Changeset{} = changeset, %{tags: tags}), - do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1)) + defp put_tags(%Changeset{} = changeset, %{tags: tags}) do + put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1)) + end defp put_tags(%Changeset{} = changeset, _), do: changeset # We need a changeset instead of a raw struct because of slug which is generated in changeset - defp process_tag(%{id: _id} = tag), do: tag + defp process_tag(%{id: id} = _tag) do + Events.get_tag(id) + end defp process_tag(tag) do Tag.changeset(%Tag{}, tag) diff --git a/lib/mobilizon/events/event_participant_stats.ex b/lib/mobilizon/events/event_participant_stats.ex index 6e687806..e5b8728d 100644 --- a/lib/mobilizon/events/event_participant_stats.ex +++ b/lib/mobilizon/events/event_participant_stats.ex @@ -39,6 +39,21 @@ defmodule Mobilizon.Events.EventParticipantStats do @doc false @spec changeset(t, map) :: Ecto.Changeset.t() def changeset(%__MODULE__{} = event_options, attrs) do - cast(event_options, attrs, @attrs) + event_options + |> cast(attrs, @attrs) + |> validate_stats() + end + + defp validate_stats(%Ecto.Changeset{} = changeset) do + changeset + |> validate_number(:not_approved, greater_than_or_equal_to: 0) + |> validate_number(:rejected, greater_than_or_equal_to: 0) + |> validate_number(:participant, greater_than_or_equal_to: 0) + |> validate_number(:moderator, greater_than_or_equal_to: 0) + |> validate_number(:administrator, greater_than_or_equal_to: 0) + |> validate_number(:creator, greater_than_or_equal_to: 0) + + # TODO: Replace me with something like the following + # Enum.reduce(@attrs, fn key, changeset -> validate_number(changeset, key, greater_than_or_equal_to: 0) end) end end diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index 9e17d782..0427c638 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -97,6 +97,7 @@ defmodule Mobilizon.Events do @comment_preloads [ :actor, + :event, :attributed_to, :in_reply_to_comment, :origin_comment, @@ -722,6 +723,13 @@ defmodule Mobilizon.Events do |> Repo.all() end + @spec list_actors_participants_for_event(String.t()) :: [Actor.t()] + def list_actors_participants_for_event(id) do + id + |> list_participant_actors_for_event_query + |> Repo.all() + end + @doc """ Returns the list of participations for an actor. @@ -864,30 +872,15 @@ defmodule Mobilizon.Events do |> Multi.run(:update_event_participation_stats, fn _repo, %{ participant: - %Participant{ - role: role, - event_id: event_id - } = _participant + %Participant{role: new_role} = + participant } -> - with {:update_event_participation_stats, true} <- - {:update_event_participation_stats, update_event_participation_stats}, - {:ok, %Event{} = event} <- get_event(event_id), - %EventParticipantStats{} = participant_stats <- - Map.get(event, :participant_stats), - %EventParticipantStats{} = participant_stats <- - Map.update(participant_stats, role, 0, &(&1 + 1)), - {:ok, %Event{} = event} <- - event - |> Event.update_changeset(%{ - participant_stats: Map.from_struct(participant_stats) - }) - |> Repo.update() do - {:ok, event} - else - {:update_event_participation_stats, false} -> {:ok, nil} - {:error, :event_not_found} -> {:error, :event_not_found} - err -> {:error, err} - end + update_participant_stats( + participant, + nil, + new_role, + update_event_participation_stats + ) end) |> Repo.transaction() do {:ok, Repo.preload(participant, [:event, :actor])} @@ -899,10 +892,21 @@ defmodule Mobilizon.Events do """ @spec update_participant(Participant.t(), map) :: {:ok, Participant.t()} | {:error, Changeset.t()} - def update_participant(%Participant{} = participant, attrs) do - participant - |> Participant.changeset(attrs) - |> Repo.update() + def update_participant(%Participant{role: old_role} = participant, attrs) do + with {:ok, %{participant: %Participant{} = participant}} <- + Multi.new() + |> Multi.update(:participant, Participant.changeset(participant, attrs)) + |> Multi.run(:update_event_participation_stats, fn _repo, + %{ + participant: + %Participant{role: new_role} = + participant + } -> + update_participant_stats(participant, old_role, new_role) + end) + |> Repo.transaction() do + {:ok, Repo.preload(participant, [:event, :actor])} + end end @doc """ @@ -910,7 +914,71 @@ defmodule Mobilizon.Events do """ @spec delete_participant(Participant.t()) :: {:ok, Participant.t()} | {:error, Changeset.t()} - def delete_participant(%Participant{} = participant), do: Repo.delete(participant) + def delete_participant(%Participant{role: old_role} = participant) do + with {:ok, %{participant: %Participant{} = participant}} <- + Multi.new() + |> Multi.delete(:participant, participant) + |> Multi.run(:update_event_participation_stats, fn _repo, + %{ + participant: + %Participant{} = participant + } -> + update_participant_stats(participant, old_role, nil) + end) + |> Repo.transaction() do + {:ok, participant} + end + end + + defp update_participant_stats( + %Participant{ + event_id: event_id + } = _participant, + old_role, + new_role, + update_event_participation_stats \\ true + ) do + with {:update_event_participation_stats, true} <- + {:update_event_participation_stats, update_event_participation_stats}, + {:ok, %Event{} = event} <- get_event(event_id), + %EventParticipantStats{} = participant_stats <- + Map.get(event, :participant_stats), + %EventParticipantStats{} = participant_stats <- + do_update_participant_stats(participant_stats, old_role, new_role), + {:ok, %Event{} = event} <- + event + |> Event.update_changeset(%{ + participant_stats: Map.from_struct(participant_stats) + }) + |> Repo.update() do + {:ok, event} + else + {:update_event_participation_stats, false} -> + {:ok, nil} + + {:error, :event_not_found} -> + {:error, :event_not_found} + + err -> + {:error, err} + end + end + + defp do_update_participant_stats(participant_stats, old_role, new_role) do + participant_stats + |> decrease_participant_stats(old_role) + |> increase_participant_stats(new_role) + end + + defp increase_participant_stats(participant_stats, nil), do: participant_stats + + defp increase_participant_stats(participant_stats, role), + do: Map.update(participant_stats, role, 0, &(&1 + 1)) + + defp decrease_participant_stats(participant_stats, nil), do: participant_stats + + defp decrease_participant_stats(participant_stats, role), + do: Map.update(participant_stats, role, 0, &(&1 - 1)) @doc """ Gets a single session. @@ -1170,11 +1238,7 @@ defmodule Mobilizon.Events do @spec delete_comment(Comment.t()) :: {:ok, Comment.t()} | {:error, Changeset.t()} def delete_comment(%Comment{} = comment) do comment - |> Comment.delete_changeset(%{ - text: nil, - actor_id: nil, - deleted_at: DateTime.utc_now() - }) + |> Comment.delete_changeset() |> Repo.update() end @@ -1561,14 +1625,22 @@ defmodule Mobilizon.Events do defp list_participants_for_event_query(event_id) do from( p in Participant, - join: e in Event, - on: p.event_id == e.id, - where: e.id == ^event_id, + where: p.event_id == ^event_id, preload: [:actor] ) end - @spec list_participants_for_event_query(String.t()) :: Ecto.Query.t() + @spec list_participant_actors_for_event_query(String.t()) :: Ecto.Query.t() + defp list_participant_actors_for_event_query(event_id) do + from( + a in Actor, + join: p in Participant, + on: p.actor_id == a.id, + where: p.event_id == ^event_id + ) + end + + @spec list_local_emails_user_participants_for_event_query(String.t()) :: Ecto.Query.t() def list_local_emails_user_participants_for_event_query(event_id) do Participant |> join(:inner, [p], a in Actor, on: p.actor_id == a.id and is_nil(a.domain)) diff --git a/lib/mobilizon/events/participant.ex b/lib/mobilizon/events/participant.ex index c3d851e1..a5bd7080 100644 --- a/lib/mobilizon/events/participant.ex +++ b/lib/mobilizon/events/participant.ex @@ -57,6 +57,7 @@ defmodule Mobilizon.Events.Participant do |> cast(attrs, @attrs) |> ensure_url() |> validate_required(@required_attrs) + |> unique_constraint(:actor_id, name: :participants_event_id_actor_id_index) end # If there's a blank URL that's because we're doing the first insert diff --git a/lib/mobilizon/share.ex b/lib/mobilizon/share.ex new file mode 100644 index 00000000..a6408fd9 --- /dev/null +++ b/lib/mobilizon/share.ex @@ -0,0 +1,75 @@ +defmodule Mobilizon.Share do + @moduledoc """ + Holds the list of shares made to external actors + """ + + use Ecto.Schema + import Ecto.Changeset + import Ecto.Query + alias Mobilizon.Storage.Repo + alias Mobilizon.Actors.Actor + + @type t :: %__MODULE__{ + uri: String.t(), + actor: Actor.t() + } + + @required_attrs [:uri, :actor_id, :owner_actor_id] + @optional_attrs [] + @attrs @required_attrs ++ @optional_attrs + + schema "shares" do + field(:uri, :string) + + belongs_to(:actor, Actor) + belongs_to(:owner_actor, Actor) + timestamps() + end + + @doc false + def changeset(share, attrs) do + share + |> cast(attrs, @attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:actor_id) + |> unique_constraint(:uri, name: :shares_uri_actor_id_index) + end + + @spec create(String.t(), integer(), integer()) :: + {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()} + def create(uri, actor_id, owner_actor_id) do + %__MODULE__{} + |> changeset(%{actor_id: actor_id, owner_actor_id: owner_actor_id, uri: uri}) + |> Repo.insert(on_conflict: :nothing) + end + + @spec get(String.t(), integer()) :: Ecto.Schema.t() | nil + def get(uri, actor_id) do + __MODULE__ + |> where(actor_id: ^actor_id, uri: ^uri) + |> Repo.one() + end + + @spec get_actors_by_share_uri(String.t()) :: [Ecto.Schema.t()] + def get_actors_by_share_uri(uri) do + Actor + |> join(:inner, [a], s in __MODULE__, on: s.actor_id == a.id) + |> where([_a, s], s.uri == ^uri) + |> Repo.all() + end + + @spec get_actors_by_owner_actor_id(integer()) :: [Ecto.Schema.t()] + def get_actors_by_owner_actor_id(actor_id) do + Actor + |> join(:inner, [a], s in __MODULE__, on: s.actor_id == a.id) + |> where([_a, s], s.owner_actor_id == ^actor_id) + |> Repo.all() + end + + @spec delete_all_by_uri(String.t()) :: {integer(), nil | [term()]} + def delete_all_by_uri(uri) do + __MODULE__ + |> where(uri: ^uri) + |> Repo.delete_all() + end +end diff --git a/lib/mobilizon/tombstone.ex b/lib/mobilizon/tombstone.ex index d0160fe4..e409fa84 100644 --- a/lib/mobilizon/tombstone.ex +++ b/lib/mobilizon/tombstone.ex @@ -28,7 +28,7 @@ defmodule Mobilizon.Tombstone do def changeset(%__MODULE__{} = tombstone, attrs) do tombstone |> cast(attrs, @attrs) - |> validate_required(@attrs) + |> validate_required(@required_attrs) end @spec create_tombstone(map()) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()} diff --git a/lib/mobilizon_web/api/follows.ex b/lib/mobilizon_web/api/follows.ex index 0c953966..c86f6d3f 100644 --- a/lib/mobilizon_web/api/follows.ex +++ b/lib/mobilizon_web/api/follows.ex @@ -12,8 +12,8 @@ defmodule MobilizonWeb.API.Follows do def follow(%Actor{} = follower, %Actor{} = followed) do case ActivityPub.follow(follower, followed) do - {:ok, activity, _} -> - {:ok, activity} + {:ok, activity, follow} -> + {:ok, activity, follow} e -> Logger.warn("Error while following actor: #{inspect(e)}") @@ -23,8 +23,8 @@ defmodule MobilizonWeb.API.Follows do def unfollow(%Actor{} = follower, %Actor{} = followed) do case ActivityPub.unfollow(follower, followed) do - {:ok, activity, _} -> - {:ok, activity} + {:ok, activity, follow} -> + {:ok, activity, follow} e -> Logger.warn("Error while unfollowing actor: #{inspect(e)}") @@ -33,15 +33,35 @@ defmodule MobilizonWeb.API.Follows do end def accept(%Actor{} = follower, %Actor{} = followed) do + Logger.debug("We're trying to accept a follow") + with %Follower{approved: false} = follow <- Actors.is_following(follower, followed), - {:ok, %Activity{} = activity, %Follower{approved: true}} <- + {:ok, %Activity{} = activity, %Follower{approved: true} = follow} <- ActivityPub.accept( :follow, follow, - %{approved: true} + true ) do - {:ok, activity} + {:ok, activity, follow} + else + %Follower{approved: true} -> + {:error, "Follow already accepted"} + end + end + + def reject(%Actor{} = follower, %Actor{} = followed) do + Logger.debug("We're trying to reject a follow") + + with %Follower{} = follow <- + Actors.is_following(follower, followed), + {:ok, %Activity{} = activity, %Follower{} = follow} <- + ActivityPub.reject( + :follow, + follow, + true + ) do + {:ok, activity, follow} else %Follower{approved: true} -> {:error, "Follow already accepted"} diff --git a/lib/mobilizon_web/api/groups.ex b/lib/mobilizon_web/api/groups.ex index 581d6630..d19329e7 100644 --- a/lib/mobilizon_web/api/groups.ex +++ b/lib/mobilizon_web/api/groups.ex @@ -17,7 +17,8 @@ defmodule MobilizonWeb.API.Groups do args |> Map.get(:preferred_username) |> HtmlSanitizeEx.strip_tags() |> String.trim(), {:existing_group, nil} <- {:existing_group, Actors.get_local_group_by_title(preferred_username)}, - {:ok, %Activity{} = activity, %Actor{} = group} <- ActivityPub.create(:group, args, true) do + {:ok, %Activity{} = activity, %Actor{} = group} <- + ActivityPub.create(:group, args, true, %{"actor" => args.creator_actor.url}) do {:ok, activity, group} else {:existing_group, _} -> diff --git a/lib/mobilizon_web/api/participations.ex b/lib/mobilizon_web/api/participations.ex index 02231906..3bd6a348 100644 --- a/lib/mobilizon_web/api/participations.ex +++ b/lib/mobilizon_web/api/participations.ex @@ -4,7 +4,6 @@ defmodule MobilizonWeb.API.Participations do """ alias Mobilizon.Actors.Actor - alias Mobilizon.Events alias Mobilizon.Events.{Event, Participant} alias Mobilizon.Service.ActivityPub alias MobilizonWeb.Email.Participation @@ -36,16 +35,13 @@ defmodule MobilizonWeb.API.Participations do %Participant{} = participation, %Actor{} = moderator ) do - with {:ok, activity, _} <- + with {:ok, activity, %Participant{role: :participant} = participation} <- ActivityPub.accept( :join, participation, - %{role: :participant}, true, - %{"to" => [moderator.url]} + %{"actor" => moderator.url} ), - {:ok, %Participant{role: :participant} = participation} <- - Events.update_participant(participation, %{"role" => :participant}), :ok <- Participation.send_emails_to_local_user(participation) do {:ok, activity, participation} end @@ -55,17 +51,12 @@ defmodule MobilizonWeb.API.Participations do %Participant{} = participation, %Actor{} = moderator ) do - with {:ok, activity, _} <- + with {:ok, activity, %Participant{role: :rejected} = participation} <- ActivityPub.reject( - %{ - to: [participation.actor.url], - actor: moderator.url, - object: participation.url - }, - "#{MobilizonWeb.Endpoint.url()}/reject/join/#{participation.id}" + :join, + participation, + %{"actor" => moderator.url} ), - {:ok, %Participant{role: :rejected} = participation} <- - Events.update_participant(participation, %{"role" => :rejected}), :ok <- Participation.send_emails_to_local_user(participation) do {:ok, activity, participation} end diff --git a/lib/mobilizon_web/api/reports.ex b/lib/mobilizon_web/api/reports.ex index 0b6dc89f..278ef828 100644 --- a/lib/mobilizon_web/api/reports.ex +++ b/lib/mobilizon_web/api/reports.ex @@ -17,7 +17,7 @@ defmodule MobilizonWeb.API.Reports do Create a report/flag on an actor, and optionally on an event or on comments. """ def report(args) do - case {:make_activity, ActivityPub.flag(args, Map.get(args, :local, false) == false)} do + case {:make_activity, ActivityPub.flag(args, Map.get(args, :forward, false) == true)} do {:make_activity, {:ok, %Activity{} = activity, %Report{} = report}} -> {:ok, activity, report} diff --git a/lib/mobilizon_web/cache/activity_pub.ex b/lib/mobilizon_web/cache/activity_pub.ex index d7d5c87d..14c76825 100644 --- a/lib/mobilizon_web/cache/activity_pub.ex +++ b/lib/mobilizon_web/cache/activity_pub.ex @@ -3,10 +3,12 @@ defmodule MobilizonWeb.Cache.ActivityPub do The ActivityPub related functions. """ - alias Mobilizon.{Actors, Events, Service} + alias Mobilizon.{Actors, Events, Service, Tombstone} alias Mobilizon.Actors.Actor alias Mobilizon.Events.{Comment, Event} alias Service.ActivityPub + alias MobilizonWeb.Router.Helpers, as: Routes + alias MobilizonWeb.Endpoint @cache :activity_pub @@ -39,7 +41,12 @@ defmodule MobilizonWeb.Cache.ActivityPub do {:commit, event} nil -> - {:ignore, nil} + with url <- Routes.page_url(Endpoint, :event, uuid), + %Tombstone{} = tomstone <- Tombstone.find_tombstone(url) do + tomstone + else + _ -> {:ignore, nil} + end end end) end diff --git a/lib/mobilizon_web/channels/graphql_socket.ex b/lib/mobilizon_web/channels/graphql_socket.ex new file mode 100644 index 00000000..72372a66 --- /dev/null +++ b/lib/mobilizon_web/channels/graphql_socket.ex @@ -0,0 +1,28 @@ +defmodule MobilizonWeb.GraphQLSocket do + use Phoenix.Socket + + use Absinthe.Phoenix.Socket, + schema: MobilizonWeb.Schema + + alias Mobilizon.Users.User + + def connect(%{"token" => token}, socket) do + with {:ok, authed_socket} <- + Guardian.Phoenix.Socket.authenticate(socket, MobilizonWeb.Guardian, token), + %User{} = user <- Guardian.Phoenix.Socket.current_resource(authed_socket) do + authed_socket = + Absinthe.Phoenix.Socket.put_options(socket, + context: %{ + current_user: user + } + ) + + {:ok, authed_socket} + else + {:error, _} -> + :error + end + end + + def id(_socket), do: nil +end diff --git a/lib/mobilizon_web/controllers/activity_pub_controller.ex b/lib/mobilizon_web/controllers/activity_pub_controller.ex index 0c749c96..51c5ecb1 100644 --- a/lib/mobilizon_web/controllers/activity_pub_controller.ex +++ b/lib/mobilizon_web/controllers/activity_pub_controller.ex @@ -17,6 +17,7 @@ defmodule MobilizonWeb.ActivityPubController do action_fallback(:errors) + plug(MobilizonWeb.Plugs.Federating when action in [:inbox, :relay]) plug(:relay_active? when action in [:relay]) def relay_active?(conn, _) do @@ -114,7 +115,7 @@ defmodule MobilizonWeb.ActivityPubController do end def relay(conn, _params) do - with {:commit, %Actor{} = actor} <- Cache.get_relay() do + with {status, %Actor{} = actor} when status in [:commit, :ok] <- Cache.get_relay() do conn |> put_resp_header("content-type", "application/activity+json") |> json(ActorView.render("actor.json", %{actor: actor})) diff --git a/lib/mobilizon_web/controllers/page_controller.ex b/lib/mobilizon_web/controllers/page_controller.ex index 6e7f5fe8..a79f94b6 100644 --- a/lib/mobilizon_web/controllers/page_controller.ex +++ b/lib/mobilizon_web/controllers/page_controller.ex @@ -28,13 +28,22 @@ defmodule MobilizonWeb.PageController do defp render_or_error(conn, check_fn, status, object_type, object) do if check_fn.(status, object) do - render(conn, object_type, object: object) + case object do + %Mobilizon.Tombstone{} -> + conn + |> put_status(:gone) + |> render(object_type, object: object) + + _ -> + render(conn, object_type, object: object) + end else {:error, :not_found} end end defp is_visible?(%{visibility: v}), do: v in [:public, :unlisted] + defp is_visible?(%Mobilizon.Tombstone{}), do: true defp ok_status?(status), do: status in [:ok, :commit] defp ok_status?(status, _), do: ok_status?(status) diff --git a/lib/mobilizon_web/controllers/web_finger_controller.ex b/lib/mobilizon_web/controllers/web_finger_controller.ex index a74bc41b..dca47c23 100644 --- a/lib/mobilizon_web/controllers/web_finger_controller.ex +++ b/lib/mobilizon_web/controllers/web_finger_controller.ex @@ -9,6 +9,7 @@ defmodule MobilizonWeb.WebFingerController do """ use MobilizonWeb, :controller + plug(MobilizonWeb.Plugs.Federating) alias Mobilizon.Service.WebFinger @doc """ diff --git a/lib/mobilizon_web/endpoint.ex b/lib/mobilizon_web/endpoint.ex index 1ff261e7..23fb15a1 100644 --- a/lib/mobilizon_web/endpoint.ex +++ b/lib/mobilizon_web/endpoint.ex @@ -3,6 +3,7 @@ defmodule MobilizonWeb.Endpoint do Endpoint for Mobilizon app """ use Phoenix.Endpoint, otp_app: :mobilizon + use Absinthe.Phoenix.Endpoint # For e2e tests if Application.get_env(:mobilizon, :sql_sandbox) do @@ -13,6 +14,11 @@ defmodule MobilizonWeb.Endpoint do ) end + socket("/graphql_socket", MobilizonWeb.GraphQLSocket, + websocket: true, + longpoll: false + ) + plug(MobilizonWeb.Plugs.UploadedMedia) # Serve at "/" the static files from "priv/static" directory. diff --git a/lib/mobilizon_web/http_signature.ex b/lib/mobilizon_web/http_signature.ex index f6c1f2db..5e0580d1 100644 --- a/lib/mobilizon_web/http_signature.ex +++ b/lib/mobilizon_web/http_signature.ex @@ -22,30 +22,36 @@ defmodule MobilizonWeb.HTTPSignaturePlug do end def call(conn, _opts) do - [signature | _] = get_req_header(conn, "signature") + case get_req_header(conn, "signature") do + [signature | _] -> + if signature do + # set (request-target) header to the appropriate value + # we also replace the digest header with the one we computed + conn = + conn + |> put_req_header( + "(request-target)", + String.downcase("#{conn.method}") <> " #{conn.request_path}" + ) - if signature do - # set (request-target) header to the appropriate value - # we also replace the digest header with the one we computed - conn = - conn - |> put_req_header( - "(request-target)", - String.downcase("#{conn.method}") <> " #{conn.request_path}" - ) + conn = + if conn.assigns[:digest] do + conn + |> put_req_header("digest", conn.assigns[:digest]) + else + conn + end - conn = - if conn.assigns[:digest] do - conn - |> put_req_header("digest", conn.assigns[:digest]) + signature_valid = HTTPSignatures.validate_conn(conn) + Logger.debug("Is signature valid ? #{inspect(signature_valid)}") + assign(conn, :valid_signature, signature_valid) else + Logger.debug("No signature header!") conn end - assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn)) - else - Logger.debug("No signature header!") - conn + _ -> + conn end end end diff --git a/lib/mobilizon_web/plugs/federating.ex b/lib/mobilizon_web/plugs/federating.ex new file mode 100644 index 00000000..282c8ab2 --- /dev/null +++ b/lib/mobilizon_web/plugs/federating.ex @@ -0,0 +1,27 @@ +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule MobilizonWeb.Plugs.Federating do + @moduledoc """ + Restrict ActivityPub routes when not federating + """ + import Plug.Conn + + def init(options) do + options + end + + def call(conn, _opts) do + if Mobilizon.Config.get([:instance, :federating]) do + conn + else + conn + |> put_status(404) + |> Phoenix.Controller.put_view(MobilizonWeb.ErrorView) + |> Phoenix.Controller.render("404.json") + |> halt() + end + end +end diff --git a/lib/mobilizon_web/plugs/mapped_signature_to_identity.ex b/lib/mobilizon_web/plugs/mapped_signature_to_identity.ex new file mode 100644 index 00000000..36132584 --- /dev/null +++ b/lib/mobilizon_web/plugs/mapped_signature_to_identity.ex @@ -0,0 +1,79 @@ +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule MobilizonWeb.Plugs.MappedSignatureToIdentity do + @moduledoc """ + Get actor identity from Signature when handing fetches + """ + alias Mobilizon.Service.HTTPSignatures.Signature + alias Mobilizon.Actors.Actor + alias Mobilizon.Service.ActivityPub.Utils + alias Mobilizon.Service.ActivityPub + + import Plug.Conn + require Logger + + def init(options), do: options + + @spec key_id_from_conn(Plug.Conn.t()) :: String.t() | nil + defp key_id_from_conn(conn) do + case HTTPSignatures.signature_for_conn(conn) do + %{"keyId" => key_id} -> + Signature.key_id_to_actor_url(key_id) + + _ -> + nil + end + end + + @spec actor_from_key_id(Plug.Conn.t()) :: Actor.t() | nil + defp actor_from_key_id(conn) do + with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn), + {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(key_actor_id) do + actor + else + _ -> + nil + end + end + + def call(%{assigns: %{actor: _}} = conn, _opts), do: conn + + # if this has payload make sure it is signed by the same actor that made it + def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do + with actor_id <- Utils.get_url(actor), + {:actor, %Actor{} = actor} <- {:actor, actor_from_key_id(conn)}, + {:actor_match, true} <- {:actor_match, actor.url == actor_id} do + assign(conn, :actor, actor) + else + {:actor_match, false} -> + Logger.debug("Failed to map identity from signature (payload actor mismatch)") + Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}") + assign(conn, :valid_signature, false) + + # remove me once testsuite uses mapped capabilities instead of what we do now + {:actor, nil} -> + Logger.debug("Failed to map identity from signature (lookup failure)") + Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}") + conn + end + end + + # no payload, probably a signed fetch + def call(%{assigns: %{valid_signature: true}} = conn, _opts) do + case actor_from_key_id(conn) do + %Actor{} = actor -> + assign(conn, :actor, actor) + + _ -> + Logger.debug("Failed to map identity from signature (no payload actor mismatch)") + Logger.debug("key_id=#{key_id_from_conn(conn)}") + assign(conn, :valid_signature, false) + end + end + + # no signature at all + def call(conn, _opts), do: conn +end diff --git a/lib/mobilizon_web/resolvers/admin.ex b/lib/mobilizon_web/resolvers/admin.ex index 21d4ec6c..2e46bf46 100644 --- a/lib/mobilizon_web/resolvers/admin.ex +++ b/lib/mobilizon_web/resolvers/admin.ex @@ -6,11 +6,15 @@ defmodule MobilizonWeb.Resolvers.Admin do import Mobilizon.Users.Guards alias Mobilizon.Admin.ActionLog + alias Mobilizon.Actors + alias Mobilizon.Actors.Actor alias Mobilizon.Events alias Mobilizon.Events.{Event, Comment} alias Mobilizon.Reports.{Note, Report} alias Mobilizon.Service.Statistics alias Mobilizon.Users.User + alias Mobilizon.Storage.Page + alias Mobilizon.Service.ActivityPub.Relay def list_action_logs( _parent, @@ -136,4 +140,76 @@ defmodule MobilizonWeb.Resolvers.Admin do def get_dashboard(_parent, _args, _resolution) do {:error, "You need to be logged-in and an administrator to access dashboard statistics"} end + + def list_relay_followers(_parent, %{page: page, limit: limit}, %{ + context: %{current_user: %User{role: role}} + }) + when is_admin(role) do + with %Actor{} = relay_actor <- Relay.get_actor() do + %Page{} = + page = Actors.list_external_followers_for_actor_paginated(relay_actor, page, limit) + + {:ok, page} + end + end + + def list_relay_followings(_parent, %{page: page, limit: limit}, %{ + context: %{current_user: %User{role: role}} + }) + when is_admin(role) do + with %Actor{} = relay_actor <- Relay.get_actor() do + %Page{} = + page = Actors.list_external_followings_for_actor_paginated(relay_actor, page, limit) + + {:ok, page} + end + end + + def create_relay(_parent, %{address: address}, %{context: %{current_user: %User{role: role}}}) + when is_admin(role) do + case Relay.follow(address) do + {:ok, _activity, follow} -> + {:ok, follow} + + {:error, {:error, err}} when is_bitstring(err) -> + {:error, err} + end + end + + def remove_relay(_parent, %{address: address}, %{context: %{current_user: %User{role: role}}}) + when is_admin(role) do + case Relay.unfollow(address) do + {:ok, _activity, follow} -> + {:ok, follow} + + {:error, {:error, err}} when is_bitstring(err) -> + {:error, err} + end + end + + def accept_subscription(_parent, %{address: address}, %{ + context: %{current_user: %User{role: role}} + }) + when is_admin(role) do + case Relay.accept(address) do + {:ok, _activity, follow} -> + {:ok, follow} + + {:error, {:error, err}} when is_bitstring(err) -> + {:error, err} + end + end + + def reject_subscription(_parent, %{address: address}, %{ + context: %{current_user: %User{role: role}} + }) + when is_admin(role) do + case Relay.reject(address) do + {:ok, _activity, follow} -> + {:ok, follow} + + {:error, {:error, err}} when is_bitstring(err) -> + {:error, err} + end + end end diff --git a/lib/mobilizon_web/resolvers/event.ex b/lib/mobilizon_web/resolvers/event.ex index 2efc5383..8d20b53e 100644 --- a/lib/mobilizon_web/resolvers/event.ex +++ b/lib/mobilizon_web/resolvers/event.ex @@ -245,6 +245,9 @@ defmodule MobilizonWeb.Resolvers.Event do {:error, "Participant #{id} can't be approved since it's already a participant (with role #{role})"} + {:has_participation, nil} -> + {:error, "Participant not found"} + {:actor_approve_permission, _} -> {:error, "Provided moderator actor ID doesn't have permission on this event"} diff --git a/lib/mobilizon_web/resolvers/group.ex b/lib/mobilizon_web/resolvers/group.ex index c367a844..92ea3a60 100644 --- a/lib/mobilizon_web/resolvers/group.ex +++ b/lib/mobilizon_web/resolvers/group.ex @@ -47,8 +47,8 @@ defmodule MobilizonWeb.Resolvers.Group do %{context: %{current_user: user}} ) do with creator_actor_id <- Map.get(args, :creator_actor_id), - {:is_owned, %Actor{} = actor} <- User.owns_actor(user, creator_actor_id), - args <- Map.put(args, :creator_actor, actor), + {:is_owned, %Actor{} = creator_actor} <- User.owns_actor(user, creator_actor_id), + args <- Map.put(args, :creator_actor, creator_actor), {:ok, _activity, %Actor{type: :Group} = group} <- API.Groups.create_group(args) do {:ok, group} diff --git a/lib/mobilizon_web/resolvers/person.ex b/lib/mobilizon_web/resolvers/person.ex index fb97f41a..9084ae30 100644 --- a/lib/mobilizon_web/resolvers/person.ex +++ b/lib/mobilizon_web/resolvers/person.ex @@ -97,7 +97,7 @@ defmodule MobilizonWeb.Resolvers.Person do {:find_actor, Actors.get_actor(id)}, {:is_owned, %Actor{}} <- User.owns_actor(user, actor.id), args <- save_attached_pictures(args), - {:ok, actor} <- Actors.update_actor(actor, args) do + {:ok, _activity, %Actor{} = actor} <- ActivityPub.update(:actor, actor, args, true) do {:ok, actor} else {:find_actor, nil} -> diff --git a/lib/mobilizon_web/router.ex b/lib/mobilizon_web/router.ex index caf0c99e..ba42d658 100644 --- a/lib/mobilizon_web/router.ex +++ b/lib/mobilizon_web/router.ex @@ -16,6 +16,7 @@ defmodule MobilizonWeb.Router do pipeline :activity_pub_signature do plug(:accepts, ["activity-json", "html"]) plug(MobilizonWeb.HTTPSignaturePlug) + plug(MobilizonWeb.Plugs.MappedSignatureToIdentity) end pipeline :relay do @@ -91,6 +92,8 @@ defmodule MobilizonWeb.Router do scope "/", MobilizonWeb do pipe_through(:activity_pub_and_html) + pipe_through(:activity_pub_signature) + get("/@:name", PageController, :actor) get("/events/:uuid", PageController, :event) get("/comments/:uuid", PageController, :comment) diff --git a/lib/mobilizon_web/schema.ex b/lib/mobilizon_web/schema.ex index e7f61efb..c78bd3f7 100644 --- a/lib/mobilizon_web/schema.ex +++ b/lib/mobilizon_web/schema.ex @@ -20,6 +20,7 @@ defmodule MobilizonWeb.Schema do import_types(MobilizonWeb.Schema.ActorInterface) import_types(MobilizonWeb.Schema.Actors.PersonType) import_types(MobilizonWeb.Schema.Actors.GroupType) + import_types(MobilizonWeb.Schema.Actors.ApplicationType) import_types(MobilizonWeb.Schema.CommentType) import_types(MobilizonWeb.Schema.SearchType) import_types(MobilizonWeb.Schema.ConfigType) @@ -140,5 +141,13 @@ defmodule MobilizonWeb.Schema do import_fields(:feed_token_mutations) import_fields(:picture_mutations) import_fields(:report_mutations) + import_fields(:admin_mutations) + end + + @desc """ + Root subscription + """ + subscription do + import_fields(:person_subscriptions) end end diff --git a/lib/mobilizon_web/schema/actor.ex b/lib/mobilizon_web/schema/actor.ex index 89ea0cee..2b21f1a2 100644 --- a/lib/mobilizon_web/schema/actor.ex +++ b/lib/mobilizon_web/schema/actor.ex @@ -3,13 +3,10 @@ defmodule MobilizonWeb.Schema.ActorInterface do Schema representation for Actor """ use Absinthe.Schema.Notation - import Absinthe.Resolution.Helpers, only: [dataloader: 1] alias Mobilizon.Actors.Actor - alias Mobilizon.{Events} import_types(MobilizonWeb.Schema.Actors.FollowerType) import_types(MobilizonWeb.Schema.EventType) - # import_types(MobilizonWeb.Schema.PictureType) @desc "An ActivityPub actor" interface :actor do @@ -21,7 +18,6 @@ defmodule MobilizonWeb.Schema.ActorInterface do field(:local, :boolean, description: "If the actor is from this instance") field(:summary, :string, description: "The actor's summary") field(:preferred_username, :string, description: "The actor's preferred username") - field(:keys, :string, description: "The actors RSA Keys") field(:manually_approves_followers, :boolean, description: "Whether the actors manually approves followers" @@ -38,17 +34,6 @@ defmodule MobilizonWeb.Schema.ActorInterface do field(:followersCount, :integer, description: "Number of followers for this actor") field(:followingCount, :integer, description: "Number of actors following this actor") - # This one should have a privacy setting - field(:organized_events, list_of(:event), - resolve: dataloader(Events), - description: "A list of the events this actor has organized" - ) - - # This one is for the person itself **only** - # field(:feed, list_of(:event), description: "List of events the actor sees in his or her feed") - - # field(:memberships, list_of(:member)) - resolve_type(fn %Actor{type: :Person}, _ -> :person @@ -56,6 +41,9 @@ defmodule MobilizonWeb.Schema.ActorInterface do %Actor{type: :Group}, _ -> :group + %Actor{type: :Application}, _ -> + :application + _, _ -> nil end) diff --git a/lib/mobilizon_web/schema/actors/application.ex b/lib/mobilizon_web/schema/actors/application.ex new file mode 100644 index 00000000..8bfdf41f --- /dev/null +++ b/lib/mobilizon_web/schema/actors/application.ex @@ -0,0 +1,38 @@ +defmodule MobilizonWeb.Schema.Actors.ApplicationType do + @moduledoc """ + Schema representation for Group. + """ + + use Absinthe.Schema.Notation + + @desc """ + Represents an application + """ + object :application do + interfaces([:actor]) + + field(:id, :id, description: "Internal ID for this application") + field(:url, :string, description: "The ActivityPub actor's URL") + field(:type, :actor_type, description: "The type of Actor (Person, Group,…)") + field(:name, :string, description: "The actor's displayed name") + field(:domain, :string, description: "The actor's domain if (null if it's this instance)") + field(:local, :boolean, description: "If the actor is from this instance") + field(:summary, :string, description: "The actor's summary") + field(:preferred_username, :string, description: "The actor's preferred username") + + field(:manually_approves_followers, :boolean, + description: "Whether the actors manually approves followers" + ) + + field(:suspended, :boolean, description: "If the actor is suspended") + + field(:avatar, :picture, description: "The actor's avatar picture") + field(:banner, :picture, description: "The actor's banner picture") + + # These one should have a privacy setting + field(:following, list_of(:follower), description: "List of followings") + field(:followers, list_of(:follower), description: "List of followers") + field(:followersCount, :integer, description: "Number of followers for this actor") + field(:followingCount, :integer, description: "Number of actors following this actor") + end +end diff --git a/lib/mobilizon_web/schema/actors/follower.ex b/lib/mobilizon_web/schema/actors/follower.ex index 50f3cb3a..258103c1 100644 --- a/lib/mobilizon_web/schema/actors/follower.ex +++ b/lib/mobilizon_web/schema/actors/follower.ex @@ -14,5 +14,13 @@ defmodule MobilizonWeb.Schema.Actors.FollowerType do field(:approved, :boolean, description: "Whether the follow has been approved by the target actor" ) + + field(:inserted_at, :datetime, description: "When the follow was created") + field(:updated_at, :datetime, description: "When the follow was updated") + end + + object :paginated_follower_list do + field(:elements, list_of(:follower), description: "A list of followers") + field(:total, :integer, description: "The total number of elements in the list") end end diff --git a/lib/mobilizon_web/schema/actors/group.ex b/lib/mobilizon_web/schema/actors/group.ex index 382df7f1..a243ef6b 100644 --- a/lib/mobilizon_web/schema/actors/group.ex +++ b/lib/mobilizon_web/schema/actors/group.ex @@ -27,7 +27,6 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do field(:local, :boolean, description: "If the actor is from this instance") field(:summary, :string, description: "The actor's summary") field(:preferred_username, :string, description: "The actor's preferred username") - field(:keys, :string, description: "The actors RSA Keys") field(:manually_approves_followers, :boolean, description: "Whether the actors manually approves followers" diff --git a/lib/mobilizon_web/schema/actors/person.ex b/lib/mobilizon_web/schema/actors/person.ex index bcddea04..2cbc4c78 100644 --- a/lib/mobilizon_web/schema/actors/person.ex +++ b/lib/mobilizon_web/schema/actors/person.ex @@ -27,7 +27,6 @@ defmodule MobilizonWeb.Schema.Actors.PersonType do field(:local, :boolean, description: "If the actor is from this instance") field(:summary, :string, description: "The actor's summary") field(:preferred_username, :string, description: "The actor's preferred username") - field(:keys, :string, description: "The actors RSA Keys") field(:manually_approves_followers, :boolean, description: "Whether the actors manually approves followers" @@ -160,4 +159,14 @@ defmodule MobilizonWeb.Schema.Actors.PersonType do resolve(handle_errors(&Person.register_person/3)) end end + + object :person_subscriptions do + field :event_person_participation_changed, :person do + arg(:person_id, non_null(:id)) + + config(fn args, _ -> + {:ok, topic: args.person_id} + end) + end + end end diff --git a/lib/mobilizon_web/schema/admin.ex b/lib/mobilizon_web/schema/admin.ex index ca5ec864..f1e7543f 100644 --- a/lib/mobilizon_web/schema/admin.ex +++ b/lib/mobilizon_web/schema/admin.ex @@ -71,5 +71,49 @@ defmodule MobilizonWeb.Schema.AdminType do field :dashboard, type: :dashboard do resolve(&Admin.get_dashboard/3) end + + field :relay_followers, type: :paginated_follower_list do + arg(:page, :integer, default_value: 1) + arg(:limit, :integer, default_value: 10) + resolve(&Admin.list_relay_followers/3) + end + + field :relay_followings, type: :paginated_follower_list do + arg(:page, :integer, default_value: 1) + arg(:limit, :integer, default_value: 10) + arg(:order_by, :string, default_value: :updated_at) + arg(:direction, :string, default_value: :desc) + resolve(&Admin.list_relay_followings/3) + end + end + + object :admin_mutations do + @desc "Add a relay subscription" + field :add_relay, type: :follower do + arg(:address, non_null(:string)) + + resolve(&Admin.create_relay/3) + end + + @desc "Delete a relay subscription" + field :remove_relay, type: :follower do + arg(:address, non_null(:string)) + + resolve(&Admin.remove_relay/3) + end + + @desc "Accept a relay subscription" + field :accept_relay, type: :follower do + arg(:address, non_null(:string)) + + resolve(&Admin.accept_subscription/3) + end + + @desc "Reject a relay subscription" + field :reject_relay, type: :follower do + arg(:address, non_null(:string)) + + resolve(&Admin.reject_subscription/3) + end end end diff --git a/lib/mobilizon_web/schema/report.ex b/lib/mobilizon_web/schema/report.ex index 65b0344a..e0edc07e 100644 --- a/lib/mobilizon_web/schema/report.ex +++ b/lib/mobilizon_web/schema/report.ex @@ -75,6 +75,7 @@ defmodule MobilizonWeb.Schema.ReportType do arg(:reported_id, non_null(:id)) arg(:event_id, :id, default_value: nil) arg(:comments_ids, list_of(:id), default_value: []) + arg(:forward, :boolean, default_value: false) resolve(&Report.create_report/3) end diff --git a/lib/mobilizon_web/templates/email/report.html.eex b/lib/mobilizon_web/templates/email/report.html.eex index 6e15092f..e4f26d06 100644 --- a/lib/mobilizon_web/templates/email/report.html.eex +++ b/lib/mobilizon_web/templates/email/report.html.eex @@ -35,7 +35,11 @@

- <%= gettext "%{reporter_name} (%{reporter_username}) reported the following content.", reporter_name: @report.reporter.name, reporter_username: Mobilizon.Actors.Actor.preferred_username_and_domain(@report.reporter) %> + <%= if @report.reporter.type == :Application and @report.reporter.preferred_username == "relay" do %> + <%= gettext "Someone on %{instance} reported the following content.", instance: @report.reporter.domain %> + <% else %> + <%= gettext "%{reporter_name} (%{reporter_username}) reported the following content.", reporter_name: @report.reporter.name, reporter_username: Mobilizon.Actors.Actor.preferred_username_and_domain(@report.reporter) %> + <% end %>

@@ -59,10 +63,10 @@ <%= if Map.has_key?(@report, :comments) && length(@report.comments) > 0 do %> -

<%= gettext "Comments" %>

+

<%= gettext "Comments" %>

<%= for comment <- @report.comments do %>

- <%= comment.text %> + <%= HtmlSanitizeEx.strip_tags(comment.text) %>

<% end %> diff --git a/lib/mobilizon_web/templates/email/report.text.eex b/lib/mobilizon_web/templates/email/report.text.eex index dae6e72f..d054fa42 100644 --- a/lib/mobilizon_web/templates/email/report.text.eex +++ b/lib/mobilizon_web/templates/email/report.text.eex @@ -4,20 +4,26 @@ <%= if Map.has_key?(@report, :event) do %> <%= gettext "Event" %> + <%= @report.event.title %> <% end %> + <%= if Map.has_key?(@report, :comments) && length(@report.comments) > 0 do %> <%= gettext "Comments" %> + <%= for comment <- @report.comments do %> <%= comment.text %> <% end %> <% end %> + <%= if @report.content do %> <%= gettext "Reason" %> + <%= @report.content %> <% end %> + View the report: <%= moderation_report_url(MobilizonWeb.Endpoint, :index, @report.id) %> diff --git a/lib/mobilizon_web/upload.ex b/lib/mobilizon_web/upload.ex index 3ba692af..5920ce58 100644 --- a/lib/mobilizon_web/upload.ex +++ b/lib/mobilizon_web/upload.ex @@ -148,6 +148,21 @@ defmodule MobilizonWeb.Upload do end end + defp prepare_upload(%{body: body, name: name} = _file, opts) do + with :ok <- check_binary_size(body, opts.size_limit), + tmp_path <- tempfile_for_image(body), + {:ok, content_type, name} <- MIME.file_mime_type(tmp_path, name) do + {:ok, + %__MODULE__{ + id: UUID.generate(), + name: name, + tempfile: tmp_path, + content_type: content_type, + size: byte_size(body) + }} + end + end + defp check_file_size(path, size_limit) when is_integer(size_limit) and size_limit > 0 do with {:ok, %{size: size}} <- File.stat(path), true <- size <= size_limit do @@ -160,6 +175,23 @@ defmodule MobilizonWeb.Upload do defp check_file_size(_, _), do: :ok + defp check_binary_size(binary, size_limit) + when is_integer(size_limit) and size_limit > 0 and byte_size(binary) >= size_limit do + {:error, :file_too_large} + end + + defp check_binary_size(_, _), do: :ok + + # Creates a tempfile using the Plug.Upload Genserver which cleans them up + # automatically. + defp tempfile_for_image(data) do + {:ok, tmp_path} = Plug.Upload.random_file("temp_files") + {:ok, tmp_file} = File.open(tmp_path, [:write, :raw, :binary]) + IO.binwrite(tmp_file, data) + + tmp_path + end + defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do path = URI.encode(path, &char_unescaped?/1) <> diff --git a/lib/mobilizon_web/views/activity_pub/actor_view.ex b/lib/mobilizon_web/views/activity_pub/actor_view.ex index e384b95d..284e1227 100644 --- a/lib/mobilizon_web/views/activity_pub/actor_view.ex +++ b/lib/mobilizon_web/views/activity_pub/actor_view.ex @@ -4,44 +4,13 @@ defmodule MobilizonWeb.ActivityPub.ActorView do alias Mobilizon.Actors alias Mobilizon.Actors.Actor alias Mobilizon.Service.ActivityPub - alias Mobilizon.Service.ActivityPub.{Activity, Utils} + alias Mobilizon.Service.ActivityPub.{Activity, Utils, Convertible} @private_visibility_empty_collection %{elements: [], total: 0} def render("actor.json", %{actor: actor}) do - public_key = Utils.pem_to_public_key_pem(actor.keys) - - %{ - "id" => actor.url, - "type" => to_string(actor.type), - "following" => actor.following_url, - "followers" => actor.followers_url, - "inbox" => actor.inbox_url, - "outbox" => actor.outbox_url, - "preferredUsername" => actor.preferred_username, - "name" => actor.name, - "summary" => actor.summary, - "url" => actor.url, - "manuallyApprovesFollowers" => actor.manually_approves_followers, - "publicKey" => %{ - "id" => "#{actor.url}#main-key", - "owner" => actor.url, - "publicKeyPem" => public_key - }, - # TODO : Make have actors have an uuid - # "uuid" => actor.uuid - "endpoints" => %{ - "sharedInbox" => actor.shared_inbox_url - } - # "icon" => %{ - # "type" => "Image", - # "url" => User.avatar_url(actor) - # }, - # "image" => %{ - # "type" => "Image", - # "url" => User.banner_url(actor) - # } - } + actor + |> Convertible.model_to_as() |> Map.merge(Utils.make_json_ld_header()) end diff --git a/lib/mobilizon_web/views/page_view.ex b/lib/mobilizon_web/views/page_view.ex index e3eea5c2..33b01cc7 100644 --- a/lib/mobilizon_web/views/page_view.ex +++ b/lib/mobilizon_web/views/page_view.ex @@ -4,69 +4,34 @@ defmodule MobilizonWeb.PageView do """ use MobilizonWeb, :view alias Mobilizon.Actors.Actor - alias Mobilizon.Service.ActivityPub.{Converter, Utils} + alias Mobilizon.Tombstone + alias Mobilizon.Service.ActivityPub.{Convertible, Utils} alias Mobilizon.Service.Metadata alias Mobilizon.Service.MetadataUtils alias Mobilizon.Service.Metadata.Instance + alias Mobilizon.Events.{Comment, Event} - def render("actor.activity-json", %{conn: %{assigns: %{object: actor}}}) do - public_key = Utils.pem_to_public_key_pem(actor.keys) - - %{ - "id" => Actor.build_url(actor.preferred_username, :page), - "type" => "Person", - "following" => Actor.build_url(actor.preferred_username, :following), - "followers" => Actor.build_url(actor.preferred_username, :followers), - "inbox" => Actor.build_url(actor.preferred_username, :inbox), - "outbox" => Actor.build_url(actor.preferred_username, :outbox), - "preferredUsername" => actor.preferred_username, - "name" => actor.name, - "summary" => actor.summary, - "url" => actor.url, - "manuallyApprovesFollowers" => actor.manually_approves_followers, - "publicKey" => %{ - "id" => "#{actor.url}#main-key", - "owner" => actor.url, - "publicKeyPem" => public_key - }, - # TODO : Make have actors have an uuid - # "uuid" => actor.uuid - "endpoints" => %{ - "sharedInbox" => actor.shared_inbox_url - } - # "icon" => %{ - # "type" => "Image", - # "url" => User.avatar_url(actor) - # }, - # "image" => %{ - # "type" => "Image", - # "url" => User.banner_url(actor) - # } - } + def render("actor.activity-json", %{conn: %{assigns: %{object: %Actor{} = actor}}}) do + actor + |> Convertible.model_to_as() |> Map.merge(Utils.make_json_ld_header()) end - def render("event.activity-json", %{conn: %{assigns: %{object: event}}}) do + def render("event.activity-json", %{conn: %{assigns: %{object: %Event{} = event}}}) do event - |> Converter.Event.model_to_as() + |> Convertible.model_to_as() |> Map.merge(Utils.make_json_ld_header()) end - def render("comment.activity-json", %{conn: %{assigns: %{object: comment}}}) do - comment = Converter.Comment.model_to_as(comment) + def render("event.activity-json", %{conn: %{assigns: %{object: %Tombstone{} = event}}}) do + event + |> Convertible.model_to_as() + |> Map.merge(Utils.make_json_ld_header()) + end - %{ - "actor" => comment["actor"], - "uuid" => comment["uuid"], - # The activity should have attributedTo, not the comment itself - # "attributedTo" => comment.attributed_to, - "type" => "Note", - "id" => comment["id"], - "content" => comment["content"], - "mediaType" => "text/html" - # "published" => Timex.format!(comment.inserted_at, "{ISO:Extended}"), - # "updated" => Timex.format!(comment.updated_at, "{ISO:Extended}") - } + def render("comment.activity-json", %{conn: %{assigns: %{object: %Comment{} = comment}}}) do + comment + |> Convertible.model_to_as() |> Map.merge(Utils.make_json_ld_header()) end @@ -74,7 +39,9 @@ defmodule MobilizonWeb.PageView do when page in ["actor.html", "event.html", "comment.html"] do with {:ok, index_content} <- File.read(index_file_path()) do tags = object |> Metadata.build_tags() |> MetadataUtils.stringify_tags() - index_content = String.replace(index_content, "", tags) + + index_content = replace_meta(index_content, tags) + {:safe, index_content} end end @@ -82,7 +49,9 @@ defmodule MobilizonWeb.PageView do def render("index.html", _assigns) do with {:ok, index_content} <- File.read(index_file_path()) do tags = Instance.build_tags() |> MetadataUtils.stringify_tags() - index_content = String.replace(index_content, "", tags) + + index_content = replace_meta(index_content, tags) + {:safe, index_content} end end @@ -90,4 +59,11 @@ defmodule MobilizonWeb.PageView do defp index_file_path do Path.join(Application.app_dir(:mobilizon, "priv/static"), "index.html") end + + # TODO: Find why it's different in dev/prod and during tests + defp replace_meta(index_content, tags) do + index_content + |> String.replace("", tags) + |> String.replace("", tags) + end end diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex index 8e3d4a0e..4c612c80 100644 --- a/lib/service/activity_pub/activity_pub.ex +++ b/lib/service/activity_pub/activity_pub.ex @@ -11,7 +11,7 @@ defmodule Mobilizon.Service.ActivityPub do import Mobilizon.Service.ActivityPub.Utils import Mobilizon.Service.ActivityPub.Visibility - alias Mobilizon.{Actors, Config, Events, Reports, Users} + alias Mobilizon.{Actors, Config, Events, Reports, Users, Share} alias Mobilizon.Actors.{Actor, Follower} alias Mobilizon.Events.{Comment, Event, Participant} alias Mobilizon.Reports.Report @@ -50,6 +50,15 @@ defmodule Mobilizon.Service.ActivityPub do def fetch_object_from_url(url) do Logger.info("Fetching object from url #{url}") + date = Mobilizon.Service.HTTPSignatures.Signature.generate_date_header() + + headers = + [{:Accept, "application/activity+json"}] + |> maybe_date_fetch(date) + |> sign_fetch(url, date) + + Logger.debug("Fetch headers: #{inspect(headers)}") + with {:not_http, true} <- {:not_http, String.starts_with?(url, "http")}, {:existing_event, nil} <- {:existing_event, Events.get_event_by_url(url)}, {:existing_comment, nil} <- {:existing_comment, Events.get_comment_from_url(url)}, @@ -58,12 +67,13 @@ defmodule Mobilizon.Service.ActivityPub do {:ok, %{body: body, status_code: code}} when code in 200..299 <- HTTPoison.get( url, - [Accept: "application/activity+json"], + headers, follow_redirect: true, timeout: 10_000, recv_timeout: 20_000 ), {:ok, data} <- Jason.decode(body), + {:origin_check, true} <- {:origin_check, origin_check?(url, data)}, params <- %{ "type" => "Create", "to" => data["to"], @@ -95,6 +105,10 @@ defmodule Mobilizon.Service.ActivityPub do {:existing_actor, {:ok, %Actor{url: actor_url}}} -> {:ok, Actors.get_actor_by_url!(actor_url, true)} + {:origin_check, false} -> + Logger.warn("Object origin check failed") + {:error, "Object origin check failed"} + e -> {:error, e} end @@ -114,9 +128,9 @@ defmodule Mobilizon.Service.ActivityPub do {:ok, %Actor{} = actor} -> {:ok, actor} - _ -> + err -> Logger.warn("Could not fetch by AP id") - + Logger.debug(inspect(err)) {:error, "Could not fetch by AP id"} end end @@ -184,11 +198,13 @@ defmodule Mobilizon.Service.ActivityPub do end end - def accept(type, entity, args, local \\ false, additional \\ %{}) do + def accept(type, entity, local \\ true, additional \\ %{}) do + Logger.debug("We're accepting something") + {:ok, entity, update_data} = case type do - :join -> accept_join(entity, args, additional) - :follow -> accept_follow(entity, args, additional) + :join -> accept_join(entity, additional) + :follow -> accept_follow(entity, additional) end with {:ok, activity} <- create_activity(update_data, local), @@ -202,63 +218,24 @@ defmodule Mobilizon.Service.ActivityPub do end end - def reject(%{to: to, actor: actor, object: object} = params, activity_wrapper_id \\ nil) do - # only accept false as false value - local = !(params[:local] == false) + def reject(type, entity, local \\ true, additional \\ %{}) do + {:ok, entity, update_data} = + case type do + :join -> reject_join(entity, additional) + :follow -> reject_follow(entity, additional) + end - with data <- %{ - "to" => to, - "type" => "Reject", - "actor" => actor, - "object" => object, - "id" => activity_wrapper_id || get_url(object) <> "/activity" - }, - {:ok, activity} <- create_activity(data, local), - {:ok, object} <- insert_full_object(data), + with {:ok, activity} <- create_activity(update_data, local), :ok <- maybe_federate(activity) do - {:ok, activity, object} + {:ok, activity, entity} + else + err -> + Logger.error("Something went wrong while creating an activity") + Logger.debug(inspect(err)) + err end end - # TODO: This is weird, maybe we shouldn't check here if we can make the activity. - # def like( - # %Actor{url: url} = actor, - # object, - # activity_id \\ nil, - # local \\ true - # ) do - # with nil <- get_existing_like(url, object), - # like_data <- make_like_data(user, object, activity_id), - # {:ok, activity} <- create_activity(like_data, local), - # {:ok, object} <- insert_full_object(data), - # {:ok, object} <- add_like_to_object(activity, object), - # :ok <- maybe_federate(activity) do - # {:ok, activity, object} - # else - # %Activity{} = activity -> {:ok, activity, object} - # error -> {:error, error} - # end - # end - - # def unlike( - # %User{} = actor, - # %Object{} = object, - # activity_id \\ nil, - # local \\ true - # ) do - # with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object), - # unlike_data <- make_unlike_data(actor, like_activity, activity_id), - # {:ok, unlike_activity} <- create_activity(unlike_data, local), - # {:ok, _object} <- insert_full_object(data), - # {:ok, _activity} <- Repo.delete(like_activity), - # {:ok, object} <- remove_like_from_object(like_activity, object), - # :ok <- maybe_federate(unlike_activity) do - # {:ok, unlike_activity, like_activity, object} - # else - # _e -> {:ok, object} - # end - # end - def announce( %Actor{} = actor, object, @@ -267,9 +244,10 @@ defmodule Mobilizon.Service.ActivityPub do public \\ true ) do with true <- is_public?(object), + {:ok, %Actor{id: object_owner_actor_id}} <- Actors.get_actor_by_url(object["actor"]), + {:ok, %Share{} = _share} <- Share.create(object["id"], actor.id, object_owner_actor_id), announce_data <- make_announce_data(actor, object, activity_id, public), {:ok, activity} <- create_activity(announce_data, local), - {:ok, object} <- insert_full_object(announce_data), :ok <- maybe_federate(activity) do {:ok, activity, object} else @@ -288,7 +266,6 @@ defmodule Mobilizon.Service.ActivityPub do with announce_activity <- make_announce_data(actor, object, cancelled_activity_id), unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id), {:ok, unannounce_activity} <- create_activity(unannounce_data, local), - {:ok, object} <- insert_full_object(unannounce_data), :ok <- maybe_federate(unannounce_activity) do {:ok, unannounce_activity, object} else @@ -327,9 +304,8 @@ defmodule Mobilizon.Service.ActivityPub do unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_unfollow_id), {:ok, activity} <- create_activity(unfollow_data, local), - {:ok, object} <- insert_full_object(unfollow_data), :ok <- maybe_federate(activity) do - {:ok, activity, object} + {:ok, activity, follow} else err -> Logger.debug("Error while unfollowing an actor #{inspect(err)}") @@ -339,6 +315,7 @@ defmodule Mobilizon.Service.ActivityPub do def delete(object, local \\ true) + @spec delete(Event.t(), boolean) :: {:ok, Activity.t(), Event.t()} def delete(%Event{url: url, organizer_actor: actor} = event, local) do data = %{ "type" => "Delete", @@ -348,15 +325,19 @@ defmodule Mobilizon.Service.ActivityPub do "id" => url <> "/delete" } - with {:ok, %Event{} = event} <- Events.delete_event(event), + with audience <- + Audience.calculate_to_and_cc_from_mentions(event), + {:ok, %Event{} = event} <- Events.delete_event(event), {:ok, %Tombstone{} = _tombstone} <- Tombstone.create_tombstone(%{uri: event.url, actor_id: actor.id}), - {:ok, activity} <- create_activity(data, local), + Share.delete_all_by_uri(event.url), + {:ok, activity} <- create_activity(Map.merge(data, audience), local), :ok <- maybe_federate(activity) do {:ok, activity, event} end end + @spec delete(Comment.t(), boolean) :: {:ok, Activity.t(), Comment.t()} def delete(%Comment{url: url, actor: actor} = comment, local) do data = %{ "type" => "Delete", @@ -366,10 +347,13 @@ defmodule Mobilizon.Service.ActivityPub do "to" => [actor.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"] } - with {:ok, %Comment{} = comment} <- Events.delete_comment(comment), + with audience <- + Audience.calculate_to_and_cc_from_mentions(comment), + {:ok, %Comment{} = comment} <- Events.delete_comment(comment), {:ok, %Tombstone{} = _tombstone} <- Tombstone.create_tombstone(%{uri: comment.url, actor_id: actor.id}), - {:ok, activity} <- create_activity(data, local), + Share.delete_all_by_uri(comment.url), + {:ok, activity} <- create_activity(Map.merge(data, audience), local), :ok <- maybe_federate(activity) do {:ok, activity, comment} end @@ -384,7 +368,7 @@ defmodule Mobilizon.Service.ActivityPub do "to" => [url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"] } - with {:ok, %Actor{} = actor} <- Actors.delete_actor(actor), + with {:ok, %Oban.Job{}} <- Actors.delete_actor(actor), {:ok, activity} <- create_activity(data, local), :ok <- maybe_federate(activity) do {:ok, activity, actor} @@ -396,6 +380,8 @@ defmodule Mobilizon.Service.ActivityPub do {:create_report, {:ok, %Report{} = report}} <- {:create_report, Reports.create_report(args)}, report_as_data <- Convertible.model_to_as(report), + cc <- if(local, do: [report.reported.url], else: []), + report_as_data <- Map.merge(report_as_data, %{"to" => [], "cc" => cc}), {:ok, activity} <- create_activity(report_as_data, local), :ok <- maybe_federate(activity) do Enum.each(Users.list_moderators(), fn moderator -> @@ -413,52 +399,56 @@ defmodule Mobilizon.Service.ActivityPub do end end - def join(object, actor, local \\ true) + def join(object, actor, local \\ true, additional \\ %{}) - def join(%Event{options: options} = event, %Actor{} = actor, local) do + def join(%Event{} = event, %Actor{} = actor, local, additional) do # TODO Refactor me for federation - with maximum_attendee_capacity <- - Map.get(options, :maximum_attendee_capacity) || 0, - {:maximum_attendee_capacity, true} <- - {:maximum_attendee_capacity, - maximum_attendee_capacity == 0 || - Mobilizon.Events.count_participant_participants(event.id) < - maximum_attendee_capacity}, - role <- Mobilizon.Events.get_default_participant_role(event), + with {:maximum_attendee_capacity, true} <- + {:maximum_attendee_capacity, check_attendee_capacity(event)}, {:ok, %Participant{} = participant} <- Mobilizon.Events.create_participant(%{ - role: role, + role: :not_approved, event_id: event.id, - actor_id: actor.id + actor_id: actor.id, + url: Map.get(additional, :url) }), join_data <- Convertible.model_to_as(participant), - join_data <- Map.put(join_data, "to", [event.organizer_actor.url]), - join_data <- Map.put(join_data, "cc", []), - {:ok, activity} <- create_activity(join_data, local), - {:ok, _object} <- insert_full_object(join_data), + audience <- + Audience.calculate_to_and_cc_from_mentions(participant), + {:ok, activity} <- create_activity(Map.merge(join_data, audience), local), :ok <- maybe_federate(activity) do - if role === :participant do - accept_join( + if event.local && Mobilizon.Events.get_default_participant_role(event) === :participant do + accept( + :join, participant, - %{} + true, + %{"actor" => event.organizer_actor.url} ) + else + {:ok, activity, participant} end - - {:ok, activity, participant} end end # TODO: Implement me - def join(%Actor{type: :Group} = _group, %Actor{} = _actor, _local) do + def join(%Actor{type: :Group} = _group, %Actor{} = _actor, _local, _additional) do :error end + defp check_attendee_capacity(%Event{options: options} = event) do + with maximum_attendee_capacity <- + Map.get(options, :maximum_attendee_capacity) || 0 do + maximum_attendee_capacity == 0 || + Mobilizon.Events.count_participant_participants(event.id) < maximum_attendee_capacity + end + end + def leave(object, actor, local \\ true) # TODO: If we want to use this for exclusion we need to have an extra field # for the actor that excluded the participant def leave( - %Event{id: event_id, url: event_url} = event, + %Event{id: event_id, url: event_url} = _event, %Actor{id: actor_id, url: actor_url} = _actor, local ) do @@ -473,11 +463,11 @@ defmodule Mobilizon.Service.ActivityPub do # If it's an exclusion it should be something else "actor" => actor_url, "object" => event_url, - "to" => [event.organizer_actor.url], - "cc" => [] + "id" => "#{MobilizonWeb.Endpoint.url()}/leave/event/#{participant.id}" }, - {:ok, activity} <- create_activity(leave_data, local), - {:ok, _object} <- insert_full_object(leave_data), + audience <- + Audience.calculate_to_and_cc_from_mentions(participant), + {:ok, activity} <- create_activity(Map.merge(leave_data, audience), local), :ok <- maybe_federate(activity) do {:ok, activity, participant} end @@ -537,16 +527,22 @@ defmodule Mobilizon.Service.ActivityPub do end end + @spec is_create_activity?(Activity.t()) :: boolean + defp is_create_activity?(%Activity{data: %{"type" => "Create"}}), do: true + defp is_create_activity?(_), do: false + @doc """ Publish an activity to all appropriated audiences inboxes """ + @spec publish(Actor.t(), Activity.t()) :: :ok def publish(actor, activity) do Logger.debug("Publishing an activity") Logger.debug(inspect(activity)) public = is_public?(activity) + Logger.debug("is public ? #{public}") - if public && !is_delete_activity?(activity) && Config.get([:instance, :allow_relay]) do + if public && is_create_activity?(activity) && Config.get([:instance, :allow_relay]) do Logger.info(fn -> "Relaying #{activity.data["id"]} out" end) Relay.publish(activity) @@ -578,15 +574,12 @@ defmodule Mobilizon.Service.ActivityPub do end) end - defp is_delete_activity?(%Activity{data: %{"type" => "Delete"}}), do: true - defp is_delete_activity?(_), do: false - @doc """ Publish an activity to a specific inbox """ def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do Logger.info("Federating #{id} to #{inbox}") - %URI{host: host, path: _path} = URI.parse(inbox) + %URI{host: host, path: path} = URI.parse(inbox) digest = Signature.build_digest(json) date = Signature.generate_date_header() @@ -594,10 +587,9 @@ defmodule Mobilizon.Service.ActivityPub do signature = Signature.sign(actor, %{ + "(request-target)": "post #{path}", host: host, "content-length": byte_size(json), - # TODO : Look me up in depth why Pleroma handles this inside lib/mobilizon_web/http_signature.ex - # "(request-target)": request_target, digest: digest, date: date }) @@ -627,7 +619,7 @@ defmodule Mobilizon.Service.ActivityPub do :ok <- Logger.debug("response okay, now decoding json"), {:ok, data} <- Jason.decode(body) do Logger.debug("Got activity+json response at actor's endpoint, now converting data") - actor_data_from_actor_object(data) + Mobilizon.Service.ActivityPub.Converter.Actor.as_to_model_data(data) else # Actor is gone, probably deleted {:ok, %HTTPoison.Response{status_code: 410}} -> @@ -642,49 +634,6 @@ defmodule Mobilizon.Service.ActivityPub do res end - @doc """ - Creating proper actor data struct from AP data - - - Convert ActivityPub data to our internal format - """ - @spec actor_data_from_actor_object(map()) :: {:ok, map()} - def actor_data_from_actor_object(data) when is_map(data) do - avatar = - data["icon"]["url"] && - %{ - "name" => data["icon"]["name"] || "avatar", - "url" => data["icon"]["url"] - } - - banner = - data["image"]["url"] && - %{ - "name" => data["image"]["name"] || "banner", - "url" => data["image"]["url"] - } - - actor_data = %{ - url: data["id"], - avatar: avatar, - banner: banner, - name: data["name"], - preferred_username: data["preferredUsername"], - summary: data["summary"], - keys: data["publicKey"]["publicKeyPem"], - inbox_url: data["inbox"], - outbox_url: data["outbox"], - following_url: data["following"], - followers_url: data["followers"], - shared_inbox_url: data["endpoints"]["sharedInbox"], - domain: URI.parse(data["id"]).host, - manually_approves_followers: data["manuallyApprovesFollowers"], - type: data["type"] - } - - {:ok, actor_data} - end - @doc """ Return all public activities (events & comments) for an actor """ @@ -736,12 +685,7 @@ defmodule Mobilizon.Service.ActivityPub do {:ok, %Event{} = event} <- Events.create_event(args), event_as_data <- Convertible.model_to_as(event), audience <- - Audience.calculate_to_and_cc_from_mentions( - event.organizer_actor, - args.mentions, - nil, - event.visibility - ), + Audience.calculate_to_and_cc_from_mentions(event), create_data <- make_create_data(event_as_data, Map.merge(audience, additional)) do {:ok, event, create_data} @@ -754,12 +698,7 @@ defmodule Mobilizon.Service.ActivityPub do {:ok, %Comment{} = comment} <- Events.create_comment(args), comment_as_data <- Convertible.model_to_as(comment), audience <- - Audience.calculate_to_and_cc_from_mentions( - comment.actor, - args.mentions, - args.in_reply_to_comment, - comment.visibility - ), + Audience.calculate_to_and_cc_from_mentions(comment), create_data <- make_create_data(comment_as_data, Map.merge(audience, additional)) do {:ok, comment, create_data} @@ -771,13 +710,7 @@ defmodule Mobilizon.Service.ActivityPub do with args <- prepare_args_for_group(args), {:ok, %Actor{type: :Group} = group} <- Actors.create_group(args), group_as_data <- Convertible.model_to_as(group), - audience <- - Audience.calculate_to_and_cc_from_mentions( - args.creator_actor, - [], - nil, - :public - ), + audience <- %{"to" => ["https://www.w3.org/ns/activitystreams#Public"], "cc" => []}, create_data <- make_create_data(group_as_data, Map.merge(audience, additional)) do {:ok, group, create_data} @@ -799,12 +732,7 @@ defmodule Mobilizon.Service.ActivityPub do {:ok, %Event{} = new_event} <- Events.update_event(old_event, args), event_as_data <- Convertible.model_to_as(new_event), audience <- - Audience.calculate_to_and_cc_from_mentions( - new_event.organizer_actor, - Map.get(args, :mentions, []), - nil, - new_event.visibility - ), + Audience.calculate_to_and_cc_from_mentions(new_event), update_data <- make_update_data(event_as_data, Map.merge(audience, additional)) do {:ok, new_event, update_data} else @@ -821,34 +749,29 @@ defmodule Mobilizon.Service.ActivityPub do with {:ok, %Actor{} = new_actor} <- Actors.update_actor(old_actor, args), actor_as_data <- Convertible.model_to_as(new_actor), audience <- - Audience.calculate_to_and_cc_from_mentions( - new_actor, - [], - nil, - :public - ), + Audience.calculate_to_and_cc_from_mentions(new_actor), additional <- Map.merge(additional, %{"actor" => old_actor.url}), update_data <- make_update_data(actor_as_data, Map.merge(audience, additional)) do {:ok, new_actor, update_data} end end - @spec accept_follow(Follower.t(), map(), map()) :: + @spec accept_follow(Follower.t(), map()) :: {:ok, Follower.t(), Activity.t()} | any() defp accept_follow( %Follower{} = follower, - args, additional ) do - with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, args), + with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, %{approved: true}), follower_as_data <- Convertible.model_to_as(follower), - audience <- - Audience.calculate_to_and_cc_from_mentions(follower.target_actor), update_data <- - make_update_data( + make_accept_join_data( follower_as_data, - Map.merge(Map.merge(audience, additional), %{ - "id" => "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follower.id}" + Map.merge(additional, %{ + "id" => "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follower.id}", + "to" => [follower.actor.url], + "cc" => [], + "actor" => follower.target_actor.url }) ) do {:ok, follower, update_data} @@ -860,17 +783,20 @@ defmodule Mobilizon.Service.ActivityPub do end end - @spec accept_join(Participant.t(), map(), map()) :: + @spec accept_join(Participant.t(), map()) :: {:ok, Participant.t(), Activity.t()} | any() defp accept_join( %Participant{} = participant, - args, - additional \\ %{} + additional ) do - with {:ok, %Participant{} = participant} <- Events.update_participant(participant, args), + with {:ok, %Participant{} = participant} <- + Events.update_participant(participant, %{role: :participant}), + Absinthe.Subscription.publish(MobilizonWeb.Endpoint, participant.actor, + event_person_participation_changed: participant.actor.id + ), participant_as_data <- Convertible.model_to_as(participant), audience <- - Audience.calculate_to_and_cc_from_mentions(participant.actor), + Audience.calculate_to_and_cc_from_mentions(participant), update_data <- make_accept_join_data( participant_as_data, @@ -887,6 +813,66 @@ defmodule Mobilizon.Service.ActivityPub do end end + @spec reject_join(Participant.t(), map()) :: + {:ok, Participant.t(), Activity.t()} | any() + defp reject_join(%Participant{} = participant, additional) do + with {:ok, %Participant{} = participant} <- + Events.update_participant(participant, %{approved: false, role: :rejected}), + Absinthe.Subscription.publish(MobilizonWeb.Endpoint, participant.actor, + event_person_participation_changed: participant.actor.id + ), + participant_as_data <- Convertible.model_to_as(participant), + audience <- + participant + |> Audience.calculate_to_and_cc_from_mentions() + |> Map.merge(additional), + reject_data <- %{ + "type" => "Reject", + "object" => participant_as_data + }, + update_data <- + reject_data + |> Map.merge(audience) + |> Map.merge(%{ + "id" => "#{MobilizonWeb.Endpoint.url()}/reject/join/#{participant.id}" + }) do + {:ok, participant, update_data} + else + err -> + Logger.error("Something went wrong while creating an update activity") + Logger.debug(inspect(err)) + err + end + end + + @spec reject_follow(Follower.t(), map()) :: + {:ok, Follower.t(), Activity.t()} | any() + defp reject_follow(%Follower{} = follower, additional) do + with {:ok, %Follower{} = follower} <- Actors.delete_follower(follower), + follower_as_data <- Convertible.model_to_as(follower), + audience <- + follower.actor |> Audience.calculate_to_and_cc_from_mentions() |> Map.merge(additional), + reject_data <- %{ + "to" => follower.actor.url, + "type" => "Reject", + "actor" => follower.actor.url, + "object" => follower_as_data + }, + update_data <- + reject_data + |> Map.merge(audience) + |> Map.merge(%{ + "id" => "#{MobilizonWeb.Endpoint.url()}/reject/follow/#{follower.id}" + }) do + {:ok, follower, update_data} + else + err -> + Logger.error("Something went wrong while creating an update activity") + Logger.debug(inspect(err)) + err + end + end + # Prepare and sanitize arguments for events defp prepare_args_for_event(args) do # If title is not set: we are not updating it @@ -923,7 +909,8 @@ defmodule Mobilizon.Service.ActivityPub do # Prepare and sanitize arguments for comments defp prepare_args_for_comment(args) do with in_reply_to_comment <- - args |> Map.get(:in_reply_to_comment_id) |> Events.get_comment(), + args |> Map.get(:in_reply_to_comment_id) |> Events.get_comment_with_preload(), + event <- args |> Map.get(:event_id) |> handle_event_for_comment(), args <- Map.update(args, :visibility, :public, & &1), {text, mentions, tags} <- APIUtils.make_content_html( @@ -940,6 +927,7 @@ defmodule Mobilizon.Service.ActivityPub do text: text, mentions: mentions, tags: tags, + event: event, in_reply_to_comment: in_reply_to_comment, in_reply_to_comment_id: if(is_nil(in_reply_to_comment), do: nil, else: Map.get(in_reply_to_comment, :id)), @@ -953,6 +941,16 @@ defmodule Mobilizon.Service.ActivityPub do end end + @spec handle_event_for_comment(String.t() | integer() | nil) :: Event.t() | nil + defp handle_event_for_comment(event_id) when not is_nil(event_id) do + case Events.get_event_with_preload(event_id) do + {:ok, %Event{} = event} -> event + {:error, :event_not_found} -> nil + end + end + + defp handle_event_for_comment(nil), do: nil + defp prepare_args_for_group(args) do with preferred_username <- args |> Map.get(:preferred_username) |> HtmlSanitizeEx.strip_tags() |> String.trim(), diff --git a/lib/service/activity_pub/audience.ex b/lib/service/activity_pub/audience.ex index 9f4c16cd..cca7d1f6 100644 --- a/lib/service/activity_pub/audience.ex +++ b/lib/service/activity_pub/audience.ex @@ -2,7 +2,13 @@ defmodule Mobilizon.Service.ActivityPub.Audience do @moduledoc """ Tools for calculating content audience """ + alias Mobilizon.Actors alias Mobilizon.Actors.Actor + alias Mobilizon.Events.Comment + alias Mobilizon.Events.Event + alias Mobilizon.Events.Participant + alias Mobilizon.Share + require Logger @ap_public "https://www.w3.org/ns/activitystreams#Public" @@ -13,35 +19,27 @@ defmodule Mobilizon.Service.ActivityPub.Audience do * `to` : the mentioned actors, the eventual actor we're replying to and the public * `cc` : the actor's followers """ - @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()} - def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :public) do + @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()} + def get_to_and_cc(%Actor{} = actor, mentions, :public) do to = [@ap_public | mentions] cc = [actor.followers_url] - if in_reply_to do - {Enum.uniq([in_reply_to.actor | to]), cc} - else - {to, cc} - end + {to, cc} end @doc """ Determines the full audience based on mentions based on a unlisted audience Audience is: - * `to` : the mentionned actors, actor's followers and the eventual actor we're replying to + * `to` : the mentioned actors, actor's followers and the eventual actor we're replying to * `cc` : public """ - @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()} - def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :unlisted) do + @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()} + def get_to_and_cc(%Actor{} = actor, mentions, :unlisted) do to = [actor.followers_url | mentions] cc = [@ap_public] - if in_reply_to do - {Enum.uniq([in_reply_to.actor | to]), cc} - else - {to, cc} - end + {to, cc} end @doc """ @@ -51,9 +49,9 @@ defmodule Mobilizon.Service.ActivityPub.Audience do * `to` : the mentioned actors, actor's followers and the eventual actor we're replying to * `cc` : none """ - @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()} - def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :private) do - {to, cc} = get_to_and_cc(actor, mentions, in_reply_to, :direct) + @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()} + def get_to_and_cc(%Actor{} = actor, mentions, :private) do + {to, cc} = get_to_and_cc(actor, mentions, :direct) {[actor.followers_url | to], cc} end @@ -64,16 +62,12 @@ defmodule Mobilizon.Service.ActivityPub.Audience do * `to` : the mentioned actors and the eventual actor we're replying to * `cc` : none """ - @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()} - def get_to_and_cc(_actor, mentions, in_reply_to, :direct) do - if in_reply_to do - {Enum.uniq([in_reply_to.actor | mentions]), []} - else - {mentions, []} - end + @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()} + def get_to_and_cc(_actor, mentions, :direct) do + {mentions, []} end - def get_to_and_cc(_actor, mentions, _in_reply_to, {:list, _}) do + def get_to_and_cc(_actor, mentions, {:list, _}) do {mentions, []} end @@ -83,16 +77,109 @@ defmodule Mobilizon.Service.ActivityPub.Audience do def get_addressed_actors(mentioned_users, _), do: mentioned_users - def calculate_to_and_cc_from_mentions( - actor, - mentions \\ [], - in_reply_to \\ nil, - visibility \\ :public - ) do - with mentioned_actors <- for({_, mentioned_actor} <- mentions, do: mentioned_actor.url), + def calculate_to_and_cc_from_mentions(%Comment{} = comment) do + with mentioned_actors <- Enum.map(comment.mentions, &process_mention/1), addressed_actors <- get_addressed_actors(mentioned_actors, nil), - {to, cc} <- get_to_and_cc(actor, addressed_actors, in_reply_to, visibility) do + {to, cc} <- get_to_and_cc(comment.actor, addressed_actors, comment.visibility), + {to, cc} <- {Enum.uniq(to ++ add_in_reply_to(comment.in_reply_to_comment)), cc}, + {to, cc} <- {Enum.uniq(to ++ add_event_author(comment.event)), cc}, + {to, cc} <- + {to, + Enum.uniq( + cc ++ + add_comments_authors([comment.origin_comment]) ++ + add_shares_actors_followers(comment.url) + )} do %{"to" => to, "cc" => cc} end end + + def calculate_to_and_cc_from_mentions(%Event{} = event) do + with mentioned_actors <- Enum.map(event.mentions, &process_mention/1), + addressed_actors <- get_addressed_actors(mentioned_actors, nil), + {to, cc} <- get_to_and_cc(event.organizer_actor, addressed_actors, event.visibility), + {to, cc} <- + {to, + Enum.uniq( + cc ++ add_comments_authors(event.comments) ++ add_shares_actors_followers(event.url) + )} do + %{"to" => to, "cc" => cc} + end + end + + def calculate_to_and_cc_from_mentions(%Participant{} = participant) do + participant = Mobilizon.Storage.Repo.preload(participant, [:actor, :event]) + + actor_participants_urls = + participant.event.id + |> Mobilizon.Events.list_actors_participants_for_event() + |> Enum.map(& &1.url) + + %{"to" => [participant.actor.url], "cc" => actor_participants_urls} + end + + def calculate_to_and_cc_from_mentions(%Actor{} = actor) do + %{ + "to" => [@ap_public], + "cc" => [actor.followers_url] ++ add_actors_that_had_our_content(actor.id) + } + end + + defp add_in_reply_to(%Comment{actor: %Actor{url: url}} = _comment), do: [url] + defp add_in_reply_to(%Event{organizer_actor: %Actor{url: url}} = _event), do: [url] + defp add_in_reply_to(_), do: [] + + defp add_event_author(nil), do: [] + + defp add_event_author(%Event{} = event) do + [Mobilizon.Storage.Repo.preload(event, [:organizer_actor]).organizer_actor.url] + end + + defp add_comment_author(nil), do: nil + + defp add_comment_author(%Comment{} = comment) do + case Mobilizon.Storage.Repo.preload(comment, [:actor]) do + %Comment{actor: %Actor{url: url}} -> + url + + _err -> + nil + end + end + + defp add_comments_authors(comments) do + authors = + comments + |> Enum.map(&add_comment_author/1) + |> Enum.filter(& &1) + + authors + end + + @spec add_shares_actors_followers(String.t()) :: list(String.t()) + defp add_shares_actors_followers(uri) do + uri + |> Share.get_actors_by_share_uri() + |> Enum.map(&Actors.list_followers_actors_for_actor/1) + |> List.flatten() + |> Enum.map(& &1.url) + |> Enum.uniq() + end + + defp add_actors_that_had_our_content(actor_id) do + actor_id + |> Share.get_actors_by_owner_actor_id() + |> Enum.map(&Actors.list_followers_actors_for_actor/1) + |> List.flatten() + |> Enum.map(& &1.url) + |> Enum.uniq() + end + + defp process_mention({_, mentioned_actor}), do: mentioned_actor.url + + defp process_mention(%{actor_id: actor_id}) do + with %Actor{url: url} <- Actors.get_actor(actor_id) do + url + end + end end diff --git a/lib/service/activity_pub/converter/actor.ex b/lib/service/activity_pub/converter/actor.ex index 3a0c4546..556a4d8b 100644 --- a/lib/service/activity_pub/converter/actor.ex +++ b/lib/service/activity_pub/converter/actor.ex @@ -7,7 +7,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Actor do """ alias Mobilizon.Actors.Actor, as: ActorModel - alias Mobilizon.Service.ActivityPub.{Converter, Convertible} + alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils} @behaviour Converter @@ -22,33 +22,40 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Actor do """ @impl Converter @spec as_to_model_data(map) :: map - def as_to_model_data(object) do + def as_to_model_data(data) do avatar = - object["icon"]["url"] && + data["icon"]["url"] && %{ - "name" => object["icon"]["name"] || "avatar", - "url" => object["icon"]["url"] + "name" => data["icon"]["name"] || "avatar", + "url" => MobilizonWeb.MediaProxy.url(data["icon"]["url"]) } banner = - object["image"]["url"] && + data["image"]["url"] && %{ - "name" => object["image"]["name"] || "banner", - "url" => object["image"]["url"] + "name" => data["image"]["name"] || "banner", + "url" => MobilizonWeb.MediaProxy.url(data["image"]["url"]) } - {:ok, - %{ - "type" => String.to_existing_atom(object["type"]), - "preferred_username" => object["preferredUsername"], - "summary" => object["summary"], - "url" => object["id"], - "name" => object["name"], - "avatar" => avatar, - "banner" => banner, - "keys" => object["publicKey"]["publicKeyPem"], - "manually_approves_followers" => object["manuallyApprovesFollowers"] - }} + actor_data = %{ + url: data["id"], + avatar: avatar, + banner: banner, + name: data["name"], + preferred_username: data["preferredUsername"], + summary: data["summary"], + keys: data["publicKey"]["publicKeyPem"], + inbox_url: data["inbox"], + outbox_url: data["outbox"], + following_url: data["following"], + followers_url: data["followers"], + shared_inbox_url: data["endpoints"]["sharedInbox"], + domain: URI.parse(data["id"]).host, + manually_approves_followers: data["manuallyApprovesFollowers"], + type: data["type"] + } + + {:ok, actor_data} end @doc """ @@ -57,18 +64,51 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Actor do @impl Converter @spec model_to_as(ActorModel.t()) :: map def model_to_as(%ActorModel{} = actor) do - %{ - "type" => Atom.to_string(actor.type), - "to" => ["https://www.w3.org/ns/activitystreams#Public"], - "preferred_username" => actor.preferred_username, + actor_data = %{ + "id" => actor.url, + "type" => actor.type, + "preferredUsername" => actor.preferred_username, "name" => actor.name, "summary" => actor.summary, - "following" => ActorModel.build_url(actor.preferred_username, :following), - "followers" => ActorModel.build_url(actor.preferred_username, :followers), - "inbox" => ActorModel.build_url(actor.preferred_username, :inbox), - "outbox" => ActorModel.build_url(actor.preferred_username, :outbox), - "id" => ActorModel.build_url(actor.preferred_username, :page), - "url" => actor.url + "following" => actor.following_url, + "followers" => actor.followers_url, + "inbox" => actor.inbox_url, + "outbox" => actor.outbox_url, + "url" => actor.url, + "endpoints" => %{ + "sharedInbox" => actor.shared_inbox_url + }, + "manuallyApprovesFollowers" => actor.manually_approves_followers, + "publicKey" => %{ + "id" => "#{actor.url}#main-key", + "owner" => actor.url, + "publicKeyPem" => + if(is_nil(actor.domain) and not is_nil(actor.keys), + do: Utils.pem_to_public_key_pem(actor.keys), + else: actor.keys + ) + } } + + actor_data = + if is_nil(actor.avatar) do + actor_data + else + Map.put(actor_data, "icon", %{ + "type" => "Image", + "mediaType" => actor.avatar.content_type, + "url" => actor.avatar.url + }) + end + + if is_nil(actor.banner) do + actor_data + else + Map.put(actor_data, "image", %{ + "type" => "Image", + "mediaType" => actor.banner.content_type, + "url" => actor.banner.url + }) + end end end diff --git a/lib/service/activity_pub/converter/comment.ex b/lib/service/activity_pub/converter/comment.ex index da73e53c..3875deb0 100644 --- a/lib/service/activity_pub/converter/comment.ex +++ b/lib/service/activity_pub/converter/comment.ex @@ -12,6 +12,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do alias Mobilizon.Service.ActivityPub alias Mobilizon.Service.ActivityPub.{Converter, Convertible} alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils + alias Mobilizon.Tombstone, as: TombstoneModel require Logger @@ -32,9 +33,11 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do Logger.debug("We're converting raw ActivityStream data to a comment entity") Logger.debug(inspect(object)) - with {:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(object["actor"]), - {:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])}, - {:mentions, mentions} <- {:mentions, ConverterUtils.fetch_mentions(object["tag"])} do + with author_url <- Map.get(object, "actor") || Map.get(object, "attributedTo"), + {:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(author_url), + {:tags, tags} <- {:tags, ConverterUtils.fetch_tags(Map.get(object, "tag", []))}, + {:mentions, mentions} <- + {:mentions, ConverterUtils.fetch_mentions(Map.get(object, "tag", []))} do Logger.debug("Inserting full comment") Logger.debug(inspect(object)) @@ -70,6 +73,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do data |> Map.put(:in_reply_to_comment_id, id) |> Map.put(:origin_comment_id, comment |> CommentModel.get_thread_id()) + |> Map.put(:event_id, comment.event_id) # Anything else is kind of a MP {:error, parent} -> @@ -106,6 +110,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do "to" => to, "cc" => [], "content" => comment.text, + "mediaType" => "text/html", "actor" => comment.actor.url, "attributedTo" => comment.actor.url, "uuid" => comment.uuid, @@ -114,23 +119,27 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do ConverterUtils.build_mentions(comment.mentions) ++ ConverterUtils.build_tags(comment.tags) } - if comment.in_reply_to_comment do - object |> Map.put("inReplyTo", comment.in_reply_to_comment.url || comment.event.url) - else - object + cond do + comment.in_reply_to_comment -> + Map.put(object, "inReplyTo", comment.in_reply_to_comment.url) + + comment.event -> + Map.put(object, "inReplyTo", comment.event.url) + + true -> + object end end @impl Converter @spec model_to_as(CommentModel.t()) :: map + @doc """ + A "soft-deleted" comment is a tombstone + """ def model_to_as(%CommentModel{} = comment) do - %{ - "type" => "Tombstone", - "uuid" => comment.uuid, - "id" => comment.url, - "published" => comment.inserted_at, - "updated" => comment.updated_at, - "deleted" => comment.deleted_at - } + Convertible.model_to_as(%TombstoneModel{ + uri: comment.url, + inserted_at: comment.deleted_at + }) end end diff --git a/lib/service/activity_pub/converter/event.ex b/lib/service/activity_pub/converter/event.ex index e5c40e22..a273b87f 100644 --- a/lib/service/activity_pub/converter/event.ex +++ b/lib/service/activity_pub/converter/event.ex @@ -6,14 +6,13 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do internal one, and back. """ - alias Mobilizon.{Addresses, Media} + alias Mobilizon.Addresses alias Mobilizon.Actors.Actor alias Mobilizon.Addresses.Address alias Mobilizon.Events.Event, as: EventModel - alias Mobilizon.Events.EventOptions alias Mobilizon.Media.Picture alias Mobilizon.Service.ActivityPub - alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils} + alias Mobilizon.Service.ActivityPub.{Converter, Convertible} alias Mobilizon.Service.ActivityPub.Converter.Address, as: AddressConverter alias Mobilizon.Service.ActivityPub.Converter.Picture, as: PictureConverter alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils @@ -37,26 +36,25 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do Logger.debug("event as_to_model_data") Logger.debug(inspect(object)) - with {:actor, {:ok, %Actor{id: actor_id}}} <- - {:actor, ActivityPub.get_or_fetch_actor_by_url(object["actor"])}, + with author_url <- Map.get(object, "actor") || Map.get(object, "attributedTo"), + {:actor, {:ok, %Actor{id: actor_id, domain: actor_domain}}} <- + {:actor, ActivityPub.get_or_fetch_actor_by_url(author_url)}, {:address, address_id} <- {:address, get_address(object["location"])}, {:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])}, + {:mentions, mentions} <- {:mentions, ConverterUtils.fetch_mentions(object["tag"])}, {:visibility, visibility} <- {:visibility, get_visibility(object)}, {:options, options} <- {:options, get_options(object)} do picture_id = with true <- Map.has_key?(object, "attachment") && length(object["attachment"]) > 0, - %Picture{id: picture_id} <- - Media.get_picture_by_url( - object["attachment"] - |> hd - |> Map.get("url") - |> hd - |> Map.get("href") - ) do + {:ok, %Picture{id: picture_id}} <- + object["attachment"] + |> hd + |> PictureConverter.find_or_create_picture(actor_id) do picture_id else - _ -> nil + _err -> + nil end entity = %{ @@ -68,16 +66,20 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do ends_on: object["endTime"], category: object["category"], visibility: visibility, - join_options: Map.get(object, "joinOptions", "free"), + join_options: Map.get(object, "joinMode", "free"), + local: is_nil(actor_domain), options: options, - status: object["status"], + status: object |> Map.get("ical:status", "CONFIRMED") |> String.downcase(), online_address: object["onlineAddress"], phone_address: object["phoneAddress"], - draft: object["draft"] || false, + draft: false, url: object["id"], uuid: object["uuid"], tags: tags, - physical_address_id: address_id + mentions: mentions, + physical_address_id: address_id, + updated_at: object["updated"], + publish_at: object["published"] } {:ok, entity} @@ -108,14 +110,17 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do "uuid" => event.uuid, "category" => event.category, "content" => event.description, - "publish_at" => (event.publish_at || event.inserted_at) |> date_to_string(), - "updated_at" => event.updated_at |> date_to_string(), + "published" => (event.publish_at || event.inserted_at) |> date_to_string(), + "updated" => event.updated_at |> date_to_string(), "mediaType" => "text/html", "startTime" => event.begins_on |> date_to_string(), - "joinOptions" => to_string(event.join_options), + "joinMode" => to_string(event.join_options), "endTime" => event.ends_on |> date_to_string(), "tag" => event.tags |> ConverterUtils.build_tags(), - "draft" => event.draft, + "maximumAttendeeCapacity" => event.options.maximum_attendee_capacity, + "repliesModerationOption" => event.options.comment_moderation, + # "draft" => event.draft, + "ical:status" => event.status |> to_string |> String.upcase(), "id" => event.url, "url" => event.url } @@ -133,17 +138,10 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do # Get only elements that we have in EventOptions @spec get_options(map) :: map defp get_options(object) do - keys = - EventOptions - |> struct - |> Map.keys() - |> List.delete(:__struct__) - |> Enum.map(&Utils.camelize/1) - - Enum.reduce(object, %{}, fn {key, value}, acc -> - (!is_nil(value) && key in keys && Map.put(acc, Utils.underscore(key), value)) || - acc - end) + %{ + maximum_attendee_capacity: object["maximumAttendeeCapacity"], + comment_moderation: object["repliesModerationOption"] + } end @spec get_address(map | binary | nil) :: integer | nil @@ -186,13 +184,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do @ap_public "https://www.w3.org/ns/activitystreams#Public" - defp get_visibility(object) do - cond do - @ap_public in object["to"] -> :public - @ap_public in object["cc"] -> :unlisted - true -> :private - end - end + defp get_visibility(object), do: if(@ap_public in object["to"], do: :public, else: :unlisted) @spec date_to_string(DateTime.t() | nil) :: String.t() defp date_to_string(nil), do: nil diff --git a/lib/service/activity_pub/converter/flag.ex b/lib/service/activity_pub/converter/flag.ex index ac2c992b..2980b0c6 100644 --- a/lib/service/activity_pub/converter/flag.ex +++ b/lib/service/activity_pub/converter/flag.ex @@ -15,6 +15,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do alias Mobilizon.Reports.Report alias Mobilizon.Service.ActivityPub.Converter alias Mobilizon.Service.ActivityPub.Convertible + alias Mobilizon.Service.ActivityPub.Relay @behaviour Converter @@ -42,8 +43,6 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do end end - @audience %{"to" => ["https://www.w3.org/ns/activitystreams#Public"], "cc" => []} - @doc """ Convert an event struct to an ActivityStream representation """ @@ -54,17 +53,13 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do object = if report.event, do: object ++ [report.event.url], else: object - audience = - if report.local, do: @audience, else: Map.put(@audience, "cc", [report.reported.url]) - %{ "type" => "Flag", - "actor" => report.reporter.url, + "actor" => Relay.get_actor().url, "id" => report.url, "content" => report.content, "object" => object } - |> Map.merge(audience) end @spec as_to_model(map) :: map @@ -91,7 +86,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do end end), - # Remove the reported user from the object list. + # Remove the reported actor and the event from the object list. comments <- Enum.filter(objects, fn url -> !(url == reported.url || (!is_nil(event) && event.url == url)) diff --git a/lib/service/activity_pub/converter/picture.ex b/lib/service/activity_pub/converter/picture.ex index 6c6e93ab..292db9bd 100644 --- a/lib/service/activity_pub/converter/picture.ex +++ b/lib/service/activity_pub/converter/picture.ex @@ -15,14 +15,48 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Picture do def model_to_as(%PictureModel{file: file}) do %{ "type" => "Document", - "url" => [ - %{ - "type" => "Link", - "mediaType" => file.content_type, - "href" => file.url - } - ], + "mediaType" => file.content_type, + "url" => file.url, "name" => file.name } end + + @doc """ + Save picture data from raw data and return AS Link data. + """ + def find_or_create_picture(%{"type" => "Link", "href" => url}, actor_id), + do: find_or_create_picture(url, actor_id) + + def find_or_create_picture( + %{"type" => "Document", "url" => picture_url, "name" => name}, + actor_id + ) + when is_bitstring(picture_url) do + with {:ok, %HTTPoison.Response{body: body}} <- HTTPoison.get(picture_url), + {:ok, + %{ + name: name, + url: url, + content_type: content_type, + size: size + }} <- + MobilizonWeb.Upload.store(%{body: body, name: name}), + {:picture_exists, nil} <- {:picture_exists, Mobilizon.Media.get_picture_by_url(url)} do + Mobilizon.Media.create_picture(%{ + "file" => %{ + "url" => url, + "name" => name, + "content_type" => content_type, + "size" => size + }, + "actor_id" => actor_id + }) + else + {:picture_exists, %PictureModel{file: _file} = picture} -> + {:ok, picture} + + err -> + err + end + end end diff --git a/lib/service/activity_pub/converter/tombstone.ex b/lib/service/activity_pub/converter/tombstone.ex new file mode 100644 index 00000000..8054ae37 --- /dev/null +++ b/lib/service/activity_pub/converter/tombstone.ex @@ -0,0 +1,40 @@ +defmodule Mobilizon.Service.ActivityPub.Converter.Tombstone do + @moduledoc """ + Comment converter. + + This module allows to convert Tombstone models to ActivityStreams data + """ + + alias Mobilizon.Tombstone, as: TombstoneModel + alias Mobilizon.Service.ActivityPub.{Converter, Convertible} + + require Logger + + @behaviour Converter + + defimpl Convertible, for: TombstoneModel do + alias Mobilizon.Service.ActivityPub.Converter.Tombstone, as: TombstoneConverter + + defdelegate model_to_as(comment), to: TombstoneConverter + end + + @doc """ + Make an AS tombstone object from an existing `Tombstone` structure. + """ + @impl Converter + @spec model_to_as(TombstoneModel.t()) :: map + def model_to_as(%TombstoneModel{} = tombstone) do + %{ + "type" => "Tombstone", + "id" => tombstone.uri, + "deleted" => tombstone.inserted_at + } + end + + @doc """ + Converting an Tombstone to an object makes no sense, nevertheless… + """ + @impl Converter + @spec as_to_model_data(map) :: map + def as_to_model_data(object), do: object +end diff --git a/lib/service/activity_pub/converter/utils.ex b/lib/service/activity_pub/converter/utils.ex index ba2f3a2a..8e637736 100644 --- a/lib/service/activity_pub/converter/utils.ex +++ b/lib/service/activity_pub/converter/utils.ex @@ -14,6 +14,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Utils do @spec fetch_tags([String.t()]) :: [Tag.t()] def fetch_tags(tags) when is_list(tags) do Logger.debug("fetching tags") + Logger.debug(inspect(tags)) tags |> Enum.flat_map(&fetch_tag/1) |> Enum.uniq() |> Enum.map(&existing_tag_or_data/1) end @@ -64,6 +65,8 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Utils do } end + defp fetch_tag(%{title: title}), do: [title] + defp fetch_tag(tag) when is_map(tag) do case tag["type"] do "Hashtag" -> diff --git a/lib/service/activity_pub/relay.ex b/lib/service/activity_pub/relay.ex index 90c2ab1a..eb45e0d1 100644 --- a/lib/service/activity_pub/relay.ex +++ b/lib/service/activity_pub/relay.ex @@ -9,27 +9,37 @@ defmodule Mobilizon.Service.ActivityPub.Relay do """ alias Mobilizon.Actors - alias Mobilizon.Actors.Actor + alias Mobilizon.Actors.{Actor, Follower} alias Mobilizon.Service.ActivityPub alias Mobilizon.Service.ActivityPub.{Activity, Transmogrifier} + alias Mobilizon.Service.WebFinger alias MobilizonWeb.API.Follows require Logger + def init() do + # Wait for everything to settle. + Process.sleep(1000 * 5) + get_actor() + end + + @spec get_actor() :: Actor.t() | {:error, Ecto.Changeset.t()} def get_actor do with {:ok, %Actor{} = actor} <- - Actors.get_or_create_actor_by_url("#{MobilizonWeb.Endpoint.url()}/relay") do + Actors.get_or_create_instance_actor_by_url("#{MobilizonWeb.Endpoint.url()}/relay") do actor end end - def follow(target_instance) do - with %Actor{} = local_actor <- get_actor(), + @spec follow(String.t()) :: {:ok, Activity.t(), Follower.t()} + def follow(address) do + with {:ok, target_instance} <- fetch_actor(address), + %Actor{} = local_actor <- get_actor(), {:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance), - {:ok, activity} <- Follows.follow(local_actor, target_actor) do + {:ok, activity, follow} <- Follows.follow(local_actor, target_actor) do Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}") - {:ok, activity} + {:ok, activity, follow} else e -> Logger.warn("Error while following remote instance: #{inspect(e)}") @@ -37,12 +47,14 @@ defmodule Mobilizon.Service.ActivityPub.Relay do end end - def unfollow(target_instance) do - with %Actor{} = local_actor <- get_actor(), + @spec unfollow(String.t()) :: {:ok, Activity.t(), Follower.t()} + def unfollow(address) do + with {:ok, target_instance} <- fetch_actor(address), + %Actor{} = local_actor <- get_actor(), {:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance), - {:ok, activity} <- Follows.unfollow(local_actor, target_actor) do + {:ok, activity, follow} <- Follows.unfollow(local_actor, target_actor) do Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}") - {:ok, activity} + {:ok, activity, follow} else e -> Logger.warn("Error while unfollowing remote instance: #{inspect(e)}") @@ -50,30 +62,38 @@ defmodule Mobilizon.Service.ActivityPub.Relay do end end - def accept(target_instance) do - with %Actor{} = local_actor <- get_actor(), + @spec accept(String.t()) :: {:ok, Activity.t(), Follower.t()} + def accept(address) do + Logger.debug("We're trying to accept a relay subscription") + + with {:ok, target_instance} <- fetch_actor(address), + %Actor{} = local_actor <- get_actor(), {:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance), - {:ok, activity} <- Follows.accept(target_actor, local_actor) do - {:ok, activity} + {:ok, activity, follow} <- Follows.accept(target_actor, local_actor) do + {:ok, activity, follow} end end - # def reject(target_instance) do - # with %Actor{} = local_actor <- get_actor(), - # {:ok, %Actor{} = target_actor} <- Activity.get_or_fetch_actor_by_url(target_instance), - # {:ok, activity} <- Follows.reject(target_actor, local_actor) do - # {:ok, activity} - # end - # end + def reject(address) do + Logger.debug("We're trying to reject a relay subscription") + + with {:ok, target_instance} <- fetch_actor(address), + %Actor{} = local_actor <- get_actor(), + {:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance), + {:ok, activity, follow} <- Follows.reject(target_actor, local_actor) do + {:ok, activity, follow} + end + end @doc """ Publish an activity to all relays following this instance """ def publish(%Activity{data: %{"object" => object}} = _activity) do with %Actor{id: actor_id} = actor <- get_actor(), - {:ok, object} <- - Transmogrifier.fetch_obj_helper_as_activity_streams(object) do - ActivityPub.announce(actor, object, "#{object["id"]}/announces/#{actor_id}", true, false) + {object, object_id} <- fetch_object(object), + id <- "#{object_id}/announces/#{actor_id}" do + Logger.info("Publishing activity #{id} to all relays") + ActivityPub.announce(actor, object, id, true, false) else e -> Logger.error("Error while getting local instance actor: #{inspect(e)}") @@ -85,4 +105,51 @@ defmodule Mobilizon.Service.ActivityPub.Relay do Logger.debug(inspect(err)) nil end + + defp fetch_object(object) when is_map(object) do + with {:ok, object} <- Transmogrifier.fetch_obj_helper_as_activity_streams(object) do + {object, object["id"]} + end + end + + defp fetch_object(object) when is_bitstring(object), do: {object, object} + + @spec fetch_actor(String.t()) :: {:ok, String.t()} | {:error, String.t()} + # Dirty hack + defp fetch_actor("https://" <> address), do: fetch_actor(address) + defp fetch_actor("http://" <> address), do: fetch_actor(address) + + defp fetch_actor(address) do + %URI{host: host} = URI.parse("http://" <> address) + + cond do + String.contains?(address, "@") -> + check_actor(address) + + !is_nil(host) -> + check_actor("relay@#{host}") + + true -> + {:error, "Bad URL"} + end + end + + @spec check_actor(String.t()) :: {:ok, String.t()} | {:error, String.t()} + defp check_actor(username_and_domain) do + case Actors.get_actor_by_name(username_and_domain) do + %Actor{url: url} -> {:ok, url} + nil -> finger_actor(username_and_domain) + end + end + + @spec finger_actor(String.t()) :: {:ok, String.t()} | {:error, String.t()} + defp finger_actor(nickname) do + case WebFinger.finger(nickname) do + {:ok, %{"url" => url}} when not is_nil(url) -> + {:ok, url} + + _e -> + {:error, "No ActivityPub URL found in WebFinger"} + end + end end diff --git a/lib/service/activity_pub/transmogrifier.ex b/lib/service/activity_pub/transmogrifier.ex index b82b3fbe..d21d965d 100644 --- a/lib/service/activity_pub/transmogrifier.ex +++ b/lib/service/activity_pub/transmogrifier.ex @@ -20,108 +20,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do require Logger - def get_actor(%{"actor" => actor}) when is_binary(actor) do - actor - end - - def get_actor(%{"actor" => actor}) when is_list(actor) do - if is_binary(Enum.at(actor, 0)) do - Enum.at(actor, 0) - else - actor - |> Enum.find(fn %{"type" => type} -> type in ["Person", "Service", "Application"] end) - |> Map.get("id") - end - end - - def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do - id - end - - def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do - get_actor(%{"actor" => actor}) - end - - @doc """ - Modifies an incoming AP object (mastodon format) to our internal format. - """ - def fix_object(object) do - object - |> Map.put("actor", object["attributedTo"]) - |> fix_attachments - - # |> fix_in_reply_to - - # |> fix_tag - end - - def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object) - when not is_nil(in_reply_to) and is_bitstring(in_reply_to) do - in_reply_to |> do_fix_in_reply_to(object) - end - - def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object) - when not is_nil(in_reply_to) and is_map(in_reply_to) do - if is_bitstring(in_reply_to["id"]) do - in_reply_to["id"] |> do_fix_in_reply_to(object) - end - end - - def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object) - when not is_nil(in_reply_to) and is_list(in_reply_to) do - if is_bitstring(Enum.at(in_reply_to, 0)) do - in_reply_to |> Enum.at(0) |> do_fix_in_reply_to(object) - end - end - - def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object) - when not is_nil(in_reply_to) do - Logger.warn("inReplyTo ID seem incorrect: #{inspect(in_reply_to)}") - do_fix_in_reply_to("", object) - end - - def fix_in_reply_to(object), do: object - - def do_fix_in_reply_to(in_reply_to_id, object) do - case fetch_obj_helper(in_reply_to_id) do - {:ok, replied_object} -> - object - |> Map.put("inReplyTo", replied_object.url) - - {:error, {:error, :not_supported}} -> - Logger.info("Object reply origin has not a supported type") - object - - e -> - Logger.warn("Couldn't fetch #{in_reply_to_id} #{inspect(e)}") - object - end - end - - def fix_attachments(object) do - attachments = - (object["attachment"] || []) - |> Enum.map(fn data -> - url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}] - Map.put(data, "url", url) - end) - - object - |> Map.put("attachment", attachments) - end - - def fix_tag(object) do - tags = - (object["tag"] || []) - |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end) - |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end) - - combined = (object["tag"] || []) ++ tags - - object - |> Map.put("tag", combined) - end - def handle_incoming(%{"id" => nil}), do: :error def handle_incoming(%{"id" => ""}), do: :error @@ -135,6 +33,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do additional: %{ "cc" => [params["reported"].url] }, + event_id: if(is_nil(params["event"]), do: nil, else: params["event"].id || nil), local: false } @@ -158,7 +57,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do Logger.info("Handle incoming to create notes") with {:ok, object_data} <- - object |> fix_object() |> Converter.Comment.as_to_model_data(), + object |> Converter.Comment.as_to_model_data(), {:existing_comment, {:error, :comment_not_found}} <- {:existing_comment, Events.get_comment_from_url_with_preload(object_data.url)}, {:ok, %Activity{} = activity, %Comment{} = comment} <- @@ -186,7 +85,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do Logger.info("Handle incoming to create event") with {:ok, object_data} <- - object |> fix_object() |> Converter.Event.as_to_model_data(), + object |> Converter.Event.as_to_model_data(), {:existing_event, nil} <- {:existing_event, Events.get_event_by_url(object_data.url)}, {:ok, %Activity{} = activity, %Event{} = event} <- ActivityPub.create(:event, object_data, false) do @@ -273,36 +172,25 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do end end - # - # def handle_incoming( - # %{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data - # ) do - # with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), - # {:ok, object} <- - # fetch_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), - # {:ok, activity, object} <- ActivityPub.like(actor, object, id, false) do - # {:ok, activity} - # else - # _e -> :error - # end - # end - # # def handle_incoming( - %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => _id} = data + %{"type" => "Announce", "object" => object, "actor" => _actor, "id" => _id} = data ) do with actor <- get_actor(data), # TODO: Is the following line useful? - {:ok, %Actor{} = _actor} <- ActivityPub.get_or_fetch_actor_by_url(actor), + {:ok, %Actor{id: actor_id} = _actor} <- ActivityPub.get_or_fetch_actor_by_url(actor), :ok <- Logger.debug("Fetching contained object"), - {:ok, object} <- fetch_obj_helper_as_activity_streams(object_id), + {:ok, object} <- fetch_obj_helper_as_activity_streams(object), :ok <- Logger.debug("Handling contained object"), create_data <- make_create_data(object), :ok <- Logger.debug(inspect(object)), - {:ok, _activity, object} <- handle_incoming(create_data), + {:ok, _activity, entity} <- handle_incoming(create_data), :ok <- Logger.debug("Finished processing contained object"), - {:ok, activity} <- ActivityPub.create_activity(data, false) do - {:ok, activity, object} + {:ok, activity} <- ActivityPub.create_activity(data, false), + {:ok, %Actor{id: object_owner_actor_id}} <- Actors.get_actor_by_url(object["actor"]), + {:ok, %Mobilizon.Share{} = _share} <- + Mobilizon.Share.create(object["id"], actor_id, object_owner_actor_id) do + {:ok, activity, entity} else e -> Logger.debug(inspect(e)) @@ -318,7 +206,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do when object_type in ["Person", "Group", "Application", "Service", "Organization"] do with {:ok, %Actor{} = old_actor} <- Actors.get_actor_by_url(object["id"]), {:ok, object_data} <- - object |> fix_object() |> Converter.Actor.as_to_model_data(), + object |> Converter.Actor.as_to_model_data(), {:ok, %Activity{} = activity, %Actor{} = new_actor} <- ActivityPub.update(:actor, old_actor, object_data, false) do {:ok, activity, new_actor} @@ -331,12 +219,15 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do def handle_incoming( %{"type" => "Update", "object" => %{"type" => "Event"} = object, "actor" => _actor} = - _update + update_data ) do - with %Event{} = old_event <- - Events.get_event_by_url(object["id"]), + with actor <- get_actor(update_data), + {:ok, %Actor{url: actor_url}} <- Actors.get_actor_by_url(actor), + {:ok, %Event{} = old_event} <- + object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(), {:ok, object_data} <- - object |> fix_object() |> Converter.Event.as_to_model_data(), + object |> Converter.Event.as_to_model_data(), + {:origin_check, true} <- {:origin_check, origin_check?(actor_url, update_data)}, {:ok, %Activity{} = activity, %Event{} = new_event} <- ActivityPub.update(:event, old_event, object_data, false) do {:ok, activity, new_event} @@ -396,16 +287,18 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do def handle_incoming( %{"type" => "Delete", "object" => object, "actor" => _actor, "id" => _id} = data ) do - object_id = Utils.get_url(object) - with actor <- get_actor(data), - {:ok, %Actor{url: _actor_url}} <- Actors.get_actor_by_url(actor), - {:ok, object} <- fetch_obj_helper(object_id), - # TODO : Validate that DELETE comes indeed form right domain (see above) - # :ok <- contain_origin(actor_url, object.data), + {:ok, %Actor{url: actor_url}} <- Actors.get_actor_by_url(actor), + object_id <- Utils.get_url(object), + {:origin_check, true} <- {:origin_check, origin_check_from_id?(actor_url, object_id)}, + {:ok, object} <- ActivityPub.fetch_object_from_url(object_id), {:ok, activity, object} <- ActivityPub.delete(object, false) do {:ok, activity, object} else + {:origin_check, false} -> + Logger.warn("Object origin check failed") + :error + e -> Logger.debug(inspect(e)) :error @@ -413,12 +306,13 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Join", "object" => object, "actor" => _actor, "id" => _id} = data + %{"type" => "Join", "object" => object, "actor" => _actor, "id" => id} = data ) do with actor <- get_actor(data), {:ok, %Actor{url: _actor_url} = actor} <- Actors.get_actor_by_url(actor), - {:ok, object} <- fetch_obj_helper(object), - {:ok, activity, object} <- ActivityPub.join(object, actor, false) do + object <- Utils.get_url(object), + {:ok, object} <- ActivityPub.fetch_object_from_url(object), + {:ok, activity, object} <- ActivityPub.join(object, actor, false, %{url: id}) do {:ok, activity, object} else e -> @@ -432,7 +326,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do ) do with actor <- get_actor(data), {:ok, %Actor{} = actor} <- Actors.get_actor_by_url(actor), - {:ok, object} <- fetch_obj_helper(object), + object <- Utils.get_url(object), + {:ok, object} <- ActivityPub.fetch_object_from_url(object), {:ok, activity, object} <- ActivityPub.leave(object, actor, false) do {:ok, activity, object} else @@ -487,7 +382,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do ActivityPub.accept( :follow, follow, - %{approved: true}, false ) do {:ok, activity, follow} @@ -511,23 +405,11 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do Handle incoming `Reject` activities wrapping a `Follow` activity """ def do_handle_incoming_reject_following(follow_object, %Actor{} = actor) do - with {:follow, - {:ok, - %Follower{approved: false, actor: follower, id: follow_id, target_actor: followed} = - follow}} <- + with {:follow, {:ok, %Follower{approved: false, target_actor: followed} = follow}} <- {:follow, get_follow(follow_object)}, {:same_actor, true} <- {:same_actor, actor.id == followed.id}, {:ok, activity, _} <- - ActivityPub.reject( - %{ - to: [follower.url], - actor: actor.url, - object: follow_object, - local: false - }, - "#{MobilizonWeb.Endpoint.url()}/reject/follow/#{follow_id}" - ), - {:ok, %Follower{}} <- Actors.delete_follower(follow) do + ActivityPub.reject(:follow, follow) do {:ok, activity, follow} else {:follow, _} -> @@ -547,7 +429,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do # Handle incoming `Accept` activities wrapping a `Join` activity on an event defp do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do - with {:join_event, {:ok, %Participant{role: :not_approved, event: event} = participant}} <- + with {:join_event, {:ok, %Participant{role: role, event: event} = participant}} + when role in [:not_approved, :rejected] <- {:join_event, get_participant(join_object)}, # TODO: The actor that accepts the Join activity may another one that the event organizer ? # Or maybe for groups it's the group that sends the Accept activity @@ -556,7 +439,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do ActivityPub.accept( :join, participant, - %{role: :participant}, false ), :ok <- @@ -587,32 +469,20 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do # Handle incoming `Reject` activities wrapping a `Join` activity on an event defp do_handle_incoming_reject_join(join_object, %Actor{} = actor_accepting) do - with {:join_event, - {:ok, - %Participant{role: :not_approved, actor: actor, id: join_id, event: event} = - participant}} <- + with {:join_event, {:ok, %Participant{event: event, role: role} = participant}} + when role != :rejected <- {:join_event, get_participant(join_object)}, # TODO: The actor that accepts the Join activity may another one that the event organizer ? # Or maybe for groups it's the group that sends the Accept activity {:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id}, - {:ok, activity, _} <- - ActivityPub.reject( - %{ - to: [actor.url], - actor: actor_accepting.url, - object: join_object, - local: false - }, - "#{MobilizonWeb.Endpoint.url()}/reject/join/#{join_id}" - ), - {:ok, %Participant{role: :rejected} = participant} <- - Events.update_participant(participant, %{"role" => :rejected}), + {:ok, activity, participant} <- + ActivityPub.reject(:join, participant, false), :ok <- Participation.send_emails_to_local_user(participant) do {:ok, activity, participant} else - {:join_event, {:ok, %Participant{role: :participant}}} -> - Logger.debug( - "Tried to handle an Reject activity on a Join activity with a event object but the participant is already validated" + {:join_event, {:ok, %Participant{role: :rejected}}} -> + Logger.warn( + "Tried to handle an Reject activity on a Join activity with a event object but the participant is already rejected" ) nil @@ -662,49 +532,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do end end - def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) do - with false <- String.starts_with?(in_reply_to, "http"), - {:ok, replied_to_object} <- fetch_obj_helper(in_reply_to) do - Map.put(object, "inReplyTo", replied_to_object["external_url"] || in_reply_to) - else - _e -> object - end - end - - def set_reply_to_uri(obj), do: obj - # - # # Prepares the object of an outgoing create activity. - def prepare_object(object) do - object - # |> set_sensitive - # |> add_hashtags - |> add_mention_tags - # |> add_emoji_tags - |> add_attributed_to - # |> prepare_attachments - |> set_reply_to_uri - end - - @doc """ - internal -> Mastodon - """ - def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do - Logger.debug("Prepare outgoing for a note creation") - - object = - object - |> prepare_object - - data = - data - |> Map.put("object", object) - |> Map.merge(Utils.make_json_ld_header()) - - Logger.debug("Finished prepare outgoing for a note creation") - - {:ok, data} - end - def prepare_outgoing(%{"type" => _type} = data) do data = data @@ -713,145 +540,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do {:ok, data} end - # def prepare_outgoing(%Event{} = event) do - # event = - # event - # |> Map.from_struct() - # |> Map.drop([:__meta__]) - # |> Map.put(:"@context", "https://www.w3.org/ns/activitystreams") - # |> prepare_object - - # {:ok, event} - # end - - # def prepare_outgoing(%Comment{} = comment) do - # comment = - # comment - # |> Map.from_struct() - # |> Map.drop([:__meta__]) - # |> Map.put(:"@context", "https://www.w3.org/ns/activitystreams") - # |> prepare_object - - # {:ok, comment} - # end - - # - # def maybe_fix_object_url(data) do - # if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do - # case ActivityPub.fetch_object_from_id(data["object"]) do - # {:ok, relative_object} -> - # if relative_object.data["external_url"] do - # data = - # data - # |> Map.put("object", relative_object.data["external_url"]) - # else - # data - # end - # - # e -> - # Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}") - # data - # end - # else - # data - # end - # end - # - - def add_hashtags(object) do - tags = - (object["tag"] || []) - |> Enum.map(fn tag -> - %{ - "href" => MobilizonWeb.Endpoint.url() <> "/tags/#{tag}", - "name" => "##{tag}", - "type" => "Hashtag" - } - end) - - object - |> Map.put("tag", tags) - end - - def add_mention_tags(object) do - Logger.debug("add mention tags") - Logger.debug(inspect(object)) - - recipients = - (object["to"] ++ (object["cc"] || [])) -- ["https://www.w3.org/ns/activitystreams#Public"] - - mentions = - recipients - |> Enum.filter(& &1) - |> Enum.map(fn url -> - case Actors.get_actor_by_url(url) do - {:ok, actor} -> actor - _ -> nil - end - end) - |> Enum.filter(& &1) - |> Enum.map(fn actor -> - %{ - "type" => "Mention", - "href" => actor.url, - "name" => "@#{Actor.preferred_username_and_domain(actor)}" - } - end) - - tags = object["tag"] || [] - - object - |> Map.put("tag", tags ++ mentions) - end - - # - # # TODO: we should probably send mtime instead of unix epoch time for updated - # def add_emoji_tags(object) do - # tags = object["tag"] || [] - # emoji = object["emoji"] || [] - # - # out = - # emoji - # |> Enum.map(fn {name, url} -> - # %{ - # "icon" => %{"url" => url, "type" => "Image"}, - # "name" => ":" <> name <> ":", - # "type" => "Emoji", - # "updated" => "1970-01-01T00:00:00Z", - # "id" => url - # } - # end) - # - # object - # |> Map.put("tag", tags ++ out) - # end - # - - # - # def set_sensitive(object) do - # tags = object["tag"] || [] - # Map.put(object, "sensitive", "nsfw" in tags) - # end - # - def add_attributed_to(object) do - attributed_to = object["attributedTo"] || object["actor"] - - object |> Map.put("attributedTo", attributed_to) - end - - # - # def prepare_attachments(object) do - # attachments = - # (object["attachment"] || []) - # |> Enum.map(fn data -> - # [%{"mediaType" => media_type, "href" => href} | _] = data["url"] - # %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"} - # end) - # - # object - # |> Map.put("attachment", attachments) - # end - @spec fetch_obj_helper(map() | String.t()) :: Event.t() | Comment.t() | Actor.t() | any() def fetch_obj_helper(object) do Logger.debug("fetch_obj_helper") @@ -862,7 +550,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do {:ok, object} err -> - Logger.info("Error while fetching #{inspect(object)}") + Logger.warn("Error while fetching #{inspect(object)}") {:error, err} end end diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex index cc79aeb9..94c06a16 100644 --- a/lib/service/activity_pub/utils.ex +++ b/lib/service/activity_pub/utils.ex @@ -8,20 +8,11 @@ defmodule Mobilizon.Service.ActivityPub.Utils do # Various ActivityPub related utils. """ - alias Ecto.Changeset - - alias Mobilizon.{Actors, Addresses, Events, Reports, Users} + alias Mobilizon.Actors alias Mobilizon.Actors.Actor - alias Mobilizon.Addresses.Address - alias Mobilizon.Events.{Comment, Event} alias Mobilizon.Media.Picture - alias Mobilizon.Reports.Report alias Mobilizon.Service.ActivityPub.{Activity, Converter} alias Mobilizon.Service.Federator - alias Mobilizon.Storage.Repo - - alias MobilizonWeb.{Email, Endpoint} - alias MobilizonWeb.Router.Helpers, as: Routes require Logger @@ -37,12 +28,31 @@ defmodule Mobilizon.Service.ActivityPub.Utils do %{ "@context" => [ "https://www.w3.org/ns/activitystreams", - "https://litepub.github.io/litepub/context.jsonld", + "https://litepub.social/litepub/context.jsonld", %{ "sc" => "http://schema.org#", + "ical" => "http://www.w3.org/2002/12/cal/ical#", "Hashtag" => "as:Hashtag", "category" => "sc:category", - "uuid" => "sc:identifier" + "uuid" => "sc:identifier", + "maximumAttendeeCapacity" => "sc:maximumAttendeeCapacity", + "mz" => "https://joinmobilizon.org/ns#", + "repliesModerationOptionType" => %{ + "@id" => "mz:repliesModerationOptionType", + "@type" => "rdfs:Class" + }, + "repliesModerationOption" => %{ + "@id" => "mz:repliesModerationOption", + "@type" => "mz:repliesModerationOptionType" + }, + "joinModeType" => %{ + "@id" => "mz:joinModeType", + "@type" => "rdfs:Class" + }, + "joinMode" => %{ + "@id" => "mz:joinMode", + "@type" => "mz:joinModeType" + } } ] } @@ -112,128 +122,56 @@ defmodule Mobilizon.Service.ActivityPub.Utils do Map.put_new_lazy(map, "published", &make_date/0) end - @doc """ - Inserts a full object if it is contained in an activity. - """ - def insert_full_object(object_data) - - @doc """ - Inserts a full object if it is contained in an activity. - """ - def insert_full_object(%{"object" => %{"type" => "Event"} = object_data, "type" => "Create"}) - when is_map(object_data) do - with {:ok, object_data} <- - Converter.Event.as_to_model_data(object_data), - {:ok, %Event{} = event} <- Events.create_event(object_data) do - {:ok, event} - end + def get_actor(%{"actor" => actor}) when is_binary(actor) do + actor end - def insert_full_object(%{"object" => %{"type" => "Group"} = object_data, "type" => "Create"}) - when is_map(object_data) do - with object_data <- - Map.put(object_data, "preferred_username", object_data["preferredUsername"]), - {:ok, %Actor{} = group} <- Actors.create_group(object_data) do - {:ok, group} - end - end - - @doc """ - Inserts a full object if it is contained in an activity. - """ - def insert_full_object(%{"object" => %{"type" => "Note"} = object_data, "type" => "Create"}) - when is_map(object_data) do - with data <- Converter.Comment.as_to_model_data(object_data), - {:ok, %Comment{} = comment} <- Events.create_comment(data) do - {:ok, comment} + def get_actor(%{"actor" => actor}) when is_list(actor) do + if is_binary(Enum.at(actor, 0)) do + Enum.at(actor, 0) else - err -> - Logger.error("Error while inserting a remote comment inside database") - Logger.debug(inspect(err)) - {:error, err} + actor + |> Enum.find(fn %{"type" => type} -> type in ["Person", "Service", "Application"] end) + |> Map.get("id") end end + def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do + id + end + + def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do + get_actor(%{"actor" => actor}) + end + @doc """ - Inserts a full object if it is contained in an activity. + Checks that an incoming AP object's actor matches the domain it came from. """ - def insert_full_object(%{"type" => "Flag"} = object_data) - when is_map(object_data) do - with data <- Converter.Flag.as_to_model_data(object_data), - {:ok, %Report{} = report} <- Reports.create_report(data) do - Enum.each(Users.list_moderators(), fn moderator -> - moderator - |> Email.Admin.report(report) - |> Email.Mailer.deliver_later() - end) + def origin_check?(id, %{"actor" => actor} = params) when not is_nil(actor) do + id_uri = URI.parse(id) + actor_uri = URI.parse(get_actor(params)) - {:ok, report} - else - err -> - Logger.error("Error while inserting report inside database") - Logger.debug(inspect(err)) - {:error, err} - end + compare_uris?(actor_uri, id_uri) end - def insert_full_object(_), do: {:ok, nil} + def origin_check?(_id, %{"actor" => nil}), do: false - @doc """ - Update an object - """ - @spec update_object(struct(), map()) :: {:ok, struct()} | any() - def update_object(object, object_data) + def origin_check?(id, %{"attributedTo" => actor} = params), + do: origin_check?(id, Map.put(params, "actor", actor)) - def update_object(event_url, %{ - "object" => %{"type" => "Event"} = object_data, - "type" => "Update" - }) - when is_map(object_data) do - with {:event_not_found, %Event{} = event} <- - {:event_not_found, Events.get_event_by_url(event_url)}, - {:ok, object_data} <- Converter.Event.as_to_model_data(object_data), - {:ok, %Event{} = event} <- Events.update_event(event, object_data) do - {:ok, event} - end + def origin_check?(_id, _data), do: false + + defp compare_uris?(%URI{} = id_uri, %URI{} = other_uri), do: id_uri.host == other_uri.host + + def origin_check_from_id?(id, other_id) when is_binary(other_id) do + id_uri = URI.parse(id) + other_uri = URI.parse(other_id) + + compare_uris?(id_uri, other_uri) end - def update_object(actor_url, %{ - "object" => %{"type" => type_actor} = object_data, - "type" => "Update" - }) - when is_map(object_data) and type_actor in @actor_types do - with {:ok, %Actor{} = actor} <- Actors.get_actor_by_url(actor_url), - object_data <- Converter.Actor.as_to_model_data(object_data), - {:ok, %Actor{} = actor} <- Actors.update_actor(actor, object_data) do - {:ok, actor} - end - end - - def update_object(_, _), do: {:ok, nil} - - #### Like-related helpers - - # @doc """ - # Returns an existing like if a user already liked an object - # """ - # def get_existing_like(actor, %{data: %{"id" => id}}) do - # query = - # from( - # activity in Activity, - # where: fragment("(?)->>'actor' = ?", activity.data, ^actor), - # # this is to use the index - # where: - # fragment( - # "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - # activity.data, - # activity.data, - # ^id - # ), - # where: fragment("(?)->>'type' = 'Like'", activity.data) - # ) - # - # Repo.one(query) - # end + def origin_check_from_id?(id, %{"id" => other_id} = _params) when is_binary(other_id), + do: origin_check_from_id?(id, other_id) @doc """ Save picture data from %Plug.Upload{} and return AS Link data. @@ -284,255 +222,6 @@ defmodule Mobilizon.Service.ActivityPub.Utils do def make_picture_data(nil), do: nil - @doc """ - Make an AP event object from an set of values - """ - @spec make_event_data( - String.t(), - map(), - String.t(), - String.t(), - map(), - list(), - map(), - String.t() - ) :: map() - def make_event_data( - actor, - %{to: to, cc: cc} = _audience, - title, - content_html, - picture \\ nil, - tags \\ [], - metadata \\ %{}, - uuid \\ nil, - url \\ nil - ) do - Logger.debug("Making event data") - uuid = uuid || Ecto.UUID.generate() - - res = %{ - "type" => "Event", - "to" => to, - "cc" => cc || [], - "content" => content_html, - "name" => title, - "startTime" => metadata.begins_on, - "endTime" => metadata.ends_on, - "category" => metadata.category, - "actor" => actor, - "id" => url || Routes.page_url(Endpoint, :event, uuid), - "joinOptions" => metadata.join_options, - "status" => metadata.status, - "onlineAddress" => metadata.online_address, - "phoneAddress" => metadata.phone_address, - "draft" => metadata.draft, - "uuid" => uuid, - "tag" => - tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end) - } - - res = - if is_nil(metadata.physical_address), - do: res, - else: Map.put(res, "location", make_address_data(metadata.physical_address)) - - res = - if is_nil(picture), do: res, else: Map.put(res, "attachment", [make_picture_data(picture)]) - - if is_nil(metadata.options) do - res - else - options = Events.EventOptions |> struct(metadata.options) |> Map.from_struct() - - Enum.reduce(options, res, fn {key, value}, acc -> - (!is_nil(value) && Map.put(acc, camelize(key), value)) || - acc - end) - end - end - - def make_address_data(%Address{} = address) do - # res = %{ - # "type" => "Place", - # "name" => address.description, - # "id" => address.url, - # "address" => %{ - # "type" => "PostalAddress", - # "streetAddress" => address.street, - # "postalCode" => address.postal_code, - # "addressLocality" => address.locality, - # "addressRegion" => address.region, - # "addressCountry" => address.country - # } - # } - # - # if is_nil(address.geom) do - # res - # else - # Map.put(res, "geo", %{ - # "type" => "GeoCoordinates", - # "latitude" => address.geom.coordinates |> elem(0), - # "longitude" => address.geom.coordinates |> elem(1) - # }) - # end - address.url - end - - def make_address_data(address) when is_map(address) do - Address - |> struct(address) - |> make_address_data() - end - - def make_address_data(address_url) when is_bitstring(address_url) do - with %Address{} = address <- Addresses.get_address_by_url(address_url) do - address.url - end - end - - @doc """ - Make an AP comment object from an set of values - """ - def make_comment_data( - actor, - to, - content_html, - # attachments, - inReplyTo \\ nil, - tags \\ [], - # _cw \\ nil, - cc \\ [] - ) do - Logger.debug("Making comment data") - uuid = Ecto.UUID.generate() - - object = %{ - "type" => "Note", - "to" => to, - "cc" => cc, - "content" => content_html, - # "summary" => cw, - # "attachment" => attachments, - "actor" => actor, - "id" => Routes.page_url(Endpoint, :comment, uuid), - "uuid" => uuid, - "tag" => tags |> Enum.uniq() - } - - if inReplyTo do - object - |> Map.put("inReplyTo", inReplyTo) - else - object - end - end - - def make_group_data( - actor, - to, - preferred_username, - content_html, - # attachments, - tags \\ [], - # _cw \\ nil, - cc \\ [] - ) do - uuid = Ecto.UUID.generate() - - %{ - "type" => "Group", - "to" => to, - "cc" => cc, - "summary" => content_html, - "attributedTo" => actor, - "preferredUsername" => preferred_username, - "id" => Actor.build_url(preferred_username, :page), - "uuid" => uuid, - "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq() - } - end - - #### Like-related helpers - - @doc """ - Returns an existing like if a user already liked an object - """ - # @spec get_existing_like(Actor.t, map()) :: nil - # def get_existing_like(%Actor{url: url} = actor, %{data: %{"id" => id}}) do - # nil - # end - - # def make_like_data(%Actor{url: url} = actor, %{data: %{"id" => id}} = object, activity_id) do - # data = %{ - # "type" => "Like", - # "actor" => url, - # "object" => id, - # "to" => [actor.followers_url, object.data["actor"]], - # "cc" => ["https://www.w3.org/ns/activitystreams#Public"], - # "context" => object.data["context"] - # } - - # if activity_id, do: Map.put(data, "id", activity_id), else: data - # end - - def update_element_in_object(property, element, object) do - with new_data <- - object.data - |> Map.put("#{property}_count", length(element)) - |> Map.put("#{property}s", element), - changeset <- Changeset.change(object, data: new_data), - {:ok, object} <- Repo.update(changeset) do - {:ok, object} - end - end - - # def update_likes_in_object(likes, object) do - # update_element_in_object("like", likes, object) - # end - # - # def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do - # with likes <- [actor | object.data["likes"] || []] |> Enum.uniq() do - # update_likes_in_object(likes, object) - # end - # end - # - # def remove_like_from_object(%Activity{data: %{"actor" => actor}}, object) do - # with likes <- (object.data["likes"] || []) |> List.delete(actor) do - # update_likes_in_object(likes, object) - # end - # end - - #### Follow-related helpers - - @doc """ - Makes a follow activity data for the given followed and follower - """ - def make_follow_data(%Actor{url: followed_id}, %Actor{url: follower_id}, activity_id) do - Logger.debug("Make follow data") - - data = %{ - "type" => "Follow", - "actor" => follower_id, - "to" => [followed_id], - "cc" => ["https://www.w3.org/ns/activitystreams#Public"], - "object" => followed_id - } - - data = - if activity_id, - do: Map.put(data, "id", activity_id), - else: data - - Logger.debug(inspect(data)) - - data - end - - #### Announce-related helpers - - require Logger - @doc """ Make announce activity data for the given actor and object """ @@ -673,42 +362,6 @@ defmodule Mobilizon.Service.ActivityPub.Utils do |> Map.merge(additional) end - #### Flag-related helpers - @spec make_flag_data(map(), map()) :: map() - def make_flag_data(params, additional) do - object = [params.reported_actor_url] ++ params.comments_url - - object = if params[:event_url], do: object ++ [params.event_url], else: object - - %{ - "type" => "Flag", - "id" => "#{MobilizonWeb.Endpoint.url()}/report/#{Ecto.UUID.generate()}", - "actor" => params.reporter_url, - "content" => params.content, - "object" => object, - "state" => "open" - } - |> Map.merge(additional) - end - - def make_join_data(%Event{} = event, %Actor{} = actor) do - %{ - "type" => "Join", - "id" => "#{actor.url}/join/event/id", - "actor" => actor.url, - "object" => event.url - } - end - - def make_join_data(%Actor{type: :Group} = event, %Actor{} = actor) do - %{ - "type" => "Join", - "id" => "#{actor.url}/join/group/id", - "actor" => actor.url, - "object" => event.url - } - end - @doc """ Make accept join activity data """ @@ -718,7 +371,6 @@ defmodule Mobilizon.Service.ActivityPub.Utils do "type" => "Accept", "to" => object["to"], "cc" => object["cc"], - "actor" => object["actor"], "object" => object, "id" => object["id"] <> "/activity" } @@ -741,37 +393,39 @@ defmodule Mobilizon.Service.ActivityPub.Utils do end end - @doc """ - Converts PEM encoded keys to a private key representation - """ - def pem_to_private_key(pem) do - [private_key_code] = :public_key.pem_decode(pem) - :public_key.pem_entry_decode(private_key_code) - end - - @doc """ - Converts PEM encoded keys to a PEM public key representation - """ def pem_to_public_key_pem(pem) do public_key = pem_to_public_key(pem) public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key) :public_key.pem_encode([public_key]) end - def camelize(word) when is_atom(word) do - camelize(to_string(word)) + defp make_signature(id, date) do + uri = URI.parse(id) + + signature = + Mobilizon.Service.ActivityPub.Relay.get_actor() + |> Mobilizon.Service.HTTPSignatures.Signature.sign(%{ + "(request-target)": "get #{uri.path}", + host: uri.host, + date: date + }) + + [{:Signature, signature}] end - def camelize(word) when is_bitstring(word) do - {first, rest} = String.split_at(Macro.camelize(word), 1) - String.downcase(first) <> rest + def sign_fetch(headers, id, date) do + if Mobilizon.Config.get([:activitypub, :sign_object_fetches]) do + headers ++ make_signature(id, date) + else + headers + end end - def underscore(word) when is_atom(word) do - underscore(to_string(word)) - end - - def underscore(word) when is_bitstring(word) do - Macro.underscore(word) + def maybe_date_fetch(headers, date) do + if Mobilizon.Config.get([:activitypub, :sign_object_fetches]) do + headers ++ [{:Date, date}] + else + headers + end end end diff --git a/lib/service/activity_pub/visibility.ex b/lib/service/activity_pub/visibility.ex index 018aacb6..fa89c6ec 100644 --- a/lib/service/activity_pub/visibility.ex +++ b/lib/service/activity_pub/visibility.ex @@ -17,7 +17,10 @@ defmodule Mobilizon.Service.ActivityPub.Visibility do def is_public?(%{data: %{"type" => "Tombstone"}}), do: false def is_public?(%{data: data}), do: is_public?(data) def is_public?(%Activity{data: data}), do: is_public?(data) - def is_public?(data) when is_map(data), do: @public in (data["to"] ++ (data["cc"] || [])) + + def is_public?(data) when is_map(data), + do: @public in (Map.get(data, "to", []) ++ Map.get(data, "cc", [])) + def is_public?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at) def is_public?(err), do: raise(ArgumentError, message: "Invalid argument #{inspect(err)}") end diff --git a/lib/service/formatter/formatter.ex b/lib/service/formatter/formatter.ex index 282fc392..33924d68 100644 --- a/lib/service/formatter/formatter.ex +++ b/lib/service/formatter/formatter.ex @@ -34,16 +34,14 @@ defmodule Mobilizon.Service.Formatter do def mention_handler("@" <> nickname, buffer, _opts, acc) do case Actors.get_actor_by_name(nickname) do - %Actor{preferred_username: preferred_username} = actor -> - link = "@#{preferred_username}" + # %Actor{preferred_username: preferred_username} = actor -> + # link = "@#{preferred_username}" + # + # {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}} - {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}} - - %Actor{type: :Person, id: id, url: url, preferred_username: preferred_username} = actor -> + %Actor{type: :Person, id: id, preferred_username: preferred_username} = actor -> link = - "@#{ - preferred_username - }" + "@#{preferred_username}" {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}} diff --git a/lib/service/html.ex b/lib/service/html.ex index a30af219..02c4d88c 100644 --- a/lib/service/html.ex +++ b/lib/service/html.ex @@ -38,7 +38,8 @@ defmodule Mobilizon.Service.HTML.Scrubber.Default do "tag", "nofollow", "noopener", - "noreferrer" + "noreferrer", + "ugc" ]) Meta.allow_tag_with_these_attributes("a", ["name", "title"]) @@ -61,8 +62,8 @@ defmodule Mobilizon.Service.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes("ul", []) Meta.allow_tag_with_these_attributes("img", ["src", "alt"]) - Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"]) - Meta.allow_tag_with_these_attributes("span", []) + Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card", "mention"]) + Meta.allow_tag_with_these_attributes("span", ["data-user"]) Meta.allow_tag_with_these_attributes("h1", []) Meta.allow_tag_with_these_attributes("h2", []) diff --git a/lib/service/http_signatures/signature.ex b/lib/service/http_signatures/signature.ex index a4eb51ea..df929617 100644 --- a/lib/service/http_signatures/signature.ex +++ b/lib/service/http_signatures/signature.ex @@ -15,6 +15,7 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do require Logger + @spec key_id_to_actor_url(String.t()) :: String.t() def key_id_to_actor_url(key_id) do %{path: path} = uri = @@ -46,12 +47,10 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do end end - @doc """ - Gets a public key for a given ActivityPub actor ID (url). - """ + # Gets a public key for a given ActivityPub actor ID (url). @spec get_public_key_for_url(String.t()) :: {:ok, String.t()} | {:error, :actor_fetch_error | :pem_decode_error} - def get_public_key_for_url(url) do + defp get_public_key_for_url(url) do with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_actor_by_url(url), {:ok, public_key} <- prepare_public_key(keys) do {:ok, public_key} @@ -103,16 +102,10 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do end end - def generate_date_header(date \\ Timex.now("GMT")) do - case Timex.format(date, "%a, %d %b %Y %H:%M:%S %Z", :strftime) do - {:ok, date} -> - date + def generate_date_header, do: generate_date_header(NaiveDateTime.utc_now()) - {:error, err} -> - Logger.error("Unable to generate date header") - Logger.debug(inspect(err)) - nil - end + def generate_date_header(%NaiveDateTime{} = date) do + Timex.format!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT") end def generate_request_target(method, path), do: "#{method} #{path}" diff --git a/lib/service/workers/background_worker.ex b/lib/service/workers/background_worker.ex new file mode 100644 index 00000000..60cbe212 --- /dev/null +++ b/lib/service/workers/background_worker.ex @@ -0,0 +1,17 @@ +defmodule Mobilizon.Service.Workers.BackgroundWorker do + @moduledoc """ + Worker to build search results + """ + + alias Mobilizon.Actors + alias Mobilizon.Actors.Actor + + use Mobilizon.Service.Workers.WorkerHelper, queue: "background" + + @impl Oban.Worker + def perform(%{"op" => "delete_actor", "actor_id" => actor_id}, _job) do + with %Actor{} = actor <- Actors.get_actor(actor_id) do + Actors.perform(:delete_actor, actor) + end + end +end diff --git a/mix.exs b/mix.exs index d6612bb2..0090f764 100644 --- a/mix.exs +++ b/mix.exs @@ -54,12 +54,13 @@ defmodule Mobilizon.Mixfile do {:phoenix, "~> 1.4.0"}, {:phoenix_pubsub, "~> 1.0"}, {:phoenix_ecto, "~> 4.0"}, - {:postgrex, ">= 0.14.2"}, + {:postgrex, ">= 0.15.3"}, {:phoenix_html, "~> 2.10"}, {:gettext, "~> 0.11"}, {:cowboy, "~> 2.6"}, {:guardian, "~> 2.0"}, {:guardian_db, "~> 2.0.2"}, + {:guardian_phoenix, "~> 2.0"}, {:argon2_elixir, "~> 2.0"}, {:cors_plug, "~> 2.0"}, {:ecto_autoslug_field, "~> 2.0"}, @@ -96,22 +97,22 @@ defmodule Mobilizon.Mixfile do {:http_signatures, git: "https://git.pleroma.social/pleroma/http_signatures.git", ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"}, - {:html_sanitize_ex, "~> 1.3.0"}, + {:html_sanitize_ex, "~> 1.4.0"}, {:ex_cldr_dates_times, "~> 2.0"}, {:ex_optimizer, "~> 0.1"}, {:progress_bar, "~> 2.0"}, - {:oban, "~> 0.11.1"}, + {:oban, "~> 0.12.0"}, # Dev and test dependencies {:phoenix_live_reload, "~> 1.2", only: [:dev, :e2e]}, {:ex_machina, "~> 2.3", only: [:dev, :test]}, - {:excoveralls, "~> 0.10", only: :test}, + {:excoveralls, "~> 0.12.1", only: :test}, {:ex_doc, "~> 0.21.1", only: [:dev, :test], runtime: false}, {:mix_test_watch, "~> 1.0", only: :dev, runtime: false}, {:ex_unit_notifier, "~> 0.1", only: :test}, {:dialyxir, "~> 1.0.0-rc.4", only: [:dev], runtime: false}, {:exvcr, "~> 0.10", only: :test}, {:credo, "~> 1.1.2", only: [:dev, :test], runtime: false}, - {:mock, "~> 0.3.0", only: :test}, + {:mock, "~> 0.3.4", only: :test}, {:elixir_feed_parser, "~> 2.1.0", only: :test} ] end diff --git a/mix.lock b/mix.lock index 09d4aef6..966915a4 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,7 @@ "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"}, "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "dataloader": {:hex, :dataloader, "1.0.6", "fb724d6d3fb6acb87d27e3b32dea3a307936ad2d245faf9cf5221d1323d6a4ba", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, - "db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, + "db_connection": {:hex, :db_connection, "2.2.0", "e923e88887cd60f9891fd324ac5e0290954511d090553c415fbf54be4c57ee63", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, "decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"}, "dialyxir": {:hex, :dialyxir, "1.0.0-rc.7", "6287f8f2cb45df8584317a4be1075b8c9b8a69de8eeb82b4d9e6c761cf2664cd", [:mix], [{:erlex, ">= 0.2.5", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"}, @@ -45,7 +45,7 @@ "ex_optimizer": {:hex, :ex_optimizer, "0.1.0", "1d12f7ea289092a38a794b84bd2f42c1e0621cb307c0f3e6a7df620839af2937", [:mix], [{:file_info, "~> 0.0.4", [hex: :file_info, repo: "hexpm", optional: false]}], "hexpm"}, "ex_unit_notifier": {:hex, :ex_unit_notifier, "0.1.4", "36a2dcab829f506e01bf17816590680dd1474407926d43e64c1263e627c364b8", [:mix], [], "hexpm"}, "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm"}, - "excoveralls": {:hex, :excoveralls, "0.12.0", "50e17a1b116fdb7facc2fe127a94db246169f38d7627b391376a0bc418413ce1", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.12.1", "a553c59f6850d0aff3770e4729515762ba7c8e41eedde03208182a8dc9d0ce07", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "exgravatar": {:hex, :exgravatar, "2.0.1", "66d595c7d63dd6bbac442c5542a724375ae29144059c6fe093e61553850aace4", [:mix], [], "hexpm"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"}, "exvcr": {:hex, :exvcr, "0.11.0", "59d5c11c9022852e9265d223fbde38c512cc350404f695a7b838cd7fb8dabed8", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, @@ -60,8 +60,9 @@ "gettext": {:hex, :gettext, "0.17.1", "8baab33482df4907b3eae22f719da492cee3981a26e649b9c2be1c0192616962", [:mix], [], "hexpm"}, "guardian": {:hex, :guardian, "2.0.0", "5d3e537832b7cf35c8674da92457b7be671666a2eff4bf0f2ccfcfb3a8c67a0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"}, "guardian_db": {:hex, :guardian_db, "2.0.2", "6247303fda5ed90e19ea1d2e4c5a65b13f58cc12810f95f71b6ffb50ef2d057f", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, + "guardian_phoenix": {:hex, :guardian_phoenix, "2.0.1", "89a817265af09a6ddf7cb1e77f17ffca90cea2db10ff888375ef34502b2731b1", [:mix], [{:guardian, "~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, + "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.0", "0310d27d7bafb662f30bff22ec732a72414799c83eaf44239781fd23b96216c0", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]}, "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, @@ -82,10 +83,10 @@ "mix_test_watch": {:hex, :mix_test_watch, "1.0.2", "34900184cbbbc6b6ed616ed3a8ea9b791f9fd2088419352a6d3200525637f785", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"}, "mmdb2_decoder": {:hex, :mmdb2_decoder, "1.1.0", "2e2347521bb3bf6b81b9ee58d3be2199cb68ea42dcbafcd0d8eb40214d2844cf", [:mix], [], "hexpm"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, - "mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, + "mock": {:hex, :mock, "0.3.4", "c5862eb3b8c64237f45f586cf00c9d892ba07bb48305a43319d428ce3c2897dd", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "mogrify": {:hex, :mogrify, "0.7.3", "1494ee739f6e90de158dec4d4edee2d854d2f2d06a522e943f996ae176bca53d", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.2", "1d71150d5293d703a9c38d4329da57d3935faed2031d64bc19e77b654ef2d177", [:mix], [], "hexpm"}, - "oban": {:hex, :oban, "0.11.1", "e34964fad7f188c2c3d006485601a8897e537f7b88a31928be2833ae1cab59af", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, + "oban": {:hex, :oban, "0.12.0", "5477d5ab4a5a201c0b6c89764040ebfc5d2c71c488a36f378016ce5990838f0f", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.4.11", "d112c862f6959f98e6e915c3b76c7a87ca3efd075850c8daa7c3c7a609014b0d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, @@ -97,7 +98,7 @@ "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.15.1", "23ce3417de70f4c0e9e7419ad85bdabcc6860a6925fe2c6f3b1b5b1e8e47bf2f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "progress_bar": {:hex, :progress_bar, "2.0.0", "447285f533b4b8717881fdb7160c7360c2f2ab57276f8904ce6d40482857e573", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, "rdf": {:hex, :rdf, "0.6.2", "1b85e37c135e232febeebda6b04ac4aba5f5e2bb1c3a2a6665ed4ccec19ade70", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, @@ -105,7 +106,7 @@ "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm"}, "slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"}, - "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, + "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm"}, "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "1.0.2", "6c4242c93332b8590a7979eaf5e11e77d971e579805c44931207e32aa6ad3db1", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, diff --git a/mkdocs.yml b/mkdocs.yml index f72f58f3..1089c44d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,7 +20,7 @@ markdown_extensions: - pymdownx.mark plugins: - search - - git-revision-date + - git-revision-date-localized theme: name: 'material' custom_dir: 'docs/theme/' diff --git a/priv/repo/migrations/20191129091227_add_timestamps_to_followers.exs b/priv/repo/migrations/20191129091227_add_timestamps_to_followers.exs new file mode 100644 index 00000000..dc582b82 --- /dev/null +++ b/priv/repo/migrations/20191129091227_add_timestamps_to_followers.exs @@ -0,0 +1,9 @@ +defmodule Mobilizon.Storage.Repo.Migrations.AddTimestampsToFollowers do + use Ecto.Migration + + def change do + alter table(:followers) do + timestamps() + end + end +end diff --git a/priv/repo/migrations/20191204164224_delete_event_cascade_to_comments.exs b/priv/repo/migrations/20191204164224_delete_event_cascade_to_comments.exs new file mode 100644 index 00000000..9d725dd8 --- /dev/null +++ b/priv/repo/migrations/20191204164224_delete_event_cascade_to_comments.exs @@ -0,0 +1,19 @@ +defmodule Mobilizon.Storage.Repo.Migrations.DeleteEventCascadeToComments do + use Ecto.Migration + + def up do + drop_if_exists(constraint(:comments, "comments_event_id_fkey")) + + alter table(:comments) do + modify(:event_id, references(:events, on_delete: :delete_all)) + end + end + + def down do + drop_if_exists(constraint(:comments, "comments_event_id_fkey")) + + alter table(:comments) do + modify(:event_id, references(:events, on_delete: :nothing)) + end + end +end diff --git a/priv/repo/migrations/20191206144028_create_shares.exs b/priv/repo/migrations/20191206144028_create_shares.exs new file mode 100644 index 00000000..ccfce195 --- /dev/null +++ b/priv/repo/migrations/20191206144028_create_shares.exs @@ -0,0 +1,23 @@ +defmodule Mobilizon.Repo.Migrations.CreateShares do + use Ecto.Migration + + def up do + create table(:shares) do + add(:uri, :string, null: false) + add(:actor_id, references(:actors, on_delete: :delete_all), null: false) + add(:owner_actor_id, references(:actors, on_delete: :delete_all), null: false) + + timestamps() + end + + create_if_not_exists( + index(:shares, [:uri, :actor_id], unique: true, name: :shares_uri_actor_id_index) + ) + end + + def down do + drop_if_exists(index(:shares, [:uri, :actor_id])) + + drop_if_exists(table(:shares)) + end +end diff --git a/schema.graphql b/schema.graphql index 66a412b0..07dc7f63 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,9 +1,10 @@ # source: http://localhost:4000/api -# timestamp: Fri Nov 22 2019 18:34:33 GMT+0100 (Central European Standard Time) +# timestamp: Wed Dec 11 2019 15:24:29 GMT+0100 (heure normale d’Europe centrale) schema { query: RootQueryType mutation: RootMutationType + subscription: RootSubscriptionType } """An action log""" @@ -25,6 +26,7 @@ type ActionLog { } enum ActionLogAction { + COMMENT_DELETION EVENT_DELETION EVENT_UPDATE NOTE_CREATION @@ -66,9 +68,6 @@ interface Actor { """Internal ID for this actor""" id: ID - """The actors RSA Keys""" - keys: String - """If the actor is from this instance""" local: Boolean @@ -78,9 +77,6 @@ interface Actor { """The actor's displayed name""" name: String - """A list of the events this actor has organized""" - organizedEvents: [Event] - """The actor's preferred username""" preferredUsername: String @@ -155,8 +151,62 @@ input AddressInput { url: String } +""" +Represents an application + +""" +type Application implements Actor { + """The actor's avatar picture""" + avatar: Picture + + """The actor's banner picture""" + banner: Picture + + """The actor's domain if (null if it's this instance)""" + domain: String + + """List of followers""" + followers: [Follower] + + """Number of followers for this actor""" + followersCount: Int + + """List of followings""" + following: [Follower] + + """Number of actors following this actor""" + followingCount: Int + + """Internal ID for this application""" + id: ID + + """If the actor is from this instance""" + local: Boolean + + """Whether the actors manually approves followers""" + manuallyApprovesFollowers: Boolean + + """The actor's displayed name""" + name: String + + """The actor's preferred username""" + preferredUsername: String + + """The actor's summary""" + summary: String + + """If the actor is suspended""" + suspended: Boolean + + """The type of Actor (Person, Group,…)""" + type: ActorType + + """The ActivityPub actor's URL""" + url: String +} + """A comment""" -type Comment { +type Comment implements ActionLogObject { actor: Person deletedAt: DateTime event: Event @@ -542,8 +592,14 @@ type Follower { """Whether the follow has been approved by the target actor""" approved: Boolean + """When the follow was created""" + insertedAt: DateTime + """What or who the profile follows""" targetActor: Actor + + """When the follow was updated""" + updatedAt: DateTime } type Geocoding { @@ -580,9 +636,6 @@ type Group implements Actor { """Internal ID for this group""" id: ID - """The actors RSA Keys""" - keys: String - """If the actor is from this instance""" local: Boolean @@ -693,6 +746,14 @@ enum Openness { OPEN } +type PaginatedFollowerList { + """A list of followers""" + elements: [Follower] + + """The total number of elements in the list""" + total: Int +} + """Represents a participant to an event""" type Participant { """The actor that participates to the event""" @@ -772,9 +833,6 @@ type Person implements Actor { """Internal ID for this person""" id: ID - """The actors RSA Keys""" - keys: String - """If the actor is from this instance""" local: Boolean @@ -857,7 +915,7 @@ input PictureInputObject { } """ -The `Point` scalar type represents Point geographic information compliant string data, +The `Point` scalar type represents Point geographic information compliant string data, represented as floats separated by a semi-colon. The geodetic system is WGS 84 """ scalar Point @@ -941,11 +999,66 @@ type RootMutationType { """Change default actor for user""" changeDefaultActor(preferredUsername: String!): User - """Change an user password""" - changePassword(newPassword: String!, oldPassword: String!): User + """Create a new person for user""" + createPerson( + """ + The avatar for the profile, either as an object or directly the ID of an existing Picture + """ + avatar: PictureInput - """Create a comment""" - createComment(actorId: ID!, eventId: ID, inReplyToCommentId: ID, text: String!): Comment + """ + The banner for the profile, either as an object or directly the ID of an existing Picture + """ + banner: PictureInput + + """The displayed name for the new profile""" + name: String = "" + preferredUsername: String! + + """The summary for the new profile""" + summary: String = "" + ): Person + + """Upload a picture""" + uploadPicture(actorId: ID!, alt: String, file: Upload!, name: String!): Picture + + """Delete an event""" + deleteEvent(actorId: ID!, eventId: ID!): DeletedObject + + """Create a note on a report""" + createReportNote(content: String, moderatorId: ID!, reportId: ID!): ReportNote + + """Accept a relay subscription""" + acceptRelay(address: String!): Follower + + """Delete a feed token""" + deleteFeedToken(token: String!): DeletedFeedToken + + """Validate an user after registration""" + validateUser(token: String!): Login + + """Resend registration confirmation token""" + resendConfirmationEmail(email: String!, locale: String): String + + """Update an identity""" + updatePerson( + """ + The avatar for the profile, either as an object or directly the ID of an existing Picture + """ + avatar: PictureInput + + """ + The banner for the profile, either as an object or directly the ID of an existing Picture + """ + banner: PictureInput + id: ID! + + """The displayed name for this profile""" + name: String + + """The summary for this profile""" + summary: String + ): Person """Create an event""" createEvent( @@ -974,95 +1087,6 @@ type RootMutationType { visibility: EventVisibility = PUBLIC ): Event - """Create a Feed Token""" - createFeedToken(actorId: ID): FeedToken - - """Create a group""" - createGroup( - """ - The avatar for the group, either as an object or directly the ID of an existing Picture - """ - avatar: PictureInput - - """ - The banner for the group, either as an object or directly the ID of an existing Picture - """ - banner: PictureInput - - """The identity that creates the group""" - creatorActorId: ID! - - """The displayed name for the group""" - name: String - - """The name for the group""" - preferredUsername: String! - - """The summary for the group""" - summary: String = "" - ): Group - - """Create a new person for user""" - createPerson( - """ - The avatar for the profile, either as an object or directly the ID of an existing Picture - """ - avatar: PictureInput - - """ - The banner for the profile, either as an object or directly the ID of an existing Picture - """ - banner: PictureInput - - """The displayed name for the new profile""" - name: String = "" - preferredUsername: String! - - """The summary for the new profile""" - summary: String = "" - ): Person - - """Create a report""" - createReport(commentsIds: [ID] = [""], content: String, eventId: ID, reportedActorId: ID!, reporterActorId: ID!): Report - - """Create a note on a report""" - createReportNote(content: String, moderatorId: ID!, reportId: ID!): ReportNote - - """Create an user""" - createUser(email: String!, locale: String, password: String!): User - deleteComment(actorId: ID!, commentId: ID!): DeletedObject - - """Delete an event""" - deleteEvent(actorId: ID!, eventId: ID!): DeletedObject - - """Delete a feed token""" - deleteFeedToken(token: String!): DeletedFeedToken - - """Delete a group""" - deleteGroup(actorId: ID!, groupId: ID!): DeletedObject - - """Delete an identity""" - deletePerson(id: ID!): Person - deleteReportNote(moderatorId: ID!, noteId: ID!): DeletedObject - - """Join an event""" - joinEvent(actorId: ID!, eventId: ID!): Participant - - """Join a group""" - joinGroup(actorId: ID!, groupId: ID!): Member - - """Leave an event""" - leaveEvent(actorId: ID!, eventId: ID!): DeletedParticipant - - """Leave an event""" - leaveGroup(actorId: ID!, groupId: ID!): DeletedMember - - """Login an user""" - login(email: String!, password: String!): Login - - """Refresh a token""" - refreshToken(refreshToken: String!): RefreshedToken - """Register a first profile on registration""" registerPerson( """ @@ -1086,14 +1110,24 @@ type RootMutationType { summary: String = "" ): Person - """Resend registration confirmation token""" - resendConfirmationEmail(email: String!, locale: String): String + """Accept a participation""" + updateParticipation(id: ID!, moderatorActorId: ID!, role: ParticipantRoleEnum!): Participant - """Reset user password""" - resetPassword(locale: String = "en", password: String!, token: String!): Login + """Delete a group""" + deleteGroup(actorId: ID!, groupId: ID!): DeletedObject + deleteComment(actorId: ID!, commentId: ID!): Comment - """Send a link through email to reset user password""" - sendResetPassword(email: String!, locale: String): String + """Create an user""" + createUser(email: String!, locale: String, password: String!): User + + """Leave an event""" + leaveEvent(actorId: ID!, eventId: ID!): DeletedParticipant + + """Refresh a token""" + refreshToken(refreshToken: String!): RefreshedToken + + """Join a group""" + joinGroup(actorId: ID!, groupId: ID!): Member """Update an event""" updateEvent( @@ -1122,37 +1156,73 @@ type RootMutationType { visibility: EventVisibility = PUBLIC ): Event - """Accept a participation""" - updateParticipation(id: ID!, moderatorActorId: ID!, role: ParticipantRoleEnum!): Participant + """Reset user password""" + resetPassword(locale: String = "en", password: String!, token: String!): Login - """Update an identity""" - updatePerson( + """Create a report""" + createReport(commentsIds: [ID] = [""], content: String, eventId: ID, forward: Boolean = false, reportedId: ID!, reporterId: ID!): Report + + """Update a report""" + updateReportStatus(moderatorId: ID!, reportId: ID!, status: ReportStatus!): Report + deleteReportNote(moderatorId: ID!, noteId: ID!): DeletedObject + + """Delete a relay subscription""" + removeRelay(address: String!): Follower + + """Create a comment""" + createComment(actorId: ID!, eventId: ID, inReplyToCommentId: ID, text: String!): Comment + + """Delete an identity""" + deletePerson(id: ID!): Person + + """Reject a relay subscription""" + rejectRelay(address: String!): Follower + + """Login an user""" + login(email: String!, password: String!): Login + + """Leave an event""" + leaveGroup(actorId: ID!, groupId: ID!): DeletedMember + + """Change an user password""" + changePassword(newPassword: String!, oldPassword: String!): User + + """Add a relay subscription""" + addRelay(address: String!): Follower + + """Join an event""" + joinEvent(actorId: ID!, eventId: ID!): Participant + + """Create a group""" + createGroup( """ - The avatar for the profile, either as an object or directly the ID of an existing Picture + The avatar for the group, either as an object or directly the ID of an existing Picture """ avatar: PictureInput """ - The banner for the profile, either as an object or directly the ID of an existing Picture + The banner for the group, either as an object or directly the ID of an existing Picture """ banner: PictureInput - id: ID! - """The displayed name for this profile""" + """The identity that creates the group""" + creatorActorId: ID! + + """The displayed name for the group""" name: String - """The summary for this profile""" - summary: String - ): Person + """The name for the group""" + preferredUsername: String! - """Update a report""" - updateReportStatus(moderatorId: ID!, reportId: ID!, status: ReportStatus!): Report + """The summary for the group""" + summary: String = "" + ): Group - """Upload a picture""" - uploadPicture(actorId: ID!, alt: String, file: Upload!, name: String!): Picture + """Send a link through email to reset user password""" + sendResetPassword(email: String!, locale: String): String - """Validate an user after registration""" - validateUser(token: String!): Login + """Create a Feed Token""" + createFeedToken(actorId: ID): FeedToken } """ @@ -1196,6 +1266,8 @@ type RootQueryType { """Get a picture""" picture(id: String!): Picture + relayFollowers(limit: Int = 10, page: Int = 1): PaginatedFollowerList + relayFollowings(direction: String = "desc", limit: Int = 10, orderBy: String = "updated_at", page: Int = 1): PaginatedFollowerList """Get a report by id""" report(id: ID!): Report @@ -1231,6 +1303,10 @@ type RootQueryType { users(direction: SortDirection = DESC, limit: Int = 10, page: Int = 1, sort: SortableUserField = ID): Users } +type RootSubscriptionType { + eventPersonParticipationChanged(personId: ID!): Person +} + """The list of possible options for the event's status""" enum SortableUserField { ID diff --git a/test/fixtures/mastodon-delete-user.json b/test/fixtures/mastodon-delete-user.json new file mode 100644 index 00000000..213354f0 --- /dev/null +++ b/test/fixtures/mastodon-delete-user.json @@ -0,0 +1,24 @@ +{ + "type": "Delete", + "object": { + "type": "Person", + "id": "https://framapiaf.org/users/admin", + "atomUri": "https://framapiaf.org/users/admin" + }, + "id": "https://framapiaf.org/users/admin#delete", + "actor": "https://framapiaf.org/users/admin", + "@context": [ + { + "toot": "http://joinmastodon.org/ns#", + "sensitive": "as:sensitive", + "ostatus": "http://ostatus.org#", + "movedTo": "as:movedTo", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "atomUri": "ostatus:atomUri", + "Hashtag": "as:Hashtag", + "Emoji": "toot:Emoji" + } + ] +} diff --git a/test/fixtures/mobilizon-join-activity.json b/test/fixtures/mobilizon-join-activity.json index f2669e3a..8580ccf4 100644 --- a/test/fixtures/mobilizon-join-activity.json +++ b/test/fixtures/mobilizon-join-activity.json @@ -11,12 +11,31 @@ "actor": "http://mobilizon2.test/@admin", "@context": [ "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", + "https://litepub.social/litepub/context.jsonld", { - "sensitive": "as:sensitive", - "movedTo": "as:movedTo", - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "Hashtag": "as:Hashtag" + "Hashtag": "as:Hashtag", + "category": "sc:category", + "ical": "http://www.w3.org/2002/12/cal/ical#", + "joinMode": { + "@id": "mz:joinMode", + "@type": "mz:joinModeType" + }, + "joinModeType": { + "@id": "mz:joinModeType", + "@type": "rdfs:Class" + }, + "maximumAttendeeCapacity": "sc:maximumAttendeeCapacity", + "mz": "https://joinmobilizon.org/ns#", + "repliesModerationOption": { + "@id": "mz:repliesModerationOption", + "@type": "mz:repliesModerationOptionType" + }, + "repliesModerationOptionType": { + "@id": "mz:repliesModerationOptionType", + "@type": "rdfs:Class" + }, + "sc": "http://schema.org#", + "uuid": "sc:identifier" } ] } \ No newline at end of file diff --git a/test/fixtures/mobilizon-leave-activity.json b/test/fixtures/mobilizon-leave-activity.json index 10d15798..58e39ffc 100644 --- a/test/fixtures/mobilizon-leave-activity.json +++ b/test/fixtures/mobilizon-leave-activity.json @@ -11,12 +11,31 @@ "actor": "http://mobilizon2.test/@admin", "@context": [ "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", + "https://litepub.social/litepub/context.jsonld", { - "sensitive": "as:sensitive", - "movedTo": "as:movedTo", - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "Hashtag": "as:Hashtag" + "Hashtag": "as:Hashtag", + "category": "sc:category", + "ical": "http://www.w3.org/2002/12/cal/ical#", + "joinMode": { + "@id": "mz:joinMode", + "@type": "mz:joinModeType" + }, + "joinModeType": { + "@id": "mz:joinModeType", + "@type": "rdfs:Class" + }, + "maximumAttendeeCapacity": "sc:maximumAttendeeCapacity", + "mz": "https://joinmobilizon.org/ns#", + "repliesModerationOption": { + "@id": "mz:repliesModerationOption", + "@type": "mz:repliesModerationOptionType" + }, + "repliesModerationOptionType": { + "@id": "mz:repliesModerationOptionType", + "@type": "rdfs:Class" + }, + "sc": "http://schema.org#", + "uuid": "sc:identifier" } ] } \ No newline at end of file diff --git a/test/fixtures/mobilizon-post-activity.json b/test/fixtures/mobilizon-post-activity.json index 21a9ef44..1cba4dd0 100644 --- a/test/fixtures/mobilizon-post-activity.json +++ b/test/fixtures/mobilizon-post-activity.json @@ -1,13 +1,30 @@ { "@context": [ "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", + "https://litepub.social/litepub/context.jsonld", { - "mblzn": "https://joinmobilizon.org/ns#", "Hashtag": "as:Hashtag", + "category": "sc:category", + "ical": "http://www.w3.org/2002/12/cal/ical#", + "joinMode": { + "@id": "mz:joinMode", + "@type": "mz:joinModeType" + }, + "joinModeType": { + "@id": "mz:joinModeType", + "@type": "rdfs:Class" + }, + "maximumAttendeeCapacity": "sc:maximumAttendeeCapacity", + "mz": "https://joinmobilizon.org/ns#", + "repliesModerationOption": { + "@id": "mz:repliesModerationOption", + "@type": "mz:repliesModerationOptionType" + }, + "repliesModerationOptionType": { + "@id": "mz:repliesModerationOptionType", + "@type": "rdfs:Class" + }, "sc": "http://schema.org#", - "Place": "sc:Place", - "PostalAddress": "sc:PostalAddress", "uuid": "sc:identifier" } ], diff --git a/test/fixtures/vcr_cassettes/activity_pub/activity_object_bogus.json b/test/fixtures/vcr_cassettes/activity_pub/activity_object_bogus.json new file mode 100644 index 00000000..f6d548a8 --- /dev/null +++ b/test/fixtures/vcr_cassettes/activity_pub/activity_object_bogus.json @@ -0,0 +1,116 @@ +[ + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "https://framapiaf.org/users/admin" + }, + "response": { + "binary": false, + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/admin\",\"type\":\"Service\",\"following\":\"https://framapiaf.org/users/admin/following\",\"followers\":\"https://framapiaf.org/users/admin/followers\",\"inbox\":\"https://framapiaf.org/users/admin/inbox\",\"outbox\":\"https://framapiaf.org/users/admin/outbox\",\"featured\":\"https://framapiaf.org/users/admin/collections/featured\",\"preferredUsername\":\"admin\",\"name\":\"Administrateur\",\"summary\":\"\\u003cp\\u003eJe ne suis qu\\u0026apos;un compte inutile. Merci nous de contacter via \\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@admin\",\"manuallyApprovesFollowers\":false,\"discoverable\":null,\"publicKey\":{\"id\":\"https://framapiaf.org/users/admin#main-key\",\"owner\":\"https://framapiaf.org/users/admin\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyHaU/AZ5dWtSxZXkPa89\\nDUQ4z+JQHGGUG/xkGuq0v8P6qJfQqtHPBO5vH0IQJqluXWQS96gqTwjZnYevcpNA\\nveYv0K25DWszx5Ehz6JX2/sSvu2rNUcQ3YZvSjdo/Yy1u5Fuc5lLmvw8uFzXYekD\\nWovTMOnp4mIKpVEm/G/v4w8jvFEKw88h743vwaEIim88GEQItMxzGAV6zSqV1DWO\\nLxtoRsinslJYfAG46ex4YUATFveWvOUeWk5W1sEa5f3c0moaTmBM/PAAo8vLxhlw\\nJhsHihsCH+BcXKVMjW8OCqYYqISMxEifUBX63HcJt78ELHpOuc1c2eG59PomtTjQ\\nywIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"News\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Support\",\"value\":\"\\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Soutenir\",\"value\":\"\\u003ca href=\\\"https://soutenir.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esoutenir.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Site\",\"value\":\"\\u003ca href=\\\"https://framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003eframasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/002/original/85fbb27ad5e3cf71.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/002/original/6aba75f1ab1ab6de.jpg\"}}", + "headers": { + "Date": "Sun, 15 Dec 2019 20:24:11 GMT", + "Content-Type": "application/activity+json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "Mastodon", + "X-Frame-Options": "DENY", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Vary": "Accept, Accept-Encoding, Origin", + "Cache-Control": "max-age=180, public", + "ETag": "W/\"773e09a2a60446fe74d997858877f7e0\"", + "Set-Cookie": "_mastodon_session=XoZzbDOQtPbBNtbz7WoWZ4ZHTySKGQUeb1Hl3%2BxqZV%2FvxVEOMGMjKArqNF%2F78EJaF5TS%2FKcHPhwonEfsI5cz--jZG8CkvbBBmaJMDx--WRqeW2u0rVAHoNKcMNfLYA%3D%3D; path=/; secure; HttpOnly", + "X-Request-Id": "2a29bc1c-9dc8-4e36-b6d5-106c2f649959", + "X-Runtime": "0.012324", + "X-Cached": "MISS", + "Strict-Transport-Security": "max-age=31536000" + }, + "status_code": 200, + "type": "ok" + } + }, + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json", + "Date": "Sun, 15 Dec 2019 20:24:11 GMT", + "Signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) date host\",signature=\"C6AGJG5xDdDBYfoAgBaL0iG0/Ru9qpvUij5vvONxJJpcjwc4LIePLaCid4LWK4aiY/z67M+LgopmD7xRn2Qht+Cyu3Kh38t5dS8EI0RWR4JesRyBauCZpzbRJG2w5SES4BPt53+5AuvPZZ81BBgPYF4A7ITBn550NLocesuFFsJJZHwfNqRCUm4cmx57/tnLBr0S4w/VDn6iQ3TBSlXdUJ7N9Za9y7p+vfkFT2PqSXu55HdWLX5NvaiVl2m7JKBCxCrB3i4Lr/Og5bsKhi6LiUoc7Lp0LX1tNftp6NOGgBIo0982NQ0v2jGsMj+eGU2stDl6bz3Z9SRnyyxirz+fag==\"" + }, + "method": "get", + "options": { + "follow_redirect": "true", + "recv_timeout": 20000, + "connect_timeout": 10000 + }, + "request_body": "", + "url": "https://info.pleroma.site/activity.json" + }, + "response": { + "binary": false, + "body": "{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"actor\": \"https://queer.hacktivis.me/users/lanodan\",\n \"announcement_count\": 3,\n \"announcements\": [\n \"https://io.markegli.com/users/mark\",\n \"https://voluntaryism.club/users/sevvie\",\n \"https://pleroma.pla1.net/users/pla\"\n ],\n \"attachment\": [],\n \"attributedTo\": \"https://queer.hacktivis.me/users/lanodan\",\n \"content\": \"

this post was not actually written by Haelwenn

\",\n \"id\": \"https://info.pleroma.site/activity.json\",\n \"published\": \"2018-09-01T22:15:00Z\",\n \"tag\": [],\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"type\": \"Note\"\n}\n", + "headers": { + "Server": "nginx", + "Date": "Sun, 15 Dec 2019 20:24:11 GMT", + "Content-Type": "application/json", + "Content-Length": "750", + "Last-Modified": "Sat, 01 Sep 2018 22:56:24 GMT", + "Connection": "keep-alive", + "ETag": "\"5b8b1918-2ee\"", + "Accept-Ranges": "bytes" + }, + "status_code": 200, + "type": "ok" + } + }, + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "https://queer.hacktivis.me/users/lanodan" + }, + "response": { + "binary": false, + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://queer.hacktivis.me/schemas/litepub-0.1.jsonld\",{\"@language\":\"und\"}],\"attachment\":[],\"discoverable\":false,\"endpoints\":{\"oauthAuthorizationEndpoint\":\"https://queer.hacktivis.me/oauth/authorize\",\"oauthRegistrationEndpoint\":\"https://queer.hacktivis.me/api/v1/apps\",\"oauthTokenEndpoint\":\"https://queer.hacktivis.me/oauth/token\",\"sharedInbox\":\"https://queer.hacktivis.me/inbox\",\"uploadMedia\":\"https://queer.hacktivis.me/api/ap/upload_media\"},\"followers\":\"https://queer.hacktivis.me/users/lanodan/followers\",\"following\":\"https://queer.hacktivis.me/users/lanodan/following\",\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/media/c8e81887-7d81-4cdc-91b3-c624ea79e6c9/425f089961270eff91b66d45f8faeeb12a725a5f87a6a52bfc54c43bd89f5fe9.png\"},\"id\":\"https://queer.hacktivis.me/users/lanodan\",\"image\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/media/37b6ce56-8c24-4e64-bd70-a76e84ab0c69/53a48a3a49ed5e5637a84e4f3663df17f8d764244bbc1027ba03cfc446e8b7bd.jpg\"},\"inbox\":\"https://queer.hacktivis.me/users/lanodan/inbox\",\"manuallyApprovesFollowers\":true,\"name\":\"Haelwenn /ɛlwən/ 🐺\",\"outbox\":\"https://queer.hacktivis.me/users/lanodan/outbox\",\"preferredUsername\":\"lanodan\",\"publicKey\":{\"id\":\"https://queer.hacktivis.me/users/lanodan#main-key\",\"owner\":\"https://queer.hacktivis.me/users/lanodan\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsWOgdjSMc010qvxC3njI\\nXJlFWMJ5gJ8QXCW/PajYdsHPM6d+jxBNJ6zp9/tIRa2m7bWHTSkuHQ7QthOpt6vu\\n+dAWpKRLS607SPLItn/qUcyXvgN+H8shfyhMxvkVs9jXdtlBsLUVE7UNpN0dxzqe\\nI79QWbf7o4amgaIWGRYB+OYMnIxKt+GzIkivZdSVSYjfxNnBYkMCeUxm5EpPIxKS\\nP5bBHAVRRambD5NUmyKILuC60/rYuc/C+vmgpY2HCWFS2q6o34dPr9enwL6t4b3m\\nS1t/EJHk9rGaaDqSGkDEfyQI83/7SDebWKuETMKKFLZi1vMgQIFuOYCIhN6bIiZm\\npQIDAQAB\\n-----END PUBLIC KEY-----\\n\\n\"},\"summary\":\"---
Website: https://hacktivis.me/
Pronouns: she/fae, elle/iel
Lang: en, fr, (LSF), ...
```
🦊🦄⚧🂡ⓥ :anarchy: 👿🐧 :gentoo:
Pleroma dev (backend, mastofe)

banner from: https://soc.flyingcube.tech/objects/56f79be2-9013-4559-9826-f7dc392417db
Federation-bots: \",\"tag\":[{\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/emoji/custom/symbols/anarchy.png\"},\"name\":\":anarchy:\",\"type\":\"Emoji\"},{\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/emoji/custom/gentoo.png\"},\"name\":\":gentoo:\",\"type\":\"Emoji\"}],\"type\":\"Person\",\"url\":\"https://queer.hacktivis.me/users/lanodan\"}", + "headers": { + "Server": "nginx/1.16.1", + "Date": "Sun, 15 Dec 2019 20:24:12 GMT", + "Content-Type": "application/activity+json; charset=utf-8", + "Content-Length": "2693", + "Connection": "keep-alive", + "Keep-Alive": "timeout=20", + "access-control-allow-credentials": "true", + "access-control-allow-origin": "*", + "access-control-expose-headers": "Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id,Idempotency-Key", + "cache-control": "max-age=0, private, must-revalidate", + "content-security-policy": "default-src 'none'; base-uri 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; manifest-src 'self'; connect-src 'self' https://queer.hacktivis.me wss://queer.hacktivis.me; script-src 'self'; upgrade-insecure-requests;", + "expect-ct": "enforce, max-age=2592000", + "referrer-policy": "no-referrer", + "strict-transport-security": "max-age=31536000; includeSubDomains", + "x-content-type-options": "nosniff", + "x-download-options": "noopen", + "x-frame-options": "DENY", + "x-permitted-cross-domain-policies": "none", + "x-request-id": "FeClJfdJwAn7WY0ABtmh", + "x-xss-protection": "1; mode=block" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/fixtures/vcr_cassettes/activity_pub/event_update_activities.json b/test/fixtures/vcr_cassettes/activity_pub/event_update_activities.json index 20fbdb57..700e12e8 100644 --- a/test/fixtures/vcr_cassettes/activity_pub/event_update_activities.json +++ b/test/fixtures/vcr_cassettes/activity_pub/event_update_activities.json @@ -17,7 +17,7 @@ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.github.io/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"endpoints\":{\"sharedInbox\":\"https://test.mobilizon.org/inbox\"},\"followers\":\"https://test.mobilizon.org/@Alicia/followers\",\"following\":\"https://test.mobilizon.org/@Alicia/following\",\"id\":\"https://test.mobilizon.org/@Alicia\",\"inbox\":\"https://test.mobilizon.org/@Alicia/inbox\",\"manuallyApprovesFollowers\":false,\"name\":\"Alicia\",\"outbox\":\"https://test.mobilizon.org/@Alicia/outbox\",\"preferredUsername\":\"Alicia\",\"publicKey\":{\"id\":\"https://test.mobilizon.org/@Alicia#main-key\",\"owner\":\"https://test.mobilizon.org/@Alicia\",\"publicKeyPem\":\"-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAvb+emDoC6FCVpfo9Bh608sVsOK+8fun3UIqaR+jr+DZCAjp8ihwa\\nFkXaeOQ744MVS2YdzBEyIlk3sSYD9GezF+zoMbbA8FcnJ5jZhnneRR7ZrEg/cpNx\\nKFVA2ZoQrAABwpnA1iv7ciLoYZKPTDpIZ7Ue5l/k1bYcfTy0d4F3c8YAayWftSWj\\nHy3FK2kZDLdKfpRyfn5a4UI6sao4uD/rHno47g8tPPVA74BBpaTntJfbTWqiR8Vn\\nmNGAzy3+47pVeeg6Rd+AALohzBpHPW3TlJ75mqxPDXk7aDRYXihHrswf4MmKuaXc\\nXdoCu6uxQp41Xf3jVYD+AWw60tv2Oj/d4wIDAQAB\\n-----END RSA PUBLIC KEY-----\\n\\n\"},\"summary\":\"J'aime le karaté, les mangas, coder en python.\",\"type\":\"Person\",\"url\":\"https://test.mobilizon.org/@Alicia\"}", "headers": { "Server": "nginx/1.14.2", - "Date": "Sun, 17 Nov 2019 18:12:35 GMT", + "Date": "Mon, 09 Dec 2019 17:24:25 GMT", "Content-Type": "application/activity+json; charset=utf-8", "Content-Length": "1293", "Connection": "keep-alive", @@ -25,12 +25,50 @@ "access-control-allow-origin": "*", "access-control-expose-headers": "", "cache-control": "max-age=0, private, must-revalidate", - "x-request-id": "FdgFt2eS9ln4-7YACVtC", + "x-request-id": "Fd7D2yizsbKxZroABLnC", "Strict-Transport-Security": "max-age=63072000; includeSubDomains", "X-Content-Type-Options": "nosniff" }, "status_code": 200, "type": "ok" } + }, + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "https://framapiaf.org/users/tcit" + }, + "response": { + "binary": false, + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"Hashtag\":\"as:Hashtag\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/tcit\",\"type\":\"Person\",\"following\":\"https://framapiaf.org/users/tcit/following\",\"followers\":\"https://framapiaf.org/users/tcit/followers\",\"inbox\":\"https://framapiaf.org/users/tcit/inbox\",\"outbox\":\"https://framapiaf.org/users/tcit/outbox\",\"featured\":\"https://framapiaf.org/users/tcit/collections/featured\",\"preferredUsername\":\"tcit\",\"name\":\"💼 Thomas Citharel (Work)\",\"summary\":\"\\u003cp\\u003e\\u003ca href=\\\"https://framapiaf.org/tags/Framasoft\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/FreeSoftware\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFreeSoftware\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Activism\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eActivism\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/wallabag\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003ewallabag\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Federation\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFederation\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Nextcloud\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eNextcloud\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Mobilizon\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eMobilizon\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Libre\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eLibre\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@tcit\",\"manuallyApprovesFollowers\":false,\"discoverable\":true,\"publicKey\":{\"id\":\"https://framapiaf.org/users/tcit#main-key\",\"owner\":\"https://framapiaf.org/users/tcit\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApscVCt06lrIiB5jT6Kqk\\nZZwPVoPkhR7HzoTGb8rnklZuOyP4goHIuBDnurklztkmDCaM7DbsUWAPgRVtwWFE\\nWuQrOenb7BPRe/m99pJfUTkBQU3IeuRMD/5Fc3OTIhHQOltTSiB900srCUxjysfw\\nnV5JFciCz8YAXTNJZD34qyv8DbtC/pCJM7wMd9Hl3ohxSPETa6CJUaTdlNwlYJa2\\nMOMCj6/7Iv5oAg14FT9lwqS5lF7jPHk9Z7PNc2wPmNVgIYA2n9d5k7JY8TdM8iu4\\nHLnIbJuqDd1uitlYgy1qsdsxjv4U2Y7Nytc+3ZKHtGsCzUltYL5kC7uWrFpGoWo1\\n0QIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/activism\",\"name\":\"#activism\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/federation\",\"name\":\"#federation\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/framasoft\",\"name\":\"#framasoft\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/freesoftware\",\"name\":\"#freesoftware\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/libre\",\"name\":\"#libre\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/mobilizon\",\"name\":\"#mobilizon\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/nextcloud\",\"name\":\"#nextcloud\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/wallabag\",\"name\":\"#wallabag\"}],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"Personal account\",\"value\":\"\\u003ca href=\\\"https://social.tcit.fr/@tcit\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esocial.tcit.fr/@tcit\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Location\",\"value\":\"Nantes, France\"},{\"type\":\"PropertyValue\",\"name\":\"Works at\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Website\",\"value\":\"\\u003ca href=\\\"https://tcit.fr\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003etcit.fr\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"IdentityProof\",\"name\":\"tcit\",\"signatureAlgorithm\":\"keybase\",\"signatureValue\":\"f66b45be42803010fe2f4d80e729b41bbe5ed056e2ff1286b7b5a5ea9c724cc70f\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/001/original/da0cad7ffd20eb61.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/001/original/198d058b3086d82d.jpg\"}}", + "headers": { + "Date": "Mon, 09 Dec 2019 17:24:25 GMT", + "Content-Type": "application/activity+json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "Mastodon", + "X-Frame-Options": "DENY", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Vary": "Accept, Accept-Encoding, Origin", + "Cache-Control": "max-age=180, public", + "ETag": "W/\"11665b12333a8c7708de7b17f58147b2\"", + "Set-Cookie": "_mastodon_session=l2BJyxnUWpNcZQ0u%2FaLJvf95IOy5b4PC1p1MVB7IGImBVhYbt6c0v6dcZcbtJzV%2FhPHF649GTTHeMixcyk1w--6lGoR%2F%2FAOMyZRQJi--fGHpQLKpoTLyRrgQQ7WW8g%3D%3D; path=/; secure; HttpOnly", + "X-Request-Id": "7d05233c-4a2d-4cf9-bcb6-6f405f6a370a", + "X-Runtime": "0.011783", + "X-Cached": "MISS", + "Strict-Transport-Security": "max-age=31536000" + }, + "status_code": 200, + "type": "ok" + } } ] \ No newline at end of file diff --git a/test/fixtures/vcr_cassettes/activity_pub/fetch_mobilizon_post_activity.json b/test/fixtures/vcr_cassettes/activity_pub/fetch_mobilizon_post_activity.json index 051ac17b..c099ca51 100644 --- a/test/fixtures/vcr_cassettes/activity_pub/fetch_mobilizon_post_activity.json +++ b/test/fixtures/vcr_cassettes/activity_pub/fetch_mobilizon_post_activity.json @@ -17,7 +17,7 @@ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.github.io/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"endpoints\":{\"sharedInbox\":\"https://test.mobilizon.org/inbox\"},\"followers\":\"https://test.mobilizon.org/@Alicia/followers\",\"following\":\"https://test.mobilizon.org/@Alicia/following\",\"id\":\"https://test.mobilizon.org/@Alicia\",\"inbox\":\"https://test.mobilizon.org/@Alicia/inbox\",\"manuallyApprovesFollowers\":false,\"name\":\"Alicia\",\"outbox\":\"https://test.mobilizon.org/@Alicia/outbox\",\"preferredUsername\":\"Alicia\",\"publicKey\":{\"id\":\"https://test.mobilizon.org/@Alicia#main-key\",\"owner\":\"https://test.mobilizon.org/@Alicia\",\"publicKeyPem\":\"-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAvb+emDoC6FCVpfo9Bh608sVsOK+8fun3UIqaR+jr+DZCAjp8ihwa\\nFkXaeOQ744MVS2YdzBEyIlk3sSYD9GezF+zoMbbA8FcnJ5jZhnneRR7ZrEg/cpNx\\nKFVA2ZoQrAABwpnA1iv7ciLoYZKPTDpIZ7Ue5l/k1bYcfTy0d4F3c8YAayWftSWj\\nHy3FK2kZDLdKfpRyfn5a4UI6sao4uD/rHno47g8tPPVA74BBpaTntJfbTWqiR8Vn\\nmNGAzy3+47pVeeg6Rd+AALohzBpHPW3TlJ75mqxPDXk7aDRYXihHrswf4MmKuaXc\\nXdoCu6uxQp41Xf3jVYD+AWw60tv2Oj/d4wIDAQAB\\n-----END RSA PUBLIC KEY-----\\n\\n\"},\"summary\":\"J'aime le karaté, les mangas, coder en python.\",\"type\":\"Person\",\"url\":\"https://test.mobilizon.org/@Alicia\"}", "headers": { "Server": "nginx/1.14.2", - "Date": "Sun, 17 Nov 2019 18:00:51 GMT", + "Date": "Mon, 09 Dec 2019 17:24:24 GMT", "Content-Type": "application/activity+json; charset=utf-8", "Content-Length": "1293", "Connection": "keep-alive", @@ -25,12 +25,50 @@ "access-control-allow-origin": "*", "access-control-expose-headers": "", "cache-control": "max-age=0, private, must-revalidate", - "x-request-id": "FdgFE5DoOmZXNz8ACVni", + "x-request-id": "Fd7D2v45MCtfaxgAB7bh", "Strict-Transport-Security": "max-age=63072000; includeSubDomains", "X-Content-Type-Options": "nosniff" }, "status_code": 200, "type": "ok" } + }, + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "https://framapiaf.org/users/tcit" + }, + "response": { + "binary": false, + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"Hashtag\":\"as:Hashtag\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/tcit\",\"type\":\"Person\",\"following\":\"https://framapiaf.org/users/tcit/following\",\"followers\":\"https://framapiaf.org/users/tcit/followers\",\"inbox\":\"https://framapiaf.org/users/tcit/inbox\",\"outbox\":\"https://framapiaf.org/users/tcit/outbox\",\"featured\":\"https://framapiaf.org/users/tcit/collections/featured\",\"preferredUsername\":\"tcit\",\"name\":\"💼 Thomas Citharel (Work)\",\"summary\":\"\\u003cp\\u003e\\u003ca href=\\\"https://framapiaf.org/tags/Framasoft\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/FreeSoftware\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFreeSoftware\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Activism\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eActivism\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/wallabag\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003ewallabag\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Federation\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFederation\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Nextcloud\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eNextcloud\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Mobilizon\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eMobilizon\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Libre\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eLibre\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@tcit\",\"manuallyApprovesFollowers\":false,\"discoverable\":true,\"publicKey\":{\"id\":\"https://framapiaf.org/users/tcit#main-key\",\"owner\":\"https://framapiaf.org/users/tcit\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApscVCt06lrIiB5jT6Kqk\\nZZwPVoPkhR7HzoTGb8rnklZuOyP4goHIuBDnurklztkmDCaM7DbsUWAPgRVtwWFE\\nWuQrOenb7BPRe/m99pJfUTkBQU3IeuRMD/5Fc3OTIhHQOltTSiB900srCUxjysfw\\nnV5JFciCz8YAXTNJZD34qyv8DbtC/pCJM7wMd9Hl3ohxSPETa6CJUaTdlNwlYJa2\\nMOMCj6/7Iv5oAg14FT9lwqS5lF7jPHk9Z7PNc2wPmNVgIYA2n9d5k7JY8TdM8iu4\\nHLnIbJuqDd1uitlYgy1qsdsxjv4U2Y7Nytc+3ZKHtGsCzUltYL5kC7uWrFpGoWo1\\n0QIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/activism\",\"name\":\"#activism\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/federation\",\"name\":\"#federation\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/framasoft\",\"name\":\"#framasoft\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/freesoftware\",\"name\":\"#freesoftware\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/libre\",\"name\":\"#libre\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/mobilizon\",\"name\":\"#mobilizon\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/nextcloud\",\"name\":\"#nextcloud\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/wallabag\",\"name\":\"#wallabag\"}],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"Personal account\",\"value\":\"\\u003ca href=\\\"https://social.tcit.fr/@tcit\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esocial.tcit.fr/@tcit\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Location\",\"value\":\"Nantes, France\"},{\"type\":\"PropertyValue\",\"name\":\"Works at\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Website\",\"value\":\"\\u003ca href=\\\"https://tcit.fr\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003etcit.fr\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"IdentityProof\",\"name\":\"tcit\",\"signatureAlgorithm\":\"keybase\",\"signatureValue\":\"f66b45be42803010fe2f4d80e729b41bbe5ed056e2ff1286b7b5a5ea9c724cc70f\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/001/original/da0cad7ffd20eb61.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/001/original/198d058b3086d82d.jpg\"}}", + "headers": { + "Date": "Mon, 09 Dec 2019 17:24:25 GMT", + "Content-Type": "application/activity+json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "Mastodon", + "X-Frame-Options": "DENY", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Vary": "Accept, Accept-Encoding, Origin", + "Cache-Control": "max-age=180, public", + "ETag": "W/\"11665b12333a8c7708de7b17f58147b2\"", + "Set-Cookie": "_mastodon_session=hkR%2BepdH7Hnl0wgxjtkiOa6%2FY8%2FIs4lElyGl%2FRMoRqdztBigOMH19196k1gDXNqqotlhjZMGBcDPv5tSOTdN--UzxEtxF4SS5Vwfhn--S3FqvLDMaBYDpE2P4o64Nw%3D%3D; path=/; secure; HttpOnly", + "X-Request-Id": "88981ba1-9aa8-428c-a868-78a918cf1317", + "X-Runtime": "0.013394", + "X-Cached": "MISS", + "Strict-Transport-Security": "max-age=31536000" + }, + "status_code": 200, + "type": "ok" + } } ] \ No newline at end of file diff --git a/test/fixtures/vcr_cassettes/activity_pub/object_bogus_origin.json b/test/fixtures/vcr_cassettes/activity_pub/object_bogus_origin.json new file mode 100644 index 00000000..88f08bbb --- /dev/null +++ b/test/fixtures/vcr_cassettes/activity_pub/object_bogus_origin.json @@ -0,0 +1,78 @@ +[ + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json", + "Date": "Sun, 15 Dec 2019 20:24:06 GMT", + "Signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) date host\",signature=\"kUYhmOSdg7kxApgTCEj1CZLwOGn31sLbPdU35xu6Y5f/I3oT65g6KYlzuvchfQTrJ0XTzdzdSpQ5ZC/Y5dQEZrc3yzGw0KOmpSFfDcbtLw0I4Ya3vRjvuw0cDsPUwxoKpW1FqkMIEXP2lTXV/Bywc2rWCytnttiJwoQdeTvPDigmCCLxo2+wNMshl169HjAjYT9T8O0ptlgZXZ+JPuuaMj6EcSnXJDAkDxdBo54D61ED+dIIDxRKJsCTDrnjvZ86E9Z/P8SIbQxPZNw+TpLeofFTi/xt0E42M76iOEk41+kKlWQBd4imwoJesYEU+7CsMRtttt7wK9hMqcQC4UCa8g==\"" + }, + "method": "get", + "options": { + "follow_redirect": "true", + "recv_timeout": 20000, + "connect_timeout": 10000 + }, + "request_body": "", + "url": "https://info.pleroma.site/activity.json" + }, + "response": { + "binary": false, + "body": "{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"actor\": \"https://queer.hacktivis.me/users/lanodan\",\n \"announcement_count\": 3,\n \"announcements\": [\n \"https://io.markegli.com/users/mark\",\n \"https://voluntaryism.club/users/sevvie\",\n \"https://pleroma.pla1.net/users/pla\"\n ],\n \"attachment\": [],\n \"attributedTo\": \"https://queer.hacktivis.me/users/lanodan\",\n \"content\": \"

this post was not actually written by Haelwenn

\",\n \"id\": \"https://info.pleroma.site/activity.json\",\n \"published\": \"2018-09-01T22:15:00Z\",\n \"tag\": [],\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"type\": \"Note\"\n}\n", + "headers": { + "Server": "nginx", + "Date": "Sun, 15 Dec 2019 20:24:07 GMT", + "Content-Type": "application/json", + "Content-Length": "750", + "Last-Modified": "Sat, 01 Sep 2018 22:56:24 GMT", + "Connection": "keep-alive", + "ETag": "\"5b8b1918-2ee\"", + "Accept-Ranges": "bytes" + }, + "status_code": 200, + "type": "ok" + } + }, + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "https://queer.hacktivis.me/users/lanodan" + }, + "response": { + "binary": false, + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://queer.hacktivis.me/schemas/litepub-0.1.jsonld\",{\"@language\":\"und\"}],\"attachment\":[],\"discoverable\":false,\"endpoints\":{\"oauthAuthorizationEndpoint\":\"https://queer.hacktivis.me/oauth/authorize\",\"oauthRegistrationEndpoint\":\"https://queer.hacktivis.me/api/v1/apps\",\"oauthTokenEndpoint\":\"https://queer.hacktivis.me/oauth/token\",\"sharedInbox\":\"https://queer.hacktivis.me/inbox\",\"uploadMedia\":\"https://queer.hacktivis.me/api/ap/upload_media\"},\"followers\":\"https://queer.hacktivis.me/users/lanodan/followers\",\"following\":\"https://queer.hacktivis.me/users/lanodan/following\",\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/media/c8e81887-7d81-4cdc-91b3-c624ea79e6c9/425f089961270eff91b66d45f8faeeb12a725a5f87a6a52bfc54c43bd89f5fe9.png\"},\"id\":\"https://queer.hacktivis.me/users/lanodan\",\"image\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/media/37b6ce56-8c24-4e64-bd70-a76e84ab0c69/53a48a3a49ed5e5637a84e4f3663df17f8d764244bbc1027ba03cfc446e8b7bd.jpg\"},\"inbox\":\"https://queer.hacktivis.me/users/lanodan/inbox\",\"manuallyApprovesFollowers\":true,\"name\":\"Haelwenn /ɛlwən/ 🐺\",\"outbox\":\"https://queer.hacktivis.me/users/lanodan/outbox\",\"preferredUsername\":\"lanodan\",\"publicKey\":{\"id\":\"https://queer.hacktivis.me/users/lanodan#main-key\",\"owner\":\"https://queer.hacktivis.me/users/lanodan\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsWOgdjSMc010qvxC3njI\\nXJlFWMJ5gJ8QXCW/PajYdsHPM6d+jxBNJ6zp9/tIRa2m7bWHTSkuHQ7QthOpt6vu\\n+dAWpKRLS607SPLItn/qUcyXvgN+H8shfyhMxvkVs9jXdtlBsLUVE7UNpN0dxzqe\\nI79QWbf7o4amgaIWGRYB+OYMnIxKt+GzIkivZdSVSYjfxNnBYkMCeUxm5EpPIxKS\\nP5bBHAVRRambD5NUmyKILuC60/rYuc/C+vmgpY2HCWFS2q6o34dPr9enwL6t4b3m\\nS1t/EJHk9rGaaDqSGkDEfyQI83/7SDebWKuETMKKFLZi1vMgQIFuOYCIhN6bIiZm\\npQIDAQAB\\n-----END PUBLIC KEY-----\\n\\n\"},\"summary\":\"---
Website: https://hacktivis.me/
Pronouns: she/fae, elle/iel
Lang: en, fr, (LSF), ...
```
🦊🦄⚧🂡ⓥ :anarchy: 👿🐧 :gentoo:
Pleroma dev (backend, mastofe)

banner from: https://soc.flyingcube.tech/objects/56f79be2-9013-4559-9826-f7dc392417db
Federation-bots: \",\"tag\":[{\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/emoji/custom/symbols/anarchy.png\"},\"name\":\":anarchy:\",\"type\":\"Emoji\"},{\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/emoji/custom/gentoo.png\"},\"name\":\":gentoo:\",\"type\":\"Emoji\"}],\"type\":\"Person\",\"url\":\"https://queer.hacktivis.me/users/lanodan\"}", + "headers": { + "Server": "nginx/1.16.1", + "Date": "Sun, 15 Dec 2019 20:24:07 GMT", + "Content-Type": "application/activity+json; charset=utf-8", + "Content-Length": "2693", + "Connection": "keep-alive", + "Keep-Alive": "timeout=20", + "access-control-allow-credentials": "true", + "access-control-allow-origin": "*", + "access-control-expose-headers": "Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id,Idempotency-Key", + "cache-control": "max-age=0, private, must-revalidate", + "content-security-policy": "default-src 'none'; base-uri 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; manifest-src 'self'; connect-src 'self' https://queer.hacktivis.me wss://queer.hacktivis.me; script-src 'self'; upgrade-insecure-requests;", + "expect-ct": "enforce, max-age=2592000", + "referrer-policy": "no-referrer", + "strict-transport-security": "max-age=31536000; includeSubDomains", + "x-content-type-options": "nosniff", + "x-download-options": "noopen", + "x-frame-options": "DENY", + "x-permitted-cross-domain-policies": "none", + "x-request-id": "FeClJPRE1U5AcP8ABtkB", + "x-xss-protection": "1; mode=block" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_not_found.json b/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_not_found.json new file mode 100644 index 00000000..194a42c6 --- /dev/null +++ b/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_not_found.json @@ -0,0 +1,39 @@ +[ + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "http://niu.moe/users/rye" + }, + "response": { + "binary": false, + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://niu.moe/users/rye\",\"type\":\"Person\",\"following\":\"https://niu.moe/users/rye/following\",\"followers\":\"https://niu.moe/users/rye/followers\",\"inbox\":\"https://niu.moe/users/rye/inbox\",\"outbox\":\"https://niu.moe/users/rye/outbox\",\"featured\":\"https://niu.moe/users/rye/collections/featured\",\"preferredUsername\":\"rye\",\"name\":\"♡ rye ♡\",\"summary\":\"\\u003cp\\u003eicon from \\u003ca href=\\\"https://twitter.com/_nitronic/status/1137776178687725568\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"ellipsis\\\"\\u003etwitter.com/_nitronic/status/1\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e137776178687725568\\u003c/span\\u003e\\u003c/a\\u003e くコ:彡\\u003c/p\\u003e\\u003cp\\u003eCome back with a warrant\\u003c/p\\u003e\",\"url\":\"https://niu.moe/@rye\",\"manuallyApprovesFollowers\":false,\"discoverable\":false,\"publicKey\":{\"id\":\"https://niu.moe/users/rye#main-key\",\"owner\":\"https://niu.moe/users/rye\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA83uRWjCFO35FwfA38mzv\\nEL0TUaXB7+2hYvPwNrn1WY6me5DRbqB5zzMrzWMGr0HSooqNqEYBafGsmVTWUqIk\\nKM9ehtIBraJI+mT5X7DPR3LrXOJF4a9EEslg8XvAk8MN9IrAhm6UljnvB67RtDcA\\nTNB01VWy9yWnxFRtz9o/EMoBPyw5giOaXE2ibVNP8lQIqGKuuBKPzPjSJygdvQ5q\\nxfow2z1TpKRqdsNDqn4n6U6zCXYTzkr0J71/tGw7fsgfv78l0Wjrc7EcuBk74OaG\\nC65UDiu3X4Q6kxCfCEhPSfuwLN+UZkzxcn6goWR0iYpWs57+4tFKu9nJYP4QJ0K9\\nTwIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[],\"endpoints\":{\"sharedInbox\":\"https://niu.moe/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/png\",\"url\":\"https://cdn.niu.moe/accounts/avatars/000/033/323/original/e4d637b2c8755a7e.png\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://cdn.niu.moe/accounts/headers/000/033/323/original/cc89e1bc66b99a65.jpeg\"}}", + "headers": { + "Cache-Control": "max-age=180, public", + "Content-Type": "application/activity+json; charset=utf-8", + "Date": "Fri, 06 Dec 2019 10:26:01 GMT", + "Etag": "W/\"b3c2a8220a20671e8ca5c7e3371e7f5a\"", + "Server": "Caddy", + "Set-Cookie": "_mastodon_session=lSwFzD6GF6%2FjEL7hWLHU61n7%2B8kC60xvZYlPZG8EBtcndPzfd2%2B976zDOf1ALGZkvqj3CdpYHbZyq%2B7cwfkX--Ut%2BKGA8YibOTCEhb--a0sE5cHGI5PicAmO2yDlZw%3D%3D; path=/; secure; HttpOnly", + "Strict-Transport-Security": "max-age=31536000", + "Vary": "Accept, Accept-Encoding, Origin", + "X-Cached": "MISS", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", + "X-Request-Id": "0cc18bab-2d72-47b2-8778-8646402e1148", + "X-Runtime": "0.009213", + "X-Xss-Protection": "1; mode=block", + "Transfer-Encoding": "chunked" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_payload.json b/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_payload.json new file mode 100644 index 00000000..f150c236 --- /dev/null +++ b/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_payload.json @@ -0,0 +1,39 @@ +[ + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "https://niu.moe/users/rye" + }, + "response": { + "binary": false, + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://niu.moe/users/rye\",\"type\":\"Person\",\"following\":\"https://niu.moe/users/rye/following\",\"followers\":\"https://niu.moe/users/rye/followers\",\"inbox\":\"https://niu.moe/users/rye/inbox\",\"outbox\":\"https://niu.moe/users/rye/outbox\",\"featured\":\"https://niu.moe/users/rye/collections/featured\",\"preferredUsername\":\"rye\",\"name\":\"♡ rye ♡\",\"summary\":\"\\u003cp\\u003eicon from \\u003ca href=\\\"https://twitter.com/_nitronic/status/1137776178687725568\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"ellipsis\\\"\\u003etwitter.com/_nitronic/status/1\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e137776178687725568\\u003c/span\\u003e\\u003c/a\\u003e くコ:彡\\u003c/p\\u003e\\u003cp\\u003eCome back with a warrant\\u003c/p\\u003e\",\"url\":\"https://niu.moe/@rye\",\"manuallyApprovesFollowers\":false,\"discoverable\":false,\"publicKey\":{\"id\":\"https://niu.moe/users/rye#main-key\",\"owner\":\"https://niu.moe/users/rye\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA83uRWjCFO35FwfA38mzv\\nEL0TUaXB7+2hYvPwNrn1WY6me5DRbqB5zzMrzWMGr0HSooqNqEYBafGsmVTWUqIk\\nKM9ehtIBraJI+mT5X7DPR3LrXOJF4a9EEslg8XvAk8MN9IrAhm6UljnvB67RtDcA\\nTNB01VWy9yWnxFRtz9o/EMoBPyw5giOaXE2ibVNP8lQIqGKuuBKPzPjSJygdvQ5q\\nxfow2z1TpKRqdsNDqn4n6U6zCXYTzkr0J71/tGw7fsgfv78l0Wjrc7EcuBk74OaG\\nC65UDiu3X4Q6kxCfCEhPSfuwLN+UZkzxcn6goWR0iYpWs57+4tFKu9nJYP4QJ0K9\\nTwIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[],\"endpoints\":{\"sharedInbox\":\"https://niu.moe/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/png\",\"url\":\"https://cdn.niu.moe/accounts/avatars/000/033/323/original/e4d637b2c8755a7e.png\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://cdn.niu.moe/accounts/headers/000/033/323/original/cc89e1bc66b99a65.jpeg\"}}", + "headers": { + "Cache-Control": "max-age=180, public", + "Content-Type": "application/activity+json; charset=utf-8", + "Date": "Fri, 06 Dec 2019 10:26:00 GMT", + "Etag": "W/\"06011deced02514fa9adc61bf61ed2fd\"", + "Server": "Caddy", + "Set-Cookie": "_mastodon_session=n67ChnKe59aqgHL9yq2ReOf2DXDK7c54n49moftpN5s3c6AJpyJ9QZUH31wz0eyDiSiHHw4A6IgzpkrhvSF0--Gm%2FvZr27eWvDBh23--BROo3uKkLhfRDQgszEDO3w%3D%3D; path=/; secure; HttpOnly", + "Strict-Transport-Security": "max-age=31536000", + "Vary": "Accept, Accept-Encoding, Origin", + "X-Cached": "MISS", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", + "X-Request-Id": "acee93a9-4da7-4495-bf03-ae7113c50b43", + "X-Runtime": "0.016198", + "X-Xss-Protection": "1; mode=block", + "Transfer-Encoding": "chunked" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/fixtures/vcr_cassettes/activity_pub/signature/valid.json b/test/fixtures/vcr_cassettes/activity_pub/signature/valid.json new file mode 100644 index 00000000..702cccdd --- /dev/null +++ b/test/fixtures/vcr_cassettes/activity_pub/signature/valid.json @@ -0,0 +1,40 @@ +[ + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "https://framapiaf.org/users/admin" + }, + "response": { + "binary": false, + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/admin\",\"type\":\"Service\",\"following\":\"https://framapiaf.org/users/admin/following\",\"followers\":\"https://framapiaf.org/users/admin/followers\",\"inbox\":\"https://framapiaf.org/users/admin/inbox\",\"outbox\":\"https://framapiaf.org/users/admin/outbox\",\"featured\":\"https://framapiaf.org/users/admin/collections/featured\",\"preferredUsername\":\"admin\",\"name\":\"Administrateur\",\"summary\":\"\\u003cp\\u003eJe ne suis qu\\u0026apos;un compte inutile. Merci nous de contacter via \\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@admin\",\"manuallyApprovesFollowers\":false,\"discoverable\":null,\"publicKey\":{\"id\":\"https://framapiaf.org/users/admin#main-key\",\"owner\":\"https://framapiaf.org/users/admin\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyHaU/AZ5dWtSxZXkPa89\\nDUQ4z+JQHGGUG/xkGuq0v8P6qJfQqtHPBO5vH0IQJqluXWQS96gqTwjZnYevcpNA\\nveYv0K25DWszx5Ehz6JX2/sSvu2rNUcQ3YZvSjdo/Yy1u5Fuc5lLmvw8uFzXYekD\\nWovTMOnp4mIKpVEm/G/v4w8jvFEKw88h743vwaEIim88GEQItMxzGAV6zSqV1DWO\\nLxtoRsinslJYfAG46ex4YUATFveWvOUeWk5W1sEa5f3c0moaTmBM/PAAo8vLxhlw\\nJhsHihsCH+BcXKVMjW8OCqYYqISMxEifUBX63HcJt78ELHpOuc1c2eG59PomtTjQ\\nywIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"News\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Support\",\"value\":\"\\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Soutenir\",\"value\":\"\\u003ca href=\\\"https://soutenir.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esoutenir.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Site\",\"value\":\"\\u003ca href=\\\"https://framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003eframasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/002/original/85fbb27ad5e3cf71.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/002/original/6aba75f1ab1ab6de.jpg\"}}", + "headers": { + "Date": "Fri, 06 Dec 2019 10:25:59 GMT", + "Content-Type": "application/activity+json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "Mastodon", + "X-Frame-Options": "DENY", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Vary": "Accept, Accept-Encoding, Origin", + "Cache-Control": "max-age=180, public", + "ETag": "W/\"773e09a2a60446fe74d997858877f7e0\"", + "Set-Cookie": "_mastodon_session=2qQRsid4lIe3JZJLG3ZxxfaDP4XDPsKEtqr9Bf3tljCQUEYrQZtQ44k74K1S1VeO3d2O2ztK7eafBlOx0KGQ--FK4A%2Bp1X4tvqnhba--KDpnaRQfBtiHOWhqW7ECEg%3D%3D; path=/; secure; HttpOnly", + "X-Request-Id": "c70d5224-5f9a-481a-a6d0-b817b0054be9", + "X-Runtime": "0.004654", + "X-Cached": "MISS", + "Strict-Transport-Security": "max-age=31536000" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/fixtures/vcr_cassettes/activity_pub/signature/valid_payload.json b/test/fixtures/vcr_cassettes/activity_pub/signature/valid_payload.json new file mode 100644 index 00000000..6bbc7ffa --- /dev/null +++ b/test/fixtures/vcr_cassettes/activity_pub/signature/valid_payload.json @@ -0,0 +1,40 @@ +[ + { + "request": { + "body": "", + "headers": { + "Accept": "application/activity+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "https://framapiaf.org/users/admin" + }, + "response": { + "binary": false, + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/admin\",\"type\":\"Service\",\"following\":\"https://framapiaf.org/users/admin/following\",\"followers\":\"https://framapiaf.org/users/admin/followers\",\"inbox\":\"https://framapiaf.org/users/admin/inbox\",\"outbox\":\"https://framapiaf.org/users/admin/outbox\",\"featured\":\"https://framapiaf.org/users/admin/collections/featured\",\"preferredUsername\":\"admin\",\"name\":\"Administrateur\",\"summary\":\"\\u003cp\\u003eJe ne suis qu\\u0026apos;un compte inutile. Merci nous de contacter via \\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@admin\",\"manuallyApprovesFollowers\":false,\"discoverable\":null,\"publicKey\":{\"id\":\"https://framapiaf.org/users/admin#main-key\",\"owner\":\"https://framapiaf.org/users/admin\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyHaU/AZ5dWtSxZXkPa89\\nDUQ4z+JQHGGUG/xkGuq0v8P6qJfQqtHPBO5vH0IQJqluXWQS96gqTwjZnYevcpNA\\nveYv0K25DWszx5Ehz6JX2/sSvu2rNUcQ3YZvSjdo/Yy1u5Fuc5lLmvw8uFzXYekD\\nWovTMOnp4mIKpVEm/G/v4w8jvFEKw88h743vwaEIim88GEQItMxzGAV6zSqV1DWO\\nLxtoRsinslJYfAG46ex4YUATFveWvOUeWk5W1sEa5f3c0moaTmBM/PAAo8vLxhlw\\nJhsHihsCH+BcXKVMjW8OCqYYqISMxEifUBX63HcJt78ELHpOuc1c2eG59PomtTjQ\\nywIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"News\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Support\",\"value\":\"\\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Soutenir\",\"value\":\"\\u003ca href=\\\"https://soutenir.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esoutenir.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Site\",\"value\":\"\\u003ca href=\\\"https://framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003eframasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/002/original/85fbb27ad5e3cf71.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/002/original/6aba75f1ab1ab6de.jpg\"}}", + "headers": { + "Date": "Fri, 06 Dec 2019 10:25:59 GMT", + "Content-Type": "application/activity+json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "Mastodon", + "X-Frame-Options": "DENY", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Vary": "Accept, Accept-Encoding, Origin", + "Cache-Control": "max-age=180, public", + "ETag": "W/\"773e09a2a60446fe74d997858877f7e0\"", + "Set-Cookie": "_mastodon_session=U%2BUwfKRPF9LVzxKAjxaywz3ySEufApGuEhddpwvpJm7%2B0cDzsW0%2Fn64%2FwOLOYuE9SLOTCjU4Ufc3yaoLvdKx--x1HVRQfU7bAeHgaF--IV9oyi7ODNo19cAi%2FULzew%3D%3D; path=/; secure; HttpOnly", + "X-Request-Id": "f36578b7-2f1a-49d1-b2c4-0cc0d652160b", + "X-Runtime": "0.010705", + "X-Cached": "MISS", + "Strict-Transport-Security": "max-age=31536000" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/fixtures/vcr_cassettes/relay/fetch_relay_follow.json b/test/fixtures/vcr_cassettes/relay/fetch_relay_follow.json index 4f911b25..b477ec24 100644 --- a/test/fixtures/vcr_cassettes/relay/fetch_relay_follow.json +++ b/test/fixtures/vcr_cassettes/relay/fetch_relay_follow.json @@ -1,4 +1,36 @@ [ + { + "request": { + "body": "", + "headers": { + "Accept": "application/json, application/activity+json, application/jrd+json" + }, + "method": "get", + "options": { + "follow_redirect": "true" + }, + "request_body": "", + "url": "http://mobilizon1.com/.well-known/webfinger?resource=acct:relay@mobilizon1.com" + }, + "response": { + "binary": false, + "body": "{\"aliases\":[\"http://mobilizon1.com/relay\"],\"links\":[{\"href\":\"http://mobilizon1.com/relay\",\"rel\":\"self\",\"type\":\"application/activity+json\"},{\"href\":\"http://mobilizon1.com/relay\",\"rel\":\"https://webfinger.net/rel/profile-page/\",\"type\":\"text/html\"}],\"subject\":\"acct:relay@mobilizon1.com\"}", + "headers": { + "Server": "nginx/1.16.1", + "Date": "Fri, 13 Dec 2019 09:41:40 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "284", + "Connection": "keep-alive", + "access-control-allow-credentials": "true", + "access-control-allow-origin": "*", + "access-control-expose-headers": "", + "cache-control": "max-age=0, private, must-revalidate", + "x-request-id": "Fd_k7OtPJ28p8-MAAAOh" + }, + "status_code": 200, + "type": "ok" + } + }, { "request": { "body": "", @@ -10,16 +42,22 @@ "follow_redirect": "true" }, "request_body": "", - "url": "http://localhost:8080/actor" + "url": "http://mobilizon1.com/relay" }, "response": { "binary": false, - "body": "{\"@context\": \"https://www.w3.org/ns/activitystreams\", \"endpoints\": {\"sharedInbox\": \"http://localhost:8080/inbox\"}, \"followers\": \"http://localhost:8080/followers\", \"following\": \"http://localhost:8080/following\", \"inbox\": \"http://localhost:8080/inbox\", \"name\": \"ActivityRelay\", \"type\": \"Application\", \"id\": \"http://localhost:8080/actor\", \"publicKey\": {\"id\": \"http://localhost:8080/actor#main-key\", \"owner\": \"http://localhost:8080/actor\", \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvs6UuAo26Sb3BiOK7xay\\nsBzqvXI3xd55JAP0pAk2faF+Vl3r67/g9MoND96JqCVMuzSJZ9oSsqa6ilJCxG3p\\nXUUfQUvqAMGW49cCvga86DG17Ennjbc4C6WIQtoW3Wm5OdDciPY2Dx+pSXdTOajB\\nFX6RHUZcgqHENrsm3jPZI138e/2OJeqdxv4/5t2xdPXEpWdPGitX9AJhrqPY4lzg\\nzQ9Y9wS2eS1CVL9vZZRf9Z4RiZvAfVb0s1iS/IUxrf4TYERRFJxEoDLD2SZVrkq6\\nvhGldCfw2ZnfTftA1ToXguC9S6nSaz+li0ajNjpK/xjZjlKvn0I078UPPe5LUlsb\\nUcYZvBx5PC5rV8yKMLlgxnTY8PqC8LEVc453wO7Ai4M5TeB0SUyEycZHSyLfvQXV\\nThEN/07u1UaJViY3U5S/SihyoCQUfJXQ3jx2SjGgM32/aJ3IwxgveLaTsaZ0VVKM\\nbawEFw6iAcWYM06hZSB6j6dkL1xh+FYGEQTPMYMqUOJi2r1cD8yMLe8dTFOmwMLt\\nBnf7xxvnjKJcv3e9zGRWIdLkQbBQn3BEuRTCUMgljipxdjbeE5/JSP1kQLB94ncb\\nb9gvYgtemJKvT8m37+HOi9MI4BMIlDwpRWjqPZmkNvkegR/1KPjJSsyAnGdd89ne\\np442vUqPyXIq0tSCDmjmU+cCAwEAAQ==\\n-----END PUBLIC KEY-----\"}, \"summary\": \"ActivityRelay bot\", \"preferredUsername\": \"relay\", \"url\": \"http://localhost:8080/actor\"}", + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.social/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"ical\":\"http://www.w3.org/2002/12/cal/ical#\",\"joinMode\":{\"@id\":\"mz:joinMode\",\"@type\":\"mz:joinModeType\"},\"joinModeType\":{\"@id\":\"mz:joinModeType\",\"@type\":\"rdfs:Class\"},\"maximumAttendeeCapacity\":\"sc:maximumAttendeeCapacity\",\"mz\":\"https://joinmobilizon.org/ns#\",\"repliesModerationOption\":{\"@id\":\"mz:repliesModerationOption\",\"@type\":\"mz:repliesModerationOptionType\"},\"repliesModerationOptionType\":{\"@id\":\"mz:repliesModerationOptionType\",\"@type\":\"rdfs:Class\"},\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"endpoints\":{\"sharedInbox\":\"http://mobilizon1.com/inbox\"},\"followers\":\"http://mobilizon1.com/relay/followers\",\"following\":\"http://mobilizon1.com/relay/following\",\"id\":\"http://mobilizon1.com/relay\",\"inbox\":\"http://mobilizon1.com/inbox\",\"manuallyApprovesFollowers\":false,\"name\":\"Mobilizon\",\"outbox\":null,\"preferredUsername\":\"relay\",\"publicKey\":{\"id\":\"http://mobilizon1.com/relay#main-key\",\"owner\":\"http://mobilizon1.com/relay\",\"publicKeyPem\":\"-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAqBbeHMV5UVw0AIVch7fWDp2it5rqbGZX6yXPYnnT8LHhdvfv3DFk\\npk74BN66MzNqsthvSVznu2BEil0sEKD5rQoE9Yirhzz/LN9SlnU+u6262nBA18E3\\nkQ10RgL2jpZ9e8Om6qYqarhN7draupJXYRKEaUoEFPT09ABbwQv+4K1YadU8klJi\\nHJ6D+IIHiXNizfsxVLDKpbUKStMYeEzyfqCkWw0EQEuzc3O7Aci5lwCMkCts2993\\nsTbNyzsYAVWJNcy/An1F1P+K4iZhWEtZInQz67MBtjMWtQUhyWib0e671HdBiWM6\\nkZq74U8c6RR6eMzBLuY7YAUCG6nWg90zxwIDAQAB\\n-----END RSA PUBLIC KEY-----\\n\\n\"},\"summary\":\"Change this to a proper description of your instance\",\"type\":\"Application\",\"url\":\"http://mobilizon1.com/relay\"}", "headers": { - "Content-Type": "application/json; charset=utf-8", - "Content-Length": "1368", - "Date": "Thu, 01 Aug 2019 14:44:38 GMT", - "Server": "Python/3.7 aiohttp/3.3.2" + "Server": "nginx/1.16.1", + "Date": "Fri, 13 Dec 2019 09:41:41 GMT", + "Content-Type": "application/activity+json", + "Content-Length": "1657", + "Connection": "keep-alive", + "access-control-allow-credentials": "true", + "access-control-allow-origin": "*", + "access-control-expose-headers": "", + "cache-control": "max-age=0, private, must-revalidate", + "x-request-id": "Fd_k7PjOAWuySL0AAAPB" }, "status_code": 200, "type": "ok" @@ -27,30 +65,36 @@ }, { "request": { - "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.github.io/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"actor\":\"http://mobilizon.test/relay\",\"cc\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"id\":\"http://mobilizon.test/follow/69/activity\",\"object\":\"http://localhost:8080/actor\",\"to\":[\"http://localhost:8080/actor\"],\"type\":\"Follow\"}", + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.social/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"ical\":\"http://www.w3.org/2002/12/cal/ical#\",\"joinMode\":{\"@id\":\"mz:joinMode\",\"@type\":\"mz:joinModeType\"},\"joinModeType\":{\"@id\":\"mz:joinModeType\",\"@type\":\"rdfs:Class\"},\"maximumAttendeeCapacity\":\"sc:maximumAttendeeCapacity\",\"mz\":\"https://joinmobilizon.org/ns#\",\"repliesModerationOption\":{\"@id\":\"mz:repliesModerationOption\",\"@type\":\"mz:repliesModerationOptionType\"},\"repliesModerationOptionType\":{\"@id\":\"mz:repliesModerationOptionType\",\"@type\":\"rdfs:Class\"},\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"actor\":\"http://mobilizon.test/relay\",\"cc\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"id\":\"http://mobilizon.test/follow/b7791977-2a75-4715-815b-6e7125065b71\",\"object\":\"http://mobilizon1.com/relay\",\"to\":[\"http://mobilizon1.com/relay\"],\"type\":\"Follow\"}", "headers": { "Content-Type": "application/activity+json", - "signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"UADlb5eaeqmujO5zGfK1mWB3WZFXU6lkUgSvEf5YyQMOIkMaudDwTfNPIa4IYh2VMLwyYSjOOXxkcBdCw4f9UnMBQBhomPNRNkJ0QBzoxILPmyxddAojH9IzwwAUL/nHSGWaO116bkCux0OcEM5AVIrCT6dENep39lOjnOGPelBB5mKMS78AxH4pU/5tTGFKmNgiRL4Q06ezPUJHKauRrMwzcqZYdjUn+U9MDBDrYyfAzqQlgBPU/fMCjwusndxaICb9c+40YE3WaXzKewIivfrMoOBzWyw6ZsgAG8/NoOH+8z9Z+hBvdjCUXeG2bvAPPclNkSJillwIA2PnMOVgpw==\"", - "digest": "SHA-256=Ady0Dj2bEXe201P9bThLaj1Kw/7O1cfrjN9IifEfVBg=", - "date": "Thu, 01 Aug 2019 14:44:38 GMT" + "signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"WbyGHT/WdvdRpWek8uCGHrFSblLpg+Iq802R5S2cjNj035OKpxRmu1r8u9Qr5KGIKgZn6LHt9YmB+PNlwsubPtTSkJpE8AAUDMHLKgCrH7A5Q6x6GlARl5bHNo4QtOxkXvnEbn31xfNDNp70QqZb/emw95TnELYUlMLZds0qYutT8U4WdDhSWcVytQmKJWNZXxEj+KlMDUaxag3lGscJ/HY0F+yGNov7FHthid1Y4LTGFsp/tismnMTlba12NH/kXPHtduNsX8uxFslM2ODwqAaospTGEpXmr9CPgbNy7626qgYaR2RdB/fYlCayLI4JJIlH8gOdocGHPrWNtVEHaQ==\"", + "digest": "SHA-256=ibNFcsnBeCCjWZo9We60tKfbRN3el0WCMVdOxtuC1cg=", + "date": "Fri, 13 Dec 2019 09:41:41 GMT" }, "method": "post", "options": { "pool": "default" }, "request_body": "", - "url": "http://localhost:8080/inbox" + "url": "http://mobilizon1.com/inbox" }, "response": { "binary": false, - "body": "signature check failed, signature did not match key", + "body": "# HTTPoison.Error at POST /inbox\n\nException:\n\n ** (HTTPoison.Error) :nxdomain\n (httpoison) lib/httpoison.ex:128: HTTPoison.request!/5\n (mobilizon) lib/service/activity_pub/activity_pub.ex:610: Mobilizon.Service.ActivityPub.fetch_and_prepare_actor_from_url/1\n (mobilizon) lib/service/activity_pub/activity_pub.ex:473: Mobilizon.Service.ActivityPub.make_actor_from_url/2\n (mobilizon) lib/service/activity_pub/activity_pub.ex:122: Mobilizon.Service.ActivityPub.get_or_fetch_actor_by_url/2\n (mobilizon) lib/service/http_signatures/signature.ex:54: Mobilizon.Service.HTTPSignatures.Signature.get_public_key_for_url/1\n (mobilizon) lib/service/http_signatures/signature.ex:74: Mobilizon.Service.HTTPSignatures.Signature.fetch_public_key/1\n (http_signatures) lib/http_signatures/http_signatures.ex:40: HTTPSignatures.validate_conn/1\n (mobilizon) lib/mobilizon_web/http_signature.ex:45: MobilizonWeb.HTTPSignaturePlug.call/2\n (mobilizon) MobilizonWeb.Router.activity_pub_signature/2\n (mobilizon) lib/mobilizon_web/router.ex:1: MobilizonWeb.Router.__pipe_through7__/1\n (phoenix) lib/phoenix/router.ex:283: Phoenix.Router.__call__/2\n (mobilizon) lib/mobilizon_web/endpoint.ex:1: MobilizonWeb.Endpoint.plug_builder_call/2\n (mobilizon) lib/plug/debugger.ex:122: MobilizonWeb.Endpoint.\"call (overridable 3)\"/2\n (mobilizon) lib/mobilizon_web/endpoint.ex:1: MobilizonWeb.Endpoint.call/2\n (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3\n (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3\n \n\n## Connection details\n\n### Params\n\n %{\"@context\" => [\"https://www.w3.org/ns/activitystreams\", \"https://litepub.social/litepub/context.jsonld\", %{\"Hashtag\" => \"as:Hashtag\", \"category\" => \"sc:category\", \"ical\" => \"http://www.w3.org/2002/12/cal/ical#\", \"joinMode\" => %{\"@id\" => \"mz:joinMode\", \"@type\" => \"mz:joinModeType\"}, \"joinModeType\" => %{\"@id\" => \"mz:joinModeType\", \"@type\" => \"rdfs:Class\"}, \"maximumAttendeeCapacity\" => \"sc:maximumAttendeeCapacity\", \"mz\" => \"https://joinmobilizon.org/ns#\", \"repliesModerationOption\" => %{\"@id\" => \"mz:repliesModerationOption\", \"@type\" => \"mz:repliesModerationOptionType\"}, \"repliesModerationOptionType\" => %{\"@id\" => \"mz:repliesModerationOptionType\", \"@type\" => \"rdfs:Class\"}, \"sc\" => \"http://schema.org#\", \"uuid\" => \"sc:identifier\"}], \"actor\" => \"http://mobilizon.test/relay\", \"cc\" => [\"https://www.w3.org/ns/activitystreams#Public\"], \"id\" => \"http://mobilizon.test/follow/b7791977-2a75-4715-815b-6e7125065b71\", \"object\" => \"http://mobilizon1.com/relay\", \"to\" => [\"http://mobilizon1.com/relay\"], \"type\" => \"Follow\"}\n\n### Request info\n\n * URI: http://mobilizon1.com:80/inbox\n * Query string: \n\n### Headers\n \n * connection: upgrade\n * content-length: 912\n * content-type: application/activity+json\n * date: Fri, 13 Dec 2019 09:41:41 GMT\n * digest: SHA-256=ibNFcsnBeCCjWZo9We60tKfbRN3el0WCMVdOxtuC1cg=\n * host: mobilizon1.com\n * signature: keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"WbyGHT/WdvdRpWek8uCGHrFSblLpg+Iq802R5S2cjNj035OKpxRmu1r8u9Qr5KGIKgZn6LHt9YmB+PNlwsubPtTSkJpE8AAUDMHLKgCrH7A5Q6x6GlARl5bHNo4QtOxkXvnEbn31xfNDNp70QqZb/emw95TnELYUlMLZds0qYutT8U4WdDhSWcVytQmKJWNZXxEj+KlMDUaxag3lGscJ/HY0F+yGNov7FHthid1Y4LTGFsp/tismnMTlba12NH/kXPHtduNsX8uxFslM2ODwqAaospTGEpXmr9CPgbNy7626qgYaR2RdB/fYlCayLI4JJIlH8gOdocGHPrWNtVEHaQ==\"\n * user-agent: hackney/1.15.2\n * x-forwarded-for: 127.0.0.1\n * x-real-ip: 127.0.0.1\n\n### Session\n\n %{}\n", "headers": { - "Content-Length": "51", - "Content-Type": "text/plain; charset=utf-8", - "Date": "Thu, 01 Aug 2019 14:44:38 GMT", - "Server": "Python/3.7 aiohttp/3.3.2" + "Server": "nginx/1.16.1", + "Date": "Fri, 13 Dec 2019 09:41:41 GMT", + "Content-Type": "text/markdown; charset=utf-8", + "Content-Length": "3977", + "Connection": "keep-alive", + "access-control-allow-credentials": "true", + "access-control-allow-origin": "*", + "access-control-expose-headers": "", + "cache-control": "max-age=0, private, must-revalidate", + "x-request-id": "Fd_k7PoZpCCBYRQAAAPh" }, - "status_code": 401, + "status_code": 500, "type": "ok" } } diff --git a/test/fixtures/vcr_cassettes/relay/fetch_relay_unfollow.json b/test/fixtures/vcr_cassettes/relay/fetch_relay_unfollow.json index 0c2abfe1..7b743e63 100644 --- a/test/fixtures/vcr_cassettes/relay/fetch_relay_unfollow.json +++ b/test/fixtures/vcr_cassettes/relay/fetch_relay_unfollow.json @@ -1,30 +1,33 @@ [ { "request": { - "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.github.io/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"actor\":\"http://mobilizon.test/relay\",\"cc\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"id\":\"http://mobilizon.test/follow/68/activity\",\"object\":\"http://localhost:8080/actor\",\"to\":[\"http://localhost:8080/actor\"],\"type\":\"Follow\"}", + "body": "", "headers": { - "Content-Type": "application/activity+json", - "signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"WsxzipdObXsApVtY5l2yTonTOPV888XLKK2+AMQRyiNZm4RGMEux8kgBKgJIODaKmRx9EsX8dIzBtTmJdLyj5gqfjvGVyj8hVeR0ERNMZmjngh5EZ3W+ySbkdFYZeYDWhwpL1i+7dTFJ3zE/ASZVaTMeIgqEpFnzHNbamwPzBZVvcnzyraB1rrmwcbzzrk3UPlJ3tA+Xz67Njr2wOiNNsjZ53abArKZB3KGbife6OyrVrKldJ+UKZS+vokgUXFwvMBZxfdmH2GD+yXHPhCIu7bVu77ASdW7bl7tM3uIV/c/Wemy5qJtPOupwbDvpLZ9ETE5IRCoUPdQ7l75kvevNxQ==\"", - "digest": "SHA-256=qIEgTH6kBorFchTiX2kxd7onyZ7BHhvLgCODLs6RAVc=", - "date": "Thu, 01 Aug 2019 14:44:37 GMT" + "Accept": "application/json, application/activity+json, application/jrd+json" }, - "method": "post", + "method": "get", "options": { - "pool": "default" + "follow_redirect": "true" }, "request_body": "", - "url": "http://localhost:8080/inbox" + "url": "http://mobilizon1.com/.well-known/webfinger?resource=acct:relay@mobilizon1.com" }, "response": { "binary": false, - "body": "signature check failed, signature did not match key", + "body": "{\"aliases\":[\"http://mobilizon1.com/relay\"],\"links\":[{\"href\":\"http://mobilizon1.com/relay\",\"rel\":\"self\",\"type\":\"application/activity+json\"},{\"href\":\"http://mobilizon1.com/relay\",\"rel\":\"https://webfinger.net/rel/profile-page/\",\"type\":\"text/html\"}],\"subject\":\"acct:relay@mobilizon1.com\"}", "headers": { - "Content-Length": "51", - "Content-Type": "text/plain; charset=utf-8", - "Date": "Thu, 01 Aug 2019 14:44:37 GMT", - "Server": "Python/3.7 aiohttp/3.3.2" + "Server": "nginx/1.16.1", + "Date": "Fri, 13 Dec 2019 09:41:39 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "284", + "Connection": "keep-alive", + "access-control-allow-credentials": "true", + "access-control-allow-origin": "*", + "access-control-expose-headers": "", + "cache-control": "max-age=0, private, must-revalidate", + "x-request-id": "Fd_k7LmY5k0CMQkAAANB" }, - "status_code": 401, + "status_code": 200, "type": "ok" } }, @@ -39,19 +42,60 @@ "follow_redirect": "true" }, "request_body": "", - "url": "http://localhost:8080/actor" + "url": "http://mobilizon1.com/relay" }, "response": { "binary": false, - "body": "{\"@context\": \"https://www.w3.org/ns/activitystreams\", \"endpoints\": {\"sharedInbox\": \"http://localhost:8080/inbox\"}, \"followers\": \"http://localhost:8080/followers\", \"following\": \"http://localhost:8080/following\", \"inbox\": \"http://localhost:8080/inbox\", \"name\": \"ActivityRelay\", \"type\": \"Application\", \"id\": \"http://localhost:8080/actor\", \"publicKey\": {\"id\": \"http://localhost:8080/actor#main-key\", \"owner\": \"http://localhost:8080/actor\", \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvs6UuAo26Sb3BiOK7xay\\nsBzqvXI3xd55JAP0pAk2faF+Vl3r67/g9MoND96JqCVMuzSJZ9oSsqa6ilJCxG3p\\nXUUfQUvqAMGW49cCvga86DG17Ennjbc4C6WIQtoW3Wm5OdDciPY2Dx+pSXdTOajB\\nFX6RHUZcgqHENrsm3jPZI138e/2OJeqdxv4/5t2xdPXEpWdPGitX9AJhrqPY4lzg\\nzQ9Y9wS2eS1CVL9vZZRf9Z4RiZvAfVb0s1iS/IUxrf4TYERRFJxEoDLD2SZVrkq6\\nvhGldCfw2ZnfTftA1ToXguC9S6nSaz+li0ajNjpK/xjZjlKvn0I078UPPe5LUlsb\\nUcYZvBx5PC5rV8yKMLlgxnTY8PqC8LEVc453wO7Ai4M5TeB0SUyEycZHSyLfvQXV\\nThEN/07u1UaJViY3U5S/SihyoCQUfJXQ3jx2SjGgM32/aJ3IwxgveLaTsaZ0VVKM\\nbawEFw6iAcWYM06hZSB6j6dkL1xh+FYGEQTPMYMqUOJi2r1cD8yMLe8dTFOmwMLt\\nBnf7xxvnjKJcv3e9zGRWIdLkQbBQn3BEuRTCUMgljipxdjbeE5/JSP1kQLB94ncb\\nb9gvYgtemJKvT8m37+HOi9MI4BMIlDwpRWjqPZmkNvkegR/1KPjJSsyAnGdd89ne\\np442vUqPyXIq0tSCDmjmU+cCAwEAAQ==\\n-----END PUBLIC KEY-----\"}, \"summary\": \"ActivityRelay bot\", \"preferredUsername\": \"relay\", \"url\": \"http://localhost:8080/actor\"}", + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.social/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"ical\":\"http://www.w3.org/2002/12/cal/ical#\",\"joinMode\":{\"@id\":\"mz:joinMode\",\"@type\":\"mz:joinModeType\"},\"joinModeType\":{\"@id\":\"mz:joinModeType\",\"@type\":\"rdfs:Class\"},\"maximumAttendeeCapacity\":\"sc:maximumAttendeeCapacity\",\"mz\":\"https://joinmobilizon.org/ns#\",\"repliesModerationOption\":{\"@id\":\"mz:repliesModerationOption\",\"@type\":\"mz:repliesModerationOptionType\"},\"repliesModerationOptionType\":{\"@id\":\"mz:repliesModerationOptionType\",\"@type\":\"rdfs:Class\"},\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"endpoints\":{\"sharedInbox\":\"http://mobilizon1.com/inbox\"},\"followers\":\"http://mobilizon1.com/relay/followers\",\"following\":\"http://mobilizon1.com/relay/following\",\"id\":\"http://mobilizon1.com/relay\",\"inbox\":\"http://mobilizon1.com/inbox\",\"manuallyApprovesFollowers\":false,\"name\":\"Mobilizon\",\"outbox\":null,\"preferredUsername\":\"relay\",\"publicKey\":{\"id\":\"http://mobilizon1.com/relay#main-key\",\"owner\":\"http://mobilizon1.com/relay\",\"publicKeyPem\":\"-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAqBbeHMV5UVw0AIVch7fWDp2it5rqbGZX6yXPYnnT8LHhdvfv3DFk\\npk74BN66MzNqsthvSVznu2BEil0sEKD5rQoE9Yirhzz/LN9SlnU+u6262nBA18E3\\nkQ10RgL2jpZ9e8Om6qYqarhN7draupJXYRKEaUoEFPT09ABbwQv+4K1YadU8klJi\\nHJ6D+IIHiXNizfsxVLDKpbUKStMYeEzyfqCkWw0EQEuzc3O7Aci5lwCMkCts2993\\nsTbNyzsYAVWJNcy/An1F1P+K4iZhWEtZInQz67MBtjMWtQUhyWib0e671HdBiWM6\\nkZq74U8c6RR6eMzBLuY7YAUCG6nWg90zxwIDAQAB\\n-----END RSA PUBLIC KEY-----\\n\\n\"},\"summary\":\"Change this to a proper description of your instance\",\"type\":\"Application\",\"url\":\"http://mobilizon1.com/relay\"}", "headers": { - "Content-Type": "application/json; charset=utf-8", - "Content-Length": "1368", - "Date": "Thu, 01 Aug 2019 14:44:36 GMT", - "Server": "Python/3.7 aiohttp/3.3.2" + "Server": "nginx/1.16.1", + "Date": "Fri, 13 Dec 2019 09:41:40 GMT", + "Content-Type": "application/activity+json", + "Content-Length": "1657", + "Connection": "keep-alive", + "access-control-allow-credentials": "true", + "access-control-allow-origin": "*", + "access-control-expose-headers": "", + "cache-control": "max-age=0, private, must-revalidate", + "x-request-id": "Fd_k7L4h92fDp5cAAANh" }, "status_code": 200, "type": "ok" } + }, + { + "request": { + "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.social/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"ical\":\"http://www.w3.org/2002/12/cal/ical#\",\"joinMode\":{\"@id\":\"mz:joinMode\",\"@type\":\"mz:joinModeType\"},\"joinModeType\":{\"@id\":\"mz:joinModeType\",\"@type\":\"rdfs:Class\"},\"maximumAttendeeCapacity\":\"sc:maximumAttendeeCapacity\",\"mz\":\"https://joinmobilizon.org/ns#\",\"repliesModerationOption\":{\"@id\":\"mz:repliesModerationOption\",\"@type\":\"mz:repliesModerationOptionType\"},\"repliesModerationOptionType\":{\"@id\":\"mz:repliesModerationOptionType\",\"@type\":\"rdfs:Class\"},\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"actor\":\"http://mobilizon.test/relay\",\"cc\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"id\":\"http://mobilizon.test/follow/57a6973e-f43f-4533-bf71-7a14a4c6e5ac\",\"object\":\"http://mobilizon1.com/relay\",\"to\":[\"http://mobilizon1.com/relay\"],\"type\":\"Follow\"}", + "headers": { + "Content-Type": "application/activity+json", + "signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"JQPqSiJ0ZYdU6llrYXNMuN/bfzoLyubwOB59bljFq6i8ORXLw62Pt7Jue5WkMsySFcCXgS8k8K/H81YZkKzfWadwQV9L5rQEFSuW/DYJ2xffsDj90GsSi+sDRaQ5Ke8nPEbEMGR9jalh/F2VL97XscCgm6i3tdpbs6aFmqjKC+LzeH665t0WCHUxTgK47wECrMHw3j7lteGdm6N6IKWoWsRYeJoyFr/QCbNdWQOaAYYpCbJd0fjhPQRHhWQXidBoaDkhwesWc3mO8pvEnply9ES7Nzc6ULK7B98hg+aWeep8/KzRbxFyJ0OgnDJj/l39QiJ9t7v0yHX/WUzn0CaiiQ==\"", + "digest": "SHA-256=Qc9d9X3qh2EqIqtn/72iY17OMDXAOINDC10hARNAc4w=", + "date": "Fri, 13 Dec 2019 09:41:40 GMT" + }, + "method": "post", + "options": { + "pool": "default" + }, + "request_body": "", + "url": "http://mobilizon1.com/inbox" + }, + "response": { + "binary": false, + "body": "# HTTPoison.Error at POST /inbox\n\nException:\n\n ** (HTTPoison.Error) :nxdomain\n (httpoison) lib/httpoison.ex:128: HTTPoison.request!/5\n (mobilizon) lib/service/activity_pub/activity_pub.ex:610: Mobilizon.Service.ActivityPub.fetch_and_prepare_actor_from_url/1\n (mobilizon) lib/service/activity_pub/activity_pub.ex:473: Mobilizon.Service.ActivityPub.make_actor_from_url/2\n (mobilizon) lib/service/activity_pub/activity_pub.ex:122: Mobilizon.Service.ActivityPub.get_or_fetch_actor_by_url/2\n (mobilizon) lib/service/http_signatures/signature.ex:54: Mobilizon.Service.HTTPSignatures.Signature.get_public_key_for_url/1\n (mobilizon) lib/service/http_signatures/signature.ex:74: Mobilizon.Service.HTTPSignatures.Signature.fetch_public_key/1\n (http_signatures) lib/http_signatures/http_signatures.ex:40: HTTPSignatures.validate_conn/1\n (mobilizon) lib/mobilizon_web/http_signature.ex:45: MobilizonWeb.HTTPSignaturePlug.call/2\n (mobilizon) MobilizonWeb.Router.activity_pub_signature/2\n (mobilizon) lib/mobilizon_web/router.ex:1: MobilizonWeb.Router.__pipe_through7__/1\n (phoenix) lib/phoenix/router.ex:283: Phoenix.Router.__call__/2\n (mobilizon) lib/mobilizon_web/endpoint.ex:1: MobilizonWeb.Endpoint.plug_builder_call/2\n (mobilizon) lib/plug/debugger.ex:122: MobilizonWeb.Endpoint.\"call (overridable 3)\"/2\n (mobilizon) lib/mobilizon_web/endpoint.ex:1: MobilizonWeb.Endpoint.call/2\n (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3\n (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3\n \n\n## Connection details\n\n### Params\n\n %{\"@context\" => [\"https://www.w3.org/ns/activitystreams\", \"https://litepub.social/litepub/context.jsonld\", %{\"Hashtag\" => \"as:Hashtag\", \"category\" => \"sc:category\", \"ical\" => \"http://www.w3.org/2002/12/cal/ical#\", \"joinMode\" => %{\"@id\" => \"mz:joinMode\", \"@type\" => \"mz:joinModeType\"}, \"joinModeType\" => %{\"@id\" => \"mz:joinModeType\", \"@type\" => \"rdfs:Class\"}, \"maximumAttendeeCapacity\" => \"sc:maximumAttendeeCapacity\", \"mz\" => \"https://joinmobilizon.org/ns#\", \"repliesModerationOption\" => %{\"@id\" => \"mz:repliesModerationOption\", \"@type\" => \"mz:repliesModerationOptionType\"}, \"repliesModerationOptionType\" => %{\"@id\" => \"mz:repliesModerationOptionType\", \"@type\" => \"rdfs:Class\"}, \"sc\" => \"http://schema.org#\", \"uuid\" => \"sc:identifier\"}], \"actor\" => \"http://mobilizon.test/relay\", \"cc\" => [\"https://www.w3.org/ns/activitystreams#Public\"], \"id\" => \"http://mobilizon.test/follow/57a6973e-f43f-4533-bf71-7a14a4c6e5ac\", \"object\" => \"http://mobilizon1.com/relay\", \"to\" => [\"http://mobilizon1.com/relay\"], \"type\" => \"Follow\"}\n\n### Request info\n\n * URI: http://mobilizon1.com:80/inbox\n * Query string: \n\n### Headers\n \n * connection: upgrade\n * content-length: 912\n * content-type: application/activity+json\n * date: Fri, 13 Dec 2019 09:41:40 GMT\n * digest: SHA-256=Qc9d9X3qh2EqIqtn/72iY17OMDXAOINDC10hARNAc4w=\n * host: mobilizon1.com\n * signature: keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"JQPqSiJ0ZYdU6llrYXNMuN/bfzoLyubwOB59bljFq6i8ORXLw62Pt7Jue5WkMsySFcCXgS8k8K/H81YZkKzfWadwQV9L5rQEFSuW/DYJ2xffsDj90GsSi+sDRaQ5Ke8nPEbEMGR9jalh/F2VL97XscCgm6i3tdpbs6aFmqjKC+LzeH665t0WCHUxTgK47wECrMHw3j7lteGdm6N6IKWoWsRYeJoyFr/QCbNdWQOaAYYpCbJd0fjhPQRHhWQXidBoaDkhwesWc3mO8pvEnply9ES7Nzc6ULK7B98hg+aWeep8/KzRbxFyJ0OgnDJj/l39QiJ9t7v0yHX/WUzn0CaiiQ==\"\n * user-agent: hackney/1.15.2\n * x-forwarded-for: 127.0.0.1\n * x-real-ip: 127.0.0.1\n\n### Session\n\n %{}\n", + "headers": { + "Server": "nginx/1.16.1", + "Date": "Fri, 13 Dec 2019 09:41:40 GMT", + "Content-Type": "text/markdown; charset=utf-8", + "Content-Length": "3977", + "Connection": "keep-alive", + "access-control-allow-credentials": "true", + "access-control-allow-origin": "*", + "access-control-expose-headers": "", + "cache-control": "max-age=0, private, must-revalidate", + "x-request-id": "Fd_k7MU4jVIgj4wAAAOB" + }, + "status_code": 500, + "type": "ok" + } } ] \ No newline at end of file diff --git a/test/mobilizon/actors/actors_test.exs b/test/mobilizon/actors/actors_test.exs index 1d065ae4..d829ecfd 100644 --- a/test/mobilizon/actors/actors_test.exs +++ b/test/mobilizon/actors/actors_test.exs @@ -1,12 +1,13 @@ defmodule Mobilizon.ActorsTest do use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney - use Mobilizon.DataCase + use Oban.Testing, repo: Mobilizon.Storage.Repo import Mobilizon.Factory - alias Mobilizon.{Actors, Config, Users} + alias Mobilizon.{Actors, Config, Users, Events, Tombstone} alias Mobilizon.Actors.{Actor, Bot, Follower, Member} + alias Mobilizon.Events.{Event, Comment} alias Mobilizon.Media.File, as: FileModel alias Mobilizon.Service.ActivityPub alias Mobilizon.Storage.Page @@ -287,6 +288,12 @@ defmodule Mobilizon.ActorsTest do test "delete_actor/1 deletes the actor", %{ actor: %Actor{avatar: %{url: avatar_url}, banner: %{url: banner_url}, id: actor_id} = actor } do + %Event{url: event1_url} = event1 = insert(:event, organizer_actor: actor) + insert(:event, organizer_actor: actor) + + %Comment{url: comment1_url} = comment1 = insert(:comment, actor: actor) + insert(:comment, actor: actor) + %URI{path: "/media/" <> avatar_path} = URI.parse(avatar_url) %URI{path: "/media/" <> banner_path} = URI.parse(banner_url) @@ -300,8 +307,34 @@ defmodule Mobilizon.ActorsTest do "/" <> banner_path ) - assert {:ok, %Actor{}} = Actors.delete_actor(actor) - assert_raise Ecto.NoResultsError, fn -> Actors.get_actor!(actor_id) end + assert {:ok, %Oban.Job{}} = Actors.delete_actor(actor) + + assert_enqueued( + worker: Mobilizon.Service.Workers.BackgroundWorker, + args: %{"actor_id" => actor.id, "op" => "delete_actor"} + ) + + assert %{success: 1, failure: 0} == Oban.drain_queue(:background) + + assert %Actor{ + name: nil, + summary: nil, + suspended: true, + avatar: nil, + banner: nil, + user_id: nil + } = Actors.get_actor(actor_id) + + assert {:error, :event_not_found} = Events.get_event(event1.id) + assert %Tombstone{} = Tombstone.find_tombstone(event1_url) + assert %Comment{deleted_at: deleted_at} = Events.get_comment(comment1.id) + refute is_nil(deleted_at) + assert %Tombstone{} = Tombstone.find_tombstone(comment1_url) + + refute File.exists?( + Config.get!([MobilizonWeb.Uploaders.Local, :uploads]) <> + "/" <> avatar_path + ) refute File.exists?( Config.get!([MobilizonWeb.Uploaders.Local, :uploads]) <> diff --git a/test/mobilizon/events/events_test.exs b/test/mobilizon/events/events_test.exs index bc0e4ba7..5ecac1ae 100644 --- a/test/mobilizon/events/events_test.exs +++ b/test/mobilizon/events/events_test.exs @@ -314,7 +314,10 @@ defmodule Mobilizon.EventsTest do setup do actor = insert(:actor) - event = insert(:event, organizer_actor: actor) + + event = + insert(:event, organizer_actor: actor, participant_stats: %{creator: 1, participant: 1}) + participant = insert(:participant, actor: actor, event: event) {:ok, participant: participant, event: event, actor: actor} end @@ -364,7 +367,8 @@ defmodule Mobilizon.EventsTest do test "update_participant/2 with invalid data returns error changeset", %{ participant: participant } do - assert {:error, %Ecto.Changeset{}} = Events.update_participant(participant, @invalid_attrs) + assert {:error, :participant, %Ecto.Changeset{}, %{}} = + Events.update_participant(participant, @invalid_attrs) end test "delete_participant/1 deletes the participant", %{participant: participant} do diff --git a/test/mobilizon/service/activity_pub/activity_pub_test.exs b/test/mobilizon/service/activity_pub/activity_pub_test.exs index 63f78167..db768388 100644 --- a/test/mobilizon/service/activity_pub/activity_pub_test.exs +++ b/test/mobilizon/service/activity_pub/activity_pub_test.exs @@ -171,7 +171,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do assert update.data["actor"] == actor.url assert update.data["to"] == [@activity_pub_public_audience] assert update.data["object"]["id"] == actor.url - assert update.data["object"]["type"] == "Person" + assert update.data["object"]["type"] == :Person assert update.data["object"]["summary"] == @updated_actor_summary end diff --git a/test/mobilizon/service/activity_pub/converter/actor_test.exs b/test/mobilizon/service/activity_pub/converter/actor_test.exs index 88485ed7..edc8b0ee 100644 --- a/test/mobilizon/service/activity_pub/converter/actor_test.exs +++ b/test/mobilizon/service/activity_pub/converter/actor_test.exs @@ -8,8 +8,8 @@ defmodule Mobilizon.Service.ActivityPub.Converter.ActorTest do test "valid actor to as" do data = ActorConverter.model_to_as(%Actor{type: :Person, preferred_username: "test_account"}) assert is_map(data) - assert data["type"] == "Person" - assert data["preferred_username"] == "test_account" + assert data["type"] == :Person + assert data["preferredUsername"] == "test_account" end end @@ -17,12 +17,13 @@ defmodule Mobilizon.Service.ActivityPub.Converter.ActorTest do test "valid as data to model" do {:ok, actor} = ActorConverter.as_to_model_data(%{ + "id" => "https://somedomain.tld/users/someone", "type" => "Person", "preferredUsername" => "test_account" }) - assert actor["type"] == :Person - assert actor["preferred_username"] == "test_account" + assert actor.type == "Person" + assert actor.preferred_username == "test_account" end end end diff --git a/test/mobilizon/service/activity_pub/transmogrifier_test.exs b/test/mobilizon/service/activity_pub/transmogrifier_test.exs index 995881ef..afc19173 100644 --- a/test/mobilizon/service/activity_pub/transmogrifier_test.exs +++ b/test/mobilizon/service/activity_pub/transmogrifier_test.exs @@ -9,10 +9,10 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do use Mobilizon.DataCase import Mobilizon.Factory + import ExUnit.CaptureLog - alias Mobilizon.Actors + alias Mobilizon.{Actors, Events, Tombstone} alias Mobilizon.Actors.Actor - alias Mobilizon.Events alias Mobilizon.Events.{Comment, Event, Participant} alias Mobilizon.Service.ActivityPub alias Mobilizon.Service.ActivityPub.{Activity, Utils} @@ -131,7 +131,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do data |> Map.put("object", object) - assert ExUnit.CaptureLog.capture_log([level: :warn], fn -> + assert capture_log([level: :warn], fn -> {:ok, _returned_activity, _entity} = Transmogrifier.handle_incoming(data) end) =~ "[warn] Parent object is something we don't handle" end @@ -145,7 +145,10 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do assert data["id"] == "https://framapiaf.org/users/admin/statuses/99512778738411822/activity" - assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"] + assert data["to"] == [ + "https://www.w3.org/ns/activitystreams#Public", + "https://framapiaf.org/users/tcit" + ] # assert data["cc"] == [ # "https://framapiaf.org/users/admin/followers", @@ -466,26 +469,70 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do refute is_nil(Events.get_comment_from_url(comment_url).deleted_at) end - # TODO : make me ASAP - # test "it fails for incoming deletes with spoofed origin" do - # activity = insert(:note_activity) + test "it fails for incoming deletes with spoofed origin" do + comment = insert(:comment) - # data = - # File.read!("test/fixtures/mastodon-delete.json") - # |> Jason.decode!() + announce_data = + File.read!("test/fixtures/mastodon-announce.json") + |> Jason.decode!() + |> Map.put("object", comment.url) - # object = - # data["object"] - # |> Map.put("id", activity.data["object"]["id"]) + {:ok, %Activity{local: false}, _} = Transmogrifier.handle_incoming(announce_data) - # data = - # data - # |> Map.put("object", object) + data = + File.read!("test/fixtures/mastodon-delete.json") + |> Jason.decode!() - # :error = Transmogrifier.handle_incoming(data) + object = + data["object"] + |> Map.put("id", comment.url) - # assert Repo.get(Activity, activity.id) - # end + data = + data + |> Map.put("object", object) + + :error = Transmogrifier.handle_incoming(data) + + assert Events.get_comment_from_url(comment.url) + end + + test "it works for incoming actor deletes" do + %Actor{url: url} = actor = insert(:actor, url: "https://framapiaf.org/users/admin") + %Event{url: event1_url} = event1 = insert(:event, organizer_actor: actor) + insert(:event, organizer_actor: actor) + + %Comment{url: comment1_url} = comment1 = insert(:comment, actor: actor) + insert(:comment, actor: actor) + + data = + File.read!("test/fixtures/mastodon-delete-user.json") + |> Poison.decode!() + + {:ok, _activity, _actor} = Transmogrifier.handle_incoming(data) + assert %{success: 1, failure: 0} == Oban.drain_queue(:background) + + assert {:ok, %Actor{suspended: true}} = Actors.get_actor_by_url(url) + assert {:error, :event_not_found} = Events.get_event(event1.id) + assert %Tombstone{} = Tombstone.find_tombstone(event1_url) + assert %Comment{deleted_at: deleted_at} = Events.get_comment(comment1.id) + refute is_nil(deleted_at) + assert %Tombstone{} = Tombstone.find_tombstone(comment1_url) + end + + test "it fails for incoming actor deletes with spoofed origin" do + %{url: url} = insert(:actor) + + data = + File.read!("test/fixtures/mastodon-delete-user.json") + |> Poison.decode!() + |> Map.put("actor", url) + + assert capture_log(fn -> + assert :error == Transmogrifier.handle_incoming(data) + end) =~ "Object origin check failed" + + assert Actors.get_actor_by_url(url) + end test "it works for incoming unannounces with an existing notice" do use_cassette "activity_pub/mastodon_unannounce_activity" do @@ -743,13 +790,14 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do end test "it accepts Flag activities" do - %Actor{url: reporter_url} = _reporter = insert(:actor) + %Actor{url: reporter_url} = Mobilizon.Service.ActivityPub.Relay.get_actor() %Actor{url: reported_url} = reported = insert(:actor) %Comment{url: comment_url} = _comment = insert(:comment, actor: reported) message = %{ "@context" => "https://www.w3.org/ns/activitystreams", + "to" => [], "cc" => [reported_url], "object" => [reported_url, comment_url], "type" => "Flag", @@ -762,11 +810,11 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do assert activity.data["object"] == [reported_url, comment_url] assert activity.data["content"] == "blocked AND reported!!!" assert activity.data["actor"] == reporter_url - assert activity.data["cc"] == [reported_url] + assert activity.data["cc"] == [] end test "it accepts Join activities" do - %Actor{url: _organizer_url} = organizer = insert(:actor) + %Actor{url: organizer_url} = organizer = insert(:actor) %Actor{url: participant_url} = _participant = insert(:actor) %Event{url: event_url} = _event = insert(:event, organizer_actor: organizer) @@ -779,8 +827,12 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do assert {:ok, activity, _} = Transmogrifier.handle_incoming(join_data) - assert activity.data["object"] == event_url - assert activity.data["actor"] == participant_url + assert activity.data["type"] == "Accept" + assert activity.data["object"]["object"] == event_url + assert activity.data["object"]["id"] =~ "/join/event/" + assert activity.data["object"]["type"] =~ "Join" + assert activity.data["actor"] == organizer_url + assert activity.data["id"] =~ "/accept/join/" end test "it accepts Accept activities for Join activities" do @@ -821,12 +873,17 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do |> Map.put("object", participation.url) {:ok, reject_activity, _} = Transmogrifier.handle_incoming(reject_data) - assert reject_activity.data["object"] == join_activity.data["id"] - assert reject_activity.data["object"] =~ "/join/" + assert reject_activity.data["object"]["id"] == join_activity.data["id"] + assert reject_activity.data["object"]["id"] =~ "/join/" assert reject_activity.data["id"] =~ "/reject/join/" # We don't accept already rejected Reject activities - assert :error == Transmogrifier.handle_incoming(reject_data) + assert capture_log([level: :warn], fn -> + assert :error == Transmogrifier.handle_incoming(reject_data) + end) =~ + "Unable to process Reject activity \"http://mastodon.example.org/users/admin#rejects/follows/4\". Object \"#{ + join_activity.data["id"] + }\" wasn't found." # Organiser is not present since we use factories directly assert event.id @@ -913,15 +970,6 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do assert Enum.member?(object["tag"], expected_mention) end - # test "it adds the sensitive property" do - # user = insert(:user) - - # {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"}) - # {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) - - # assert modified["object"]["sensitive"] - # end - test "it adds the json-ld context and the conversation property" do actor = insert(:actor) @@ -975,125 +1023,28 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do assert is_nil(modified["object"]["announcement_count"]) assert is_nil(modified["object"]["context_id"]) end + end - # describe "actor rewriting" do - # test "it fixes the actor URL property to be a proper URI" do - # data = %{ - # "url" => %{"href" => "http://example.com"} - # } + describe "actor origin check" do + test "it rejects objects with a bogus origin" do + use_cassette "activity_pub/object_bogus_origin" do + {:error, _} = ActivityPub.fetch_object_from_url("https://info.pleroma.site/activity.json") + end + end - # rewritten = Transmogrifier.maybe_fix_user_object(data) - # assert rewritten["url"] == "http://example.com" - # end - # end + test "it rejects activities which reference objects with bogus origins" do + use_cassette "activity_pub/activity_object_bogus" do + data = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => "https://framapiaf.org/users/admin/activities/1234", + "actor" => "https://framapiaf.org/users/admin", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "object" => "https://info.pleroma.site/activity.json", + "type" => "Announce" + } - # describe "actor origin containment" do - # test "it rejects objects with a bogus origin" do - # {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity.json") - # end - - # test "it rejects activities which reference objects with bogus origins" do - # data = %{ - # "@context" => "https://www.w3.org/ns/activitystreams", - # "id" => "http://mastodon.example.org/users/admin/activities/1234", - # "actor" => "http://mastodon.example.org/users/admin", - # "to" => ["https://www.w3.org/ns/activitystreams#Public"], - # "object" => "https://info.pleroma.site/activity.json", - # "type" => "Announce" - # } - - # :error = Transmogrifier.handle_incoming(data) - # end - - # test "it rejects objects when attributedTo is wrong (variant 1)" do - # {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity2.json") - # end - - # test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do - # data = %{ - # "@context" => "https://www.w3.org/ns/activitystreams", - # "id" => "http://mastodon.example.org/users/admin/activities/1234", - # "actor" => "http://mastodon.example.org/users/admin", - # "to" => ["https://www.w3.org/ns/activitystreams#Public"], - # "object" => "https://info.pleroma.site/activity2.json", - # "type" => "Announce" - # } - - # :error = Transmogrifier.handle_incoming(data) - # end - - # test "it rejects objects when attributedTo is wrong (variant 2)" do - # {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity3.json") - # end - - # test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do - # data = %{ - # "@context" => "https://www.w3.org/ns/activitystreams", - # "id" => "http://mastodon.example.org/users/admin/activities/1234", - # "actor" => "http://mastodon.example.org/users/admin", - # "to" => ["https://www.w3.org/ns/activitystreams#Public"], - # "object" => "https://info.pleroma.site/activity3.json", - # "type" => "Announce" - # } - - # :error = Transmogrifier.handle_incoming(data) - # end - # end - - # describe "general origin containment" do - # test "contain_origin_from_id() catches obvious spoofing attempts" do - # data = %{ - # "id" => "http://example.com/~alyssa/activities/1234.json" - # } - - # :error = - # Transmogrifier.contain_origin_from_id( - # "http://example.org/~alyssa/activities/1234.json", - # data - # ) - # end - - # test "contain_origin_from_id() allows alternate IDs within the same origin domain" do - # data = %{ - # "id" => "http://example.com/~alyssa/activities/1234.json" - # } - - # :ok = - # Transmogrifier.contain_origin_from_id( - # "http://example.com/~alyssa/activities/1234", - # data - # ) - # end - - # test "contain_origin_from_id() allows matching IDs" do - # data = %{ - # "id" => "http://example.com/~alyssa/activities/1234.json" - # } - - # :ok = - # Transmogrifier.contain_origin_from_id( - # "http://example.com/~alyssa/activities/1234.json", - # data - # ) - # end - - # test "users cannot be collided through fake direction spoofing attempts" do - # user = - # insert(:user, %{ - # nickname: "rye@niu.moe", - # local: false, - # ap_id: "https://niu.moe/users/rye", - # follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"}) - # }) - - # {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye") - # end - - # test "all objects with fake directions are rejected by the object fetcher" do - # {:error, _} = - # ActivityPub.fetch_and_contain_remote_object_from_id( - # "https://info.pleroma.site/activity4.json" - # ) - # end + :error = Transmogrifier.handle_incoming(data) + end + end end end diff --git a/test/mobilizon/service/activity_pub/utils_test.exs b/test/mobilizon/service/activity_pub/utils_test.exs index 572792e5..9cf960e1 100644 --- a/test/mobilizon/service/activity_pub/utils_test.exs +++ b/test/mobilizon/service/activity_pub/utils_test.exs @@ -5,7 +5,7 @@ defmodule Mobilizon.Service.ActivityPub.UtilsTest do import Mobilizon.Factory - alias Mobilizon.Service.ActivityPub.{Converter, Utils} + alias Mobilizon.Service.ActivityPub.Converter alias MobilizonWeb.Endpoint alias MobilizonWeb.Router.Helpers, as: Routes @@ -36,7 +36,8 @@ defmodule Mobilizon.Service.ActivityPub.UtilsTest do "uuid" => reply.uuid, "id" => Routes.page_url(Endpoint, :comment, reply.uuid), "inReplyTo" => comment.url, - "attributedTo" => reply.actor.url + "attributedTo" => reply.actor.url, + "mediaType" => "text/html" } == Converter.Comment.model_to_as(reply) end @@ -44,7 +45,7 @@ defmodule Mobilizon.Service.ActivityPub.UtilsTest do comment = insert(:comment) reply = insert(:comment, in_reply_to_comment: comment) to = ["https://www.w3.org/ns/activitystreams#Public"] - comment_data = Utils.make_comment_data(reply.actor.url, to, reply.text, comment.url) + comment_data = Converter.Comment.model_to_as(reply) assert comment_data["type"] == "Note" assert comment_data["to"] == to assert comment_data["content"] == reply.text diff --git a/test/mobilizon/service/formatter/formatter_test.exs b/test/mobilizon/service/formatter/formatter_test.exs index c1194a4f..4948543e 100644 --- a/test/mobilizon/service/formatter/formatter_test.exs +++ b/test/mobilizon/service/formatter/formatter_test.exs @@ -33,21 +33,21 @@ defmodule Mobilizon.Service.FormatterTest do text = "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." expected = - "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." + "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." assert {^expected, [], []} = Formatter.linkify(text) text = "https://mastodon.social/@lambadalambda" expected = - "https://mastodon.social/@lambadalambda" + "https://mastodon.social/@lambadalambda" assert {^expected, [], []} = Formatter.linkify(text) text = "https://mastodon.social:4000/@lambadalambda" expected = - "https://mastodon.social:4000/@lambadalambda" + "https://mastodon.social:4000/@lambadalambda" assert {^expected, [], []} = Formatter.linkify(text) @@ -59,56 +59,57 @@ defmodule Mobilizon.Service.FormatterTest do text = "http://www.cs.vu.nl/~ast/intel/" expected = - "http://www.cs.vu.nl/~ast/intel/" + "http://www.cs.vu.nl/~ast/intel/" assert {^expected, [], []} = Formatter.linkify(text) text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087" expected = - "https://forum.zdoom.org/viewtopic.php?f=44&t=57087" + "https://forum.zdoom.org/viewtopic.php?f=44&t=57087" assert {^expected, [], []} = Formatter.linkify(text) text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul" expected = - "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul" + "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul" assert {^expected, [], []} = Formatter.linkify(text) text = "https://www.google.co.jp/search?q=Nasim+Aghdam" expected = - "https://www.google.co.jp/search?q=Nasim+Aghdam" + "https://www.google.co.jp/search?q=Nasim+Aghdam" assert {^expected, [], []} = Formatter.linkify(text) text = "https://en.wikipedia.org/wiki/Duff's_device" expected = - "https://en.wikipedia.org/wiki/Duff's_device" + "https://en.wikipedia.org/wiki/Duff's_device" assert {^expected, [], []} = Formatter.linkify(text) text = "https://pleroma.com https://pleroma.com/sucks" expected = - "https://pleroma.comhttps://pleroma.com/sucks" + "https://pleroma.comhttps://pleroma.com/sucks" assert {^expected, [], []} = Formatter.linkify(text) text = "xmpp:contact@hacktivis.me" expected = - "xmpp:contact@hacktivis.me" + "xmpp:contact@hacktivis.me" assert {^expected, [], []} = Formatter.linkify(text) text = "magnet:?xt=urn:btih:7ec9d298e91d6e4394d1379caf073c77ff3e3136&tr=udp%3A%2F%2Fopentor.org%3A2710&tr=udp%3A%2F%2Ftracker.blackunicorn.xyz%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com" - expected = "#{text}" + expected = + "#{text}" assert {^expected, [], []} = Formatter.linkify(text) end @@ -117,32 +118,36 @@ defmodule Mobilizon.Service.FormatterTest do describe "add_user_links" do test "gives a replacement for user links, using local nicknames in user links text" do text = "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme@archae.me" - _gsimg = insert(:actor, preferred_username: "gsimg") + gsimg = insert(:actor, preferred_username: "gsimg") - _archaeme = + archaeme = insert(:actor, preferred_username: "archa_eme_", url: "https://archeme/@archa_eme_") - _archaeme_remote = insert(:actor, preferred_username: "archaeme", domain: "archae.me") + archaeme_remote = insert(:actor, preferred_username: "archaeme", domain: "archae.me") {text, mentions, []} = Formatter.linkify(text) assert length(mentions) == 3 expected_text = - "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme" + "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme" assert expected_text == text end test "gives a replacement for single-character local nicknames" do text = "@o hi" - _o = insert(:actor, preferred_username: "o") + o = insert(:actor, preferred_username: "o") {text, mentions, []} = Formatter.linkify(text) assert length(mentions) == 1 - expected_text = "@o hi" + expected_text = "@o hi" assert expected_text == text end diff --git a/test/mobilizon_web/api/report_test.exs b/test/mobilizon_web/api/report_test.exs index e2e80c08..db7d3f9b 100644 --- a/test/mobilizon_web/api/report_test.exs +++ b/test/mobilizon_web/api/report_test.exs @@ -14,7 +14,8 @@ defmodule MobilizonWeb.API.ReportTest do describe "reports" do test "creates a report on a event" do - %Actor{id: reporter_id, url: reporter_url} = insert(:actor) + %Actor{url: relay_reporter_url} = Mobilizon.Service.ActivityPub.Relay.get_actor() + %Actor{id: reporter_id} = insert(:actor) %Actor{id: reported_id, url: reported_url} = reported = insert(:actor) %Event{id: event_id, url: event_url} = _event = insert(:event, organizer_actor: reported) @@ -28,11 +29,11 @@ defmodule MobilizonWeb.API.ReportTest do content: comment, event_id: event_id, comments_ids: [], - local: true + forward: false }) assert %Activity{ - actor: ^reporter_url, + actor: ^relay_reporter_url, data: %{ "type" => "Flag", "cc" => [], @@ -43,7 +44,8 @@ defmodule MobilizonWeb.API.ReportTest do end test "creates a report on several comments" do - %Actor{id: reporter_id, url: reporter_url} = insert(:actor) + %Actor{url: relay_reporter_url} = Mobilizon.Service.ActivityPub.Relay.get_actor() + %Actor{id: reporter_id} = insert(:actor) %Actor{id: reported_id, url: reported_url} = reported = insert(:actor) %Comment{id: comment_1_id, url: comment_1_url} = @@ -64,20 +66,21 @@ defmodule MobilizonWeb.API.ReportTest do }) assert %Activity{ - actor: ^reporter_url, + actor: ^relay_reporter_url, data: %{ "type" => "Flag", "content" => ^comment, "object" => [^reported_url, ^comment_1_url, ^comment_2_url], - "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "to" => [], "cc" => [], - "actor" => ^reporter_url + "actor" => ^relay_reporter_url } } = flag_activity end test "creates a report that gets federated" do - %Actor{id: reporter_id, url: reporter_url} = insert(:actor) + %Actor{url: relay_reporter_url} = Mobilizon.Service.ActivityPub.Relay.get_actor() + %Actor{id: reporter_id} = insert(:actor) %Actor{id: reported_id, url: reported_url} = reported = insert(:actor) %Comment{id: comment_1_id, url: comment_1_url} = @@ -96,21 +99,21 @@ defmodule MobilizonWeb.API.ReportTest do content: comment, event_id: nil, comments_ids: [comment_1_id, comment_2_id], - local: false + forward: true }) assert %Activity{ - actor: ^reporter_url, + actor: ^relay_reporter_url, data: %{ "type" => "Flag", - "actor" => ^reporter_url, + "actor" => ^relay_reporter_url, "cc" => [^reported_url], "content" => ^encoded_comment, "object" => [^reported_url, ^comment_1_url, ^comment_2_url], - "to" => ["https://www.w3.org/ns/activitystreams#Public"] + "to" => [] }, local: true, - recipients: ["https://www.w3.org/ns/activitystreams#Public", ^reported_url] + recipients: [^reported_url] } = flag_activity end diff --git a/test/mobilizon_web/controllers/activity_pub_controller_test.exs b/test/mobilizon_web/controllers/activity_pub_controller_test.exs index 30e1f67a..6c1b9a3d 100644 --- a/test/mobilizon_web/controllers/activity_pub_controller_test.exs +++ b/test/mobilizon_web/controllers/activity_pub_controller_test.exs @@ -19,6 +19,11 @@ defmodule MobilizonWeb.ActivityPubControllerTest do alias MobilizonWeb.PageView alias MobilizonWeb.Router.Helpers, as: Routes + setup_all do + Mobilizon.Config.put([:instance, :federating], true) + :ok + end + setup do conn = build_conn() |> put_req_header("accept", "application/activity+json") {:ok, conn: conn} @@ -34,7 +39,10 @@ defmodule MobilizonWeb.ActivityPubControllerTest do actor = Actors.get_actor!(actor.id) - assert json_response(conn, 200) == ActorView.render("actor.json", %{actor: actor}) + assert json_response(conn, 200) == + ActorView.render("actor.json", %{actor: actor}) + |> Jason.encode!() + |> Jason.decode!() end end diff --git a/test/mobilizon_web/controllers/webfinger_controller_test.exs b/test/mobilizon_web/controllers/webfinger_controller_test.exs index e97db6f4..d980d1be 100644 --- a/test/mobilizon_web/controllers/webfinger_controller_test.exs +++ b/test/mobilizon_web/controllers/webfinger_controller_test.exs @@ -9,6 +9,11 @@ defmodule MobilizonWeb.WebFingerTest do alias Mobilizon.Service.WebFinger import Mobilizon.Factory + setup_all do + Mobilizon.Config.put([:instance, :federating], true) + :ok + end + test "GET /.well-known/host-meta", %{conn: conn} do conn = get(conn, "/.well-known/host-meta") diff --git a/test/mobilizon_web/plugs/federating_plug_test.exs b/test/mobilizon_web/plugs/federating_plug_test.exs new file mode 100644 index 00000000..74aa72ae --- /dev/null +++ b/test/mobilizon_web/plugs/federating_plug_test.exs @@ -0,0 +1,30 @@ +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule MobilizonWeb.Plug.FederatingTest do + use MobilizonWeb.ConnCase + + test "returns and halt the conn when federating is disabled" do + Mobilizon.Config.put([:instance, :federating], false) + + conn = + build_conn() + |> MobilizonWeb.Plugs.Federating.call(%{}) + + assert conn.status == 404 + assert conn.halted + end + + test "does nothing when federating is enabled" do + Mobilizon.Config.put([:instance, :federating], true) + + conn = + build_conn() + |> MobilizonWeb.Plugs.Federating.call(%{}) + + refute conn.status + refute conn.halted + end +end diff --git a/test/mobilizon_web/plugs/mapped_identity_to_signature_plug_test.exs b/test/mobilizon_web/plugs/mapped_identity_to_signature_plug_test.exs new file mode 100644 index 00000000..9af71da9 --- /dev/null +++ b/test/mobilizon_web/plugs/mapped_identity_to_signature_plug_test.exs @@ -0,0 +1,60 @@ +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule MobilizonWeb.Plugs.MappedSignatureToIdentityPlugTest do + use MobilizonWeb.ConnCase + use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney + alias MobilizonWeb.Plugs.MappedSignatureToIdentity + + defp set_signature(conn, key_id) do + conn + |> put_req_header("signature", "keyId=\"#{key_id}\"") + |> assign(:valid_signature, true) + end + + test "it successfully maps a valid identity with a valid signature" do + use_cassette "activity_pub/signature/valid" do + conn = + build_conn(:get, "/doesntmattter") + |> set_signature("https://framapiaf.org/users/admin") + |> MappedSignatureToIdentity.call(%{}) + + refute is_nil(conn.assigns.actor) + end + end + + test "it successfully maps a valid identity with a valid signature with payload" do + use_cassette "activity_pub/signature/valid_payload" do + conn = + build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"}) + |> set_signature("https://framapiaf.org/users/admin") + |> MappedSignatureToIdentity.call(%{}) + + refute is_nil(conn.assigns.actor) + end + end + + test "it considers a mapped identity to be invalid when it mismatches a payload" do + use_cassette "activity_pub/signature/invalid_payload" do + conn = + build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"}) + |> set_signature("https://niu.moe/users/rye") + |> MappedSignatureToIdentity.call(%{}) + + assert %{valid_signature: false} == conn.assigns + end + end + + test "it considers a mapped identity to be invalid when the identity cannot be found" do + use_cassette "activity_pub/signature/invalid_not_found" do + conn = + build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"}) + |> set_signature("http://niu.moe/users/rye") + |> MappedSignatureToIdentity.call(%{}) + + assert %{valid_signature: false} == conn.assigns + end + end +end diff --git a/test/mobilizon_web/resolvers/admin_resolver_test.exs b/test/mobilizon_web/resolvers/admin_resolver_test.exs index a6a079b3..7e6cf996 100644 --- a/test/mobilizon_web/resolvers/admin_resolver_test.exs +++ b/test/mobilizon_web/resolvers/admin_resolver_test.exs @@ -121,4 +121,99 @@ defmodule MobilizonWeb.Resolvers.AdminResolverTest do title end end + + describe "Resolver: Get the list of relay followers" do + test "test list_relay_followers/3 returns relay followers", %{conn: conn} do + %User{} = user_admin = insert(:user, role: :administrator) + + follower_actor = + insert(:actor, + domain: "localhost", + user: nil, + url: "http://localhost:8080/actor", + preferred_username: "instance_actor", + name: "I am an instance actor" + ) + + %Actor{} = relay_actor = Mobilizon.Service.ActivityPub.Relay.get_actor() + insert(:follower, actor: follower_actor, target_actor: relay_actor) + + query = """ + { + relayFollowers { + elements { + actor { + preferredUsername, + domain, + }, + approved + }, + total + } + } + """ + + res = + conn + |> auth_conn(user_admin) + |> AbsintheHelpers.graphql_query(query: query) + + assert is_nil(res["errors"]) + + assert hd(res["data"]["relayFollowers"]["elements"]) == %{ + "actor" => %{"preferredUsername" => "instance_actor", "domain" => "localhost"}, + "approved" => false + } + end + + test "test list_relay_followers/3 returns relay followings", %{conn: conn} do + %User{} = user_admin = insert(:user, role: :administrator) + + %Actor{ + preferred_username: following_actor_preferred_username, + domain: following_actor_domain + } = + following_actor = + insert(:actor, + domain: "localhost", + user: nil, + url: "http://localhost:8080/actor", + preferred_username: "instance_actor", + name: "I am an instance actor" + ) + + %Actor{} = relay_actor = Mobilizon.Service.ActivityPub.Relay.get_actor() + insert(:follower, actor: relay_actor, target_actor: following_actor) + + query = """ + { + relayFollowings { + elements { + targetActor { + preferredUsername, + domain, + }, + approved + }, + total + } + } + """ + + res = + conn + |> auth_conn(user_admin) + |> AbsintheHelpers.graphql_query(query: query) + + assert is_nil(res["errors"]) + + assert hd(res["data"]["relayFollowings"]["elements"]) == %{ + "targetActor" => %{ + "preferredUsername" => following_actor_preferred_username, + "domain" => following_actor_domain + }, + "approved" => false + } + end + end end diff --git a/test/mobilizon_web/resolvers/participant_resolver_test.exs b/test/mobilizon_web/resolvers/participant_resolver_test.exs index 5e063966..7aadfd4a 100644 --- a/test/mobilizon_web/resolvers/participant_resolver_test.exs +++ b/test/mobilizon_web/resolvers/participant_resolver_test.exs @@ -199,7 +199,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do user: user, actor: actor } do - event = insert(:event, %{organizer_actor: actor}) + event = + insert(:event, %{organizer_actor: actor, participant_stats: %{creator: 1, participant: 1}}) + insert(:participant, %{actor: actor, event: event, role: :creator}) user2 = insert(:user) actor2 = insert(:actor, user: user2) diff --git a/test/mobilizon_web/resolvers/person_resolver_test.exs b/test/mobilizon_web/resolvers/person_resolver_test.exs index b8cd88d7..e0e7ae60 100644 --- a/test/mobilizon_web/resolvers/person_resolver_test.exs +++ b/test/mobilizon_web/resolvers/person_resolver_test.exs @@ -3,6 +3,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do alias MobilizonWeb.AbsintheHelpers alias Mobilizon.Actors.Actor import Mobilizon.Factory + use Oban.Testing, repo: Mobilizon.Storage.Repo @non_existent_username "nonexistent" @@ -478,7 +479,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do "Cannot remove the last administrator of a group" end - test "delete_person/3 should delete a user identity", context do + test "delete_person/3 should delete an actor identity", context do user = insert(:user) %Actor{id: person_id} = insert(:actor, user: user, preferred_username: "riri") insert(:actor, user: user, preferred_username: "fifi") @@ -498,6 +499,13 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do assert json_response(res, 200)["errors"] == nil + assert_enqueued( + worker: Mobilizon.Service.Workers.BackgroundWorker, + args: %{"actor_id" => person_id, "op" => "delete_actor"} + ) + + assert %{success: 1, failure: 0} == Oban.drain_queue(:background) + query = """ { person(id: "#{person_id}") { diff --git a/test/support/abinthe_helpers.ex b/test/support/abinthe_helpers.ex index 33df27da..d9c8a0c1 100644 --- a/test/support/abinthe_helpers.ex +++ b/test/support/abinthe_helpers.ex @@ -25,7 +25,7 @@ defmodule MobilizonWeb.AbsintheHelpers do conn |> post( "/api", - build_query(options[:query], options[:variables]) + build_query(options[:query], Keyword.get(options, :variables, %{})) ) |> json_response(200) end diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs index 26ee7cb2..cdcedddb 100644 --- a/test/tasks/relay_test.exs +++ b/test/tasks/relay_test.exs @@ -14,14 +14,14 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do describe "running follow" do test "relay is followed" do use_cassette "relay/fetch_relay_follow" do - target_instance = "http://localhost:8080/actor" + target_instance = "mobilizon1.com" Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance]) local_actor = Relay.get_actor() assert local_actor.url =~ "/relay" - {:ok, target_actor} = Actors.get_actor_by_url(target_instance) + {:ok, target_actor} = Actors.get_actor_by_url("http://#{target_instance}/relay") refute is_nil(target_actor.domain) assert Actors.is_following(local_actor, target_actor) @@ -32,12 +32,15 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do describe "running unfollow" do test "relay is unfollowed" do use_cassette "relay/fetch_relay_unfollow" do - target_instance = "http://localhost:8080/actor" + target_instance = "mobilizon1.com" Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance]) %Actor{} = local_actor = Relay.get_actor() - {:ok, %Actor{} = target_actor} = Actors.get_actor_by_url(target_instance) + + {:ok, %Actor{} = target_actor} = + Actors.get_actor_by_url("http://#{target_instance}/relay") + assert %Follower{} = Actors.is_following(local_actor, target_actor) Mix.Tasks.Mobilizon.Relay.run(["unfollow", target_instance])