Add anonymous and remote participations

This commit is contained in:
Thomas Citharel
2019-12-20 13:04:34 +01:00
parent 17e0b3968f
commit 2ed9050a90
135 changed files with 10141 additions and 2271 deletions

View File

@@ -5,18 +5,18 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
import Mobilizon.Users.Guards
alias Mobilizon.Actors
alias Mobilizon.{Actors, Admin, Config, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Admin.ActionLog
alias Mobilizon.Events
alias Mobilizon.Admin.{ActionLog, Setting}
alias Mobilizon.Config
alias Mobilizon.Events.{Comment, Event}
alias Mobilizon.Events.{Comment, Event}
alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Reports.{Note, Report}
alias Mobilizon.Service.Statistics
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
alias Mobilizon.Federation.ActivityPub.Relay
def list_action_logs(
_parent,
%{page: page, limit: limit},
@@ -132,6 +132,43 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
{:error, "You need to be logged-in and an administrator to access dashboard statistics"}
end
def get_settings(_parent, _args, %{
context: %{current_user: %User{role: role}}
})
when is_admin(role) do
{:ok,
%{
instance_description: Config.instance_description(),
instance_name: Config.instance_name(),
registrations_open: Config.instance_registrations_open?(),
instance_terms: Config.instance_terms(),
instance_terms_type: Config.instance_terms_type(),
instance_terms_url: Config.instance_terms_url()
}}
end
def get_settings(_parent, _args, _resolution) do
{:error, "You need to be logged-in and an administrator to access admin settings"}
end
def save_settings(_parent, args, %{
context: %{current_user: %User{role: role}}
})
when is_admin(role) do
with {:ok, res} <- Admin.save_settings("instance", args) do
res =
res |> Enum.map(fn {key, %Setting{value: value}} -> {key, value} end) |> Enum.into(%{})
Config.clear_config_cache()
{:ok, res}
end
end
def save_settings(_parent, _args, _resolution) do
{:error, "You need to be logged-in and an administrator to save admin settings"}
end
def list_relay_followers(
_parent,
%{page: page, limit: limit},

View File

@@ -25,25 +25,82 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
_ -> nil
end
{:ok,
%{
name: Config.instance_name(),
registrations_open: Config.instance_registrations_open?(),
registrations_whitelist: Config.instance_registrations_whitelist?(),
demo_mode: Config.instance_demo_mode?(),
description: Config.instance_description(),
location: location,
country_code: country_code,
geocoding: %{
provider: Config.instance_geocoding_provider(),
autocomplete: Config.instance_geocoding_autocomplete()
},
maps: %{
tiles: %{
endpoint: Config.instance_maps_tiles_endpoint(),
attribution: Config.instance_maps_tiles_attribution()
}
}
}}
data = Map.merge(config_cache(), %{location: location, country_code: country_code})
{:ok, data}
end
def terms(_parent, %{locale: locale}, _resolution) do
type = Config.instance_terms_type()
{url, body_html} =
case type do
"URL" -> {Config.instance_terms_url(), nil}
"DEFAULT" -> {nil, Config.generate_terms(locale)}
_ -> {nil, Config.instance_terms(locale)}
end
{:ok, %{body_html: body_html, type: type, url: url}}
end
defp config_cache do
case Cachex.fetch(:config, "full_config", fn _key ->
case build_config_cache() do
value when not is_nil(value) -> {:commit, value}
err -> {:ignore, err}
end
end) do
{status, value} when status in [:ok, :commit] -> value
_err -> nil
end
end
defp build_config_cache do
%{
name: Config.instance_name(),
registrations_open: Config.instance_registrations_open?(),
registrations_whitelist: Config.instance_registrations_whitelist?(),
demo_mode: Config.instance_demo_mode?(),
description: Config.instance_description(),
anonymous: %{
participation: %{
allowed: Config.anonymous_participation?(),
validation: %{
email: %{
enabled: Config.anonymous_participation_email_required?(),
confirmation_required:
Config.anonymous_event_creation_email_confirmation_required?()
},
captcha: %{
enabled: Config.anonymous_event_creation_email_captcha_required?()
}
}
},
event_creation: %{
allowed: Config.anonymous_event_creation?(),
validation: %{
email: %{
enabled: Config.anonymous_event_creation_email_required?(),
confirmation_required:
Config.anonymous_event_creation_email_confirmation_required?()
},
captcha: %{
enabled: Config.anonymous_event_creation_email_captcha_required?()
}
}
},
actor_id: Config.anonymous_actor_id()
},
geocoding: %{
provider: Config.instance_geocoding_provider(),
autocomplete: Config.instance_geocoding_autocomplete()
},
maps: %{
tiles: %{
endpoint: Config.instance_maps_tiles_endpoint(),
attribution: Config.instance_maps_tiles_attribution()
}
}
}
end
end

View File

@@ -5,7 +5,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
alias Mobilizon.{Actors, Admin, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.{Event, EventParticipantStats, Participant}
alias Mobilizon.Events.{Event, EventParticipantStats}
alias Mobilizon.Users.User
alias Mobilizon.GraphQL.API
@@ -19,7 +19,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
def list_events(_parent, %{page: page, limit: limit}, _resolution)
when limit < @event_max_limit do
{:ok, Mobilizon.Events.list_events(page, limit)}
{:ok, Events.list_events(page, limit)}
end
def list_events(_parent, %{page: _page, limit: _limit}, _resolution) do
@@ -31,7 +31,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
%{uuid: uuid},
%{context: %{current_user: %User{id: user_id}}} = _resolution
) do
case {:has_event, Mobilizon.Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
case {:has_event, Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
{:has_event, %Event{} = event} ->
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
@@ -45,7 +45,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
end
def find_event(parent, %{uuid: uuid} = args, resolution) do
case {:has_event, Mobilizon.Events.get_public_event_by_uuid_with_preload(uuid)} do
case {:has_event, Events.get_public_event_by_uuid_with_preload(uuid)} do
{:has_event, %Event{} = event} ->
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
@@ -65,7 +65,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
with {:is_owned, %Actor{} = _actor} <- User.owns_actor(user, actor_id),
# Check that moderator has right
{:actor_approve_permission, true} <-
{:actor_approve_permission, Mobilizon.Events.moderator_for_event?(event_id, actor_id)} do
{:actor_approve_permission, Events.moderator_for_event?(event_id, actor_id)} do
roles =
case roles do
"" ->
@@ -78,7 +78,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|> Enum.map(&String.to_existing_atom/1)
end
{:ok, Mobilizon.Events.list_participants_for_event(event_id, roles, page, limit)}
{:ok, Events.list_participants_for_event(event_id, roles, page, limit)}
else
{:is_owned, nil} ->
{:error, "Moderator Actor ID is not owned by authenticated user"}
@@ -142,118 +142,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
defp uniq_events(events), do: Enum.uniq_by(events, fn event -> event.uuid end)
@doc """
Join an event for an actor
"""
def actor_join_event(
_parent,
%{actor_id: actor_id, event_id: event_id},
%{context: %{current_user: user}}
) do
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
{:has_event, {:ok, %Event{} = event}} <-
{:has_event, Mobilizon.Events.get_event_with_preload(event_id)},
{:error, :participant_not_found} <- Mobilizon.Events.get_participant(event_id, actor_id),
{:ok, _activity, participant} <- API.Participations.join(event, actor),
participant <-
participant
|> Map.put(:event, event)
|> Map.put(:actor, Person.proxify_pictures(actor)) do
{:ok, participant}
else
{:maximum_attendee_capacity, _} ->
{:error, "The event has already reached its maximum capacity"}
{:has_event, _} ->
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
{:error, :event_not_found} ->
{:error, "Event id not found"}
{:ok, %Participant{}} ->
{:error, "You are already a participant of this event"}
end
end
def actor_join_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to join an event"}
end
@doc """
Leave an event for an actor
"""
def actor_leave_event(
_parent,
%{actor_id: actor_id, event_id: event_id},
%{context: %{current_user: user}}
) do
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
{:has_event, {:ok, %Event{} = event}} <-
{:has_event, Mobilizon.Events.get_event_with_preload(event_id)},
{:ok, _activity, _participant} <- API.Participations.leave(event, actor) do
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}}
else
{:has_event, _} ->
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
{:only_organizer, true} ->
{:error, "You can't leave event because you're the only event creator participant"}
{:error, :participant_not_found} ->
{:error, "Participant not found"}
end
end
def actor_leave_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to leave an event"}
end
def update_participation(
_parent,
%{id: participation_id, moderator_actor_id: moderator_actor_id, role: new_role},
%{context: %{current_user: user}}
) do
# Check that moderator provided is rightly authenticated
with {:is_owned, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
# Check that participation already exists
{:has_participation, %Participant{role: old_role} = participation} <-
{:has_participation, Mobilizon.Events.get_participant(participation_id)},
{:same_role, false} <- {:same_role, new_role == old_role},
# Check that moderator has right
{:actor_approve_permission, true} <-
{:actor_approve_permission,
Mobilizon.Events.moderator_for_event?(participation.event.id, moderator_actor_id)},
{:ok, _activity, participation} <-
API.Participations.update(participation, moderator_actor, new_role) do
{:ok, participation}
else
{:is_owned, nil} ->
{:error, "Moderator Actor ID is not owned by authenticated user"}
{:has_participation, %Participant{role: role, id: id}} ->
{: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"}
{:same_role, true} ->
{:error, "Participant already has role #{new_role}"}
{:error, :participant_not_found} ->
{:error, "Participant not found"}
end
end
@doc """
Create an event
"""

View File

@@ -0,0 +1,262 @@
defmodule Mobilizon.GraphQL.Resolvers.Participant do
@moduledoc """
Handles the participation-related GraphQL calls.
"""
alias Mobilizon.{Actors, Config, Crypto, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.GraphQL.API.Participations
alias Mobilizon.GraphQL.Resolvers.Person
alias Mobilizon.Users.User
alias Mobilizon.Web.Email
alias Mobilizon.Web.Email.Checker
require Logger
@doc """
Join an event for an regular actor
"""
def actor_join_event(
_parent,
%{actor_id: actor_id, event_id: event_id},
%{context: %{current_user: %User{} = user}}
) do
case User.owns_actor(user, actor_id) do
{:is_owned, %Actor{} = actor} ->
do_actor_join_event(actor, event_id)
_ ->
{:error, "Actor id is not owned by authenticated user"}
end
end
@doc """
Join an event for an anonymous actor
"""
def actor_join_event(
_parent,
%{actor_id: actor_id, event_id: event_id} = args,
_resolution
) do
with {:has_event, {:ok, %Event{} = event}} <-
{:has_event, Mobilizon.Events.get_event_with_preload(event_id)},
{:anonymous_participation_enabled, true} <-
{:anonymous_participation_enabled,
event.local == true && Config.anonymous_participation?() &&
event.options.anonymous_participation == true},
{:anonymous_actor_id, true} <-
{:anonymous_actor_id, to_string(Config.anonymous_actor_id()) == actor_id},
{:email_required, true} <-
{:email_required,
Config.anonymous_participation_email_required?() &&
args |> Map.get(:email) |> valid_email?()},
{:confirmation_token, {confirmation_token, role}} <-
{:confirmation_token,
if(Config.anonymous_participation_email_confirmation_required?(),
do: {Crypto.random_string(30), :not_confirmed},
else: {nil, :participant}
)},
# We only federate if the participation is not to be confirmed later
args <-
args
|> Map.put(:confirmation_token, confirmation_token)
|> Map.put(:cancellation_token, Crypto.random_string(30))
|> Map.put(:role, role)
|> Map.put(:local, role == :participant),
{:actor_not_found, %Actor{} = actor} <-
{:actor_not_found, Actors.get_actor_with_preload(actor_id)},
{:ok, %Participant{} = participant} <- do_actor_join_event(actor, event_id, args) do
if Config.anonymous_participation_email_required?() &&
Config.anonymous_participation_email_confirmation_required?() do
args
|> Map.get(:email)
|> Email.Participation.anonymous_participation_confirmation(participant)
|> Email.Mailer.deliver_later()
end
{:ok, participant}
else
{:error, err} ->
{:error, err}
{:has_event, _} ->
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
{:anonymous_participation_enabled, false} ->
{:error, "Anonymous participation is not enabled"}
{:anonymous_actor_id, false} ->
{:error, "Actor ID provided is not the anonymous actor one"}
{:email_required, _} ->
{:error, "A valid email is required by your instance"}
{:actor_not_found, _} ->
Logger.error(
"The actor ID \"#{actor_id}\" provided by configuration doesn't match any actor in database"
)
{:error, "Internal Error"}
end
end
def actor_join_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to join an event"}
end
@spec do_actor_join_event(Actor.t(), integer | String.t(), map()) ::
{:ok, Participant.t()} | {:error, String.t()}
defp do_actor_join_event(actor, event_id, args \\ %{}) do
with {:has_event, {:ok, %Event{} = event}} <-
{:has_event, Events.get_event_with_preload(event_id)},
{:ok, _activity, participant} <- Participations.join(event, actor, args),
%Participant{} = participant <-
participant
|> Map.put(:event, event)
|> Map.put(:actor, Person.proxify_pictures(actor)) do
{:ok, participant}
else
{:maximum_attendee_capacity, _} ->
{:error, "The event has already reached its maximum capacity"}
{:has_event, _} ->
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
{:error, :event_not_found} ->
{:error, "Event id not found"}
{:ok, %Participant{}} ->
{:error, "You are already a participant of this event"}
end
end
@doc """
Leave an event for an actor
"""
def actor_leave_event(
_parent,
%{actor_id: actor_id, event_id: event_id, token: token},
_resolution
) do
with {:anonymous_participation_enabled, true} <-
{:anonymous_participation_enabled, Config.anonymous_participation?()},
{:anonymous_actor_id, true} <-
{:anonymous_actor_id, to_string(Config.anonymous_actor_id()) == actor_id},
{:has_event, {:ok, %Event{} = event}} <-
{:has_event, Mobilizon.Events.get_event_with_preload(event_id)},
%Actor{} = actor <- Actors.get_actor_with_preload(actor_id),
{:ok, _activity, %Participant{id: participant_id} = _participant} <-
Participations.leave(event, actor, %{local: false, cancellation_token: token}) do
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}, id: participant_id}}
else
{:has_event, _} ->
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
{:only_organizer, true} ->
{:error, "You can't leave event because you're the only event creator participant"}
{:error, :participant_not_found} ->
{:error, "Participant not found"}
end
end
def actor_leave_event(
_parent,
%{actor_id: actor_id, event_id: event_id},
%{context: %{current_user: user}}
) do
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
{:has_event, {:ok, %Event{} = event}} <-
{:has_event, Events.get_event_with_preload(event_id)},
{:ok, _activity, _participant} <- Participations.leave(event, actor) do
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}}
else
{:has_event, _} ->
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
{:only_organizer, true} ->
{:error, "You can't leave event because you're the only event creator participant"}
{:error, :participant_not_found} ->
{:error, "Participant not found"}
end
end
def actor_leave_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to leave an event"}
end
def update_participation(
_parent,
%{id: participation_id, moderator_actor_id: moderator_actor_id, role: new_role},
%{
context: %{
current_user: user
}
}
) do
# Check that moderator provided is rightly authenticated
with {:is_owned, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
# Check that participation already exists
{:has_participation, %Participant{role: old_role} = participation} <-
{:has_participation, Events.get_participant(participation_id)},
{:same_role, false} <- {:same_role, new_role == old_role},
# Check that moderator has right
{:actor_approve_permission, true} <-
{:actor_approve_permission,
Events.moderator_for_event?(participation.event.id, moderator_actor_id)},
{:ok, _activity, participation} <-
Participations.update(participation, moderator_actor, new_role) do
{:ok, participation}
else
{:is_owned, nil} ->
{:error, "Moderator Actor ID is not owned by authenticated user"}
{:has_participation, nil} ->
{:error, "Participant not found"}
{:actor_approve_permission, _} ->
{:error, "Provided moderator actor ID doesn't have permission on this event"}
{:same_role, true} ->
{:error, "Participant already has role #{new_role}"}
{:error, :participant_not_found} ->
{:error, "Participant not found"}
end
end
@spec confirm_participation_from_token(map(), map(), map()) ::
{:ok, Participant.t()} | {:error, String.t()}
def confirm_participation_from_token(
_parent,
%{confirmation_token: confirmation_token},
_context
) do
with {:has_participant,
%Participant{actor: actor, role: :not_confirmed, event: event} = participant} <-
{:has_participant, Events.get_participant_by_confirmation_token(confirmation_token)},
default_role <- Events.get_default_participant_role(event),
{:ok, _activity, %Participant{} = participant} <-
Participations.update(participant, actor, default_role) do
{:ok, participant}
else
{:has_participant, _} ->
{:error, "This token is invalid"}
end
end
@spec valid_email?(String.t() | nil) :: boolean
defp valid_email?(email) when is_nil(email), do: false
defp valid_email?(email) when is_bitstring(email) do
email
|> String.trim()
|> Checker.valid?()
end
end