cerner-oauth1a 2.3.0 → 2.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []