Skip to content

Commit

Permalink
Hide text results for events with passwords
Browse files Browse the repository at this point in the history
This is not a good solution as it relies on
a frontend check. Ideally this would be done in backend.
This same applies for thumbnails in search results.
  • Loading branch information
owi92 committed Sep 12, 2024
1 parent a68dd84 commit 5fd35f1
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 23 deletions.
20 changes: 11 additions & 9 deletions backend/src/api/model/search/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub(crate) struct SearchEvent {
pub host_realms: Vec<search::Realm>,
pub text_matches: Vec<TextMatch>,
pub has_password: bool,
pub user_is_authorized: bool,
}

#[derive(Debug, GraphQLObject)]
Expand Down Expand Up @@ -73,17 +74,17 @@ impl SearchEvent {
let mut text_matches = Vec::new();
src.slide_texts.resolve_matches(slide_matches, &mut text_matches, TextAssetType::SlideText);
src.caption_texts.resolve_matches(caption_matches, &mut text_matches, TextAssetType::Caption);

let read_roles: Vec<String> = src.read_roles.iter()
.map(|role| {
let bytes = hex::decode(role).expect("Failed to decode role");

String::from_utf8(bytes).expect("Failed to convert bytes to string")
})
.collect();
let user_is_authorized = context.auth.overlaps_roles(read_roles);
let thumbnail = {
let read_roles: Vec<String> = src.read_roles.iter()
.map(|role| {
let bytes = hex::decode(role).expect("Failed to decode role");

String::from_utf8(bytes).expect("Failed to convert bytes to string")
})
.collect();

if context.auth.overlaps_roles(read_roles) {
if user_is_authorized {
src.thumbnail
} else {
None
Expand All @@ -107,6 +108,7 @@ impl SearchEvent {
host_realms: src.host_realms,
text_matches,
has_password: src.has_password,
user_is_authorized,
}
}
}
15 changes: 11 additions & 4 deletions frontend/src/routes/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import { ellipsisOverflowCss, focusStyle } from "../ui";
import { COLORS } from "../color";
import { BREAKPOINT_MEDIUM, BREAKPOINT_SMALL } from "../GlobalStyle";
import { eventId, isExperimentalFlagSet, keyOfId, secondsToTimeString } from "../util";
import { DirectVideoRoute, VideoRoute } from "./Video";
import { DirectVideoRoute, getCredentials, VideoRoute } from "./Video";
import { DirectSeriesRoute } from "./Series";
import { PartOfSeriesLink } from "../ui/Blocks/VideoList";
import { SearchSlidePreviewQuery } from "./__generated__/SearchSlidePreviewQuery.graphql";
Expand Down Expand Up @@ -150,6 +150,7 @@ const query = graphql`
endTime
created
hasPassword
userIsAuthorized
hostRealms { path }
textMatches {
start
Expand Down Expand Up @@ -404,6 +405,7 @@ const SearchResults: React.FC<SearchResultsProps> = ({ items }) => (
hostRealms: unwrapUndefined(item.hostRealms),
textMatches: unwrapUndefined(item.textMatches),
hasPassword: unwrapUndefined(item.hasPassword),
userIsAuthorized: unwrapUndefined(item.userIsAuthorized),
}} />;
} else if (item.__typename === "SearchSeries") {
return <SearchSeries key={item.id} {...{
Expand Down Expand Up @@ -483,6 +485,7 @@ type SearchEventProps = {
hostRealms: readonly { readonly path: string }[];
textMatches: NonNullable<Results["items"][number]["textMatches"]>;
hasPassword: boolean;
userIsAuthorized: boolean;
};

const SearchEvent: React.FC<SearchEventProps> = ({
Expand All @@ -502,13 +505,17 @@ const SearchEvent: React.FC<SearchEventProps> = ({
hostRealms,
textMatches,
hasPassword,
userIsAuthorized,
}) => {
// TODO: decide what to do in the case of more than two host realms. Direct
// link should be avoided.
const link = hostRealms.length !== 1
? DirectVideoRoute.url({ videoId: id })
: VideoRoute.url({ realmPath: hostRealms[0].path, videoID: id });

// TODO: This check should be done in backend.
const showMatches = userIsAuthorized || (hasPassword && getCredentials(keyOfId(id)));

return (
<Item key={id} link={link}>
<WithIcon Icon={LuPlayCircle} hideIconOnMobile>
Expand Down Expand Up @@ -545,7 +552,7 @@ const SearchEvent: React.FC<SearchEventProps> = ({
{seriesTitle && seriesId && <PartOfSeriesLink {...{ seriesTitle, seriesId }} />}

{/* Show timeline with matches if there are any */}
{textMatches.length > 0 && (
{textMatches.length > 0 && showMatches && (
<TextMatchTimeline {...{ id, duration, link, textMatches }} />
)}
</div>
Expand Down Expand Up @@ -594,11 +601,11 @@ type TextMatchTimelineProps = Pick<SearchEventProps, "id" | "duration" | "textMa
};

const slidePreviewQuery = graphql`
query SearchSlidePreviewQuery($id: ID!) {
query SearchSlidePreviewQuery($id: ID!, $seriesUser: String, $seriesPassword: String) {
eventById(id: $id) {
...on AuthorizedEvent {
id
authorizedData {
authorizedData(user: $seriesUser, password: $seriesPassword) {
segments { startTime uri }
}
}
Expand Down
21 changes: 11 additions & 10 deletions frontend/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ type SearchEvent implements Node {
hostRealms: [SearchRealm!]!
textMatches: [TextMatch!]!
hasPassword: Boolean!
userIsAuthorized: Boolean!
}

input ChildIndex {
Expand Down Expand Up @@ -217,16 +218,6 @@ input NewTextBlock {
content: String!
}

input NewVideoBlock {
event: ID!
showTitle: Boolean!
showLink: Boolean!
}

type CreateRealmLineageOutcome {
numCreated: Int!
}

input NewPlaylistBlock {
playlist: ID!
showTitle: Boolean!
Expand All @@ -235,6 +226,12 @@ input NewPlaylistBlock {
layout: VideoListLayout!
}

input NewVideoBlock {
event: ID!
showTitle: Boolean!
showLink: Boolean!
}

"A `Block`: a UI element that belongs to a realm."
interface Block {
id: ID!
Expand Down Expand Up @@ -486,6 +483,10 @@ type ByteSpan {
len: Int!
}

type CreateRealmLineageOutcome {
numCreated: Int!
}

"A block just showing the list of videos in an Opencast playlist"
type PlaylistBlock implements Block & RealmNameSourceBlock {
playlist: Playlist
Expand Down

0 comments on commit 5fd35f1

Please sign in to comment.