action_ip_filter 0.3.1 → 0.4.0
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 +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +48 -15
- data/lib/action_ip_filter/ip_filterable.rb +22 -56
- data/lib/action_ip_filter/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd87d049abbc429bef20f3c4d684efd1f94a97cf9758603e6a404ecc97c487c0
|
|
4
|
+
data.tar.gz: 1460f97edeb6ddd39c71190ec07d866c3bc473818ab2ae852624a034027b1d7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e279fadffc67defc41e2447f985780fc0332022a1bce62de27b065f9e715bbf18d173f8c87aa72780d2b8b91b743d45130608235f0b6d7c42cdfa24b699518e
|
|
7
|
+
data.tar.gz: e2918f5170516b232838a516e0ddf7c80f81cd5ef7c9359a1c07820e9ec96d1a8d91f9579824df7b535b24064596cd0da2e1d046db63146a4d3c524eeea9c0cb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2025-12-01
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
#### Change both the behavior and the interface of `filter_ip` [#5](https://github.com/smartbank-inc/action_ip_filter/pull/5)
|
|
8
|
+
|
|
9
|
+
Previously, `filter_ip` accepted a variable number of action names along with an `allowed_ips:` parameter. It now instead accepts a variable number of allowed IP addresses.
|
|
10
|
+
|
|
11
|
+
When neither `only:` nor `except:` is specified, the filter is applied to all actions in the controller. To restric filtering to specific actions, `only:` or `except:` must be used—similar to how `before_action` is scoped.
|
|
12
|
+
|
|
13
|
+
**Example:**
|
|
14
|
+
|
|
15
|
+
Before:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
filter_ip :index, :show, allowed_ips: %w[192.0.2.0/24 198.51.100.1]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
filter_ip "192.0.2.0/24", "198.51.100.1", only: [:index, :show]
|
|
25
|
+
```
|
|
26
|
+
|
|
3
27
|
## [0.3.1] - 2025-12-01
|
|
4
28
|
|
|
5
29
|
- Simplify IpMatcher#match? by removing unnecessary branching [#3](https://github.com/smartbank-inc/action_ip_filter/pull/3)
|
data/README.md
CHANGED
|
@@ -54,13 +54,13 @@ bundle install
|
|
|
54
54
|
|
|
55
55
|
### Basic Usage
|
|
56
56
|
|
|
57
|
-
Include the concern and use `filter_ip` to protect
|
|
57
|
+
Include the concern and use `filter_ip` to protect all actions when neither `:only` nor `:except` is specified:
|
|
58
58
|
|
|
59
59
|
```ruby
|
|
60
60
|
class AdminController < ApplicationController
|
|
61
61
|
include ActionIpFilter::IpFilterable
|
|
62
62
|
|
|
63
|
-
filter_ip
|
|
63
|
+
filter_ip "192.0.2.0/24", "198.51.100.1"
|
|
64
64
|
|
|
65
65
|
def index
|
|
66
66
|
# Only accessible from 192.0.2.0/24 or 198.51.100.1
|
|
@@ -71,27 +71,50 @@ class AdminController < ApplicationController
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def public_action
|
|
74
|
-
#
|
|
74
|
+
# Also restricted
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
#### With modifiers
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
class AdminController < ApplicationController
|
|
83
|
+
include ActionIpFilter::IpFilterable
|
|
84
|
+
|
|
85
|
+
filter_ip "192.0.2.0/24", "198.51.100.1", only: [:index, :show]
|
|
86
|
+
|
|
87
|
+
def index
|
|
88
|
+
# Only accessible from 192.0.2.0/24 or 198.51.100.1
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def show
|
|
92
|
+
# Also restricted
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def public_action
|
|
96
|
+
# Not restricted
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
80
100
|
|
|
81
|
-
|
|
101
|
+
The above example is functionally equivalent to the following:
|
|
82
102
|
|
|
83
103
|
```ruby
|
|
84
|
-
class
|
|
104
|
+
class AdminController < ApplicationController
|
|
85
105
|
include ActionIpFilter::IpFilterable
|
|
86
106
|
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
filter_ip "192.0.2.0/24", "198.51.100.1", except: [:public_action]
|
|
108
|
+
|
|
109
|
+
def index
|
|
110
|
+
# Only accessible from 192.0.2.0/24 or 198.51.100.1
|
|
111
|
+
end
|
|
89
112
|
|
|
90
|
-
def
|
|
91
|
-
#
|
|
113
|
+
def show
|
|
114
|
+
# Also restricted
|
|
92
115
|
end
|
|
93
116
|
|
|
94
|
-
def
|
|
117
|
+
def public_action
|
|
95
118
|
# Not restricted
|
|
96
119
|
end
|
|
97
120
|
end
|
|
@@ -105,8 +128,19 @@ Pass a Proc for dynamic IP resolution:
|
|
|
105
128
|
class SecureController < ApplicationController
|
|
106
129
|
include ActionIpFilter::IpFilterable
|
|
107
130
|
|
|
108
|
-
filter_ip :
|
|
109
|
-
|
|
131
|
+
filter_ip -> { Rails.application.credentials.dig(:allowed_ips) || [] }
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The Proc must return a value of type `Array[String]`.
|
|
136
|
+
|
|
137
|
+
You can also combine static IPs with dynamic resolution:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
class SecureController < ApplicationController
|
|
141
|
+
include ActionIpFilter::IpFilterable
|
|
142
|
+
|
|
143
|
+
filter_ip "192.0.2.0/24", -> { Rails.application.credentials.dig(:allowed_ips) || [] }
|
|
110
144
|
end
|
|
111
145
|
```
|
|
112
146
|
|
|
@@ -118,8 +152,7 @@ Customize the response when access is denied. The block is executed via `instanc
|
|
|
118
152
|
class ApiController < ApplicationController
|
|
119
153
|
include ActionIpFilter::IpFilterable
|
|
120
154
|
|
|
121
|
-
filter_ip
|
|
122
|
-
allowed_ips: %w[192.0.2.0/24],
|
|
155
|
+
filter_ip "192.0.2.0/24",
|
|
123
156
|
on_denied: -> { render json: { error: "Access denied from #{request.remote_ip}" }, status: :forbidden }
|
|
124
157
|
end
|
|
125
158
|
```
|
|
@@ -6,80 +6,46 @@ module ActionIpFilter
|
|
|
6
6
|
module IpFilterable
|
|
7
7
|
extend ActiveSupport::Concern
|
|
8
8
|
|
|
9
|
-
included do
|
|
10
|
-
class_attribute :action_ip_restrictions, default: {}
|
|
11
|
-
end
|
|
12
|
-
|
|
13
9
|
# @rbs!
|
|
14
|
-
# type restriction_options = { allowed_ips: Array[String] | ^() -> Array[String], on_denied: (^() -> void)? }
|
|
15
|
-
# def action_ip_restrictions: () -> Hash[Symbol, untyped]
|
|
16
|
-
# def action_ip_restrictions=: (Hash[Symbol, untyped]) -> void
|
|
17
|
-
|
|
18
|
-
# @rbs!
|
|
19
|
-
# def request: () -> ActionDispatch::Request
|
|
20
10
|
# def action_name: () -> String
|
|
21
|
-
# def instance_exec: (*untyped) { () ->
|
|
11
|
+
# def instance_exec: [T] (*untyped) { () -> T } -> T
|
|
22
12
|
# def before_action: (*untyped, **untyped) -> void
|
|
23
13
|
|
|
24
14
|
class_methods do
|
|
25
|
-
# @rbs
|
|
26
|
-
# @rbs allowed_ips: Array[String] | ^() -> Array[String]
|
|
27
|
-
# @rbs on_denied: (^() -> void)?
|
|
28
|
-
# @rbs return: void
|
|
29
|
-
def filter_ip(*actions, allowed_ips:, on_denied: nil)
|
|
30
|
-
actions.flatten.each do |action|
|
|
31
|
-
self.action_ip_restrictions = action_ip_restrictions.merge(action.to_sym => {allowed_ips:, on_denied:})
|
|
32
|
-
before_action -> { check_ip_restriction(action) }, only: action
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# @rbs allowed_ips: Array[String] | ^() -> Array[String]
|
|
37
|
-
# @rbs except: Array[Symbol]
|
|
15
|
+
# @rbs allowed_ips: String | ^() -> Array[String]
|
|
38
16
|
# @rbs on_denied: (^() -> void)?
|
|
17
|
+
# @rbs only: Array[Symbol]?
|
|
18
|
+
# @rbs except: Array[Symbol]?
|
|
39
19
|
# @rbs return: void
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
self.action_ip_restrictions = action_ip_restrictions.merge("all-marker": {allowed_ips:, on_denied:})
|
|
43
|
-
before_action :check_ip_restriction_for_all, except:
|
|
20
|
+
def filter_ip(*allowed_ips, on_denied: nil, only: nil, except: nil)
|
|
21
|
+
before_action -> { verify_ip_access(allowed_ips:, on_denied:) }, only:, except:
|
|
44
22
|
end
|
|
45
23
|
end
|
|
46
24
|
|
|
47
25
|
private
|
|
48
26
|
|
|
49
|
-
# @rbs
|
|
27
|
+
# @rbs allowed_ips: Array[String | ^() -> Array[String]]
|
|
28
|
+
# @rbs on_denied: (^() -> void)?
|
|
50
29
|
# @rbs return: void
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
end
|
|
30
|
+
def verify_ip_access(allowed_ips:, on_denied:)
|
|
31
|
+
return if ActionIpFilter.test_mode?
|
|
54
32
|
|
|
55
|
-
|
|
56
|
-
def check_ip_restriction_for_all
|
|
57
|
-
verify_ip_access(action_ip_restrictions[:"all-marker"])
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# @rbs restriction: restriction_options?
|
|
61
|
-
# @rbs return: void
|
|
62
|
-
def verify_ip_access(restriction)
|
|
63
|
-
return if restriction.nil? || ActionIpFilter.test_mode?
|
|
33
|
+
client_ip = instance_exec(&ActionIpFilter.configuration.ip_resolver)
|
|
64
34
|
|
|
65
|
-
|
|
66
|
-
|
|
35
|
+
allowed = allowed_ips.any? do |allowed_ip|
|
|
36
|
+
ips = case allowed_ip
|
|
37
|
+
when Proc
|
|
38
|
+
instance_exec(&allowed_ip)
|
|
39
|
+
else
|
|
40
|
+
[allowed_ip]
|
|
41
|
+
end
|
|
67
42
|
|
|
68
|
-
|
|
69
|
-
log_ip_denial(client_ip)
|
|
70
|
-
on_denied = restriction[:on_denied] || ActionIpFilter.configuration.on_denied
|
|
71
|
-
instance_exec(&on_denied)
|
|
43
|
+
IpMatcher.allowed?(client_ip, ips)
|
|
72
44
|
end
|
|
73
|
-
end
|
|
74
45
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
case allowed_ips
|
|
79
|
-
when Proc
|
|
80
|
-
instance_exec(&allowed_ips) #: Array[String]
|
|
81
|
-
else
|
|
82
|
-
Array(allowed_ips)
|
|
46
|
+
unless allowed
|
|
47
|
+
log_ip_denial(client_ip)
|
|
48
|
+
instance_exec(&on_denied || ActionIpFilter.configuration.on_denied)
|
|
83
49
|
end
|
|
84
50
|
end
|
|
85
51
|
|