Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit db63d9b

Browse files
committed
V3: valid timestamps → subset of ISO 8601
Timestamps are typically used when querying audit_events, e.g. `GET /v3/audit_events?created_at=2020-06-30T23:49:04Z` We enforce an opinionated subset of ISO 8601: - must match `YYYY-MM-DDThh:mm:ssZ` - no subseconds - no local timezones We updated our tests to accommodate the "no local timezones" stricture (we used `Time.now.utc` to force UTC). We did a simple regex to guard against bad timestamps. It is admittedly ugly, but late is the hour. [#173542711] Authored-by: Brian Cunnie <bcunnie@vmware.com>
1 parent 2e946eb commit db63d9b

3 files changed

Lines changed: 49 additions & 19 deletions

File tree

app/messages/events_list_message.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ def validate(record)
2727
timestamp = record.created_at.values[0]
2828
end
2929
begin
30+
raise ArgumentError.new('invalid date') unless timestamp =~ /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\Z/
31+
3032
Time.iso8601(timestamp)
3133
rescue
32-
record.errors[:created_at] << "Invalid timestamp format: '#{timestamp}'"
34+
record.errors[:created_at] << "has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'"
3335
return
3436
end
3537
end

spec/request/events_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,28 @@
359359
expect(last_response).to have_error_message("Invalid comparison operator: 'goat'")
360360
end
361361
end
362+
363+
context 'using an invalid timestamp (with fractional seconds)' do
364+
let(:fractional_second_timestamp) { '2020-06-30T23:45:67.890Z' }
365+
it 'returns a useful error' do
366+
get "/v3/audit_events?created_at[lt]=#{fractional_second_timestamp}", nil, admin_header
367+
368+
expect(last_response).to have_status_code(400)
369+
expect(last_response).to have_error_message(
370+
"The query parameter is invalid: Created at has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'")
371+
end
372+
end
373+
374+
context 'using an invalid timestamp (local time zone)' do
375+
let(:local_timezone_timestamp) { '2020-06-30T23:45:67-0700' }
376+
it 'returns a useful error' do
377+
get "/v3/audit_events?created_at[lt]=#{local_timezone_timestamp}", nil, admin_header
378+
379+
expect(last_response).to have_status_code(400)
380+
expect(last_response).to have_error_message(
381+
"The query parameter is invalid: Created at has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'")
382+
end
383+
end
362384
end
363385

364386
context 'filtering by organization_guid' do

spec/unit/messages/events_list_message_spec.rb

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module VCAP::CloudController
2727
target_guids: ['guid1', 'guid2'],
2828
space_guids: ['guid3', 'guid4'],
2929
organization_guids: ['guid5', 'guid6'],
30-
created_at: { lt: Time.now.iso8601 },
30+
created_at: { lt: Time.now.utc.iso8601 },
3131
})
3232
expect(message).to be_valid
3333
end
@@ -66,52 +66,58 @@ module VCAP::CloudController
6666

6767
context 'validates the created_at filter' do
6868
it 'requires a hash or a timestamp' do
69-
message = EventsListMessage.from_params({ created_at: [Time.now.iso8601] })
69+
message = EventsListMessage.from_params({ created_at: [Time.now.utc.iso8601] })
7070
expect(message).not_to be_valid
7171
expect(message.errors[:created_at]).to include('comparison operator and timestamp must be specified')
7272
end
7373

7474
it 'requires a valid comparison operator' do
75-
message = EventsListMessage.from_params({ created_at: { garbage: Time.now.iso8601 } })
75+
message = EventsListMessage.from_params({ created_at: { garbage: Time.now.utc.iso8601 } })
7676
expect(message).not_to be_valid
7777
expect(message.errors[:created_at]).to include("Invalid comparison operator: 'garbage'")
7878
end
7979

80-
it 'requires a valid comparison operator' do
81-
message = EventsListMessage.from_params({ created_at: { garbage: Time.now.iso8601 } })
82-
expect(message).not_to be_valid
83-
expect(message.errors[:created_at]).to include("Invalid comparison operator: 'garbage'")
84-
end
85-
86-
it 'requires a valid timestamp' do
87-
message = EventsListMessage.from_params({ created_at: { gt: 123 } })
88-
expect(message).not_to be_valid
89-
expect(message.errors[:created_at]).to include("Invalid timestamp format: '123'")
80+
context 'requires a valid timestamp' do
81+
it "won't accept garbage" do
82+
message = EventsListMessage.from_params({ created_at: { gt: 123 } })
83+
expect(message).not_to be_valid
84+
expect(message.errors[:created_at]).to include("has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'")
85+
end
86+
it "won't accept fractional seconds even though it's ISO 8601-compliant" do
87+
message = EventsListMessage.from_params({ created_at: { gt: '2020-06-30T12:34:56.78Z' } })
88+
expect(message).not_to be_valid
89+
expect(message.errors[:created_at]).to include("has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'")
90+
end
91+
it "won't accept local time zones even though it's ISO 8601-compliant" do
92+
message = EventsListMessage.from_params({ created_at: { gt: '2020-06-30T12:34:56.78-0700' } })
93+
expect(message).not_to be_valid
94+
expect(message.errors[:created_at]).to include("has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'")
95+
end
9096
end
9197

9298
it 'allows the lt operator' do
93-
message = EventsListMessage.from_params({ created_at: { lt: Time.now.iso8601 } })
99+
message = EventsListMessage.from_params({ created_at: { lt: Time.now.utc.iso8601 } })
94100
expect(message).to be_valid
95101
end
96102

97103
it 'allows the lte operator' do
98-
message = EventsListMessage.from_params({ created_at: { lte: Time.now.iso8601 } })
104+
message = EventsListMessage.from_params({ created_at: { lte: Time.now.utc.iso8601 } })
99105
expect(message).to be_valid
100106
end
101107

102108
it 'allows the gt operator' do
103-
message = EventsListMessage.from_params({ created_at: { gt: Time.now.iso8601 } })
109+
message = EventsListMessage.from_params({ created_at: { gt: Time.now.utc.iso8601 } })
104110
expect(message).to be_valid
105111
end
106112

107113
it 'allows the gte operator' do
108-
message = EventsListMessage.from_params({ created_at: { gte: Time.now.iso8601 } })
114+
message = EventsListMessage.from_params({ created_at: { gte: Time.now.utc.iso8601 } })
109115
expect(message).to be_valid
110116
end
111117

112118
context 'when the operator is an equals operator' do
113119
it 'allows the equals operator' do
114-
message = EventsListMessage.from_params({ created_at: Time.now.iso8601 })
120+
message = EventsListMessage.from_params({ created_at: Time.now.utc.iso8601 })
115121
expect(message).to be_valid
116122
end
117123

0 commit comments

Comments
 (0)