Skip to content

Commit 2140cdf

Browse files
APIv2 stream signing and slug validation (#1600)
* update stream endpoints to include slug in path parameters * include signed urls when building response * add slug validation to stream endpoints * fix linting
1 parent 49e3de0 commit 2140cdf

8 files changed

Lines changed: 595 additions & 910 deletions

File tree

apiv2/helpers/parser.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
protobuf "github.com/TUM-Dev/gocast/apiv2/protobuf/server"
88
"github.com/TUM-Dev/gocast/model"
9+
"github.com/TUM-Dev/gocast/tools"
910
"google.golang.org/protobuf/types/known/timestamppb"
1011
)
1112

@@ -89,10 +90,11 @@ func ParseSemesterToProto(semester model.Semester) *protobuf.Semester {
8990
}
9091

9192
// ParseStreamToProto converts a Stream model to its protobuf representation.
92-
// It returns an error if the conversion of timestamps fails.
93-
func ParseStreamToProto(stream model.Stream, downloads []model.DownloadableVod) *protobuf.Stream {
93+
func ParseStreamToProto(stream model.Stream, course model.Course, user *model.User) *protobuf.Stream {
9494
liveNow := stream.LiveNowTimestamp.After(time.Now())
9595

96+
_ = tools.SetSignedPlaylists(&stream, user, course.DownloadsEnabled)
97+
9698
s := &protobuf.Stream{
9799
Id: uint32(stream.ID),
98100
Name: stream.Name,
@@ -126,8 +128,10 @@ func ParseStreamToProto(stream model.Stream, downloads []model.DownloadableVod)
126128
s.Duration = uint32(stream.Duration.Int32)
127129
}
128130

129-
for _, download := range downloads {
130-
s.Downloads = append(s.Downloads, ParseDownloadToProto(download))
131+
if course.DownloadsEnabled {
132+
for _, download := range stream.GetVodFiles() {
133+
s.Downloads = append(s.Downloads, ParseDownloadToProto(download))
134+
}
131135
}
132136

133137
return s

apiv2/protobuf/server/apiv2.pb.go

Lines changed: 388 additions & 853 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apiv2/protobuf/server/apiv2.pb.gw.go

Lines changed: 128 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apiv2/server/apiv2.proto

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,46 +183,46 @@ service API {
183183
// STREAM ENDPOINTS (./stream.go)
184184

185185
rpc getStream(GetStreamRequest) returns (CourseStream) {
186-
option (google.api.http) = {get: "/streams/{stream_id}"};
186+
option (google.api.http) = {get: "/streams/{slug}/{stream_id}"};
187187
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
188188
tags: "Streams"
189-
summary: "Get stream and course by stream ID."
189+
summary: "Get stream and course by course slug and stream ID."
190190
description: "Retrieves a stream and its course by its stream ID."
191191
};
192192
}
193193

194194
rpc getVideoSections(GetVideoSectionsRequest) returns (GetVideoSectionsResponse) {
195-
option (google.api.http) = {get: "/streams/{stream_id}/sections"};
195+
option (google.api.http) = {get: "/streams/{slug}/{stream_id}/sections"};
196196
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
197197
tags: "Streams"
198-
summary: "Get video sections."
198+
summary: "Get video sections by course slug and stream ID."
199199
description: "Retrieves the video sections for a stream."
200200
};
201201
}
202202

203203
rpc getStreamPlaylist(GetStreamPlaylistRequest) returns (GetStreamPlaylistResponse) {
204-
option (google.api.http) = {get: "/streams/{stream_id}/playlist"};
204+
option (google.api.http) = {get: "/streams/{slug}/{stream_id}/playlist"};
205205
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
206206
tags: "Streams"
207-
summary: "Get stream playlist."
207+
summary: "Get stream playlist by course slug and stream ID."
208208
description: "Retrieves the playlist for a stream including watch progress."
209209
};
210210
}
211211

212212
rpc getSubtitles(GetSubtitlesRequest) returns (google.api.HttpBody) {
213-
option (google.api.http) = {get: "/streams/{stream_id}/subtitles/{lang}"};
213+
option (google.api.http) = {get: "/streams/{slug}/{stream_id}/subtitles/{lang}"};
214214
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
215215
tags: "Streams"
216-
summary: "Get subtitles."
216+
summary: "Get subtitles by course slug, stream ID, and language."
217217
description: "Retrieves the subtitles for a stream in a specific language."
218218
};
219219
}
220220

221221
rpc getThumbs(GetThumbsRequest) returns (google.api.HttpBody) {
222-
option (google.api.http) = {get: "/streams/{stream_id}/thumbs"};
222+
option (google.api.http) = {get: "/streams/{slug}/{stream_id}/thumbs"};
223223
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
224224
tags: "Streams"
225-
summary: "Get thumbs."
225+
summary: "Get thumbs by course slug, stream ID, and optional thumb type."
226226
description: "Retrieves the thumbs for a stream."
227227
};
228228
}
@@ -644,24 +644,29 @@ enum VideoType {
644644

645645
message GetStreamRequest {
646646
uint32 stream_id = 1;
647+
string slug = 2;
647648
}
648649

649650
message GetVideoSectionsRequest {
650651
uint32 stream_id = 1;
652+
string slug = 2;
651653
}
652654

653655
message GetSubtitlesRequest {
654656
uint32 stream_id = 1;
655657
string lang = 2;
658+
string slug = 3;
656659
}
657660

658661
message GetStreamPlaylistRequest {
659662
uint32 stream_id = 1;
663+
string slug = 2;
660664
}
661665

662666
message GetThumbsRequest {
663667
uint32 stream_id = 1;
664668
optional VideoType thumb_type = 2;
669+
string slug = 3;
665670
}
666671

667672
///////////////////////////////

apiv2/server/authorization.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ type StreamRequest interface {
101101
GetStreamId() uint32
102102
}
103103

104-
// Checks if the user is allowed to access the stream and course and returns the user, stream and course
105104
func (a *API) authorizeUserForStreamCourse(ctx context.Context, req StreamRequest) (*model.User, model.Stream, model.Course, error) {
106105
stream := model.Stream{}
107106
course := model.Course{}
@@ -119,6 +118,16 @@ func (a *API) authorizeUserForStreamCourse(ctx context.Context, req StreamReques
119118
return nil, stream, course, e.WithStatus(http.StatusInternalServerError, err)
120119
}
121120

121+
// Only check slug if request requires it
122+
type slugGetter interface {
123+
GetSlug() string
124+
}
125+
if r, ok := req.(slugGetter); ok {
126+
if r.GetSlug() != course.Slug {
127+
return nil, stream, course, e.WithStatus(http.StatusBadRequest, errors.New("slug does not match course"))
128+
}
129+
}
130+
122131
user, _ := a.getCurrent(ctx)
123132
if !user.IsEligibleToWatchCourse(course) {
124133
return nil, stream, course, e.WithStatus(http.StatusForbidden, errors.New("User is not eligible to access course content"))

apiv2/server/course.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (a *API) GetLiveCourses(ctx context.Context, req *emptypb.Empty) (*protobuf
6767

6868
resp = append(resp, &protobuf.CourseStream{
6969
Course: h.ParseCourseToProto(courseForLiveStream, user),
70-
Stream: h.ParseStreamToProto(stream, nil),
70+
Stream: h.ParseStreamToProto(stream, courseForLiveStream, user),
7171
LectureHall: h.ParseLectureHallToProto(lectureHall),
7272
// Viewers: viewers,
7373
})
@@ -140,10 +140,10 @@ func (a *API) GetCourseBySlug(ctx context.Context, req *protobuf.GetCourseBySlug
140140
return nil, e.WithStatus(http.StatusUnauthorized, errors.New("unauthorized"))
141141
}
142142

143-
streams := make([]*protobuf.Stream, len(course.Streams))
144-
for i, stream := range course.Streams {
143+
streams := make([]*protobuf.Stream, 0, len(course.Streams))
144+
for _, stream := range course.Streams {
145145
if !stream.Private || user.IsAdminOfCourse(course) {
146-
streams[i] = h.ParseStreamToProto(stream, nil)
146+
streams = append(streams, h.ParseStreamToProto(stream, course, user))
147147
}
148148
}
149149

apiv2/server/docs/apiv2.swagger.json

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -631,9 +631,9 @@
631631
]
632632
}
633633
},
634-
"/streams/{streamId}": {
634+
"/streams/{slug}/{streamId}": {
635635
"get": {
636-
"summary": "Get stream and course by stream ID.",
636+
"summary": "Get stream and course by course slug and stream ID.",
637637
"description": "Retrieves a stream and its course by its stream ID.",
638638
"operationId": "API_getStream",
639639
"responses": {
@@ -651,6 +651,12 @@
651651
}
652652
},
653653
"parameters": [
654+
{
655+
"name": "slug",
656+
"in": "path",
657+
"required": true,
658+
"type": "string"
659+
},
654660
{
655661
"name": "streamId",
656662
"in": "path",
@@ -664,9 +670,9 @@
664670
]
665671
}
666672
},
667-
"/streams/{streamId}/playlist": {
673+
"/streams/{slug}/{streamId}/playlist": {
668674
"get": {
669-
"summary": "Get stream playlist.",
675+
"summary": "Get stream playlist by course slug and stream ID.",
670676
"description": "Retrieves the playlist for a stream including watch progress.",
671677
"operationId": "API_getStreamPlaylist",
672678
"responses": {
@@ -684,6 +690,12 @@
684690
}
685691
},
686692
"parameters": [
693+
{
694+
"name": "slug",
695+
"in": "path",
696+
"required": true,
697+
"type": "string"
698+
},
687699
{
688700
"name": "streamId",
689701
"in": "path",
@@ -697,9 +709,9 @@
697709
]
698710
}
699711
},
700-
"/streams/{streamId}/sections": {
712+
"/streams/{slug}/{streamId}/sections": {
701713
"get": {
702-
"summary": "Get video sections.",
714+
"summary": "Get video sections by course slug and stream ID.",
703715
"description": "Retrieves the video sections for a stream.",
704716
"operationId": "API_getVideoSections",
705717
"responses": {
@@ -717,6 +729,12 @@
717729
}
718730
},
719731
"parameters": [
732+
{
733+
"name": "slug",
734+
"in": "path",
735+
"required": true,
736+
"type": "string"
737+
},
720738
{
721739
"name": "streamId",
722740
"in": "path",
@@ -730,9 +748,9 @@
730748
]
731749
}
732750
},
733-
"/streams/{streamId}/subtitles/{lang}": {
751+
"/streams/{slug}/{streamId}/subtitles/{lang}": {
734752
"get": {
735-
"summary": "Get subtitles.",
753+
"summary": "Get subtitles by course slug, stream ID, and language.",
736754
"description": "Retrieves the subtitles for a stream in a specific language.",
737755
"operationId": "API_getSubtitles",
738756
"responses": {
@@ -750,6 +768,12 @@
750768
}
751769
},
752770
"parameters": [
771+
{
772+
"name": "slug",
773+
"in": "path",
774+
"required": true,
775+
"type": "string"
776+
},
753777
{
754778
"name": "streamId",
755779
"in": "path",
@@ -769,9 +793,9 @@
769793
]
770794
}
771795
},
772-
"/streams/{streamId}/thumbs": {
796+
"/streams/{slug}/{streamId}/thumbs": {
773797
"get": {
774-
"summary": "Get thumbs.",
798+
"summary": "Get thumbs by course slug, stream ID, and optional thumb type.",
775799
"description": "Retrieves the thumbs for a stream.",
776800
"operationId": "API_getThumbs",
777801
"responses": {
@@ -789,6 +813,12 @@
789813
}
790814
},
791815
"parameters": [
816+
{
817+
"name": "slug",
818+
"in": "path",
819+
"required": true,
820+
"type": "string"
821+
},
792822
{
793823
"name": "streamId",
794824
"in": "path",

apiv2/server/stream.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
func (a *API) GetStream(ctx context.Context, req *protobuf.GetStreamRequest) (*protobuf.CourseStream, error) {
2222
a.log.Info("GetStream")
2323

24-
_, stream, course, err := a.authorizeUserForStreamCourse(ctx, req)
24+
user, stream, course, err := a.authorizeUserForStreamCourse(ctx, req)
2525
if err != nil {
2626
return nil, err
2727
}
@@ -38,7 +38,7 @@ func (a *API) GetStream(ctx context.Context, req *protobuf.GetStreamRequest) (*p
3838

3939
return &protobuf.CourseStream{
4040
Course: h.ParseCourseToProto(course, nil),
41-
Stream: h.ParseStreamToProto(stream, nil),
41+
Stream: h.ParseStreamToProto(stream, course, user),
4242
LectureHall: h.ParseLectureHallToProto(lectureHall),
4343
}, nil
4444
}

0 commit comments

Comments
 (0)