Merge branch 'fix-security-issues' into 'master'

Fix security issues

Closes #385 et #384

See merge request framasoft/mobilizon!599
This commit is contained in:
Thomas Citharel 2020-10-09 19:51:13 +02:00
commit 507877ab18
40 changed files with 1517 additions and 870 deletions

View File

@ -63,6 +63,7 @@ config :mobilizon, Mobilizon.Web.Upload,
Mobilizon.Web.Upload.Filter.Dedupe,
Mobilizon.Web.Upload.Filter.Optimize
],
allow_list_mime_types: ["image/gif", "image/jpeg", "image/png", "image/webp"],
link_name: true,
proxy_remote: false,
proxy_opts: [

View File

@ -1,8 +1,9 @@
<template>
<div id="mobilizon">
<NavBar />
<div class="container" v-if="config && config.demoMode">
<div v-if="config && config.demoMode">
<b-message
class="container"
type="is-danger"
:title="$t('Warning').toLocaleUpperCase()"
closable
@ -112,4 +113,14 @@ $mdi-font-path: "~@mdi/font/fonts";
@import "~@mdi/font/scss/materialdesignicons";
@import "common";
#mobilizon {
min-height: 100vh;
display: flex;
flex-direction: column;
main {
flex-grow: 1;
}
}
</style>

View File

@ -524,16 +524,23 @@ export default class EditorComponent extends Vue {
*/
async showImagePrompt(command: Function): Promise<void> {
const image = await listenFileUpload();
const { data } = await this.$apollo.mutate({
mutation: UPLOAD_PICTURE,
variables: {
file: image,
name: image.name,
actorId: this.currentActor.id,
},
});
if (data.uploadPicture && data.uploadPicture.url) {
command({ src: data.uploadPicture.url });
try {
const { data } = await this.$apollo.mutate({
mutation: UPLOAD_PICTURE,
variables: {
file: image,
name: image.name,
actorId: this.currentActor.id,
},
});
if (data.uploadPicture && data.uploadPicture.url) {
command({ src: data.uploadPicture.url });
}
} catch (error) {
console.error(error);
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);
}
}
}

View File

@ -71,8 +71,8 @@ export default class Image extends Node {
return false;
}
const images = Array.from(realEvent.dataTransfer.files).filter((file: any) =>
/image/i.test(file.type)
const images = Array.from(realEvent.dataTransfer.files).filter(
(file: any) => /image/i.test(file.type) && !/svg/i.test(file.type)
);
if (images.length === 0) {
@ -91,20 +91,25 @@ export default class Image extends Node {
const editorElem = document.getElementById("tiptab-editor");
const actorId = editorElem && editorElem.dataset.actorId;
images.forEach(async (image) => {
const { data } = await client.mutate({
mutation: UPLOAD_PICTURE,
variables: {
actorId,
file: image,
name: image.name,
},
try {
images.forEach(async (image) => {
const { data } = await client.mutate({
mutation: UPLOAD_PICTURE,
variables: {
actorId,
file: image,
name: image.name,
},
});
const node = schema.nodes.image.create({ src: data.uploadPicture.url });
const transaction = view.state.tr.insert(coordinates.pos, node);
view.dispatch(transaction);
});
const node = schema.nodes.image.create({ src: data.uploadPicture.url });
const transaction = view.state.tr.insert(coordinates.pos, node);
view.dispatch(transaction);
});
return true;
return true;
} catch (error) {
console.error(error);
return false;
}
},
},
},

49
js/src/mixins/group.ts Normal file
View File

@ -0,0 +1,49 @@
import { PERSON_MEMBERSHIPS, CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
import { FETCH_GROUP } from "@/graphql/group";
import { Group, IActor, IGroup, IPerson, MemberRole } from "@/types/actor";
import { Component, Vue } from "vue-property-decorator";
@Component({
apollo: {
group: {
query: FETCH_GROUP,
fetchPolicy: "cache-and-network",
variables() {
return {
name: this.$route.params.preferredUsername,
};
},
skip() {
return !this.$route.params.preferredUsername;
},
},
person: {
query: PERSON_MEMBERSHIPS,
fetchPolicy: "cache-and-network",
variables() {
return {
id: this.currentActor.id,
};
},
skip() {
return !this.currentActor || !this.currentActor.id;
},
},
currentActor: CURRENT_ACTOR_CLIENT,
},
})
export default class GroupMixin extends Vue {
group: IGroup = new Group();
currentActor!: IActor;
person!: IPerson;
get isCurrentActorAGroupAdmin(): boolean {
return (
this.person &&
this.person.memberships.elements.some(
({ parent: { id }, role }) => id === this.group.id && role === MemberRole.ADMINISTRATOR
)
);
}
}

View File

@ -275,7 +275,7 @@ export class EventModel implements IEvent {
this.title = hash.title;
this.slug = hash.slug;
this.description = hash.description;
this.description = hash.description || "";
this.beginsOn = new Date(hash.beginsOn);
if (hash.endsOn) this.endsOn = new Date(hash.endsOn);

View File

@ -1,6 +1,6 @@
<template>
<section>
<div class="container">
<div class="container" v-if="isCurrentActorOrganizer">
<h1 class="title" v-if="isUpdate === true">
{{ $t("Update event {name}", { name: event.title }) }}
</h1>
@ -255,6 +255,7 @@
aria-label="main navigation"
class="navbar"
:class="{ 'is-fixed-bottom': showFixedNavbar }"
v-if="isCurrentActorOrganizer"
>
<div class="container">
<div class="navbar-menu">
@ -511,6 +512,8 @@ export default class EditEvent extends Vue {
this.limitedPlaces = this.event.options.maximumAttendeeCapacity > 0;
if (!(this.isUpdate || this.isDuplicate)) {
this.initializeEvent();
} else {
this.event.description = this.event.description || "";
}
}
@ -533,11 +536,6 @@ export default class EditEvent extends Vue {
}
}
@Watch("currentActor")
setCurrentActor(): void {
this.event.organizerActor = this.currentActor;
}
@Watch("event")
setInitialData(): void {
if (this.isUpdate && this.unmodifiedEvent === undefined && this.event && this.event.uuid) {
@ -588,6 +586,7 @@ export default class EditEvent extends Vue {
} catch (err) {
this.saving = false;
console.error(err);
this.handleError(err);
}
}
@ -615,10 +614,18 @@ export default class EditEvent extends Vue {
});
} catch (err) {
this.saving = false;
console.error(err);
this.handleError(err);
}
}
get isCurrentActorOrganizer(): boolean {
return !(
this.eventId &&
this.event.organizerActor &&
this.currentActor.id !== this.event.organizerActor.id
) as boolean;
}
get updateEventMessage(): string {
if (this.unmodifiedEvent.draft && !this.event.draft)
return this.$i18n.t("The event has been updated and published") as string;
@ -627,6 +634,16 @@ export default class EditEvent extends Vue {
: this.$i18n.t("The event has been updated")) as string;
}
private handleError(err: any) {
console.error(err);
if (err.graphQLErrors !== undefined) {
err.graphQLErrors.forEach(({ message }: { message: string }) => {
this.$notifier.error(message);
});
}
}
/**
* Put in cache the updated or created event.
* If the event is not a draft anymore, also put in cache the participation
@ -709,6 +726,10 @@ export default class EditEvent extends Vue {
* Build variables for Event GraphQL creation query
*/
private async buildVariables() {
this.event.organizerActor =
this.event.organizerActor && this.event.organizerActor.id
? this.event.organizerActor
: this.currentActor;
let res = this.event.toEditJSON();
if (this.event.organizerActor) {
res = Object.assign(res, {

View File

@ -338,19 +338,9 @@
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { Component, Prop, Watch } from "vue-property-decorator";
import EventCard from "@/components/Event/EventCard.vue";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor";
import { FETCH_GROUP } from "@/graphql/group";
import {
IActor,
IGroup,
IPerson,
usernameWithDomain,
Group as GroupModel,
MemberRole,
IMember,
} from "@/types/actor";
import { IActor, usernameWithDomain, MemberRole, IMember } from "@/types/actor";
import Subtitle from "@/components/Utils/Subtitle.vue";
import CompactTodo from "@/components/Todo/CompactTodo.vue";
import EventMinimalistCard from "@/components/Event/EventMinimalistCard.vue";
@ -365,34 +355,14 @@ import { CONFIG } from "@/graphql/config";
import { CREATE_REPORT } from "@/graphql/report";
import { IReport } from "@/types/report.model";
import { IConfig } from "@/types/config.model";
import GroupMixin from "@/mixins/group";
import { mixins } from "vue-class-component";
import RouteName from "../../router/name";
import GroupSection from "../../components/Group/GroupSection.vue";
import ReportModal from "../../components/Report/ReportModal.vue";
@Component({
apollo: {
group: {
query: FETCH_GROUP,
fetchPolicy: "cache-and-network",
variables() {
return {
name: this.preferredUsername,
};
},
},
person: {
query: PERSON_MEMBERSHIPS,
fetchPolicy: "cache-and-network",
variables() {
return {
id: this.currentActor.id,
};
},
skip() {
return !this.currentActor || !this.currentActor.id;
},
},
currentActor: CURRENT_ACTOR_CLIENT,
config: CONFIG,
},
components: {
@ -425,15 +395,9 @@ import ReportModal from "../../components/Report/ReportModal.vue";
};
},
})
export default class Group extends Vue {
export default class Group extends mixins(GroupMixin) {
@Prop({ type: String, required: true }) preferredUsername!: string;
currentActor!: IActor;
person!: IPerson;
group: IGroup = new GroupModel();
config!: IConfig;
loading = true;
@ -550,15 +514,6 @@ export default class Group extends Vue {
);
}
get isCurrentActorAGroupAdmin(): boolean {
return (
this.person &&
this.person.memberships.elements.some(
({ parent: { id }, role }) => id === this.group.id && role === MemberRole.ADMINISTRATOR
)
);
}
/**
* New members, if on a different server,
* can take a while to refresh the group and fetch all private data

View File

@ -31,7 +31,7 @@
</li>
</ul>
</nav>
<section class="container section" v-if="group">
<section class="container section" v-if="group && isCurrentActorAGroupAdmin">
<form @submit.prevent="inviteMember">
<b-field :label="$t('Invite a new member')" custom-class="add-relay" horizontal>
<b-field
@ -171,42 +171,23 @@
</template>
</b-table>
</section>
<b-message v-else-if="group">
{{ $t("You are not an administrator for this group.") }}
</b-message>
</div>
</template>
<script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator";
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
import { Component, Watch } from "vue-property-decorator";
import GroupMixin from "@/mixins/group";
import { mixins } from "vue-class-component";
import RouteName from "../../router/name";
import { INVITE_MEMBER, GROUP_MEMBERS, REMOVE_MEMBER, UPDATE_MEMBER } from "../../graphql/member";
import { IGroup, IPerson, usernameWithDomain } from "../../types/actor";
import { IGroup, usernameWithDomain } from "../../types/actor";
import { IMember, MemberRole } from "../../types/actor/group.model";
@Component({
apollo: {
currentActor: CURRENT_ACTOR_CLIENT,
group: {
query: GROUP_MEMBERS,
fetchPolicy: "network-only",
variables() {
return {
name: this.$route.params.preferredUsername,
page: 1,
limit: this.MEMBERS_PER_PAGE,
roles: this.roles,
};
},
skip() {
return !this.$route.params.preferredUsername;
},
},
},
})
export default class GroupMembers extends Vue {
group!: IGroup;
currentActor!: IPerson;
@Component
export default class GroupMembers extends mixins(GroupMixin) {
loading = true;
newMemberUsername = "";

View File

@ -31,7 +31,7 @@
</li>
</ul>
</nav>
<section class="container section">
<section class="container section" v-if="isCurrentActorAGroupAdmin">
<form @submit.prevent="updateGroup">
<b-field :label="$t('Group name')">
<b-input v-model="group.name" />
@ -114,44 +114,32 @@
</div>
</form>
</section>
<b-message>
{{ $t("You are not an administrator for this group.") }}
</b-message>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Component } from "vue-property-decorator";
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
import { Route } from "vue-router";
import PictureUpload from "@/components/PictureUpload.vue";
import { mixins } from "vue-class-component";
import GroupMixin from "@/mixins/group";
import RouteName from "../../router/name";
import { FETCH_GROUP, UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group";
import { UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group";
import { IGroup, usernameWithDomain } from "../../types/actor";
import { Address, IAddress } from "../../types/address.model";
import { Group } from "../../types/actor/group.model";
@Component({
apollo: {
group: {
query: FETCH_GROUP,
fetchPolicy: "cache-and-network",
variables() {
return {
name: this.$route.params.preferredUsername,
};
},
skip() {
return !this.$route.params.preferredUsername;
},
},
},
components: {
FullAddressAutoComplete,
PictureUpload,
editor: () => import("../../components/Editor.vue"),
},
})
export default class GroupSettings extends Vue {
group: IGroup = new Group();
export default class GroupSettings extends mixins(GroupMixin) {
loading = true;
RouteName = RouteName;

View File

@ -23,8 +23,9 @@
</aside>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { IGroup } from "@/types/actor";
import { Component } from "vue-property-decorator";
import { mixins } from "vue-class-component";
import GroupMixin from "@/mixins/group";
import RouteName from "../../router/name";
import SettingMenuSection from "../../components/Settings/SettingMenuSection.vue";
import SettingMenuItem from "../../components/Settings/SettingMenuItem.vue";
@ -32,10 +33,8 @@ import SettingMenuItem from "../../components/Settings/SettingMenuItem.vue";
@Component({
components: { SettingMenuSection, SettingMenuItem },
})
export default class Settings extends Vue {
export default class Settings extends mixins(GroupMixin) {
RouteName = RouteName;
group!: IGroup[];
}
</script>

View File

@ -219,14 +219,16 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
def update_event(
_parent,
%{event_id: event_id} = args,
%{context: %{current_user: user}} = _resolution
%{context: %{current_user: %User{} = user}} = _resolution
) do
# See https://github.com/absinthe-graphql/absinthe/issues/490
with args <- Map.put(args, :options, args[:options] || %{}),
{:ok, %Event{} = event} <- Events.get_event_with_preload(event_id),
organizer_actor_id <- args |> Map.get(:organizer_actor_id, event.organizer_actor_id),
{:is_owned, %Actor{} = organizer_actor} <-
User.owns_actor(user, organizer_actor_id),
{:old_actor, {:is_owned, %Actor{}}} <-
{:old_actor, User.owns_actor(user, event.organizer_actor_id)},
new_organizer_actor_id <- args |> Map.get(:organizer_actor_id, event.organizer_actor_id),
{:new_actor, {:is_owned, %Actor{} = organizer_actor}} <-
{:new_actor, User.owns_actor(user, new_organizer_actor_id)},
args <- Map.put(args, :organizer_actor, organizer_actor),
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
API.Events.update_event(args, event) do
@ -235,8 +237,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:error, :event_not_found} ->
{:error, dgettext("errors", "Event not found")}
{:is_owned, nil} ->
{:error, dgettext("errors", "User doesn't own profile")}
{:old_actor, _} ->
{:error, dgettext("errors", "You can't edit this event.")}
{:new_actor, _} ->
{:error, dgettext("errors", "You can't attribute this event to this profile.")}
{:error, _, %Ecto.Changeset{} = error, _} ->
{:error, error}

View File

@ -145,11 +145,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
end
@doc """
Create a new group. The creator is automatically added as admin
Update a group. The creator is automatically added as admin
"""
def update_group(
_parent,
args,
%{id: group_id} = args,
%{
context: %{
current_user: %User{} = user
@ -157,6 +157,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
}
) do
with %Actor{} = updater_actor <- Users.get_actor_for_user(user),
{:administrator, true} <-
{:administrator, Actors.is_administrator?(updater_actor.id, group_id)},
args <- Map.put(args, :updater_actor, updater_actor),
args <- save_attached_pictures(args),
{:ok, _activity, %Actor{type: :Group} = group} <-
@ -166,8 +168,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:error, err} when is_binary(err) ->
{:error, err}
{:is_owned, nil} ->
{:error, dgettext("errors", "Creator profile is not owned by the current user")}
{:administrator, false} ->
{:error, dgettext("errors", "Profile is not administrator for the group")}
end
end

View File

@ -74,6 +74,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:error, :mime_type_not_allowed} ->
{:error, dgettext("errors", "File doesn't have an allowed MIME type.")}
error ->
{:error, error}
end

View File

@ -704,6 +704,22 @@ defmodule Mobilizon.Actors do
)
end
@spec is_moderator?(integer | String.t(), integer | String.t()) :: boolean()
def is_moderator?(actor_id, parent_id) do
match?(
{:ok, %Member{}},
get_member(actor_id, parent_id, @moderator_roles)
)
end
@spec is_administrator?(integer | String.t(), integer | String.t()) :: boolean()
def is_administrator?(actor_id, parent_id) do
match?(
{:ok, %Member{}},
get_member(actor_id, parent_id, @administrator_roles)
)
end
@doc """
Gets a single member of an actor (for example a group).
"""

View File

@ -38,8 +38,8 @@
<body class="error">
<main role="main" class="dialog">
<div class="error_illustration">
<img src="/static/img/mobilizon_logo.png" />
<!-- <img src="/static/img/error.png" alt="" width="500" /> -->
<img src="/img/mobilizon_logo.png" />
<!-- <img src="/img/error.png" alt="" width="500" /> -->
</div>
<div class="error__message">
<h1><%= gettext "We're sorry, but something went wrong on our end." %></h1>

View File

@ -126,6 +126,12 @@ defmodule Mobilizon.Web.Upload do
uploader: Keyword.get(opts, :uploader, Config.get([__MODULE__, :uploader])),
filters: Keyword.get(opts, :filters, Config.get([__MODULE__, :filters])),
description: Keyword.get(opts, :description),
allow_list_mime_types:
Keyword.get(
opts,
:allow_list_mime_types,
Config.get([__MODULE__, :allow_list_mime_types])
),
base_url:
Keyword.get(
opts,
@ -137,7 +143,8 @@ defmodule Mobilizon.Web.Upload do
defp prepare_upload(%Plug.Upload{} = file, opts) do
with {:ok, size} <- check_file_size(file.path, opts.size_limit),
{:ok, content_type, name} <- MIME.file_mime_type(file.path, file.filename) do
{:ok, content_type, name} <- MIME.file_mime_type(file.path, file.filename),
:ok <- check_allowed_mime_type(content_type, opts.allow_list_mime_types) do
{:ok,
%__MODULE__{
id: UUID.generate(),
@ -152,7 +159,8 @@ defmodule Mobilizon.Web.Upload do
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, content_type, name} <- MIME.file_mime_type(tmp_path, name),
:ok <- check_allowed_mime_type(content_type, opts.allow_list_mime_types) do
{:ok,
%__MODULE__{
id: UUID.generate(),
@ -207,4 +215,11 @@ defmodule Mobilizon.Web.Upload do
end
defp url_from_spec(_upload, _base_url, {:url, url}), do: url
@spec check_allowed_mime_type(String.t(), List.t()) :: :ok | {:error, :atom}
defp check_allowed_mime_type(content_type, allow_list_mime_types) do
if Enum.any?(allow_list_mime_types, &(&1 == content_type)),
do: :ok,
else: {:error, :mime_type_not_allowed}
end
end

View File

@ -124,17 +124,17 @@ msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
#: lib/graphql/resolvers/group.ex:139
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
#: lib/graphql/resolvers/group.ex:203
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
#: lib/graphql/resolvers/group.ex:207
msgid "Current profile is not an administrator of the selected group"
msgstr ""
@ -144,8 +144,8 @@ msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
#: lib/graphql/resolvers/group.ex:200 lib/graphql/resolvers/group.ex:248
#: lib/graphql/resolvers/group.ex:283 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
@ -165,7 +165,7 @@ msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
#: lib/graphql/resolvers/group.ex:280
msgid "Member not found"
msgstr ""
@ -188,7 +188,7 @@ msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:50 lib/graphql/resolvers/comment.ex:112
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/event.ex:286 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:245
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:157
#: lib/graphql/resolvers/person.ex:191 lib/graphql/resolvers/person.ex:256 lib/graphql/resolvers/person.ex:288
@ -259,17 +259,17 @@ msgid "User requested is not logged-in"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:252
#: lib/graphql/resolvers/group.ex:254
msgid "You are already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:285
#: lib/graphql/resolvers/group.ex:287
msgid "You can't leave this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:249
#: lib/graphql/resolvers/group.ex:251
msgid "You cannot join this group"
msgstr ""
@ -289,7 +289,7 @@ msgid "You need to be logged-in to change your password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:210
#: lib/graphql/resolvers/group.ex:212
msgid "You need to be logged-in to delete a group"
msgstr ""
@ -299,17 +299,17 @@ msgid "You need to be logged-in to delete your account"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:257
#: lib/graphql/resolvers/group.ex:259
msgid "You need to be logged-in to join a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:290
#: lib/graphql/resolvers/group.ex:292
msgid "You need to be logged-in to leave a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:175
#: lib/graphql/resolvers/group.ex:177
msgid "You need to be logged-in to update a group"
msgstr ""
@ -414,8 +414,8 @@ msgid "Event id not found"
msgstr ""
#, elixir-format
#: lib/graphql/error.ex:89 lib/graphql/resolvers/event.ex:236
#: lib/graphql/resolvers/event.ex:278
#: lib/graphql/error.ex:89 lib/graphql/resolvers/event.ex:238
#: lib/graphql/resolvers/event.ex:283
msgid "Event not found"
msgstr ""
@ -567,11 +567,6 @@ msgstr ""
msgid "Token is not a valid UUID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:239
msgid "User doesn't own profile"
msgstr ""
#, elixir-format
#: lib/graphql/error.ex:87 lib/graphql/resolvers/person.ex:323
msgid "User not found"
@ -644,7 +639,7 @@ msgid "You cannot delete this comment"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:274
#: lib/graphql/resolvers/event.ex:279
msgid "You cannot delete this event"
msgstr ""
@ -724,7 +719,7 @@ msgid "You need to be logged-in to create resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:286
#: lib/graphql/resolvers/event.ex:291
msgid "You need to be logged-in to delete an event"
msgstr ""
@ -749,7 +744,7 @@ msgid "You need to be logged-in to leave an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:247
#: lib/graphql/resolvers/event.ex:252
msgid "You need to be logged-in to update an event"
msgstr ""
@ -769,7 +764,7 @@ msgid "You need to be logged-in to view a resource preview"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:83
#: lib/graphql/resolvers/picture.ex:86
msgid "You need to login to upload a picture"
msgstr ""
@ -862,3 +857,23 @@ msgstr ""
#: lib/graphql/resolvers/member.ex:129
msgid "You can't reject this invitation with this profile."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:78
msgid "File doesn't have an allowed MIME type."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:172
msgid "Profile is not administrator for the group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:241
msgid "You can't edit this event."
msgstr ""
#, elixir-format, fuzzy
#: lib/graphql/resolvers/event.ex:244
msgid "You can't attribute this event to this profile."
msgstr ""

View File

@ -98,17 +98,17 @@ msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
#: lib/graphql/resolvers/group.ex:139
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
#: lib/graphql/resolvers/group.ex:203
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
#: lib/graphql/resolvers/group.ex:207
msgid "Current profile is not an administrator of the selected group"
msgstr ""
@ -118,8 +118,8 @@ msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
#: lib/graphql/resolvers/group.ex:200 lib/graphql/resolvers/group.ex:248
#: lib/graphql/resolvers/group.ex:283 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
@ -139,7 +139,7 @@ msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
#: lib/graphql/resolvers/group.ex:280
msgid "Member not found"
msgstr ""
@ -162,7 +162,7 @@ msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:50 lib/graphql/resolvers/comment.ex:112
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/event.ex:286 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:245
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:157
#: lib/graphql/resolvers/person.ex:191 lib/graphql/resolvers/person.ex:256 lib/graphql/resolvers/person.ex:288
@ -233,17 +233,17 @@ msgid "User requested is not logged-in"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:252
#: lib/graphql/resolvers/group.ex:254
msgid "You are already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:285
#: lib/graphql/resolvers/group.ex:287
msgid "You can't leave this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:249
#: lib/graphql/resolvers/group.ex:251
msgid "You cannot join this group"
msgstr ""
@ -263,7 +263,7 @@ msgid "You need to be logged-in to change your password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:210
#: lib/graphql/resolvers/group.ex:212
msgid "You need to be logged-in to delete a group"
msgstr ""
@ -273,17 +273,17 @@ msgid "You need to be logged-in to delete your account"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:257
#: lib/graphql/resolvers/group.ex:259
msgid "You need to be logged-in to join a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:290
#: lib/graphql/resolvers/group.ex:292
msgid "You need to be logged-in to leave a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:175
#: lib/graphql/resolvers/group.ex:177
msgid "You need to be logged-in to update a group"
msgstr ""
@ -388,8 +388,8 @@ msgid "Event id not found"
msgstr ""
#, elixir-format
#: lib/graphql/error.ex:89 lib/graphql/resolvers/event.ex:236
#: lib/graphql/resolvers/event.ex:278
#: lib/graphql/error.ex:89 lib/graphql/resolvers/event.ex:238
#: lib/graphql/resolvers/event.ex:283
msgid "Event not found"
msgstr ""
@ -541,11 +541,6 @@ msgstr ""
msgid "Token is not a valid UUID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:239
msgid "User doesn't own profile"
msgstr ""
#, elixir-format
#: lib/graphql/error.ex:87 lib/graphql/resolvers/person.ex:323
msgid "User not found"
@ -618,7 +613,7 @@ msgid "You cannot delete this comment"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:274
#: lib/graphql/resolvers/event.ex:279
msgid "You cannot delete this event"
msgstr ""
@ -698,7 +693,7 @@ msgid "You need to be logged-in to create resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:286
#: lib/graphql/resolvers/event.ex:291
msgid "You need to be logged-in to delete an event"
msgstr ""
@ -723,7 +718,7 @@ msgid "You need to be logged-in to leave an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:247
#: lib/graphql/resolvers/event.ex:252
msgid "You need to be logged-in to update an event"
msgstr ""
@ -743,7 +738,7 @@ msgid "You need to be logged-in to view a resource preview"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:83
#: lib/graphql/resolvers/picture.ex:86
msgid "You need to login to upload a picture"
msgstr ""
@ -836,3 +831,23 @@ msgstr ""
#: lib/graphql/resolvers/member.ex:129
msgid "You can't reject this invitation with this profile."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:78
msgid "File doesn't have an allowed MIME type."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:172
msgid "Profile is not administrator for the group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:241
msgid "You can't edit this event."
msgstr ""
#, elixir-format, fuzzy
#: lib/graphql/resolvers/event.ex:244
msgid "You can't attribute this event to this profile."
msgstr ""

View File

@ -92,17 +92,17 @@ msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
#: lib/graphql/resolvers/group.ex:139
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
#: lib/graphql/resolvers/group.ex:203
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
#: lib/graphql/resolvers/group.ex:207
msgid "Current profile is not an administrator of the selected group"
msgstr ""
@ -112,8 +112,8 @@ msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
#: lib/graphql/resolvers/group.ex:200 lib/graphql/resolvers/group.ex:248
#: lib/graphql/resolvers/group.ex:283 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
@ -133,7 +133,7 @@ msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
#: lib/graphql/resolvers/group.ex:280
msgid "Member not found"
msgstr ""
@ -156,7 +156,7 @@ msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:50 lib/graphql/resolvers/comment.ex:112
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/event.ex:286 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:245
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:157
#: lib/graphql/resolvers/person.ex:191 lib/graphql/resolvers/person.ex:256 lib/graphql/resolvers/person.ex:288
@ -227,17 +227,17 @@ msgid "User requested is not logged-in"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:252
#: lib/graphql/resolvers/group.ex:254
msgid "You are already a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:285
#: lib/graphql/resolvers/group.ex:287
msgid "You can't leave this group because you are the only administrator"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:249
#: lib/graphql/resolvers/group.ex:251
msgid "You cannot join this group"
msgstr ""
@ -257,7 +257,7 @@ msgid "You need to be logged-in to change your password"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:210
#: lib/graphql/resolvers/group.ex:212
msgid "You need to be logged-in to delete a group"
msgstr ""
@ -267,17 +267,17 @@ msgid "You need to be logged-in to delete your account"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:257
#: lib/graphql/resolvers/group.ex:259
msgid "You need to be logged-in to join a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:290
#: lib/graphql/resolvers/group.ex:292
msgid "You need to be logged-in to leave a group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:175
#: lib/graphql/resolvers/group.ex:177
msgid "You need to be logged-in to update a group"
msgstr ""
@ -382,8 +382,8 @@ msgid "Event id not found"
msgstr ""
#, elixir-format
#: lib/graphql/error.ex:89 lib/graphql/resolvers/event.ex:236
#: lib/graphql/resolvers/event.ex:278
#: lib/graphql/error.ex:89 lib/graphql/resolvers/event.ex:238
#: lib/graphql/resolvers/event.ex:283
msgid "Event not found"
msgstr ""
@ -535,11 +535,6 @@ msgstr ""
msgid "Token is not a valid UUID"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:239
msgid "User doesn't own profile"
msgstr ""
#, elixir-format
#: lib/graphql/error.ex:87 lib/graphql/resolvers/person.ex:323
msgid "User not found"
@ -612,7 +607,7 @@ msgid "You cannot delete this comment"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:274
#: lib/graphql/resolvers/event.ex:279
msgid "You cannot delete this event"
msgstr ""
@ -692,7 +687,7 @@ msgid "You need to be logged-in to create resources"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:286
#: lib/graphql/resolvers/event.ex:291
msgid "You need to be logged-in to delete an event"
msgstr ""
@ -717,7 +712,7 @@ msgid "You need to be logged-in to leave an event"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:247
#: lib/graphql/resolvers/event.ex:252
msgid "You need to be logged-in to update an event"
msgstr ""
@ -737,7 +732,7 @@ msgid "You need to be logged-in to view a resource preview"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:83
#: lib/graphql/resolvers/picture.ex:86
msgid "You need to login to upload a picture"
msgstr ""
@ -830,3 +825,23 @@ msgstr ""
#: lib/graphql/resolvers/member.ex:129
msgid "You can't reject this invitation with this profile."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/picture.ex:78
msgid "File doesn't have an allowed MIME type."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:172
msgid "Profile is not administrator for the group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/event.ex:241
msgid "You can't edit this event."
msgstr ""
#, elixir-format, fuzzy
#: lib/graphql/resolvers/event.ex:244
msgid "You can't attribute this event to this profile."
msgstr ""

View File

@ -98,17 +98,17 @@ msgid "Cannot refresh the token"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:139 lib/graphql/resolvers/group.ex:170
#: lib/graphql/resolvers/group.ex:139
msgid "Creator profile is not owned by the current user"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:201
#: lib/graphql/resolvers/group.ex:203
msgid "Current profile is not a member of this group"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:205
#: lib/graphql/resolvers/group.ex:207
msgid "Current profile is not an administrator of the selected group"
msgstr ""
@ -118,8 +118,8 @@ msgid "Error while saving user settings"
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:198 lib/graphql/resolvers/group.ex:246
#: lib/graphql/resolvers/group.ex:281 lib/graphql/resolvers/member.ex:80
#: lib/graphql/resolvers/group.ex:200 lib/graphql/resolvers/group.ex:248
#: lib/graphql/resolvers/group.ex:283 lib/graphql/resolvers/member.ex:80
msgid "Group not found"
msgstr ""
@ -139,7 +139,7 @@ msgid "Impossible to authenticate, either your email or password are invalid."
msgstr ""
#, elixir-format
#: lib/graphql/resolvers/group.ex:278
#: lib/graphql/resolvers/group.ex:280
msgid "Member not found"
msgstr ""
@ -162,7 +162,7 @@ msgstr ""
#, elixir-format
#: lib/graphql/resolvers/comment.ex:50 lib/graphql/resolvers/comment.ex:112
#: lib/graphql/resolvers/event.ex:281 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:243
#: lib/graphql/resolvers/event.ex:286 lib/graphql/resolvers/feed_token.ex:28 lib/graphql/resolvers/group.ex:245
#: lib/graphql/resolvers/member.ex:77 lib/graphql/resolvers/participant.ex:29
#: lib/graphql/resolvers/participant.ex:163 lib/graphql/resolvers/participant.ex:192 lib/graphql/resolvers/person.ex:157
#: lib/graphql/resolvers/person.ex:191 lib/graphql/resolvers/person.ex:256 lib/graphql/resolvers/person.ex:288