Skip to content

Commit 609bbde

Browse files
committed
feat(events): [PPT-2247] public event access
1 parent bbbb06c commit 609bbde

1 file changed

Lines changed: 33 additions & 11 deletions

File tree

src/controllers/events.cr

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@ class Events < Application
22
base "/api/staff/v1/events"
33

44
# Skip scope check for relevant routes
5-
skip_action :check_jwt_scope, only: [:show, :get_metadata, :patch_metadata, :guest_checkin, :add_attendee, :delete_attendee]
5+
skip_action :check_jwt_scope, only: [:index, :show, :get_metadata, :patch_metadata, :guest_checkin, :add_attendee, :delete_attendee]
66

77
# Skip actions that requres login
88
# If a user is logged in then they will be run as part of
99
# #set_tenant_from_domain
10-
skip_action :determine_tenant_from_domain, only: [:add_attendee, :delete_attendee]
10+
skip_action :determine_tenant_from_domain, only: [:index, :show, :add_attendee, :delete_attendee]
1111

1212
# Set the tenant based on the domain
1313
# This allows unauthenticated requests through
1414
# (for public bookings, further checks are done later)
15-
@[AC::Route::Filter(:before_action, only: [:add_attendee, :delete_attendee])]
15+
@[AC::Route::Filter(:before_action, only: [:index, :show, :add_attendee, :delete_attendee])]
1616
private def set_tenant_from_domain
1717
if auth_token_present?
18-
check_jwt_scope
18+
# guest-scoped tokens have their own access controls, skip the scope check
19+
check_jwt_scope unless user_token.guest_scope?
1920
determine_tenant_from_domain
2021
else
2122
domain = request.hostname.as?(String)
@@ -134,7 +135,16 @@ class Events < Application
134135
period_start = Time.unix(starting)
135136
period_end = Time.unix(ending)
136137

137-
calendars = matching_calendar_ids(calendars, zone_ids, system_ids, allow_default: true)
138+
# For unauthenticated (public) access, require system_ids or zone_ids
139+
# and use the resource calendar identity for calendar API calls.
140+
# Raw calendar IDs are ignored to prevent arbitrary calendar access.
141+
unless auth_token_present?
142+
raise AC::Route::Param::MissingError.new("system_ids or zone_ids required for public event listing", "system_ids", "String") unless system_ids.presence || zone_ids.presence
143+
calendars = nil
144+
end
145+
user_email = user.email if auth_token_present?
146+
147+
calendars = matching_calendar_ids(calendars, zone_ids, system_ids, allow_default: auth_token_present?)
138148

139149
Log.context.set(calendar_size: calendars.size.to_s)
140150
return [] of PlaceCalendar::Event unless calendars.size > 0
@@ -143,7 +153,7 @@ class Events < Application
143153
requests = [] of HTTP::Request
144154
mappings = calendars.map { |calendar_id, system|
145155
request = client.list_events_request(
146-
user.email,
156+
user_email || calendar_id,
147157
calendar_id,
148158
period_start: period_start,
149159
period_end: period_end,
@@ -156,7 +166,7 @@ class Events < Application
156166
{request, calendar_id, system}
157167
}
158168

159-
responses = client.batch(user.email, requests)
169+
responses = client.batch(user_email || calendars.keys.first, requests)
160170

161171
# Process the response (map requests back to responses)
162172
errors = 0
@@ -166,7 +176,7 @@ class Events < Application
166176
results = [] of Tuple(String, PlaceOS::Client::API::Models::System?, PlaceCalendar::Event)
167177
mappings.each do |(request, calendar_id, system)|
168178
begin
169-
results.concat client.list_events(user.email, responses[request]).map { |event| {calendar_id, system, event} }
179+
results.concat client.list_events(user_email || calendar_id, responses[request]).map { |event| {calendar_id, system, event} }
170180
rescue error
171181
(error.message =~ /MailboxConcurrency/i ? calendar_rate_limit : calendar_other_error) << calendar_id
172182
errors += 1
@@ -310,6 +320,9 @@ class Events < Application
310320
parent_meta = true if metadata
311321
end
312322

323+
# For unauthenticated requests, only return public events
324+
next unless auth_token_present? || metadata.try(&.permission.public?)
325+
313326
if system.nil?
314327
if cal_id = event_resources[event.id.as(String)]?
315328
system = system_emails[cal_id]?
@@ -1399,7 +1412,7 @@ class Events < Application
13991412
event_id = original_id
14001413

14011414
# Guest access
1402-
if user_token.guest_scope?
1415+
if auth_token_present? && user_token.guest_scope?
14031416
guest_event_id, guest_system_id = user.roles
14041417
system_id ||= guest_system_id
14051418
raise Error::Forbidden.new("guest #{user_token.id} attempting to view an event they are not associated with") unless system_id == guest_system_id
@@ -1409,7 +1422,7 @@ class Events < Application
14091422
# Need to grab the calendar associated with this system
14101423
system = placeos_client.systems.fetch(system_id)
14111424
cal_id = system.email
1412-
user_email = user_token.guest_scope? ? cal_id : user.email
1425+
user_email = (!auth_token_present? || user_token.guest_scope?) ? cal_id : user.email
14131426
raise AC::Route::Param::ValueError.new("system '#{system.name}' (#{system_id}) does not have a resource email address specified", "system_id") unless cal_id
14141427

14151428
raise Error::InconsistentState.new("user_email must be present") unless user_email
@@ -1427,14 +1440,23 @@ class Events < Application
14271440
end
14281441
end
14291442

1430-
if user_token.guest_scope?
1443+
if auth_token_present? && user_token.guest_scope?
14311444
raise Error::Forbidden.new("guest #{user_token.id} attempting to view an event they are not associated with") unless guest_event_id.in?({original_id, event_id, event.recurring_event_id}) && system_id == guest_system_id
14321445
end
14331446

14341447
metadata = get_event_metadata(event, system_id, search_recurring: true)
1448+
1449+
# For unauthenticated requests, only allow access to public events
1450+
unless auth_token_present?
1451+
raise Error::Forbidden.new("event is not publicly accessible") unless metadata.try(&.permission.public?)
1452+
end
1453+
14351454
parent_meta = !metadata.try &.for_event_instance?(event, client.client_id)
14361455
StaffApi::Event.augment(event, cal_id, system, metadata, parent_meta)
14371456
else
1457+
# Personal calendar access requires authentication
1458+
raise Error::Forbidden.new("system_id is required for public event access") unless auth_token_present?
1459+
14381460
# Need to confirm the user can access this calendar
14391461
user_cal = user_cal.try &.downcase
14401462
if user_cal == user.email

0 commit comments

Comments
 (0)