prx_auth 1.7.1 → 1.7.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b11c5af8dd4126044aa9d468a50dee909483bc6de84b6fa8f46362e208dac776
4
- data.tar.gz: 8e308914d3c6f254130bbc02f245ec9a690ea3efbd67c7e03af27d89248e1285
3
+ metadata.gz: 8db9794b5957f93a863dd566c371420ee1d02002c7ab8a681fdc3a518cbe4f1c
4
+ data.tar.gz: 9938ebf515cc8c9918f8ae3e597600245a76ae48b62fecb3f102ddfbb4e2fe8c
5
5
  SHA512:
6
- metadata.gz: 1e41ac8e49eb3bbf6e77a60dbd7a607766dceecc131a221eaefb9d1c6251507cf9b526a7d031dc0a686dd32683ef9113031db1a6de957a86a19738ec9ad5a7d5
7
- data.tar.gz: 27a2c10518729f37f366ff301835618c959ea7ef5a8446ee0ed0176b6f3046e820fedc58cbce6ce790f114875f98b2c68fbbc06592e8d9685a4eb985e0305d43
6
+ metadata.gz: 4dd977316195060b437eca1ce1c2fb8f4fa033ee8262ca406d769200e0360d1b69a95cdc72ebb5c85b9d54d149a52faf531b453f82a3612dc423abed9a3fe590
7
+ data.tar.gz: 29057475f034443ef955eaf69773526044bfdd85de2e753676fcaaaa57ae275b74a64158043d0afa363961c98876c4aea169ba54887173330d1ac49c4ee57ace
@@ -0,0 +1,2 @@
1
+ # 2023-04-12 Auto-fix all Ruby files with standardrb
2
+ 09e6ab7a47e225887bd0bdea221f2e54ed3cb101
@@ -0,0 +1,21 @@
1
+ name: Check project standards
2
+
3
+ on:
4
+ push
5
+ jobs:
6
+ lint:
7
+ runs-on: ubuntu-latest
8
+ env:
9
+ RAILS_ENV: test
10
+ steps:
11
+ - name: Checkout code
12
+ uses: actions/checkout@v3
13
+ with:
14
+ fetch-depth: 0
15
+ - name: Install Ruby and gems
16
+ uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: '3.0'
19
+ bundler-cache: true
20
+ - name: Lint Ruby files
21
+ run: bundle exec standardrb
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in rack-prx_auth.gemspec
4
4
  gemspec
data/Guardfile CHANGED
@@ -1,8 +1,8 @@
1
1
  guard :minitest, all_after_pass: true do
2
- watch(%r{^test/(.*)\/?test_(.*)\.rb})
3
- watch(%r{^lib/(.*/)?([^/]+)\.rb}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
4
- watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
5
- watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
2
+ watch(%r{^test/(.*)/?test_(.*)\.rb})
3
+ watch(%r{^lib/(.*/)?([^/]+)\.rb}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
4
+ watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
5
+ watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
6
6
  watch(%r{^test/.+_test\.rb})
7
- watch(%r{^test/test_helper\.rb}) { 'test' }
7
+ watch(%r{^test/test_helper\.rb}) { "test" }
8
8
  end
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
- require 'bundler/gem_tasks'
2
- require 'rake'
3
- require 'rake/testtask'
1
+ require "bundler/gem_tasks"
2
+ require "rake"
3
+ require "rake/testtask"
4
4
 
5
5
  Rake::TestTask.new do |t|
6
- t.libs << 'test'
7
- t.pattern = 'test/**/*test.rb'
6
+ t.libs << "test"
7
+ t.pattern = "test/**/*test.rb"
8
8
  end
9
9
 
10
10
  task default: :test
@@ -1,32 +1,30 @@
1
1
  module PrxAuth
2
2
  class ResourceMap < Hash
3
- WILDCARD_KEY = '*'
3
+ WILDCARD_KEY = "*"
4
4
 
5
5
  def initialize(mapped_values)
6
6
  super() do |hash, key|
7
7
  if key == WILDCARD_KEY
8
8
  @wildcard
9
- else
10
- nil
11
9
  end
12
10
  end
13
11
  input = mapped_values.clone
14
- @wildcard = ScopeList.new(input.delete(WILDCARD_KEY)||'')
12
+ @wildcard = ScopeList.new(input.delete(WILDCARD_KEY) || "")
15
13
  input.each do |(key, values)|
16
14
  self[key.to_s] = ScopeList.new(values)
17
15
  end
18
16
  end
19
17
 
20
- def contains?(resource, namespace=nil, scope=nil)
18
+ def contains?(resource, namespace = nil, scope = nil)
21
19
  resource = resource.to_s
22
20
 
23
21
  if resource == WILDCARD_KEY
24
22
  raise ArgumentError if namespace.nil?
25
-
23
+
26
24
  @wildcard.contains?(namespace, scope)
27
25
  else
28
26
  mapped_resource = self[resource]
29
-
27
+
30
28
  if mapped_resource && !namespace.nil?
31
29
  mapped_resource.contains?(namespace, scope) || @wildcard.contains?(namespace, scope)
32
30
  elsif !namespace.nil?
@@ -47,35 +45,35 @@ module PrxAuth
47
45
 
48
46
  def condense
49
47
  condensed_wildcard = @wildcard.condense
50
- condensed_map = Hash[map do |resource, list|
48
+ condensed_map = map do |resource, list|
51
49
  [resource, (list - condensed_wildcard).condense]
52
- end]
50
+ end.to_h
53
51
  ResourceMap.new(condensed_map.merge(WILDCARD_KEY => condensed_wildcard))
54
52
  end
55
53
 
56
- def +(other_map)
54
+ def +(other)
57
55
  result = {}
58
- (resources + other_map.resources + [WILDCARD_KEY]).uniq.each do |resource|
56
+ (resources + other.resources + [WILDCARD_KEY]).uniq.each do |resource|
59
57
  list_a = list_for_resource(resource)
60
- list_b = other_map.list_for_resource(resource)
58
+ list_b = other.list_for_resource(resource)
61
59
  result[resource] = if list_a.nil?
62
- list_b
63
- elsif list_b.nil?
64
- list_a
65
- else
66
- list_a + list_b
67
- end
60
+ list_b
61
+ elsif list_b.nil?
62
+ list_a
63
+ else
64
+ list_a + list_b
65
+ end
68
66
  end
69
67
 
70
68
  ResourceMap.new(result).condense
71
69
  end
72
70
 
73
- def -(other_map)
71
+ def -(other)
74
72
  result = {}
75
- other_wildcard = other_map.list_for_resource(WILDCARD_KEY) || PrxAuth::ScopeList.new('')
73
+ other_wildcard = other.list_for_resource(WILDCARD_KEY) || PrxAuth::ScopeList.new("")
76
74
 
77
75
  resources.each do |resource|
78
- result[resource] = list_for_resource(resource) - (other_wildcard + other_map.list_for_resource(resource))
76
+ result[resource] = list_for_resource(resource) - (other_wildcard + other.list_for_resource(resource))
79
77
  end
80
78
 
81
79
  if @wildcard.length
@@ -85,21 +83,21 @@ module PrxAuth
85
83
  ResourceMap.new(result)
86
84
  end
87
85
 
88
- def &(other_map)
86
+ def &(other)
89
87
  result = {}
90
- other_wildcard = other_map.list_for_resource(WILDCARD_KEY)
91
-
92
- (resources + other_map.resources).uniq.each do |res|
88
+ other_wildcard = other.list_for_resource(WILDCARD_KEY)
89
+
90
+ (resources + other.resources).uniq.each do |res|
93
91
  left = list_for_resource(res)
94
- right = other_map.list_for_resource(res)
92
+ right = other.list_for_resource(res)
95
93
 
96
94
  result[res] = if left.nil?
97
- right & @wildcard
98
- elsif right.nil?
99
- left & other_wildcard
100
- else
101
- (left + @wildcard) & (right + other_wildcard)
102
- end
95
+ right & @wildcard
96
+ elsif right.nil?
97
+ left & other_wildcard
98
+ else
99
+ (left + @wildcard) & (right + other_wildcard)
100
+ end
103
101
  end
104
102
 
105
103
  if @wildcard.length > 0
@@ -109,11 +107,11 @@ module PrxAuth
109
107
  ResourceMap.new(result).condense
110
108
  end
111
109
 
112
- def as_json(opts={})
113
- super(opts).merge(@wildcard.length > 0 ? {WILDCARD_KEY => @wildcard}.as_json(opts) : {})
110
+ def as_json(opts = {})
111
+ super(opts).merge((@wildcard.length > 0) ? {WILDCARD_KEY => @wildcard}.as_json(opts) : {})
114
112
  end
115
113
 
116
- def resources(namespace=nil, scope=nil)
114
+ def resources(namespace = nil, scope = nil)
117
115
  if namespace.nil?
118
116
  keys
119
117
  else
@@ -1,14 +1,14 @@
1
1
  module PrxAuth
2
2
  class ScopeList < Array
3
- SCOPE_SEPARATOR = ' '
4
- NAMESPACE_SEPARATOR = ':'
3
+ SCOPE_SEPARATOR = " "
4
+ NAMESPACE_SEPARATOR = ":"
5
5
  NO_NAMESPACE = :_
6
6
 
7
7
  Entry = Struct.new(:namespace, :scope, :string)
8
8
 
9
9
  class Entry
10
- def ==(other_entry)
11
- namespace == other_entry.namespace && scope == other_entry.scope
10
+ def ==(other)
11
+ namespace == other.namespace && scope == other.scope
12
12
  end
13
13
 
14
14
  def to_s
@@ -21,21 +21,21 @@ module PrxAuth
21
21
 
22
22
  def unnamespaced
23
23
  if namespaced?
24
- Entry.new(NO_NAMESPACE, scope, string.split(':').last)
24
+ Entry.new(NO_NAMESPACE, scope, string.split(":").last)
25
25
  else
26
26
  self
27
27
  end
28
28
  end
29
29
 
30
30
  def inspect
31
- "#<ScopeList::Entry \"#{to_s}\">"
31
+ "#<ScopeList::Entry \"#{self}\">"
32
32
  end
33
33
  end
34
34
 
35
35
  def self.new(list)
36
36
  case list
37
37
  when PrxAuth::ScopeList then list
38
- when Array then super(list.join(' '))
38
+ when Array then super(list.join(" "))
39
39
  else super(list)
40
40
  end
41
41
  end
@@ -54,15 +54,15 @@ module PrxAuth
54
54
  end
55
55
  end
56
56
 
57
- def contains?(namespace, scope=nil)
57
+ def contains?(namespace, scope = nil)
58
58
  entries = if scope.nil?
59
- scope, namespace = namespace, NO_NAMESPACE
60
- [Entry.new(namespace, symbolize(scope), nil)]
61
- else
62
- scope = symbolize(scope)
63
- namespace = symbolize(namespace)
64
- [Entry.new(namespace, scope, nil), Entry.new(NO_NAMESPACE, scope, nil)]
65
- end
59
+ scope, namespace = namespace, NO_NAMESPACE
60
+ [Entry.new(namespace, symbolize(scope), nil)]
61
+ else
62
+ scope = symbolize(scope)
63
+ namespace = symbolize(namespace)
64
+ [Entry.new(namespace, scope, nil), Entry.new(NO_NAMESPACE, scope, nil)]
65
+ end
66
66
 
67
67
  entries.any? do |possible_match|
68
68
  include?(possible_match)
@@ -92,18 +92,18 @@ module PrxAuth
92
92
  end
93
93
  end
94
94
 
95
- def as_json(opts=())
95
+ def as_json(opts = ()) # standard:disable Lint/EmptyExpression
96
96
  to_s.as_json(opts)
97
97
  end
98
98
 
99
- def -(other_scope_list)
100
- return self if other_scope_list.nil?
99
+ def -(other)
100
+ return self if other.nil?
101
101
 
102
102
  tripped = false
103
103
  result = []
104
104
 
105
105
  each do |entry|
106
- if other_scope_list.include?(entry) || other_scope_list.include?(entry.unnamespaced)
106
+ if other.include?(entry) || other.include?(entry.unnamespaced)
107
107
  tripped = true
108
108
  else
109
109
  result << entry
@@ -117,16 +117,16 @@ module PrxAuth
117
117
  end
118
118
  end
119
119
 
120
- def +(other_list)
121
- return self if other_list.nil?
120
+ def +(other)
121
+ return self if other.nil?
122
122
 
123
- ScopeList.new([to_s, other_list.to_s].join(SCOPE_SEPARATOR)).condense
123
+ ScopeList.new([to_s, other.to_s].join(SCOPE_SEPARATOR)).condense
124
124
  end
125
125
 
126
- def &(other_list)
127
- return ScopeList.new('') if other_list.nil?
126
+ def &(other)
127
+ return ScopeList.new("") if other.nil?
128
128
 
129
- self - (self - other_list) + (other_list - (other_list - self))
129
+ self - (self - other) + (other - (other - self))
130
130
  end
131
131
 
132
132
  def ==(other)
@@ -138,7 +138,7 @@ module PrxAuth
138
138
  def symbolize(value)
139
139
  case value
140
140
  when Symbol then value
141
- when String then value.downcase.gsub('-', '_').intern
141
+ when String then value.downcase.tr("-", "_").intern
142
142
  else symbolize value.to_s
143
143
  end
144
144
  end
@@ -1,3 +1,3 @@
1
1
  module PrxAuth
2
- VERSION = "1.7.1"
2
+ VERSION = "1.7.2"
3
3
  end
data/lib/prx_auth.rb CHANGED
@@ -1,3 +1,3 @@
1
- require 'prx_auth/resource_map'
2
- require 'prx_auth/scope_list'
3
- require 'prx_auth/version'
1
+ require "prx_auth/resource_map"
2
+ require "prx_auth/scope_list"
3
+ require "prx_auth/version"
@@ -1,9 +1,8 @@
1
- require 'json/jwt'
1
+ require "json/jwt"
2
2
 
3
3
  module Rack
4
4
  class PrxAuth
5
5
  class AuthValidator
6
-
7
6
  attr_reader :issuer, :token
8
7
 
9
8
  def initialize(token, certificate = nil, issuer = nil)
@@ -40,18 +39,18 @@ module Rack
40
39
 
41
40
  def time_to_live
42
41
  now = Time.now.to_i
43
- if claims['exp'].nil?
42
+ if claims["exp"].nil?
44
43
  0
45
- elsif claims['iat'].nil? || claims['iat'] <= claims['exp']
46
- claims['exp'] - now
44
+ elsif claims["iat"].nil? || claims["iat"] <= claims["exp"]
45
+ claims["exp"] - now
47
46
  else
48
47
  # malformed - exp is a num-seconds offset from issued-at-time
49
- (claims['iat'] + claims['exp']) - now
48
+ (claims["iat"] + claims["exp"]) - now
50
49
  end
51
50
  end
52
51
 
53
52
  def token_issuer_matches?
54
- claims['iss'] == @issuer
53
+ claims["iss"] == @issuer
55
54
  end
56
55
  end
57
56
  end
@@ -1,11 +1,11 @@
1
- require 'json/jwt'
2
- require 'net/http'
1
+ require "json/jwt"
2
+ require "net/http"
3
3
 
4
4
  module Rack
5
5
  class PrxAuth
6
6
  class Certificate
7
7
  EXPIRES_IN = 43200
8
- DEFAULT_CERT_LOC = URI('https://id.prx.org/api/v1/certs')
8
+ DEFAULT_CERT_LOC = URI("https://id.prx.org/api/v1/certs")
9
9
 
10
10
  attr_reader :cert_location
11
11
 
@@ -15,13 +15,11 @@ module Rack
15
15
  end
16
16
 
17
17
  def valid?(token)
18
- begin
19
- JSON::JWT.decode(token, public_key)
20
- rescue JSON::JWT::VerificationFailed
21
- false
22
- else
23
- true
24
- end
18
+ JSON::JWT.decode(token, public_key)
19
+ rescue JSON::JWT::VerificationFailed
20
+ false
21
+ else
22
+ true
25
23
  end
26
24
 
27
25
  private
@@ -39,7 +37,7 @@ module Rack
39
37
 
40
38
  def fetch
41
39
  certs = JSON.parse(Net::HTTP.get(cert_location))
42
- cert_string = certs['certificates'].values[0]
40
+ cert_string = certs["certificates"].values[0]
43
41
  @refresh_at = Time.now.to_i + EXPIRES_IN
44
42
  OpenSSL::X509::Certificate.new(cert_string)
45
43
  end
@@ -1,4 +1,4 @@
1
- require 'prx_auth/resource_map'
1
+ require "prx_auth/resource_map"
2
2
 
3
3
  module Rack
4
4
  class PrxAuth
@@ -8,28 +8,28 @@ module Rack
8
8
  def initialize(attrs = {})
9
9
  @attributes = attrs
10
10
 
11
- @authorized_resources = ::PrxAuth::ResourceMap.new(unpack_aur(attrs['aur'])).freeze
12
-
13
- if attrs['scope']
14
- @scopes = attrs['scope'].split(' ').freeze
11
+ @authorized_resources = ::PrxAuth::ResourceMap.new(unpack_aur(attrs["aur"])).freeze
12
+
13
+ @scopes = if attrs["scope"]
14
+ attrs["scope"].split(" ").freeze
15
15
  else
16
- @scopes = [].freeze
16
+ [].freeze
17
17
  end
18
18
  end
19
19
 
20
- def resources(namespace=nil, scope=nil)
20
+ def resources(namespace = nil, scope = nil)
21
21
  @authorized_resources.resources(namespace, scope)
22
22
  end
23
23
 
24
24
  def user_id
25
- @attributes['sub']
25
+ @attributes["sub"]
26
26
  end
27
27
 
28
- def authorized?(resource, namespace=nil, scope=nil)
28
+ def authorized?(resource, namespace = nil, scope = nil)
29
29
  @authorized_resources.contains?(resource, namespace, scope)
30
30
  end
31
31
 
32
- def globally_authorized?(namespace, scope=nil)
32
+ def globally_authorized?(namespace, scope = nil)
33
33
  authorized?(::PrxAuth::ResourceMap::WILDCARD_KEY, namespace, scope)
34
34
  end
35
35
 
@@ -43,8 +43,8 @@ module Rack
43
43
  return {} if aur.nil?
44
44
 
45
45
  aur.clone.tap do |result|
46
- unless result['$'].nil?
47
- result.delete('$').each do |role, resources|
46
+ unless result["$"].nil?
47
+ result.delete("$").each do |role, resources|
48
48
  resources.each do |res|
49
49
  result[res.to_s] = role
50
50
  end
data/lib/rack/prx_auth.rb CHANGED
@@ -1,17 +1,17 @@
1
- require 'json/jwt'
2
- require 'rack/prx_auth/certificate'
3
- require 'rack/prx_auth/token_data'
4
- require 'rack/prx_auth/auth_validator'
5
- require 'prx_auth'
1
+ require "json/jwt"
2
+ require "rack/prx_auth/certificate"
3
+ require "rack/prx_auth/token_data"
4
+ require "rack/prx_auth/auth_validator"
5
+ require "prx_auth"
6
6
 
7
7
  module Rack
8
8
  class PrxAuth
9
9
  INVALID_TOKEN = [
10
- 401, {'Content-Type' => 'application/json'},
11
- [{status: 401, error: 'Invalid JSON Web Token'}.to_json]
10
+ 401, {"Content-Type" => "application/json"},
11
+ [{status: 401, error: "Invalid JSON Web Token"}.to_json]
12
12
  ]
13
13
 
14
- DEFAULT_ISS = 'id.prx.org'
14
+ DEFAULT_ISS = "id.prx.org"
15
15
 
16
16
  attr_reader :issuer
17
17
 
@@ -26,16 +26,16 @@ module Rack
26
26
  end
27
27
 
28
28
  def call(env)
29
- return @app.call(env) unless env['HTTP_AUTHORIZATION']
29
+ return @app.call(env) unless env["HTTP_AUTHORIZATION"]
30
30
 
31
- token = env['HTTP_AUTHORIZATION'].split[1]
31
+ token = env["HTTP_AUTHORIZATION"].split[1]
32
32
 
33
33
  auth_validator = build_auth_validator(token)
34
34
 
35
35
  return @app.call(env) unless should_validate_token?(auth_validator)
36
36
 
37
37
  if auth_validator.valid?
38
- env['prx.auth'] = TokenData.new(auth_validator.claims)
38
+ env["prx.auth"] = TokenData.new(auth_validator.claims)
39
39
  @app.call(env)
40
40
  else
41
41
  INVALID_TOKEN
data/prx_auth.gemspec CHANGED
@@ -1,32 +1,31 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("../lib", __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'prx_auth/version'
3
+ require "prx_auth/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "prx_auth"
8
- spec.version = PrxAuth::VERSION
9
- spec.authors = ["Eve Asher", "Chris Rhoden"]
10
- spec.email = ["eve@prx.org", "carhoden@gmail.com"]
11
- spec.summary = %q{Utilites for parsing PRX JWTs and Rack middleware that verifies and attaches the token's claims to env.}
12
- spec.description = %q{Specific to PRX. Will ignore tokens that were not issued by PRX.}
13
- spec.homepage = "https://github.com/PRX/prx_auth"
14
- spec.license = "MIT"
6
+ spec.name = "prx_auth"
7
+ spec.version = PrxAuth::VERSION
8
+ spec.authors = ["Eve Asher", "Chris Rhoden"]
9
+ spec.email = ["eve@prx.org", "carhoden@gmail.com"]
10
+ spec.summary = "Utilites for parsing PRX JWTs and Rack middleware that verifies and attaches the token's claims to env."
11
+ spec.description = "Specific to PRX. Will ignore tokens that were not issued by PRX."
12
+ spec.homepage = "https://github.com/PRX/prx_auth"
13
+ spec.license = "MIT"
15
14
 
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^test/})
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
17
  spec.require_paths = ["lib"]
20
18
 
21
- spec.required_ruby_version = '>= 2.3'
19
+ spec.required_ruby_version = ">= 2.3"
22
20
 
23
- spec.add_development_dependency 'bundler', '~> 2.0'
24
- spec.add_development_dependency 'rake', '~> 12.3.3'
25
- spec.add_development_dependency 'coveralls', '~> 0'
26
- spec.add_development_dependency 'guard'
27
- spec.add_development_dependency 'guard-minitest'
21
+ spec.add_development_dependency "bundler", "~> 2.0"
22
+ spec.add_development_dependency "rake", "~> 12.3.3"
23
+ spec.add_development_dependency "coveralls", "~> 0"
24
+ spec.add_development_dependency "guard"
25
+ spec.add_development_dependency "guard-minitest"
26
+ spec.add_development_dependency "standard"
28
27
 
29
- spec.add_dependency 'rack', '>= 1.5.2'
30
- spec.add_dependency 'json', '>= 1.8.1'
31
- spec.add_dependency 'json-jwt', '~> 1.12.0'
28
+ spec.add_dependency "rack", ">= 1.5.2"
29
+ spec.add_dependency "json", ">= 1.8.1"
30
+ spec.add_dependency "json-jwt", ">= 1.12.0"
32
31
  end