api_signature 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e907dd2adc3e3558f0f5fc7a00131ccac6d71aac
4
+ data.tar.gz: 7db50a3800bc9c2e77eea7d7a543a1054d40073c
5
+ SHA512:
6
+ metadata.gz: 527fd053ef63b32c5ce09cfa0e020c7dbc9a125e52b8332703e4bc2b20380e44557e6e51f30fbc64565238236daef9db38716f738c49c23fb2e05bcadece8fb1
7
+ data.tar.gz: fb6449eb2de6530c14175f8faa775ba0a62466ac8581714255b9f62134d007525a3cc9f3aef62ad2d65402aea39f9f569e13dc748c7c4277fc4a2872e455356b
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,45 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4.4
3
+ Exclude:
4
+ - "bin/*"
5
+ - "Guardfile"
6
+
7
+ ##################### Styles ##################################
8
+
9
+ Style/Documentation:
10
+ Enabled: false
11
+
12
+ Style/SymbolArray:
13
+ Enabled: false
14
+
15
+ ##################### Metrics ##################################
16
+
17
+ Metrics/LineLength:
18
+ Max: 110
19
+
20
+ Metrics/ClassLength:
21
+ Max: 200
22
+
23
+ Metrics/ModuleLength:
24
+ Max: 200
25
+ Exclude:
26
+ - "**/*_spec.rb"
27
+
28
+ Metrics/BlockLength:
29
+ Max: 50
30
+ Exclude:
31
+ - "**/*_spec.rb"
32
+
33
+ Metrics/MethodLength:
34
+ Max: 15
35
+
36
+ ##################### Rails ##################################
37
+
38
+ Rails:
39
+ Enabled: true
40
+
41
+ Rails/SkipsModelValidations:
42
+ Enabled: false
43
+
44
+ Rails/InverseOf:
45
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.4
5
+ before_install: gem install bundler -v 1.15.4
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in api_signature.gemspec
6
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.call("routing/#{m[1]}_routing"),
51
+ rspec.spec.call("controllers/#{m[1]}_controller"),
52
+ rspec.spec.call("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ # Rails config changes
57
+ watch(rails.spec_helper) { rspec.spec_dir }
58
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # Capybara features specs
62
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
63
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
64
+
65
+ # Turnip features and steps
66
+ watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ end
70
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Igor Malinovskiy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ [![Build Status](https://semaphoreci.com/api/v1/igormalinovskiy/api_signature/branches/master/shields_badge.svg)](https://semaphoreci.com/igormalinovskiy/api_signature)
2
+ [![Code Climate](https://codeclimate.com/github/psyipm/api_signature/badges/gpa.svg)](https://codeclimate.com/github/psyipm/api_signature)
3
+ [![Gem Version](https://badge.fury.io/rb/api_signature.svg)](https://badge.fury.io/rb/api_signature)
4
+
5
+ # ApiSignature
6
+
7
+ Simple HMAC-SHA1 authentication via headers
8
+
9
+ This gem will generate signature for the client requests and verify that signature on the server side
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'api_signature'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ ## Usage
24
+
25
+ ### Server side
26
+
27
+ Implement warden strategy:
28
+ ```ruby
29
+ module MyApplication
30
+ module API
31
+ class ClientAuthenticatable < Warden::Strategies::Base
32
+ delegate :valid?, to: :api_request
33
+
34
+ def authenticate!
35
+ # Find client in database by public api_key
36
+ resource = Client.find_for_token_authentication(api_request.access_key)
37
+ return fail!(:not_found_in_database) unless resource
38
+
39
+ # Check request signature
40
+ return unless api_request.correct?(resource.api_key, resource.api_secret)
41
+
42
+ # Perform some after_authentication callbacks
43
+ resource.after_authentication
44
+
45
+ # Tell warden that authentication was successful
46
+ success!(resource)
47
+ end
48
+
49
+ private
50
+
51
+ def api_request
52
+ @api_request ||= ::ApiSignature::Request.new(env)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ ```
58
+
59
+ ```ruby
60
+ module MyApplication
61
+ module API
62
+ module Authentication
63
+ extend ActiveSupport::Concern
64
+
65
+ protected
66
+
67
+ def warden
68
+ @warden ||= request.env['warden']
69
+ end
70
+
71
+ def current_client
72
+ @current_client ||= warden.user(:client)
73
+ end
74
+
75
+ def authenticate_client!
76
+ warden.authenticate!(:client_authenticatable, scope: :client)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ ```
82
+
83
+ ```ruby
84
+ class Api::BaseController < ActionController::API do
85
+ abstract!
86
+
87
+ include MyApplication::API::Authentication
88
+
89
+ before_action :authenticate_client!
90
+ end
91
+ ```
92
+
93
+ ### On client side:
94
+
95
+ ```ruby
96
+ options = {
97
+ request_method: 'GET',
98
+ path: '/api/v1/some_path'
99
+ access_key: 'client public api_key',
100
+ timestamp: Time.zone.now.to_i
101
+ }
102
+
103
+ signature = ApiSignature::Generator.new(options).generate_signatute('api_secret')
104
+ ```
105
+
106
+ By default, the generated signature will be valid for 2 hours
107
+ This could be changed via initializer:
108
+
109
+ ```ruby
110
+ # config/initializers/api_signature.rb
111
+
112
+ ApiSignature.setup do |config|
113
+ config.signature_ttl = 1.minute
114
+ end
115
+ ```
116
+
117
+ ## Development
118
+
119
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
120
+
121
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
122
+
123
+ ## Contributing
124
+
125
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/api_signature.
126
+
127
+ ## License
128
+
129
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'api_signature/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'api_signature'
9
+ spec.version = ApiSignature::VERSION
10
+ spec.authors = ['Igor Galeta', 'Igor Malinovskiy']
11
+ spec.email = ['igor.malinovskiy@netfix.xyz']
12
+
13
+ spec.summary = 'Sign API requests with HMAC signature'
14
+ spec.homepage = 'https://github.com/psyipm/api_signature'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.15'
25
+ spec.add_development_dependency 'guard-rspec', '~> 4.7', '>= 4.7.3'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rb-fsevent', '0.9.8'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+
30
+ spec.add_dependency 'activesupport', '>= 4.0'
31
+ spec.add_dependency 'rack', '~> 2.0', '>= 2.0.5'
32
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "api_signature"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/hash_with_indifferent_access'
4
+ require 'ostruct'
5
+
6
+ module ApiSignature
7
+ class Builder
8
+ OPTIONS_KEYS = [
9
+ :access_key, :secret, :request_method, :scheme, :host, :port, :path, :params, :timestamp
10
+ ].freeze
11
+
12
+ delegate(*OPTIONS_KEYS, to: :@settings)
13
+ delegate :expired?, to: :signature_generator
14
+
15
+ def initialize(settings = {})
16
+ settings = HashWithIndifferentAccess.new(settings)
17
+
18
+ settings['timestamp'] ||= Time.now.utc.to_i.to_s
19
+ settings['request_method'] = (settings['request_method'] || settings['method']).upcase
20
+
21
+ @settings = OpenStruct.new(settings.select { |k, _v| OPTIONS_KEYS.include?(k.to_sym) })
22
+ end
23
+
24
+ def headers
25
+ {
26
+ 'X-Access-Key' => options[:access_key],
27
+ 'X-Timestamp' => options[:timestamp],
28
+ 'X-Signature' => signature
29
+ }
30
+ end
31
+
32
+ def string_headers
33
+ headers.map { |key, value| "#{key}:#{value}" }.join(' ')
34
+ end
35
+
36
+ def options
37
+ {
38
+ timestamp: timestamp,
39
+ request_method: request_method,
40
+ path: path,
41
+ access_key: access_key
42
+ }
43
+ end
44
+
45
+ def signature
46
+ @signature ||= signature_generator.generate_signature(secret)
47
+ end
48
+
49
+ def url
50
+ klass = scheme.try(:downcase) == 'https' ? URI::HTTPS : URI::HTTP
51
+ klass.build(host: host, port: port, path: options[:path])
52
+ end
53
+
54
+ private
55
+
56
+ def signature_generator
57
+ @signature_generator ||= Generator.new(options)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'digest/sha1'
5
+
6
+ module ApiSignature
7
+ class Generator
8
+ SPLITTER = '|'
9
+ TTL = 2.hours
10
+
11
+ delegate :valid?, :expired?, :timestamp, to: :validator
12
+
13
+ def initialize(options = {})
14
+ @options = options
15
+ end
16
+
17
+ def generate_signature(secret)
18
+ hmac = OpenSSL::HMAC.digest(digest, secret, string_to_sign)
19
+ Base64.encode64(hmac).chomp
20
+ end
21
+
22
+ private
23
+
24
+ def validator
25
+ Validator.new(@options)
26
+ end
27
+
28
+ def digest
29
+ OpenSSL::Digest::SHA256.new
30
+ end
31
+
32
+ def string_to_sign
33
+ [
34
+ @options[:request_method],
35
+ @options[:path],
36
+ @options[:access_key],
37
+ timestamp.to_i
38
+ ].map(&:to_s).join(SPLITTER)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/request'
4
+
5
+ module ApiSignature
6
+ class Request < ::Rack::Request
7
+ HEADER_KEYS = {
8
+ access_key: 'HTTP_X_ACCESS_KEY',
9
+ signature: 'HTTP_X_SIGNATURE',
10
+ timestamp: 'HTTP_X_TIMESTAMP'
11
+ }.freeze
12
+
13
+ def correct?(token, secret)
14
+ access_key == token && validator.valid?(signature, secret)
15
+ end
16
+
17
+ def valid?
18
+ timestamp.present? && signature.present? && access_key.present?
19
+ end
20
+
21
+ def timestamp
22
+ @timestamp ||= @env[HEADER_KEYS[:timestamp]]
23
+ end
24
+
25
+ def signature
26
+ @signature ||= @env[HEADER_KEYS[:signature]]
27
+ end
28
+
29
+ def access_key
30
+ @access_key ||= @env[HEADER_KEYS[:access_key]]
31
+ end
32
+
33
+ private
34
+
35
+ def validator
36
+ @validator ||= Validator.new(validator_params)
37
+ end
38
+
39
+ def validator_params
40
+ {
41
+ request_method: request_method,
42
+ path: path,
43
+ access_key: access_key,
44
+ timestamp: timestamp
45
+ }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiSignature
4
+ class Validator
5
+ attr_reader :timestamp
6
+
7
+ def initialize(options)
8
+ @options = options
9
+ @timestamp = Time.zone.at(@options[:timestamp].to_i)
10
+ end
11
+
12
+ def valid?(signature, secret)
13
+ return false if signature.blank? || secret.blank? || expired?
14
+ generator.generate_signature(secret) == signature
15
+ end
16
+
17
+ def expired?
18
+ !alive?
19
+ end
20
+
21
+ private
22
+
23
+ def generator
24
+ @generator ||= Generator.new(@options)
25
+ end
26
+
27
+ def alive?
28
+ alive_timerange.cover?(timestamp)
29
+ end
30
+
31
+ def alive_timerange
32
+ @alive_timerange ||= (ttl.ago..ttl.from_now)
33
+ end
34
+
35
+ def ttl
36
+ ApiSignature.signature_ttl || TTL
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiSignature
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api_signature/version'
4
+ require 'active_support/time'
5
+ require 'active_support/time_with_zone'
6
+ require 'active_support/core_ext/class'
7
+ require 'active_support/core_ext/object/try'
8
+
9
+ module ApiSignature
10
+ autoload :Builder, 'api_signature/builder'
11
+ autoload :Validator, 'api_signature/validator'
12
+ autoload :Generator, 'api_signature/generator'
13
+ autoload :Request, 'api_signature/request'
14
+
15
+ # Time to live for generated signature
16
+ mattr_accessor :signature_ttl
17
+ self.signature_ttl = 2.hours
18
+
19
+ # @example
20
+ # ApiSignature.setup do |config|
21
+ # config.signature_ttl = 2.minutes
22
+ # end
23
+ #
24
+ def self.setup
25
+ yield self
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: api_signature
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Igor Galeta
8
+ - Igor Malinovskiy
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2018-06-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.15'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.15'
28
+ - !ruby/object:Gem::Dependency
29
+ name: guard-rspec
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '4.7'
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: 4.7.3
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - "~>"
43
+ - !ruby/object:Gem::Version
44
+ version: '4.7'
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 4.7.3
48
+ - !ruby/object:Gem::Dependency
49
+ name: rake
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rb-fsevent
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.8
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.9.8
76
+ - !ruby/object:Gem::Dependency
77
+ name: rspec
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ - !ruby/object:Gem::Dependency
91
+ name: activesupport
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '4.0'
97
+ type: :runtime
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '4.0'
104
+ - !ruby/object:Gem::Dependency
105
+ name: rack
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.0'
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: 2.0.5
114
+ type: :runtime
115
+ prerelease: false
116
+ version_requirements: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - "~>"
119
+ - !ruby/object:Gem::Version
120
+ version: '2.0'
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 2.0.5
124
+ description:
125
+ email:
126
+ - igor.malinovskiy@netfix.xyz
127
+ executables: []
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - ".gitignore"
132
+ - ".rspec"
133
+ - ".rubocop.yml"
134
+ - ".travis.yml"
135
+ - Gemfile
136
+ - Guardfile
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - api_signature.gemspec
141
+ - bin/console
142
+ - bin/setup
143
+ - lib/api_signature.rb
144
+ - lib/api_signature/builder.rb
145
+ - lib/api_signature/generator.rb
146
+ - lib/api_signature/request.rb
147
+ - lib/api_signature/validator.rb
148
+ - lib/api_signature/version.rb
149
+ homepage: https://github.com/psyipm/api_signature
150
+ licenses:
151
+ - MIT
152
+ metadata: {}
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubyforge_project:
169
+ rubygems_version: 2.5.2
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Sign API requests with HMAC signature
173
+ test_files: []