mirror of
https://framagit.org/framasoft/mobilizon.git
synced 2024-12-21 23:44:30 +00:00
add "only platform admin can create groups" and "only groups can create events" restrictions
This commit is contained in:
parent
7885151220
commit
7940d69d5a
13 changed files with 135 additions and 34 deletions
|
@ -40,9 +40,11 @@ config :mobilizon, :instance,
|
||||||
email_reply_to: "noreply@localhost"
|
email_reply_to: "noreply@localhost"
|
||||||
|
|
||||||
config :mobilizon, :groups, enabled: true
|
config :mobilizon, :groups, enabled: true
|
||||||
|
|
||||||
config :mobilizon, :events, creation: true
|
config :mobilizon, :events, creation: true
|
||||||
|
|
||||||
|
config :mobilizon, :restrictions, only_admin_can_create_groups: false
|
||||||
|
config :mobilizon, :restrictions, only_groups_can_create_events: false
|
||||||
|
|
||||||
# Configures the endpoint
|
# Configures the endpoint
|
||||||
config :mobilizon, Mobilizon.Web.Endpoint,
|
config :mobilizon, Mobilizon.Web.Endpoint,
|
||||||
url: [
|
url: [
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<b-button
|
<b-button
|
||||||
|
v-if="!hideCreateEventsButton"
|
||||||
tag="router-link"
|
tag="router-link"
|
||||||
:to="{ name: RouteName.CREATE_EVENT }"
|
:to="{ name: RouteName.CREATE_EVENT }"
|
||||||
type="is-primary"
|
type="is-primary"
|
||||||
|
@ -313,6 +314,10 @@ export default class NavBar extends Vue {
|
||||||
});
|
});
|
||||||
return changeIdentity(this.$apollo.provider.defaultClient, identity);
|
return changeIdentity(this.$apollo.provider.defaultClient, identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hideCreateEventsButton(): boolean {
|
||||||
|
return !!this.config?.restrictions?.onlyGroupsCanCreateEvents;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -69,6 +69,10 @@ export const CONFIG = gql`
|
||||||
eventCreation
|
eventCreation
|
||||||
koenaConnect
|
koenaConnect
|
||||||
}
|
}
|
||||||
|
restrictions {
|
||||||
|
onlyAdminCanCreateGroups
|
||||||
|
onlyGroupsCanCreateEvents
|
||||||
|
}
|
||||||
auth {
|
auth {
|
||||||
ldap
|
ldap
|
||||||
oauthProviders {
|
oauthProviders {
|
||||||
|
|
|
@ -84,6 +84,10 @@ export interface IConfig {
|
||||||
groups: boolean;
|
groups: boolean;
|
||||||
koenaConnect: boolean;
|
koenaConnect: boolean;
|
||||||
};
|
};
|
||||||
|
restrictions: {
|
||||||
|
onlyAdminCanCreateGroups: boolean;
|
||||||
|
onlyGroupsCanCreateEvents: boolean;
|
||||||
|
};
|
||||||
federating: boolean;
|
federating: boolean;
|
||||||
version: string;
|
version: string;
|
||||||
auth: {
|
auth: {
|
||||||
|
|
|
@ -14,6 +14,13 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div class="buttons" v-if="showCreateGroupsButton">
|
||||||
|
<router-link
|
||||||
|
class="button is-primary"
|
||||||
|
:to="{ name: RouteName.CREATE_GROUP }"
|
||||||
|
>{{ $t("Create group") }}</router-link
|
||||||
|
>
|
||||||
|
</div>
|
||||||
<div v-if="groups">
|
<div v-if="groups">
|
||||||
<b-switch v-model="local">{{ $t("Local") }}</b-switch>
|
<b-switch v-model="local">{{ $t("Local") }}</b-switch>
|
||||||
<b-switch v-model="suspended">{{ $t("Suspended") }}</b-switch>
|
<b-switch v-model="suspended">{{ $t("Suspended") }}</b-switch>
|
||||||
|
@ -100,6 +107,8 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
|
import { CONFIG } from "@/graphql/config";
|
||||||
|
import { IConfig } from "@/types/config.model";
|
||||||
import { LIST_GROUPS } from "@/graphql/group";
|
import { LIST_GROUPS } from "@/graphql/group";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import EmptyContent from "../../components/Utils/EmptyContent.vue";
|
import EmptyContent from "../../components/Utils/EmptyContent.vue";
|
||||||
|
@ -110,6 +119,7 @@ const PROFILES_PER_PAGE = 10;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
apollo: {
|
apollo: {
|
||||||
|
config: CONFIG,
|
||||||
groups: {
|
groups: {
|
||||||
query: LIST_GROUPS,
|
query: LIST_GROUPS,
|
||||||
variables() {
|
variables() {
|
||||||
|
@ -139,6 +149,7 @@ export default class GroupProfiles extends Vue {
|
||||||
|
|
||||||
PROFILES_PER_PAGE = PROFILES_PER_PAGE;
|
PROFILES_PER_PAGE = PROFILES_PER_PAGE;
|
||||||
|
|
||||||
|
config!: IConfig;
|
||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
async onPageChange(): Promise<void> {
|
async onPageChange(): Promise<void> {
|
||||||
|
@ -185,6 +196,10 @@ export default class GroupProfiles extends Vue {
|
||||||
this.pushRouter({ suspended: suspended ? "1" : "0" });
|
this.pushRouter({ suspended: suspended ? "1" : "0" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showCreateGroupsButton(): boolean {
|
||||||
|
return !!this.config?.restrictions?.onlyAdminCanCreateGroups;
|
||||||
|
}
|
||||||
|
|
||||||
onFiltersChange({
|
onFiltersChange({
|
||||||
preferredUsername,
|
preferredUsername,
|
||||||
domain,
|
domain,
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
<div class="buttons">
|
<div class="buttons" v-if="!hideCreateEventButton">
|
||||||
<router-link
|
<router-link
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
:to="{ name: RouteName.CREATE_EVENT }"
|
:to="{ name: RouteName.CREATE_EVENT }"
|
||||||
|
@ -126,6 +126,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { CONFIG } from "../../graphql/config";
|
||||||
|
import { IConfig } from "../../types/config.model";
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
import { ParticipantRole } from "@/types/enums";
|
import { ParticipantRole } from "@/types/enums";
|
||||||
import RouteName from "@/router/name";
|
import RouteName from "@/router/name";
|
||||||
|
@ -147,6 +149,7 @@ import Subtitle from "../../components/Utils/Subtitle.vue";
|
||||||
EventListCard,
|
EventListCard,
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
|
config: CONFIG,
|
||||||
futureParticipations: {
|
futureParticipations: {
|
||||||
query: LOGGED_USER_PARTICIPATIONS,
|
query: LOGGED_USER_PARTICIPATIONS,
|
||||||
fetchPolicy: "cache-and-network",
|
fetchPolicy: "cache-and-network",
|
||||||
|
@ -197,6 +200,8 @@ export default class MyEvents extends Vue {
|
||||||
|
|
||||||
limit = 10;
|
limit = 10;
|
||||||
|
|
||||||
|
config!: IConfig;
|
||||||
|
|
||||||
futureParticipations: IParticipant[] = [];
|
futureParticipations: IParticipant[] = [];
|
||||||
|
|
||||||
hasMoreFutureParticipations = true;
|
hasMoreFutureParticipations = true;
|
||||||
|
@ -286,6 +291,10 @@ export default class MyEvents extends Vue {
|
||||||
(participation) => participation.event.id !== eventid
|
(participation) => participation.event.id !== eventid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hideCreateEventButton(): boolean {
|
||||||
|
return !!this.config?.restrictions?.onlyGroupsCanCreateEvents;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
<div class="buttons">
|
<div class="buttons" v-if="!hideCreateGroupButton">
|
||||||
<router-link
|
<router-link
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
:to="{ name: RouteName.CREATE_GROUP }"
|
:to="{ name: RouteName.CREATE_GROUP }"
|
||||||
|
@ -72,6 +72,8 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
|
import { CONFIG } from "@/graphql/config";
|
||||||
|
import { IConfig } from "@/types/config.model";
|
||||||
import { LOGGED_USER_MEMBERSHIPS } from "@/graphql/actor";
|
import { LOGGED_USER_MEMBERSHIPS } from "@/graphql/actor";
|
||||||
import { LEAVE_GROUP } from "@/graphql/group";
|
import { LEAVE_GROUP } from "@/graphql/group";
|
||||||
import GroupMemberCard from "@/components/Group/GroupMemberCard.vue";
|
import GroupMemberCard from "@/components/Group/GroupMemberCard.vue";
|
||||||
|
@ -90,6 +92,9 @@ import RouteName from "../../router/name";
|
||||||
Invitations,
|
Invitations,
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
|
config: {
|
||||||
|
query: CONFIG,
|
||||||
|
},
|
||||||
membershipsPages: {
|
membershipsPages: {
|
||||||
query: LOGGED_USER_MEMBERSHIPS,
|
query: LOGGED_USER_MEMBERSHIPS,
|
||||||
fetchPolicy: "cache-and-network",
|
fetchPolicy: "cache-and-network",
|
||||||
|
@ -114,6 +119,8 @@ export default class MyGroups extends Vue {
|
||||||
|
|
||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
|
config!: IConfig;
|
||||||
|
|
||||||
page = 1;
|
page = 1;
|
||||||
|
|
||||||
limit = 10;
|
limit = 10;
|
||||||
|
@ -177,6 +184,10 @@ export default class MyGroups extends Vue {
|
||||||
![MemberRole.INVITED, MemberRole.REJECTED].includes(member.role)
|
![MemberRole.INVITED, MemberRole.REJECTED].includes(member.role)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hideCreateGroupButton(): boolean {
|
||||||
|
return !!this.config?.restrictions?.onlyAdminCanCreateGroups;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,11 @@ export const configMock = {
|
||||||
groups: true,
|
groups: true,
|
||||||
koenaConnect: false,
|
koenaConnect: false,
|
||||||
},
|
},
|
||||||
|
restrictions: {
|
||||||
|
__typename: "Restrictions",
|
||||||
|
onlyAdminCanCreateGroups: false,
|
||||||
|
onlyGroupsCanCreateEvents: false,
|
||||||
|
},
|
||||||
geocoding: {
|
geocoding: {
|
||||||
__typename: "Geocoding",
|
__typename: "Geocoding",
|
||||||
autocomplete: true,
|
autocomplete: true,
|
||||||
|
|
|
@ -134,6 +134,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||||
event_creation: Config.instance_event_creation_enabled?(),
|
event_creation: Config.instance_event_creation_enabled?(),
|
||||||
koena_connect: Config.get([:instance, :koena_connect_link], false)
|
koena_connect: Config.get([:instance, :koena_connect_link], false)
|
||||||
},
|
},
|
||||||
|
restrictions: %{
|
||||||
|
only_admin_can_create_groups: Config.only_admin_can_create_groups?(),
|
||||||
|
only_groups_can_create_events: Config.only_groups_can_create_events?()
|
||||||
|
},
|
||||||
rules: Config.instance_rules(),
|
rules: Config.instance_rules(),
|
||||||
version: Config.instance_version(),
|
version: Config.instance_version(),
|
||||||
federating: Config.instance_federating(),
|
federating: Config.instance_federating(),
|
||||||
|
|
|
@ -265,29 +265,33 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||||
%{context: %{current_user: user}} = _resolution
|
%{context: %{current_user: user}} = _resolution
|
||||||
) do
|
) do
|
||||||
# See https://github.com/absinthe-graphql/absinthe/issues/490
|
# See https://github.com/absinthe-graphql/absinthe/issues/490
|
||||||
with {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
|
if Config.only_groups_can_create_events?() and Map.get(args, :attributed_to_id) == nil do
|
||||||
args <- Map.put(args, :options, args[:options] || %{}),
|
{:error, "only groups can create events"}
|
||||||
{:group_check, true} <- {:group_check, is_organizer_group_member?(args)},
|
|
||||||
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
|
|
||||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
|
||||||
API.Events.create_event(args_with_organizer) do
|
|
||||||
{:ok, event}
|
|
||||||
else
|
else
|
||||||
{:group_check, false} ->
|
with {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
|
||||||
{:error,
|
args <- Map.put(args, :options, args[:options] || %{}),
|
||||||
dgettext(
|
{:group_check, true} <- {:group_check, is_organizer_group_member?(args)},
|
||||||
"errors",
|
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
|
||||||
"Organizer profile doesn't have permission to create an event on behalf of this group"
|
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||||
)}
|
API.Events.create_event(args_with_organizer) do
|
||||||
|
{:ok, event}
|
||||||
|
else
|
||||||
|
{:group_check, false} ->
|
||||||
|
{:error,
|
||||||
|
dgettext(
|
||||||
|
"errors",
|
||||||
|
"Organizer profile doesn't have permission to create an event on behalf of this group"
|
||||||
|
)}
|
||||||
|
|
||||||
{:is_owned, nil} ->
|
{:is_owned, nil} ->
|
||||||
{:error, dgettext("errors", "Organizer profile is not owned by the user")}
|
{:error, dgettext("errors", "Organizer profile is not owned by the user")}
|
||||||
|
|
||||||
{:error, _, %Ecto.Changeset{} = error, _} ->
|
{:error, _, %Ecto.Changeset{} = error, _} ->
|
||||||
{:error, error}
|
{:error, error}
|
||||||
|
|
||||||
{:error, %Ecto.Changeset{} = error} ->
|
{:error, %Ecto.Changeset{} = error} ->
|
||||||
{:error, error}
|
{:error, error}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Mobilizon.Users.Guards
|
import Mobilizon.Users.Guards
|
||||||
|
alias Mobilizon.Config
|
||||||
alias Mobilizon.{Actors, Events}
|
alias Mobilizon.{Actors, Events}
|
||||||
alias Mobilizon.Actors.{Actor, Member}
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
alias Mobilizon.Federation.ActivityPub.Actions
|
alias Mobilizon.Federation.ActivityPub.Actions
|
||||||
|
@ -137,23 +138,29 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||||
args,
|
args,
|
||||||
%{
|
%{
|
||||||
context: %{
|
context: %{
|
||||||
current_actor: %Actor{id: creator_actor_id} = creator_actor
|
current_actor: %Actor{id: creator_actor_id} = creator_actor,
|
||||||
|
current_user: %User{role: role} = _resolution
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) do
|
) do
|
||||||
with args when is_map(args) <- Map.update(args, :preferred_username, "", &String.downcase/1),
|
if Config.only_admin_can_create_groups?() and not is_admin(role) do
|
||||||
args when is_map(args) <- Map.put(args, :creator_actor, creator_actor),
|
{:error, "only admins can create groups"}
|
||||||
args when is_map(args) <- Map.put(args, :creator_actor_id, creator_actor_id),
|
|
||||||
{:picture, args} when is_map(args) <- {:picture, save_attached_pictures(args)},
|
|
||||||
{:ok, _activity, %Actor{type: :Group} = group} <-
|
|
||||||
API.Groups.create_group(args) do
|
|
||||||
{:ok, group}
|
|
||||||
else
|
else
|
||||||
{:picture, {:error, :file_too_large}} ->
|
with args when is_map(args) <-
|
||||||
{:error, dgettext("errors", "The provided picture is too heavy")}
|
Map.update(args, :preferred_username, "", &String.downcase/1),
|
||||||
|
args when is_map(args) <- Map.put(args, :creator_actor, creator_actor),
|
||||||
|
args when is_map(args) <- Map.put(args, :creator_actor_id, creator_actor_id),
|
||||||
|
{:picture, args} when is_map(args) <- {:picture, save_attached_pictures(args)},
|
||||||
|
{:ok, _activity, %Actor{type: :Group} = group} <-
|
||||||
|
API.Groups.create_group(args) do
|
||||||
|
{:ok, group}
|
||||||
|
else
|
||||||
|
{:picture, {:error, :file_too_large}} ->
|
||||||
|
{:error, dgettext("errors", "The provided picture is too heavy")}
|
||||||
|
|
||||||
{:error, err} when is_binary(err) ->
|
{:error, err} when is_binary(err) ->
|
||||||
{:error, err}
|
{:error, err}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
||||||
|
|
||||||
field(:timezones, list_of(:string), description: "The instance's available timezones")
|
field(:timezones, list_of(:string), description: "The instance's available timezones")
|
||||||
field(:features, :features, description: "The instance's features")
|
field(:features, :features, description: "The instance's features")
|
||||||
|
field(:restrictions, :restrictions, description: "The instance's restrictions")
|
||||||
field(:version, :string, description: "The instance's version")
|
field(:version, :string, description: "The instance's version")
|
||||||
field(:federating, :boolean, description: "Whether this instance is federation")
|
field(:federating, :boolean, description: "Whether this instance is federation")
|
||||||
|
|
||||||
|
@ -275,6 +276,19 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
||||||
field(:koena_connect, :boolean, description: "Activate link to Koena Connect")
|
field(:koena_connect, :boolean, description: "Activate link to Koena Connect")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@desc """
|
||||||
|
The instance's restrictions
|
||||||
|
"""
|
||||||
|
object :restrictions do
|
||||||
|
field(:only_admin_can_create_groups, :boolean,
|
||||||
|
description: "Whether groups creation is allowed only for admin, not for all users"
|
||||||
|
)
|
||||||
|
|
||||||
|
field(:only_groups_can_create_events, :boolean,
|
||||||
|
description: "Whether events creation is allowed only for groups, not for persons"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
@desc """
|
@desc """
|
||||||
The instance's auth configuration
|
The instance's auth configuration
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -288,6 +288,9 @@ defmodule Mobilizon.Config do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# config :mobilizon, :groups, enabled: true
|
||||||
|
# config :mobilizon, :events, creation: true
|
||||||
|
|
||||||
@spec instance_group_feature_enabled? :: boolean
|
@spec instance_group_feature_enabled? :: boolean
|
||||||
def instance_group_feature_enabled?,
|
def instance_group_feature_enabled?,
|
||||||
do: :mobilizon |> Application.get_env(:groups) |> Keyword.get(:enabled)
|
do: :mobilizon |> Application.get_env(:groups) |> Keyword.get(:enabled)
|
||||||
|
@ -303,6 +306,20 @@ defmodule Mobilizon.Config do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec only_admin_can_create_groups? :: boolean
|
||||||
|
def only_admin_can_create_groups?,
|
||||||
|
do:
|
||||||
|
:mobilizon
|
||||||
|
|> Application.get_env(:restrictions)
|
||||||
|
|> Keyword.get(:only_admin_can_create_groups)
|
||||||
|
|
||||||
|
@spec only_groups_can_create_events? :: boolean
|
||||||
|
def only_groups_can_create_events?,
|
||||||
|
do:
|
||||||
|
:mobilizon
|
||||||
|
|> Application.get_env(:restrictions)
|
||||||
|
|> Keyword.get(:only_groups_can_create_events)
|
||||||
|
|
||||||
@spec anonymous_actor_id :: integer
|
@spec anonymous_actor_id :: integer
|
||||||
def anonymous_actor_id, do: get_cached_value(:anonymous_actor_id)
|
def anonymous_actor_id, do: get_cached_value(:anonymous_actor_id)
|
||||||
@spec relay_actor_id :: integer
|
@spec relay_actor_id :: integer
|
||||||
|
|
Loading…
Reference in a new issue