Fix search exposing events to unlogged users

Closes #892

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-11-08 18:46:04 +01:00
parent 729a6a7113
commit 3961a2067b
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
6 changed files with 78 additions and 36 deletions

View file

@ -9,19 +9,19 @@
> >
<template slot="label"> <template slot="label">
{{ actualLabel }} {{ actualLabel }}
<span
class="is-size-6 has-text-weight-normal"
v-if="gettingLocation"
>{{ $t("Getting location") }}</span
>
</template>
<p class="control" v-if="canShowLocateMeButton && !gettingLocation">
<b-button <b-button
v-if="canShowLocateMeButton && !gettingLocation"
size="is-small"
icon-right="map-marker" icon-right="map-marker"
@click="locateMe" @click="locateMe"
:title="$t('Use my location')" :title="$t('Use my location')"
/> />
<span </p>
class="is-size-6 has-text-weight-normal"
v-else-if="gettingLocation"
>{{ $t("Getting location") }}</span
>
</template>
<b-autocomplete <b-autocomplete
:data="addressData" :data="addressData"
v-model="queryText" v-model="queryText"
@ -29,7 +29,7 @@
field="fullName" field="fullName"
:loading="isFetching" :loading="isFetching"
@typing="fetchAsyncData" @typing="fetchAsyncData"
icon="map-marker" :icon="canShowLocateMeButton ? null : 'map-marker'"
expanded expanded
@select="updateSelected" @select="updateSelected"
v-bind="$attrs" v-bind="$attrs"
@ -71,7 +71,10 @@
:title="$t('Clear address field')" :title="$t('Clear address field')"
/> />
</b-field> </b-field>
<div class="card" v-if="selected.originId || selected.url"> <div
class="card"
v-if="!hideSelected && (selected.originId || selected.url)"
>
<div class="card-content"> <div class="card-content">
<address-info <address-info
:address="selected" :address="selected"
@ -119,6 +122,8 @@ export default class FullAddressAutoComplete extends Mixins(
@Prop({ required: false }) userTimezone!: string; @Prop({ required: false }) userTimezone!: string;
@Prop({ required: false, default: false, type: Boolean }) disabled!: boolean; @Prop({ required: false, default: false, type: Boolean }) disabled!: boolean;
@Prop({ required: false, default: false, type: Boolean }) hideMap!: boolean; @Prop({ required: false, default: false, type: Boolean }) hideMap!: boolean;
@Prop({ required: false, default: false, type: Boolean })
hideSelected!: boolean;
addressModalActive = false; addressModalActive = false;

View file

@ -34,6 +34,8 @@
ref="aac" ref="aac"
:placeholder="$t('For instance: London')" :placeholder="$t('For instance: London')"
@input="locchange" @input="locchange"
:hideMap="true"
:hideSelected="true"
/> />
<b-field <b-field
:label="$t('Radius')" :label="$t('Radius')"
@ -144,9 +146,16 @@
</b-pagination> </b-pagination>
</div> </div>
</div> </div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">{{ <b-message v-else-if="$apollo.loading === false" type="is-danger">
$t("No events found") <p>{{ $t("No events found") }}</p>
}}</b-message> <p v-if="searchIsUrl && !currentUser.id">
{{
$t(
"Only registered users may fetch remote events from their URL."
)
}}
</p>
</b-message>
</b-tab-item> </b-tab-item>
<b-tab-item v-if="!tag"> <b-tab-item v-if="!tag">
<template slot="header"> <template slot="header">
@ -211,6 +220,8 @@ import MultiGroupCard from "../components/Group/MultiGroupCard.vue";
import { CONFIG } from "../graphql/config"; import { CONFIG } from "../graphql/config";
import { REVERSE_GEOCODE } from "../graphql/address"; import { REVERSE_GEOCODE } from "../graphql/address";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import { CURRENT_USER_CLIENT } from "@/graphql/user";
import { ICurrentUser } from "@/types/current-user.model";
interface ISearchTimeOption { interface ISearchTimeOption {
label: string; label: string;
@ -267,6 +278,7 @@ const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
this.searchGroups = data.searchGroups; this.searchGroups = data.searchGroups;
}, },
}, },
currentUser: CURRENT_USER_CLIENT,
}, },
metaInfo() { metaInfo() {
return { return {
@ -292,6 +304,8 @@ export default class Search extends Vue {
location: IAddress = new Address(); location: IAddress = new Address();
currentUser!: ICurrentUser;
dateOptions: Record<string, ISearchTimeOption> = { dateOptions: Record<string, ISearchTimeOption> = {
past: { past: {
label: this.$t("In the past") as string, label: this.$t("In the past") as string,
@ -570,6 +584,18 @@ export default class Search extends Vue {
private stringExists(value: string | null | undefined): boolean { private stringExists(value: string | null | undefined): boolean {
return this.valueExists(value) && (value as string).length > 0; return this.valueExists(value) && (value as string).length > 0;
} }
get searchIsUrl(): boolean {
let url;
if (!this.search) return false;
try {
url = new URL(this.search);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
} }
</script> </script>

View file

@ -11,6 +11,7 @@ defmodule Mobilizon.GraphQL.API.Search do
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
import Mobilizon.GraphQL.Resolvers.Event.Utils
require Logger require Logger
@ -67,10 +68,14 @@ defmodule Mobilizon.GraphQL.API.Search do
term = String.trim(term) term = String.trim(term)
if is_url(term) do if is_url(term) do
# skip, if it's w not an actor # skip, if it's not an event
case process_from_url(term) do case process_from_url(term) do
%Page{total: _total, elements: [%Event{} = _event]} = page -> %Page{total: _total, elements: [%Event{} = event]} = page ->
{:ok, page} if Map.get(args, :current_user) != nil || check_event_access?(event) do
{:ok, page}
else
{:ok, %{total: 0, elements: []}}
end
_ -> _ ->
{:ok, %{total: 0, elements: []}} {:ok, %{total: 0, elements: []}}

View file

@ -117,28 +117,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
@spec find_event(any(), map(), Absinthe.Resolution.t()) :: @spec find_event(any(), map(), Absinthe.Resolution.t()) ::
{:ok, Event.t()} | {:error, :event_not_found} {:ok, Event.t()} | {:error, :event_not_found}
def find_event(parent, %{uuid: uuid} = args, %{context: context} = resolution) do def find_event(parent, %{uuid: uuid} = args, %{context: context} = resolution) do
with {:has_event, %Event{} = event} <- case Events.get_public_event_by_uuid_with_preload(uuid) do
{:has_event, Events.get_public_event_by_uuid_with_preload(uuid)}, %Event{} = event ->
{:access_valid, true} <- if Map.has_key?(context, :current_user) || check_event_access?(event) do
{:access_valid, Map.has_key?(context, :current_user) || check_event_access(event)} do {:ok, event}
{:ok, event} else
else {:error, :event_not_found}
{:has_event, _} -> end
_ ->
find_private_event(parent, args, resolution) find_private_event(parent, args, resolution)
{:access_valid, _} ->
{:error, :event_not_found}
end end
end end
@spec check_event_access(Event.t()) :: boolean()
defp check_event_access(%Event{local: true}), do: true
defp check_event_access(%Event{url: url}) do
relay_actor_id = Config.relay_actor_id()
Events.check_if_event_has_instance_follow(url, relay_actor_id)
end
@doc """ @doc """
List participants for event (through an event request) List participants for event (through an event request)
""" """

View file

@ -4,6 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event.Utils do
""" """
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.{Config, Events}
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityPub.Permission alias Mobilizon.Federation.ActivityPub.Permission
import Mobilizon.Service.Guards, only: [is_valid_string: 1] import Mobilizon.Service.Guards, only: [is_valid_string: 1]
@ -37,4 +38,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Event.Utils do
def can_event_be_deleted_by?(%Event{} = event, %Actor{id: actor_member_id}) do def can_event_be_deleted_by?(%Event{} = event, %Actor{id: actor_member_id}) do
Event.can_be_managed_by?(event, actor_member_id) Event.can_be_managed_by?(event, actor_member_id)
end end
@spec check_event_access?(Event.t()) :: boolean()
def check_event_access?(%Event{local: true}), do: true
def check_event_access?(%Event{url: url}) do
relay_actor_id = Config.relay_actor_id()
Events.check_if_event_has_instance_follow(url, relay_actor_id)
end
end end

View file

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Search do
%{page: page, limit: limit} = args, %{page: page, limit: limit} = args,
%{context: context} = _resolution %{context: context} = _resolution
) do ) do
current_actor = Map.get(context, :current_actor, nil) current_actor = Map.get(context, :current_actor)
current_actor_id = if current_actor, do: current_actor.id, else: nil current_actor_id = if current_actor, do: current_actor.id, else: nil
args = Map.put(args, :current_actor_id, current_actor_id) args = Map.put(args, :current_actor_id, current_actor_id)
Search.search_actors(args, page, limit, :Group) Search.search_actors(args, page, limit, :Group)
@ -37,7 +37,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Search do
""" """
@spec search_events(any(), map(), Absinthe.Resolution.t()) :: @spec search_events(any(), map(), Absinthe.Resolution.t()) ::
{:ok, Page.t(Event.t())} | {:error, String.t()} {:ok, Page.t(Event.t())} | {:error, String.t()}
def search_events(_parent, %{page: page, limit: limit} = args, _resolution) do def search_events(
_parent,
%{page: page, limit: limit} = args,
%{context: context} = _resolution
) do
current_user = Map.get(context, :current_user)
args = Map.put(args, :current_user, current_user)
Search.search_events(args, page, limit) Search.search_events(args, page, limit)
end end