cerner-oauth1a 2.3.0 → 2.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,18 @@ module Cerner
6
6
  module OAuth1a
7
7
  # Public: OAuth 1.0a protocol utilities.
8
8
  module Protocol
9
+ # Public: Encodes the passed text using the percent encoding variant described in the OAuth
10
+ # 1.0a specification.
11
+ #
12
+ # Reference: https://tools.ietf.org/html/rfc5849#section-3.6
13
+ #
14
+ # text - A String containing the text to encode.
15
+ #
16
+ # Returns a String that has been encoded.
17
+ def self.percent_encode(text)
18
+ URI.encode_www_form_component(text).gsub('+', '%20')
19
+ end
20
+
9
21
  # Public: Parses a URL-encoded query string into a Hash with symbolized keys.
10
22
  #
11
23
  # query - String containing a URL-encoded query string to parse.
@@ -37,6 +49,10 @@ module Cerner
37
49
  # Cerner::OAuth1a::Protocol.parse_www_authenticate_header(header)
38
50
  # # => {:realm=>"https://test.host", :oauth_problem=>"token_expired"}
39
51
  #
52
+ # header = 'OAuth realm="https://test.host", oauth_problem=token_expired'
53
+ # Cerner::OAuth1a::Protocol.parse_www_authenticate_header(header)
54
+ # # => {:realm=>"https://test.host", :oauth_problem=>"token_expired"}
55
+ #
40
56
  # Returns a Hash with symbolized keys of all of the parameters.
41
57
  def self.parse_authorization_header(value)
42
58
  params = {}
@@ -45,10 +61,18 @@ module Cerner
45
61
  value = value.strip
46
62
  return params unless value.size > 6 && value[0..5].casecmp?('OAuth ')
47
63
 
48
- value.scan(/([^,\s=]*)=\"([^\"]*)\"/).each do |pair|
49
- k = URI.decode_www_form_component(pair[0])
50
- v = URI.decode_www_form_component(pair[1])
51
- params[k.to_sym] = v
64
+ # trim off 'OAuth ' prefix
65
+ value = value[6..-1]
66
+
67
+ # split value on comma separators
68
+ value.split(/,\s*/).each do |kv_part|
69
+ # split each part on '=' separator
70
+ key, value = kv_part.split('=')
71
+ key = URI.decode_www_form_component(key)
72
+ # trim off surrounding double quotes, if they exist
73
+ value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
74
+ value = URI.decode_www_form_component(value)
75
+ params[key.to_sym] = value
52
76
  end
53
77
 
54
78
  params
@@ -73,16 +97,12 @@ module Cerner
73
97
  #
74
98
  # Returns the String containing the generated value or nil if params is nil or empty.
75
99
  def self.generate_authorization_header(params)
76
- return nil unless params && !params.empty?
100
+ return unless params && !params.empty?
77
101
 
78
102
  realm = "realm=\"#{params.delete(:realm)}\"" if params[:realm]
79
- realm += ', ' if realm && !params.empty?
103
+ realm += ',' if realm && !params.empty?
80
104
 
81
- encoded_params = params.map do |k, v|
82
- k = URI.encode_www_form_component(k).gsub('+', '%20')
83
- v = URI.encode_www_form_component(v).gsub('+', '%20')
84
- "#{k}=\"#{v}\""
85
- end
105
+ encoded_params = params.map { |k, v| "#{percent_encode(k)}=\"#{percent_encode(v)}\"" }
86
106
 
87
107
  "OAuth #{realm}#{encoded_params.join(',')}"
88
108
  end
@@ -100,8 +120,12 @@ module Cerner
100
120
  # The values come from http://wiki.oauth.net/w/page/12238543/ProblemReporting
101
121
  # and are mapped based on https://oauth.net/core/1.0/#rfc.section.10.
102
122
  BAD_REQUEST_PROBLEMS = %w[
103
- additional_authorization_required parameter_absent parameter_rejected
104
- signature_method_rejected timestamp_refused verifier_invalid
123
+ additional_authorization_required
124
+ parameter_absent
125
+ parameter_rejected
126
+ signature_method_rejected
127
+ timestamp_refused
128
+ verifier_invalid
105
129
  version_rejected
106
130
  ].freeze
107
131
 
@@ -109,9 +133,18 @@ module Cerner
109
133
  # The values come from http://wiki.oauth.net/w/page/12238543/ProblemReporting
110
134
  # and are mapped based on https://oauth.net/core/1.0/#rfc.section.10.
111
135
  UNAUTHORIZED_PROBLEMS = %w[
112
- consumer_key_refused consumer_key_rejected consumer_key_unknown
113
- nonce_used permission_denied permission_unknown signature_invalid
114
- token_expired token_rejected token_revoked token_used user_refused
136
+ consumer_key_refused
137
+ consumer_key_rejected
138
+ consumer_key_unknown
139
+ nonce_used
140
+ permission_denied
141
+ permission_unknown
142
+ signature_invalid
143
+ token_expired
144
+ token_rejected
145
+ token_revoked
146
+ token_used
147
+ user_refused
115
148
  ].freeze
116
149
 
117
150
  # Public: Converts a oauth_problem value to an HTTP Status using the
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'cerner/oauth1a/protocol'
5
+ require 'openssl'
6
+ require 'uri'
7
+
8
+ module Cerner
9
+ module OAuth1a
10
+ # Public: OAuth 1.0a signature utilities.
11
+ module Signature
12
+ METHODS = ['PLAINTEXT', 'HMAC-SHA1'].freeze
13
+
14
+ # Public: Creates a PLAINTEXT signature.
15
+ #
16
+ # Reference: https://tools.ietf.org/html/rfc5849#section-3.4.4
17
+ #
18
+ # keywords - The keyword arguments:
19
+ # :client_shared_secret - Either the Accessor Secret or the Consumer Secret.
20
+ # :token_shared_secret - The Token Secret.
21
+ #
22
+ # Returns a String containing the signature.
23
+ def self.sign_via_plaintext(client_shared_secret:, token_shared_secret:)
24
+ client_shared_secret = Protocol.percent_encode(client_shared_secret)
25
+ token_shared_secret = Protocol.percent_encode(token_shared_secret)
26
+ "#{client_shared_secret}&#{token_shared_secret}"
27
+ end
28
+
29
+ # Public: Creates a HMAC-SHA1 signature.
30
+ #
31
+ # Reference: https://tools.ietf.org/html/rfc5849#section-3.4.2
32
+ #
33
+ # keywords - The keyword arguments:
34
+ # :client_shared_secret - Either the Accessor Secret or the Consumer Secret.
35
+ # :token_shared_secret - The Token Secret.
36
+ # :signature_base_string - The Signature Base String to sign. See
37
+ # Signature.build_signature_base_string.
38
+ #
39
+ # Returns a String containing the signature.
40
+ def self.sign_via_hmacsha1(client_shared_secret:, token_shared_secret:, signature_base_string:)
41
+ client_shared_secret = Protocol.percent_encode(client_shared_secret)
42
+ token_shared_secret = Protocol.percent_encode(token_shared_secret)
43
+ signature_key = "#{client_shared_secret}&#{token_shared_secret}"
44
+ signature = OpenSSL::HMAC.digest('sha1', signature_key, signature_base_string)
45
+ encoded_signature = Base64.strict_encode64(signature)
46
+ encoded_signature
47
+ end
48
+
49
+ # Public: Normalizes a text value as an HTTP method name for use in constructing a Signature
50
+ # Base String.
51
+ #
52
+ # Reference https://tools.ietf.org/html/rfc5849#section-3.4.1.1
53
+ #
54
+ # http_method - A String or Symbol containing an HTTP method name.
55
+ #
56
+ # Returns the normalized value as a String.
57
+ #
58
+ # Raises ArgumentError if http_method is nil.
59
+ def self.normalize_http_method(http_method)
60
+ raise ArgumentError, 'http_method is nil' unless http_method
61
+
62
+ # accepts Symbol or String
63
+ Protocol.percent_encode(http_method.to_s.upcase)
64
+ end
65
+
66
+ # Public: Normalizes a fully qualified URL for use as the Base String URI in constructing a
67
+ # Signature Base String.
68
+ #
69
+ # Reference https://tools.ietf.org/html/rfc5849#section-3.4.1.2
70
+ #
71
+ # fully_qualified_url - A String or URI that contains the scheme, host, port (optional) and
72
+ # path of a URL.
73
+ #
74
+ # Returns the normalized value as a String.
75
+ #
76
+ # Raises ArgumentError if fully_qualified_url is nil.
77
+ def self.normalize_base_string_uri(fully_qualified_url)
78
+ raise ArgumentError, 'fully_qualified_url is nil' unless fully_qualified_url
79
+
80
+ u = fully_qualified_url.is_a?(URI) ? fully_qualified_url : URI(fully_qualified_url)
81
+
82
+ Protocol.percent_encode(URI("#{u.scheme}://#{u.host}:#{u.port}#{u.path}").to_s)
83
+ end
84
+
85
+ # Public: Normalizes the parameters (query string and OAuth parameters) for use as the
86
+ # request parameters in constructing a Signature Base String.
87
+ #
88
+ # Reference: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
89
+ #
90
+ # params - A Hash of name/value pairs representing the parameters. The keys and values of the
91
+ # Hash will be assumed to be represented by the value returned from #to_s.
92
+ #
93
+ # Returns the normalized value as a String.
94
+ #
95
+ # Raises ArgumentError if params is nil.
96
+ def self.normalize_parameters(params)
97
+ raise ArgumentError, 'params is nil' unless params
98
+
99
+ encoded_params =
100
+ params.map do |name, value|
101
+ result = [Protocol.percent_encode(name.to_s), nil]
102
+ result[1] =
103
+ if value.is_a?(Array)
104
+ value = value.map { |e| Protocol.percent_encode(e.to_s) }
105
+ value.sort
106
+ else
107
+ Protocol.percent_encode(value.to_s)
108
+ end
109
+ result
110
+ end
111
+
112
+ sorted_params = encoded_params.sort_by { |name, _| name }
113
+
114
+ exploded_params =
115
+ sorted_params.map do |pair|
116
+ name = pair[0]
117
+ value = pair[1]
118
+ if value.is_a?(Array)
119
+ value.map { |e| "#{name}=#{e}" }
120
+ else
121
+ "#{name}=#{value}"
122
+ end
123
+ end
124
+ exploded_params.flatten!
125
+
126
+ joined_params = exploded_params.join('&')
127
+ Protocol.percent_encode(joined_params)
128
+ end
129
+
130
+ # Public: Builds a Signature Base String.
131
+ #
132
+ # keywords - The keyword arguments:
133
+ # :http_method - A String or Symbol containing an HTTP method name.
134
+ # :fully_qualified_url - A String or URI that contains the scheme, host, port
135
+ # (optional) and path of a URL.
136
+ # :params - A Hash of name/value pairs representing the parameters.
137
+ # The keys and values of the Hash will be assumed to be
138
+ # represented by the value returned from #to_s.
139
+ #
140
+ # Returns the Signature Base String as a String.
141
+ #
142
+ # Raises ArgumentError if http_method, fully_qualified_url or params is nil.
143
+ def self.build_signature_base_string(http_method:, fully_qualified_url:, params:)
144
+ raise ArgumentError, 'http_method is nil' unless http_method
145
+ raise ArgumentError, 'fully_qualified_url is nil' unless fully_qualified_url
146
+ raise ArgumentError, 'params is nil' unless params
147
+
148
+ parts = [
149
+ normalize_http_method(http_method),
150
+ normalize_base_string_uri(fully_qualified_url),
151
+ normalize_parameters(params)
152
+ ]
153
+ parts.join('&')
154
+ end
155
+ end
156
+ end
157
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cerner
4
4
  module OAuth1a
5
- VERSION = '2.3.0'
5
+ VERSION = '2.5.3'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cerner-oauth1a
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Beyer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-30 00:00:00.000000000 Z
11
+ date: 2020-10-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  A minimal dependency library for interacting with a Cerner OAuth 1.0a Access
@@ -30,15 +30,17 @@ files:
30
30
  - lib/cerner/oauth1a/access_token_agent.rb
31
31
  - lib/cerner/oauth1a/cache.rb
32
32
  - lib/cerner/oauth1a/cache_rails.rb
33
+ - lib/cerner/oauth1a/internal.rb
33
34
  - lib/cerner/oauth1a/keys.rb
34
35
  - lib/cerner/oauth1a/oauth_error.rb
35
36
  - lib/cerner/oauth1a/protocol.rb
37
+ - lib/cerner/oauth1a/signature.rb
36
38
  - lib/cerner/oauth1a/version.rb
37
39
  homepage: http://github.com/cerner/cerner-oauth1a
38
40
  licenses:
39
41
  - Apache-2.0
40
42
  metadata: {}
41
- post_install_message:
43
+ post_install_message:
42
44
  rdoc_options: []
43
45
  require_paths:
44
46
  - lib
@@ -53,8 +55,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
55
  - !ruby/object:Gem::Version
54
56
  version: '0'
55
57
  requirements: []
56
- rubygems_version: 3.0.3
57
- signing_key:
58
+ rubygems_version: 3.0.8
59
+ signing_key:
58
60
  specification_version: 4
59
61
  summary: Cerner OAuth 1.0a Consumer and Service Provider Library.
60
62
  test_files: []