Skip to content

Commit 018d079

Browse files
authored
Merge pull request #586 from futurelearn/sc-duration-regexp
Support milliseconds in cacheDuration parsing
2 parents b55733f + f6366d4 commit 018d079

File tree

2 files changed

+60
-20
lines changed

2 files changed

+60
-20
lines changed

lib/onelogin/ruby-saml/utils.rb

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,21 @@ class Utils
1515

1616
DSIG = "http://www.w3.org/2000/09/xmldsig#"
1717
XENC = "http://www.w3.org/2001/04/xmlenc#"
18-
DURATION_FORMAT = %r(^(-?)P(?:(?:(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?)|(?:(\d+)W))$)
18+
DURATION_FORMAT = %r(^
19+
(-?)P # 1: Duration sign
20+
(?:
21+
(?:(\d+)Y)? # 2: Years
22+
(?:(\d+)M)? # 3: Months
23+
(?:(\d+)D)? # 4: Days
24+
(?:T
25+
(?:(\d+)H)? # 5: Hours
26+
(?:(\d+)M)? # 6: Minutes
27+
(?:(\d+(?:[.,]\d+)?)S)? # 7: Seconds
28+
)?
29+
|
30+
(\d+)W # 8: Weeks
31+
)
32+
$)x
1933

2034
# Checks if the x509 cert provided is expired
2135
#
@@ -37,31 +51,18 @@ def self.is_cert_expired(cert)
3751
# current time.
3852
#
3953
# @return [Integer] The new timestamp, after the duration is applied.
40-
#
54+
#
4155
def self.parse_duration(duration, timestamp=Time.now.utc)
4256
matches = duration.match(DURATION_FORMAT)
43-
57+
4458
if matches.nil?
4559
raise Exception.new("Invalid ISO 8601 duration")
4660
end
4761

48-
durYears = matches[2].to_i
49-
durMonths = matches[3].to_i
50-
durDays = matches[4].to_i
51-
durHours = matches[5].to_i
52-
durMinutes = matches[6].to_i
53-
durSeconds = matches[7].to_f
54-
durWeeks = matches[8].to_i
55-
56-
if matches[1] == "-"
57-
durYears = -durYears
58-
durMonths = -durMonths
59-
durDays = -durDays
60-
durHours = -durHours
61-
durMinutes = -durMinutes
62-
durSeconds = -durSeconds
63-
durWeeks = -durWeeks
64-
end
62+
sign = matches[1] == '-' ? -1 : 1
63+
64+
durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks =
65+
matches[2..8].map { |match| match ? sign * match.tr(',', '.').to_f : 0.0 }
6566

6667
initial_datetime = Time.at(timestamp).utc.to_datetime
6768
final_datetime = initial_datetime.next_year(durYears)

test/utils_test.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,45 @@
11
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
22

33
class UtilsTest < Minitest::Test
4+
describe ".parse_duration" do
5+
DURATIONS_FROM_EPOCH = {
6+
# Basic formats
7+
"P1Y1M1D" => "1971-02-02T00:00:00.000Z",
8+
"PT1H1M1S" => "1970-01-01T01:01:01.000Z",
9+
"P1W" => "1970-01-08T00:00:00.000Z",
10+
"P1Y1M1DT1H1M1S" => "1971-02-02T01:01:01.000Z",
11+
12+
# Negative duration
13+
"-P1Y1M1DT1H1M1S" => "1968-11-29T22:58:59.000Z",
14+
15+
# Nominal wraparounds
16+
"P13M" => "1971-02-01T00:00:00.000Z",
17+
"P31D" => "1970-02-01T00:00:00.000Z",
18+
19+
# Decimal seconds
20+
"PT0.5S" => "1970-01-01T00:00:00.500Z",
21+
"PT0,5S" => "1970-01-01T00:00:00.500Z"
22+
}
23+
24+
def result(duration, reference = 0)
25+
Time.at(
26+
OneLogin::RubySaml::Utils.parse_duration(duration, reference)
27+
).utc.iso8601(3)
28+
end
29+
30+
DURATIONS_FROM_EPOCH.each do |duration, expected|
31+
it "parses #{duration} to return #{expected} from the given timestamp" do
32+
assert_equal expected, result(duration)
33+
end
34+
end
35+
36+
it "returns the last calendar day of the next month when advancing from a longer month to a shorter one" do
37+
initial_timestamp = Time.iso8601("1970-01-31T00:00:00.000Z").to_i
38+
39+
assert_equal "1970-02-28T00:00:00.000Z", result("P1M", initial_timestamp)
40+
end
41+
end
42+
443
describe ".format_cert" do
544
let(:formatted_certificate) {read_certificate("formatted_certificate")}
645
let(:formatted_chained_certificate) {read_certificate("formatted_chained_certificate")}

0 commit comments

Comments
 (0)