Skip to content

Commit 9ab4d3f

Browse files
authored
Merge pull request #735 from johnnyshields/v2.x-rework-uuid
v2.x - Rework SP request UUID generation to remove mutable constant
2 parents 4b24ac3 + e42990b commit 9ab4d3f

14 files changed

+194
-98
lines changed

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ AllCops:
1616
- 'tmp/**/*'
1717
- 'vendor/**/*'
1818

19+
Style/Alias:
20+
EnforcedStyle: prefer_alias_method
21+
1922
Style/ModuleFunction:
2023
EnforcedStyle: extend_self
2124

.rubocop_todo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ Lint/UselessAssignment:
181181
# Offense count: 42
182182
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
183183
Metrics/AbcSize:
184-
Max: 100
184+
Max: 200
185185

186186
# Offense count: 1
187187
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
* [#709](https://github.com/SAML-Toolkits/ruby-saml/pull/709) Allow passing in `Net::HTTP` `:open_timeout`, `:read_timeout`, and `:max_retries` settings to `IdpMetadataParser#parse_remote`.
1414
* [#715](https://github.com/SAML-Toolkits/ruby-saml/pull/715) Fix typo in error when SPNameQualifier value does not match the SP entityID.
1515
* [#718](https://github.com/SAML-Toolkits/ruby-saml/pull/718) Add support to retrieve from SAMLResponse the AuthnInstant and AuthnContextClassRef values
16-
* [#711](https://github.com/SAML-Toolkits/ruby-saml/pull/711) Standardize how RubySaml reads and formats certificate and private_key PEM values, including the `RubySaml::Util#format_cert` and `#format_private_key` methods.
16+
* [#711](https://github.com/SAML-Toolkits/ruby-saml/pull/711) Standardize how RubySaml reads and formats certificate and private_key PEM values, including the `RubySaml::Utils#format_cert` and `#format_private_key` methods.
1717
* [#732](https://github.com/SAML-Toolkits/ruby-saml/pull/732) Return original certificate and private key objects from `RubySaml::Utils` build functions.
1818
* [#732](https://github.com/SAML-Toolkits/ruby-saml/pull/732) Allow SP `certificate`, `certificate_new`, and `private_key` settings to be `OpenSSL::X509::Certificate` and `OpenSSL::PKey::PKey` objects respectively.
1919
* [#733](https://github.com/SAML-Toolkits/ruby-saml/pull/733) Allow `RubySaml::Utils.is_cert_expired` and `is_cert_active` to accept an optional time argument.
2020
* [#731](https://github.com/SAML-Toolkits/ruby-saml/pull/731) Add CI coverage for Ruby 3.4. Remove CI coverage for Ruby 1.x and 2.x.
21+
* [#735](https://github.com/SAML-Toolkits/ruby-saml/pull/735) Add `Settings#sp_uuid_prefix` and deprecate `Utils#set_prefix`.
2122

2223
### 1.18.0 (???)
2324
* [#718](https://github.com/SAML-Toolkits/ruby-saml/pull/718) Add support to retrieve from SAMLResponse the AuthnInstant and AuthnContextClassRef values

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,15 @@ end
963963
964964
The `attribute_value` option additionally accepts an array of possible values.
965965
966+
## SP-Originated Message IDs
967+
968+
Ruby SAML automatically generates message IDs for SP-originated messages (AuthNRequest, etc.)
969+
By default, this is a UUID prefixed by the `_` character, for example `"_ea8b5fdf-0a71-4bef-9f87-5406ee746f5b"`.
970+
To override this behavior, you may set `settings.sp_uuid_prefix` to a string of your choice.
971+
Note that the SAML specification requires that this type (`xsd:ID`) be an
972+
[NCName](https://www.w3.org/TR/xmlschema-2/#NCName), meaning that it must start with a letter
973+
or underscore, and can only contain letters, digits, underscores, hyphens, and periods.
974+
966975
## Custom Metadata Fields
967976
968977
Some IdPs may require to add SPs to add additional fields (Organization, ContactPerson, etc.)

UPGRADING.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,30 @@ settings.idp_slo_service_binding = :redirect
7777

7878
For clarity, the default value of both parameters is `:redirect` if they are not set.
7979

80+
### Change to message UUID prefix customization
81+
82+
On SP-originated messages (`Authrequest`, `Logoutrequest`, `Logoutresponse`), RubySaml generates the
83+
`uuid` (aliased to `request_id` / `response_id`) using the `_` character as a default prefix,
84+
for example `_a1b3c5d7-9f1e-3d5c-7b1a-9f1e3d5c7b1a`. In RubySaml versions prior to `2.0.0`, it was
85+
possible to change this default prefix by either `RubySaml::Utils.set_prefix` or by mutating
86+
the `RubySaml::Utils::UUID_PREFIX` constant (which was what `.set_prefix` did.) In RubySaml `2.0.0`,
87+
this prefix is now set using `settings.sp_uuid_prefix`:
88+
89+
```ruby
90+
# Change the default prefix from `_` to `my_id_`
91+
settings.sp_uuid_prefix = 'my_id_'
92+
93+
# Create the AuthNRequest message
94+
request = RubySaml::Authrequest.new
95+
request.create(settings)
96+
request.uuid #=> "my_id_a1b3c5d7-9f1e-3d5c-7b1a-9f1e3d5c7b1a"
97+
```
98+
99+
A side-effect of this change is that the `uuid` of the `Authrequest`, `Logoutrequest`, and `Logoutresponse`
100+
classes now is `nil` until the `#create` method is called (previously, it was set in the constructor.)
101+
After calling `#create` for the first time the `uuid` will not change, even if a `Settings` object with
102+
a different `sp_uuid_prefix` is passed-in on subsequent calls.
103+
80104
### Deprecation of compression settings
81105

82106
The `settings.compress_request` and `settings.compress_response` parameters have been deprecated
@@ -103,11 +127,10 @@ The following parameters in `RubySaml::Settings` are deprecated and will be remo
103127

104128
### Minor changes to Util#format_cert and #format_private_key
105129

106-
107130
Version `2.0.0` standardizes how RubySaml reads and formats certificate and private key
108131
PEM strings. In general, version `2.0.0` is more permissive than `1.x`, and the changes
109132
are not anticipated to affect most users. Please note the change affects parameters
110-
such `#idp_cert` and `#certificate`, as well as the `RubySaml::Util#format_cert`
133+
such `#idp_cert` and `#certificate`, as well as the `RubySaml::Utils#format_cert`
111134
and `#format_private_key` methods. Specifically:
112135

113136

lib/ruby_saml/authrequest.rb

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,15 @@ class Authrequest < SamlMessage
1616

1717
# AuthNRequest ID
1818
attr_accessor :uuid
19-
20-
# Initializes the AuthNRequest. An Authrequest Object that is an extension of the SamlMessage class.
21-
# Asigns an ID, a random uuid.
22-
#
23-
def initialize
24-
@uuid = RubySaml::Utils.uuid
25-
super()
26-
end
27-
28-
def request_id
29-
@uuid
30-
end
19+
alias_method :request_id, :uuid
3120

3221
# Creates the AuthNRequest string.
3322
# @param settings [RubySaml::Settings|nil] Toolkit settings
3423
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
3524
# @return [String] AuthNRequest string that includes the SAMLRequest
3625
#
3726
def create(settings, params = {})
27+
assign_uuid(settings)
3828
params = create_params(settings, params)
3929
params_prefix = /\?/.match?(settings.idp_sso_service_url) ? '&' : '?'
4030
saml_request = CGI.escape(params.delete("SAMLRequest"))
@@ -107,6 +97,7 @@ def create_authentication_xml_doc(settings)
10797

10898
def create_xml_document(settings)
10999
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
100+
assign_uuid(settings)
110101

111102
request_doc = RubySaml::XML::Document.new
112103
request_doc.uuid = uuid
@@ -190,5 +181,9 @@ def sign_document(document, settings)
190181

191182
document
192183
end
184+
185+
def assign_uuid(settings)
186+
@uuid ||= RubySaml::Utils.generate_uuid(settings.sp_uuid_prefix) # rubocop:disable Naming/MemoizedInstanceVariableName
187+
end
193188
end
194189
end

lib/ruby_saml/logoutrequest.rb

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,15 @@ class Logoutrequest < SamlMessage
1414

1515
# Logout Request ID
1616
attr_accessor :uuid
17-
18-
# Initializes the Logout Request. A Logoutrequest Object that is an extension of the SamlMessage class.
19-
# Asigns an ID, a random uuid.
20-
#
21-
def initialize
22-
@uuid = RubySaml::Utils.uuid
23-
super()
24-
end
25-
26-
def request_id
27-
@uuid
28-
end
17+
alias_method :request_id, :uuid
2918

3019
# Creates the Logout Request string.
3120
# @param settings [RubySaml::Settings|nil] Toolkit settings
3221
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
3322
# @return [String] Logout Request string that includes the SAMLRequest
3423
#
3524
def create(settings, params={})
25+
assign_uuid(settings)
3626
params = create_params(settings, params)
3727
params_prefix = /\?/.match?(settings.idp_slo_service_url) ? '&' : '?'
3828
saml_request = CGI.escape(params.delete("SAMLRequest"))
@@ -105,6 +95,7 @@ def create_logout_request_xml_doc(settings)
10595

10696
def create_xml_document(settings)
10797
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
98+
assign_uuid(settings)
10899

109100
request_doc = RubySaml::XML::Document.new
110101
request_doc.uuid = uuid
@@ -149,5 +140,9 @@ def sign_document(document, settings)
149140

150141
document
151142
end
143+
144+
def assign_uuid(settings)
145+
@uuid ||= RubySaml::Utils.generate_uuid(settings.sp_uuid_prefix) # rubocop:disable Naming/MemoizedInstanceVariableName
146+
end
152147
end
153148
end

lib/ruby_saml/settings.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def initialize(overrides = {}, keep_security_attributes = false)
4848
attr_reader :assertion_consumer_service_binding
4949
attr_accessor :single_logout_service_url
5050
attr_reader :single_logout_service_binding
51+
attr_accessor :sp_uuid_prefix
5152
attr_accessor :sp_name_qualifier
5253
attr_accessor :name_identifier_format
5354
attr_accessor :name_identifier_value

lib/ruby_saml/slo_logoutresponse.rb

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# frozen_string_literal: true
22

33
require "ruby_saml/logging"
4-
54
require "ruby_saml/saml_message"
65
require "ruby_saml/utils"
76
require "ruby_saml/setting_error"
@@ -15,18 +14,7 @@ class SloLogoutresponse < SamlMessage
1514

1615
# Logout Response ID
1716
attr_accessor :uuid
18-
19-
# Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
20-
# Asigns an ID, a random uuid.
21-
#
22-
def initialize
23-
@uuid = RubySaml::Utils.uuid
24-
super()
25-
end
26-
27-
def response_id
28-
@uuid
29-
end
17+
alias_method :response_id, :uuid
3018

3119
# Creates the Logout Response string.
3220
# @param settings [RubySaml::Settings|nil] Toolkit settings
@@ -37,6 +25,7 @@ def response_id
3725
# @return [String] Logout Request string that includes the SAMLRequest
3826
#
3927
def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
28+
assign_uuid(settings)
4029
params = create_params(settings, request_id, logout_message, params, logout_status_code)
4130
params_prefix = /\?/.match?(settings.idp_slo_service_url) ? '&' : '?'
4231
url = settings.idp_slo_response_service_url || settings.idp_slo_service_url
@@ -117,6 +106,7 @@ def create_logout_response_xml_doc(settings, request_id = nil, logout_message =
117106

118107
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
119108
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
109+
assign_uuid(settings)
120110

121111
response_doc = RubySaml::XML::Document.new
122112
response_doc.uuid = uuid
@@ -160,5 +150,9 @@ def sign_document(document, settings)
160150

161151
document
162152
end
153+
154+
def assign_uuid(settings)
155+
@uuid ||= RubySaml::Utils.generate_uuid(settings.sp_uuid_prefix) # rubocop:disable Naming/MemoizedInstanceVariableName
156+
end
163157
end
164158
end

lib/ruby_saml/utils.rb

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ module Utils # rubocop:disable Metrics/ModuleLength
3030
(\d+)W # 8: Weeks
3131
)
3232
$/x
33-
UUID_PREFIX = +'_'
33+
UUID_DEFAULT_PREFIX = '_'
34+
35+
# @deprecated Use UUID_DEFAULT_PREFIX instead.
36+
# This constant is intentionally unfrozen for backwards compatibility.
37+
UUID_PREFIX = UUID_DEFAULT_PREFIX.dup
3438

3539
# Checks if the x509 cert provided is expired.
3640
#
@@ -382,13 +386,22 @@ def retrieve_plaintext(cipher_text, symmetric_key, algorithm)
382386
end
383387
end
384388

385-
def set_prefix(value)
386-
UUID_PREFIX.replace value
389+
# @deprecated Use RubySaml::Settings#sp_uuid_prefix instead.
390+
def set_prefix(_value)
391+
raise NoMethodError.new('RubySaml::Util.set_prefix has been removed. Please use RubySaml::Settings#sp_uuid_prefix instead.')
387392
end
388393

389-
def uuid
390-
"#{UUID_PREFIX}#{SecureRandom.uuid}"
394+
# Generates a UUID with a prefix.
395+
#
396+
# @param prefix [String|false|nil] An explicit prefix override.
397+
# Using nil will use the default prefix, and false will use no prefix.
398+
# @return [String] The generated UUID.
399+
def generate_uuid(prefix = nil)
400+
prefix = prefix.is_a?(FalseClass) ? nil : prefix || UUID_DEFAULT_PREFIX
401+
"#{prefix}#{SecureRandom.uuid}"
391402
end
403+
# @deprecated Use #generate_uuid
404+
alias_method :uuid, :generate_uuid
392405

393406
# Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed,
394407
# then the fully-qualified domain name and the host should performa a case-insensitive match, per the

0 commit comments

Comments
 (0)