client-auth 1.0.6
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.
- checksums.yaml +7 -0
- data/lib/client_auth.rb +9 -0
- data/lib/client_auth/authenticator.rb +76 -0
- data/lib/client_auth/client.rb +44 -0
- data/lib/client_auth/config.rb +11 -0
- data/lib/client_auth/models/error_serializer.rb +15 -0
- data/lib/client_auth/models/errors/base_error.rb +23 -0
- data/lib/client_auth/models/errors/client_error.rb +6 -0
- data/lib/client_auth/models/errors/internal_server_error.rb +11 -0
- data/lib/client_auth/models/errors/precondition_failed.rb +6 -0
- data/lib/client_auth/models/errors/resource_not_found.rb +6 -0
- data/lib/client_auth/models/errors/unprocessable_entity.rb +6 -0
- data/lib/client_auth/request.rb +19 -0
- data/lib/client_auth/resource.rb +32 -0
- data/lib/client_auth/signer.rb +70 -0
- data/lib/client_auth/version.rb +4 -0
- metadata +184 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8eabe211ff51313482abc3b4597aa8e693022b1f
|
4
|
+
data.tar.gz: 847b377e7ab06b87cf57dbc2082521dc8d4804cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8eaea007d890fb968d64bb048f0e9ae1c6946e77da5c39137302c514046f7b0adf4f6b3e10784968d18166bab3e4055e2f81a0ec8f2a8a4cbd9439bdf380b0bd
|
7
|
+
data.tar.gz: 4363a967840ff0928ecba398a47dcd4b789b695854380d5ba316c14a9f140df2eaa5173e36357ac3c04d1db10e99885530ddc47c836c322a8a58727a0294b5af
|
data/lib/client_auth.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module ClientAuth
|
2
|
+
class Authenticator
|
3
|
+
EXPIRATION = 10.minutes
|
4
|
+
DELIMITER = ':'.freeze
|
5
|
+
|
6
|
+
def initialize(request, public_key)
|
7
|
+
@request = request
|
8
|
+
@public_key = public_key
|
9
|
+
end
|
10
|
+
|
11
|
+
def authenticate!
|
12
|
+
validate_client_name!
|
13
|
+
validate_timestamp!
|
14
|
+
validate_signature!
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def validate_client_name!
|
21
|
+
raise_error('No client name') unless client_name
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_timestamp!
|
25
|
+
raise_error('No timestamp') unless timestamp
|
26
|
+
raise_error('Timestamp is expired') if expired_timestamp?
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_signature!
|
30
|
+
raise_error('Signature is missing') unless signature.present?
|
31
|
+
raise_error('Invalid signature') unless signature_valid?
|
32
|
+
end
|
33
|
+
|
34
|
+
def timestamp
|
35
|
+
Integer(@request.headers['X-Timestamp'].to_s)
|
36
|
+
rescue ArgumentError
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def expired_timestamp?
|
41
|
+
(Time.current - Time.at(timestamp)) > EXPIRATION
|
42
|
+
end
|
43
|
+
|
44
|
+
def signature_valid?
|
45
|
+
key = OpenSSL::PKey::RSA.new(@public_key)
|
46
|
+
key.verify(OpenSSL::Digest::SHA256.new, signature, concat_secret_string)
|
47
|
+
end
|
48
|
+
|
49
|
+
def concat_secret_string
|
50
|
+
[
|
51
|
+
client_name,
|
52
|
+
@request.request_method.upcase,
|
53
|
+
@request.fullpath,
|
54
|
+
request_body,
|
55
|
+
timestamp
|
56
|
+
].join(DELIMITER)
|
57
|
+
end
|
58
|
+
|
59
|
+
def request_body
|
60
|
+
return @request.body.read if @request.request_method == 'GET'
|
61
|
+
@request.raw_post
|
62
|
+
end
|
63
|
+
|
64
|
+
def signature
|
65
|
+
[@request.headers['X-Signature']].pack('H*')
|
66
|
+
end
|
67
|
+
|
68
|
+
def client_name
|
69
|
+
@request.headers['X-Client']
|
70
|
+
end
|
71
|
+
|
72
|
+
def raise_error(message)
|
73
|
+
raise ClientAuth::Errors::PreconditionFailed.new('412', message)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ClientAuth
|
2
|
+
class Client
|
3
|
+
attr_reader :config
|
4
|
+
|
5
|
+
delegate :api_host, :app_name, :key, to: :config
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(path, params = {})
|
12
|
+
resource = resource('GET', path, params)
|
13
|
+
with_rescue { resource[URI.escape(path)].get(params: params) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def post(path, params = {})
|
17
|
+
resource = resource('POST', path)
|
18
|
+
with_rescue { resource[URI.escape(path)].post(params) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def patch(path, params = {})
|
22
|
+
resource = resource('PATCH', path)
|
23
|
+
with_rescue { resource[URI.escape(path)].patch(params) }
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def with_rescue
|
29
|
+
yield
|
30
|
+
rescue RestClient::NotFound, RestClient::PreconditionFailed,
|
31
|
+
RestClient::UnprocessableEntity => exception
|
32
|
+
raise ClientAuth::ErrorSerializer.deserialize(exception.response)
|
33
|
+
rescue RestClient::Exception => exception
|
34
|
+
raise ClientAuth::Errors::ClientError.new(exception.http_code, exception.message)
|
35
|
+
end
|
36
|
+
|
37
|
+
def resource(method, path, params = nil)
|
38
|
+
signer = ClientAuth::Signer.new(method, path, params)
|
39
|
+
signer.configure(key, app_name)
|
40
|
+
headers = signer.headers.merge(content_type: :json, accept: :json)
|
41
|
+
ClientAuth::Resource.new(api_host, signer: signer, headers: headers)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ClientAuth
|
2
|
+
class ErrorSerializer
|
3
|
+
def self.serialize(error)
|
4
|
+
err = {status: error.status.to_s, title: error.title, detail: error.detail}
|
5
|
+
serialization = {errors: [err]}
|
6
|
+
serialization.to_json
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.deserialize(data)
|
10
|
+
attrs = JSON.parse(data)['errors'].first
|
11
|
+
klass = attrs['title'].constantize
|
12
|
+
klass.new(attrs['status'], attrs['detail'])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ClientAuth
|
2
|
+
module Errors
|
3
|
+
class BaseError < StandardError
|
4
|
+
attr_accessor :status, :title, :detail
|
5
|
+
|
6
|
+
alias message detail
|
7
|
+
|
8
|
+
def initialize(status, detail)
|
9
|
+
@status = status
|
10
|
+
@title = self.class.name
|
11
|
+
@detail = detail
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"#{status} #{title}: '#{detail}'"
|
16
|
+
end
|
17
|
+
|
18
|
+
def headers
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ClientAuth
|
2
|
+
class Request < RestClient::Request
|
3
|
+
|
4
|
+
def init_headers(args, signer)
|
5
|
+
@headers = (args[:headers] || {}).dup
|
6
|
+
@headers.merge!(signer.headers)
|
7
|
+
|
8
|
+
if args[:url]
|
9
|
+
@url = process_url_params(normalize_url(args[:url]), @headers)
|
10
|
+
else
|
11
|
+
raise ArgumentError, 'must pass :url'
|
12
|
+
end
|
13
|
+
parse_url_with_auth!(url)
|
14
|
+
@cookie_jar = process_cookie_args!(@uri, @headers, args)
|
15
|
+
@processed_headers = make_headers @headers
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ClientAuth
|
2
|
+
class Resource < RestClient::Resource
|
3
|
+
attr_reader :signer
|
4
|
+
|
5
|
+
def initialize(url, options = {}, backwards_compatibility = nil, &block)
|
6
|
+
@signer = options[:signer]
|
7
|
+
super(url, options, backwards_compatibility, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def post(payload, additional_headers = {}, &block)
|
11
|
+
args = args_for(:post, payload, additional_headers)
|
12
|
+
client_request(args).execute(&(block || @block))
|
13
|
+
end
|
14
|
+
|
15
|
+
def patch(payload, additional_headers = {}, &block)
|
16
|
+
args = args_for(:patch, payload, additional_headers)
|
17
|
+
client_request(args).execute(&(block || @block))
|
18
|
+
end
|
19
|
+
|
20
|
+
def client_request(args)
|
21
|
+
ClientAuth::Request.new(args).tap do |client_request|
|
22
|
+
signer.payload = client_request.payload
|
23
|
+
client_request.init_headers(args, signer)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def args_for(name, payload, additional_headers)
|
28
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
29
|
+
options.merge(method: name, url: url, payload: payload, headers: headers)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ClientAuth
|
2
|
+
class Signer
|
3
|
+
attr_reader :client_name, :payload
|
4
|
+
|
5
|
+
def initialize(method, path, params = {})
|
6
|
+
@method = method.upcase
|
7
|
+
@path = path
|
8
|
+
@payload = params
|
9
|
+
end
|
10
|
+
|
11
|
+
def payload=(value)
|
12
|
+
@payload = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def headers
|
16
|
+
raise NotImplementedError, 'Client name not configured' unless client_name
|
17
|
+
|
18
|
+
{
|
19
|
+
'X-Client' => client_name,
|
20
|
+
'X-Timestamp' => timestamp,
|
21
|
+
'X-Signature' => signature
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def configure(client_key, client_name)
|
26
|
+
@client_key = client_key
|
27
|
+
@client_name = client_name
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def timestamp
|
33
|
+
@timestamp ||= Time.now.to_i.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def signature
|
37
|
+
raise NotImplementedError, 'Client key not configured' unless @client_key
|
38
|
+
key.sign(OpenSSL::Digest::SHA256.new, secret_string).unpack('H*').first
|
39
|
+
end
|
40
|
+
|
41
|
+
def key
|
42
|
+
@key ||= OpenSSL::PKey::RSA.new(@client_key)
|
43
|
+
end
|
44
|
+
|
45
|
+
def secret_string
|
46
|
+
[
|
47
|
+
client_name,
|
48
|
+
@method,
|
49
|
+
fullpath,
|
50
|
+
request_body,
|
51
|
+
timestamp
|
52
|
+
].join(ClientAuth::Authenticator::DELIMITER)
|
53
|
+
end
|
54
|
+
|
55
|
+
def request_body
|
56
|
+
return if @method == 'GET'
|
57
|
+
payload
|
58
|
+
end
|
59
|
+
|
60
|
+
def fullpath
|
61
|
+
fullpath = [safe_path]
|
62
|
+
fullpath.push(payload.to_query) if @method == 'GET' && payload.present?
|
63
|
+
fullpath.join('?')
|
64
|
+
end
|
65
|
+
|
66
|
+
def safe_path
|
67
|
+
'/' + URI.encode(@path).gsub(%r{\A\/}, '')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: client-auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yuriy Lavryk
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rest-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-its
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.46'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.46'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop-rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1.8'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.8'
|
139
|
+
description: Authentication for matic clients
|
140
|
+
email:
|
141
|
+
- yuriy@getmatic.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- lib/client_auth.rb
|
147
|
+
- lib/client_auth/authenticator.rb
|
148
|
+
- lib/client_auth/client.rb
|
149
|
+
- lib/client_auth/config.rb
|
150
|
+
- lib/client_auth/models/error_serializer.rb
|
151
|
+
- lib/client_auth/models/errors/base_error.rb
|
152
|
+
- lib/client_auth/models/errors/client_error.rb
|
153
|
+
- lib/client_auth/models/errors/internal_server_error.rb
|
154
|
+
- lib/client_auth/models/errors/precondition_failed.rb
|
155
|
+
- lib/client_auth/models/errors/resource_not_found.rb
|
156
|
+
- lib/client_auth/models/errors/unprocessable_entity.rb
|
157
|
+
- lib/client_auth/request.rb
|
158
|
+
- lib/client_auth/resource.rb
|
159
|
+
- lib/client_auth/signer.rb
|
160
|
+
- lib/client_auth/version.rb
|
161
|
+
homepage: https://github.com/matic-insurance/client-auth
|
162
|
+
licenses: []
|
163
|
+
metadata: {}
|
164
|
+
post_install_message:
|
165
|
+
rdoc_options: []
|
166
|
+
require_paths:
|
167
|
+
- lib
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - ">="
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
requirements: []
|
179
|
+
rubyforge_project:
|
180
|
+
rubygems_version: 2.5.1
|
181
|
+
signing_key:
|
182
|
+
specification_version: 4
|
183
|
+
summary: Authentication client
|
184
|
+
test_files: []
|