forest_liana 2.12.0 → 2.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df754c6c7de93be5e155ea18ca2a236e737ff9ed
4
- data.tar.gz: fb4fb5714e04982a5db0e474491c153cbbf28b47
3
+ metadata.gz: 90cfcbc6a189d0f813ab9be3b691b651147080b1
4
+ data.tar.gz: 20a7fb7fe5095658f63cd4e83f926959f90d45c3
5
5
  SHA512:
6
- metadata.gz: 9b9dd0d21d2a2001d980b6aee0d8f519731a331d559d58e394e5d8723033a5e17c1327da5ba8e1db83352189d3b4917c8d85abbe46b2ae5f54e8d23a140f34eb
7
- data.tar.gz: 9f10c5cca47362935d125593bb107eeea8d0119bf46ac275d3d096a134f8410ca7fca16e08ea01962ab7b9f9b6059f9f0c465e19e2f7c5e91e23ca1da72f48c9
6
+ metadata.gz: 33199e1098b84e64b5a90fc289b4f4aa67e40d77296a55887d77f8cd15332eac37eac91ec27df47679d97978366047b6ae3a05ad394d74dce26251c00e53071e
7
+ data.tar.gz: 59668615c97545bd736bff03f24273fd08e37ee89c51d0b3af2ea331776579c5f2deda5686158f58de8652f7f0e320f284dc43eedc6828d9889b6b205f6eba39
@@ -2,5 +2,35 @@ module ForestLiana
2
2
  class BaseController < ::ActionController::Base
3
3
  skip_before_action :verify_authenticity_token, raise: false
4
4
  wrap_parameters false
5
+ before_action :reject_unauthorized_ip
6
+
7
+ private
8
+
9
+ def reject_unauthorized_ip
10
+ begin
11
+ ip = request.remote_ip
12
+
13
+ if !IpWhitelist.is_ip_whitelist_retrieved || !IpWhitelist.is_ip_valid(ip)
14
+ unless IpWhitelist.retrieve
15
+ raise Errors::HTTP403Error.new("IP whitelist not retrieved")
16
+ end
17
+
18
+ unless IpWhitelist.is_ip_valid(ip)
19
+ raise Errors::HTTP403Error.new("IP address rejected (#{ip})")
20
+ end
21
+ end
22
+ rescue Errors::ExpectedError => exception
23
+ exception.display_error
24
+ error_data = JSONAPI::Serializer.serialize_errors([{
25
+ status: exception.error_code,
26
+ detail: exception.message
27
+ }])
28
+ render(serializer: nil, json: error_data, status: exception.status)
29
+ rescue => exception
30
+ FOREST_LOGGER.error(exception)
31
+ FOREST_LOGGER.error(exception.backtrace.join("\n"))
32
+ render(serializer: nil, json: nil, status: :internal_server_error)
33
+ end
34
+ end
5
35
  end
6
36
  end
@@ -53,7 +53,12 @@ module ForestLiana
53
53
  raise Errors::HTTP401Error
54
54
  end
55
55
 
56
- # TODO: Add ip whitelist retrieving when it exists.
56
+ # NOTICE: The IP Whitelist is retrieved on any request if it was not retrieved yet, or when
57
+ # an IP is rejected, to ensure the IP is still rejected (meaning the configuration
58
+ # on the projects has not changed). To handle the last case, which is rejecting an
59
+ # IP which was not initaliy rejected, we need periodically refresh the whitelist.
60
+ # This is done here on the login of any user.
61
+ IpWhitelist.retrieve
57
62
 
58
63
  reponse_data = LoginHandler.new(
59
64
  rendering_id,
@@ -0,0 +1,40 @@
1
+ module ForestLiana
2
+ class IpWhitelist
3
+ @@use_ip_whitelist = true
4
+ @@ip_whitelist_rules = nil
5
+
6
+ def self.retrieve
7
+ begin
8
+ response = ForestApiRequester.get('/liana/v1/ip-whitelist-rules')
9
+
10
+ if response.is_a?(Net::HTTPOK)
11
+ body = JSON.parse(response.body)
12
+ ip_whitelist_data = body['data']['attributes']
13
+
14
+ @@use_ip_whitelist = ip_whitelist_data['use_ip_whitelist']
15
+ @@ip_whitelist_rules = ip_whitelist_data['rules']
16
+ true
17
+ else
18
+ raise "Cannot retrieve the data from the Forest server. Forest API returned an #{Errors::HTTPErrorHelper.format(response)}"
19
+ end
20
+ rescue => exception
21
+ FOREST_LOGGER.error 'Cannot retrieve the IP Whitelist from the Forest server.'
22
+ FOREST_LOGGER.error 'Which was caused by:'
23
+ Errors::ExceptionHelper.recursively_print(exception, margin: ' ', is_error: true)
24
+ false
25
+ end
26
+ end
27
+
28
+ def self.is_ip_whitelist_retrieved
29
+ !@@use_ip_whitelist || !@@ip_whitelist_rules.nil?
30
+ end
31
+
32
+ def self.is_ip_valid(ip)
33
+ if @@use_ip_whitelist
34
+ return IpWhitelistChecker.is_ip_matches_any_rule(ip, @@ip_whitelist_rules)
35
+ end
36
+
37
+ true
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,71 @@
1
+ require 'ipaddress'
2
+
3
+ module ForestLiana
4
+ class IpWhitelistChecker
5
+ module RuleType
6
+ IP = 0
7
+ RANGE = 1
8
+ SUBNET = 2
9
+ end
10
+
11
+ def self.is_ip_matches_any_rule(ip, rules)
12
+ rules.any? { |rule| IpWhitelistChecker.is_ip_matches_rule(ip, rule) }
13
+ end
14
+
15
+ def self.is_ip_matches_rule(ip, rule)
16
+ if rule['type'] == RuleType::IP
17
+ return IpWhitelistChecker.is_ip_match_ip(ip, rule['ip'])
18
+ elsif rule['type'] == RuleType::RANGE
19
+ return IpWhitelistChecker.is_ip_match_range(ip, rule)
20
+ elsif rule['type'] == RuleType::SUBNET
21
+ return IpWhitelistChecker.is_ip_match_subnet(ip, rule['range'])
22
+ end
23
+
24
+ raise 'Invalid rule type'
25
+ end
26
+
27
+ def self.ip_version(ip)
28
+ (IPAddress ip).is_a?(IPAddress::IPv4) ? :ip_v4 : :ip_v6
29
+ end
30
+
31
+ def self.is_same_ip_version(ip1, ip2)
32
+ ip1_version = IpWhitelistChecker.ip_version(ip1)
33
+ ip2_version = IpWhitelistChecker.ip_version(ip2)
34
+
35
+ ip1_version == ip2_version
36
+ end
37
+
38
+ def self.is_both_loopback(ip1, ip2)
39
+ IPAddress(ip1).loopback? && IPAddress(ip2).loopback?
40
+ end
41
+
42
+ def self.is_ip_match_ip(ip1, ip2)
43
+ if !IpWhitelistChecker.is_same_ip_version(ip1, ip2)
44
+ return IpWhitelistChecker.is_both_loopback(ip1, ip2)
45
+ end
46
+
47
+ if IPAddress(ip1) == IPAddress(ip2)
48
+ true
49
+ else
50
+ IpWhitelistChecker.is_both_loopback(ip1, ip2)
51
+ end
52
+ end
53
+
54
+ def self.is_ip_match_range(ip, rule)
55
+ return false if !IpWhitelistChecker.is_same_ip_version(ip, rule['ip_minimum'])
56
+
57
+ ip_range_minimum = (IPAddress rule['ip_minimum']).to_i
58
+ ip_range_maximum = (IPAddress rule['ip_maximum']).to_i
59
+ ip_value = (IPAddress ip).to_i
60
+
61
+ return ip_value >= ip_range_minimum && ip_value <= ip_range_maximum;
62
+ end
63
+
64
+ def self.is_ip_match_subnet(ip, subnet)
65
+ return false if !IpWhitelistChecker.is_same_ip_version(ip, subnet)
66
+
67
+ IPAddress(subnet).include?(IPAddress(ip))
68
+ end
69
+ end
70
+
71
+ end
@@ -22,7 +22,7 @@ module ForestLiana
22
22
  @message = message
23
23
  end
24
24
 
25
- def display_error()
25
+ def display_error
26
26
  ExceptionHelper.recursively_print(self)
27
27
  end
28
28
  end
@@ -33,6 +33,12 @@ module ForestLiana
33
33
  end
34
34
  end
35
35
 
36
+ class HTTP403Error < ExpectedError
37
+ def initialize(message = "Forbidden")
38
+ super(403, :forbidden, message)
39
+ end
40
+ end
41
+
36
42
  class ExceptionHelper
37
43
  def self.recursively_print(error, margin: '', is_error: false)
38
44
  logger = is_error ?
@@ -1,3 +1,3 @@
1
1
  module ForestLiana
2
- VERSION = "2.12.0"
2
+ VERSION = "2.13.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_liana
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.0
4
+ version: 2.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sandro Munda
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: ipaddress
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  description: Forest is a modern admin interface that works on all major web frameworks.
168
182
  forest_liana is the gem that makes Forest admin work on any Rails application (Rails
169
183
  >= 4.0).
@@ -227,6 +241,8 @@ files:
227
241
  - app/services/forest_liana/intercom_attributes_getter.rb
228
242
  - app/services/forest_liana/intercom_conversation_getter.rb
229
243
  - app/services/forest_liana/intercom_conversations_getter.rb
244
+ - app/services/forest_liana/ip_whitelist.rb
245
+ - app/services/forest_liana/ip_whitelist_checker.rb
230
246
  - app/services/forest_liana/line_stat_getter.rb
231
247
  - app/services/forest_liana/live_query_checker.rb
232
248
  - app/services/forest_liana/login_handler.rb