Merge branch 'usage-restrictions' into 'master'

add "only platform admin can create groups" and "only groups can create events" restrictions

See merge request framasoft/mobilizon!1078
This commit is contained in:
Thomas Citharel 2021-10-11 14:36:24 +00:00
commit db55859855
13 changed files with 135 additions and 34 deletions

View file

@ -40,9 +40,11 @@ config :mobilizon, :instance,
email_reply_to: "noreply@localhost"
config :mobilizon, :groups, enabled: 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
config :mobilizon, Mobilizon.Web.Endpoint,
url: [

View file

@ -44,6 +44,7 @@
"
>
<b-button
v-if="!hideCreateEventsButton"
tag="router-link"
:to="{ name: RouteName.CREATE_EVENT }"
type="is-primary"
@ -313,6 +314,10 @@ export default class NavBar extends Vue {
});
return changeIdentity(this.$apollo.provider.defaultClient, identity);
}
get hideCreateEventsButton(): boolean {
return !!this.config?.restrictions?.onlyGroupsCanCreateEvents;
}
}
</script>
<style lang="scss" scoped>

View file

@ -69,6 +69,10 @@ export const CONFIG = gql`
eventCreation
koenaConnect
}
restrictions {
onlyAdminCanCreateGroups
onlyGroupsCanCreateEvents
}
auth {
ldap
oauthProviders {

View file

@ -84,6 +84,10 @@ export interface IConfig {
groups: boolean;
koenaConnect: boolean;
};
restrictions: {
onlyAdminCanCreateGroups: boolean;
onlyGroupsCanCreateEvents: boolean;
};
federating: boolean;
version: string;
auth: {

View file

@ -14,6 +14,13 @@
</li>
</ul>
</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">
<b-switch v-model="local">{{ $t("Local") }}</b-switch>
<b-switch v-model="suspended">{{ $t("Suspended") }}</b-switch>
@ -100,6 +107,8 @@
</template>
<script lang="ts">
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 RouteName from "../../router/name";
import EmptyContent from "../../components/Utils/EmptyContent.vue";
@ -110,6 +119,7 @@ const PROFILES_PER_PAGE = 10;
@Component({
apollo: {
config: CONFIG,
groups: {
query: LIST_GROUPS,
variables() {
@ -139,6 +149,7 @@ export default class GroupProfiles extends Vue {
PROFILES_PER_PAGE = PROFILES_PER_PAGE;
config!: IConfig;
RouteName = RouteName;
async onPageChange(): Promise<void> {
@ -185,6 +196,10 @@ export default class GroupProfiles extends Vue {
this.pushRouter({ suspended: suspended ? "1" : "0" });
}
get showCreateGroupsButton(): boolean {
return !!this.config?.restrictions?.onlyAdminCanCreateGroups;
}
onFiltersChange({
preferredUsername,
domain,

View file

@ -10,7 +10,7 @@
)
}}
</p>
<div class="buttons">
<div class="buttons" v-if="!hideCreateEventButton">
<router-link
class="button is-primary"
:to="{ name: RouteName.CREATE_EVENT }"
@ -126,6 +126,8 @@
</template>
<script lang="ts">
import { CONFIG } from "../../graphql/config";
import { IConfig } from "../../types/config.model";
import { Component, Vue } from "vue-property-decorator";
import { ParticipantRole } from "@/types/enums";
import RouteName from "@/router/name";
@ -147,6 +149,7 @@ import Subtitle from "../../components/Utils/Subtitle.vue";
EventListCard,
},
apollo: {
config: CONFIG,
futureParticipations: {
query: LOGGED_USER_PARTICIPATIONS,
fetchPolicy: "cache-and-network",
@ -197,6 +200,8 @@ export default class MyEvents extends Vue {
limit = 10;
config!: IConfig;
futureParticipations: IParticipant[] = [];
hasMoreFutureParticipations = true;
@ -286,6 +291,10 @@ export default class MyEvents extends Vue {
(participation) => participation.event.id !== eventid
);
}
get hideCreateEventButton(): boolean {
return !!this.config?.restrictions?.onlyGroupsCanCreateEvents;
}
}
</script>

View file

@ -8,7 +8,7 @@
)
}}
</p>
<div class="buttons">
<div class="buttons" v-if="!hideCreateGroupButton">
<router-link
class="button is-primary"
:to="{ name: RouteName.CREATE_GROUP }"
@ -72,6 +72,8 @@
<script lang="ts">
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 { LEAVE_GROUP } from "@/graphql/group";
import GroupMemberCard from "@/components/Group/GroupMemberCard.vue";
@ -90,6 +92,9 @@ import RouteName from "../../router/name";
Invitations,
},
apollo: {
config: {
query: CONFIG,
},
membershipsPages: {
query: LOGGED_USER_MEMBERSHIPS,
fetchPolicy: "cache-and-network",
@ -114,6 +119,8 @@ export default class MyGroups extends Vue {
RouteName = RouteName;
config!: IConfig;
page = 1;
limit = 10;
@ -177,6 +184,10 @@ export default class MyGroups extends Vue {
![MemberRole.INVITED, MemberRole.REJECTED].includes(member.role)
);
}
get hideCreateGroupButton(): boolean {
return !!this.config?.restrictions?.onlyAdminCanCreateGroups;
}
}
</script>

View file

@ -56,6 +56,11 @@ export const configMock = {
groups: true,
koenaConnect: false,
},
restrictions: {
__typename: "Restrictions",
onlyAdminCanCreateGroups: false,
onlyGroupsCanCreateEvents: false,
},
geocoding: {
__typename: "Geocoding",
autocomplete: true,

View file

@ -134,6 +134,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
event_creation: Config.instance_event_creation_enabled?(),
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(),
version: Config.instance_version(),
federating: Config.instance_federating(),

View file

@ -265,29 +265,33 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
%{context: %{current_user: user}} = _resolution
) do
# See https://github.com/absinthe-graphql/absinthe/issues/490
with {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
args <- Map.put(args, :options, args[:options] || %{}),
{: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}
if Config.only_groups_can_create_events?() and Map.get(args, :attributed_to_id) == nil do
{:error, "only groups can create events"}
else
{:group_check, false} ->
{:error,
dgettext(
"errors",
"Organizer profile doesn't have permission to create an event on behalf of this group"
)}
with {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
args <- Map.put(args, :options, args[:options] || %{}),
{: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
{:group_check, false} ->
{:error,
dgettext(
"errors",
"Organizer profile doesn't have permission to create an event on behalf of this group"
)}
{:is_owned, nil} ->
{:error, dgettext("errors", "Organizer profile is not owned by the user")}
{:is_owned, nil} ->
{:error, dgettext("errors", "Organizer profile is not owned by the user")}
{:error, _, %Ecto.Changeset{} = error, _} ->
{:error, error}
{:error, _, %Ecto.Changeset{} = error, _} ->
{:error, error}
{:error, %Ecto.Changeset{} = error} ->
{:error, error}
{:error, %Ecto.Changeset{} = error} ->
{:error, error}
end
end
end

View file

@ -4,6 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
"""
import Mobilizon.Users.Guards
alias Mobilizon.Config
alias Mobilizon.{Actors, Events}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub.Actions
@ -137,23 +138,29 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
args,
%{
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
with args when is_map(args) <- 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}
if Config.only_admin_can_create_groups?() and not is_admin(role) do
{:error, "only admins can create groups"}
else
{:picture, {:error, :file_too_large}} ->
{:error, dgettext("errors", "The provided picture is too heavy")}
with args when is_map(args) <-
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}
{:error, err} when is_binary(err) ->
{:error, err}
end
end
end

View file

@ -37,6 +37,7 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
field(:timezones, list_of(:string), description: "The instance's available timezones")
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(: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")
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 """
The instance's auth configuration
"""

View file

@ -288,6 +288,9 @@ defmodule Mobilizon.Config do
end
end
# config :mobilizon, :groups, enabled: true
# config :mobilizon, :events, creation: true
@spec instance_group_feature_enabled? :: boolean
def instance_group_feature_enabled?,
do: :mobilizon |> Application.get_env(:groups) |> Keyword.get(:enabled)
@ -303,6 +306,20 @@ defmodule Mobilizon.Config do
}
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
def anonymous_actor_id, do: get_cached_value(:anonymous_actor_id)
@spec relay_actor_id :: integer