local helpers = require "spec.helpers"
local cjson   = require "cjson"

local MESSAGE = "echo, ping, pong. echo, ping, pong. echo, ping, pong.\n"

for _, strategy in helpers.each_strategy() do
  describe("Plugin: ip-restriction (access) [#" .. strategy .. "]", function()
    local plugin
    local proxy_client
    local admin_client
    local db

    lazy_setup(function()
      local bp
      bp, db = helpers.get_db_utils(strategy, {
        "routes",
        "services",
        "plugins",
      })

      local route1 = bp.routes:insert {
        hosts = { "ip-restriction1.test" },
      }

      local route2 = bp.routes:insert {
        hosts = { "ip-restriction2.test" },
      }

      local route3 = bp.routes:insert {
        hosts = { "ip-restriction3.test" },
      }

      local route4 = bp.routes:insert {
        hosts = { "ip-restriction4.test" },
      }

      local route5 = bp.routes:insert {
        hosts = { "ip-restriction5.test" },
      }

      local route6 = bp.routes:insert {
        hosts = { "ip-restriction6.test" },
      }

      local route7 = bp.routes:insert {
        hosts = { "ip-restriction7.test" },
      }

      local route8 = bp.routes:insert {
        hosts = { "ip-restriction8.test" },
      }

      local route9 = bp.routes:insert {
        hosts = { "ip-restriction9.test" },
      }

      local route10 = bp.routes:insert {
        hosts = { "ip-restriction10.test" },
      }

      local route11 = bp.routes:insert {
        hosts = { "ip-restriction11.test" },
      }

      local route12 = bp.routes:insert {
        hosts = { "ip-restriction12.test" },
      }

      local grpc_service = bp.services:insert {
          name = "grpc1",
          url = helpers.grpcbin_url,
      }

      local route_grpc_deny = assert(bp.routes:insert {
        protocols = { "grpc" },
        paths = { "/hello.HelloService/" },
        hosts = { "ip-restriction-grpc1.test" },
        service = grpc_service,
      })

      local route_grpc_allow = assert(bp.routes:insert {
        protocols = { "grpc" },
        paths = { "/hello.HelloService/" },
        hosts = { "ip-restriction-grpc2.test" },
        service = grpc_service,
      })

      local route_grpc_xforwarded_deny = assert(bp.routes:insert {
        protocols = { "grpc" },
        paths = { "/hello.HelloService/" },
        hosts = { "ip-restriction-grpc3.test" },
        service = grpc_service,
      })

      -- tcp services/routes
      local tcp_srv = bp.services:insert({
        name = "tcp",
        host = helpers.mock_upstream_host,
        port = helpers.mock_upstream_stream_port,
        protocol = "tcp"
      })

      local tls_srv = bp.services:insert({
        name = "tls",
        host = helpers.mock_upstream_host,
        port = helpers.mock_upstream_stream_ssl_port,
        protocol = "tls"
      })

      local route_tcp_allow = bp.routes:insert {
        destinations = {
          {
            port = 19000,
          },
        },
        protocols = {
          "tcp",
        },
        service = tcp_srv,
      }

      local route_tcp_deny = bp.routes:insert {
        destinations = {
          {
            port = 19443,
          },
        },
        protocols = {
          "tls",
        },
        service = tls_srv,
      }

      bp.plugins:insert {
        name     = "ip-restriction",
        route = { id = route1.id },
        config   = {
          deny = { "127.0.0.1", "127.0.0.2" }
        },
      }

      plugin = assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route2.id },
        config   = {
          deny = { "127.0.0.2" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route3.id },
        config   = {
          allow = { "127.0.0.2" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route4.id },
        config   = {
          allow = { "127.0.0.1" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route5.id },
        config   = {
          deny = { "127.0.0.0/24" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route6.id },
        config   = {
          allow = { "127.0.0.4" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route7.id },
        config   = {
          deny = { "127.0.0.4" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route8.id },
        config   = {
          allow = { "0.0.0.0/0" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route9.id },
        config   = {
          allow = { "127.0.0.1" },
          deny = { "127.0.0.1" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route10.id },
        config   = {
          allow = { "127.0.0.0/24" },
          deny = { "127.0.0.1" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route11.id },
        config   = {
          allow = { "127.0.0.0/24" },
          deny = { "127.0.0.0/24" },
        },
      })

      bp.plugins:insert {
        name     = "ip-restriction",
        route = { id = route12.id },
        config   = {
          deny = { "127.0.0.0/24" },
          status = 401,
          message = "Forbidden"
        },
      }

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route_tcp_allow.id },
        config   = {
          allow = { "127.0.0.0/24" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route_tcp_deny.id },
        config   = {
          deny = { "127.0.0.0/24" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route_grpc_deny.id },
        config   = {
          deny = { "127.0.0.0/24" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route_grpc_allow.id },
        config   = {
          deny = { "127.0.0.2/32" }
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route_grpc_xforwarded_deny.id },
        config   = {
          allow = { "127.0.0.4/32" },
        },
      })

      assert(helpers.start_kong {
        database          = strategy,
        real_ip_header    = "X-Forwarded-For",
        real_ip_recursive = "on",
        trusted_ips       = "0.0.0.0/0, ::/0",
        nginx_conf        = "spec/fixtures/custom_nginx.template",
        stream_listen     = helpers.get_proxy_ip(false) .. ":19000," ..
                            helpers.get_proxy_ip(false) .. ":19443 ssl"
      })

      proxy_client = helpers.proxy_client()
      admin_client = helpers.admin_client()
    end)

    lazy_teardown(function()
      if proxy_client and admin_client then
        proxy_client:close()
        admin_client:close()
      end

      helpers.stop_kong()
    end)

    describe("deny", function()
      it("blocks a request when the IP is denied", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction1.test"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)

      it("blocks a request when the IP is denied with status/message", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction12.test"
          }
        })
        local body = assert.res_status(401, res)
        local json = cjson.decode(body)

        assert.not_nil(json)
        assert.matches("Forbidden", json.message)
      end)

      it("blocks a request when the IP is denied #grpc", function()
        local ok, err =   helpers.proxy_client_grpc(){
          service = "hello.HelloService.SayHello",
          opts = {
            ["-authority"] = "ip-restriction-grpc1.test",
            ["-v"] = true,
          },
        }
        assert.falsy(ok)
        assert.matches("Code: PermissionDenied", err)
      end)

      it("blocks a request when the IP is denied #tcp", function()
        local tcp = ngx.socket.tcp()
        assert(tcp:connect(helpers.get_proxy_ip(true), 19443))
        assert(tcp:sslhandshake(nil, nil, false))
        assert(tcp:send(MESSAGE))
        assert(tcp:receive("*a"))
        tcp:close()

        assert.logfile().has.line("IP address not allowed", true)
        -- Ensure no preread phase errors occur (regression test for #14749)
        assert.logfile().has.no.line("[error]", true)
        assert.logfile().has.no.line("traceback", true)
        assert.logfile().has.no.line("function cannot be called in preread phase", true)
      end)

      it("allows a request when the IP is not denied", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/request",
          headers = {
            ["Host"] = "ip-restriction2.test"
          }
        })
        local body = assert.res_status(200, res)
        local json = cjson.decode(body)
        assert.equal("127.0.0.1", json.vars.remote_addr)
      end)

      it("allows a request when the IP is not denied #grpc", function()
        local ok = helpers.proxy_client_grpc(){
          service = "hello.HelloService.SayHello",
          opts = {
            ["-authority"] = "ip-restriction-grpc2.test",
            ["-v"] = true,
          },
        }
        assert.truthy(ok)
      end)

      it("allows a request when the IP is not denied #tcp", function()
        local tcp = ngx.socket.tcp()
        local ip = helpers.get_proxy_ip(false)
        assert(tcp:connect(ip, 19000))
        assert(tcp:send(MESSAGE))
        local body = assert(tcp:receive("*a"))
        assert.equal(MESSAGE, body)
        tcp:close()

        -- Ensure no preread phase errors occur (regression test for #14749)
        assert.logfile().has.no.line("[error]", true)
        assert.logfile().has.no.line("traceback", true)
        assert.logfile().has.no.line("function cannot be called in preread phase", true)
      end)

      it("blocks IP with CIDR", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction5.test"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("blocks an IP on a allowed CIDR range", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction10.test"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("takes precedence over an allowed IP", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction9.test"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("takes precedence over an allowed CIDR range", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction11.test"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)

      describe("X-Forwarded-For", function()
        it("allows without any X-Forwarded-For and allowed IP", function()
          local res = assert(proxy_client:send {
            method  = "GET",
            path    = "/request",
            headers = {
              ["Host"] = "ip-restriction7.test"
            }
          })
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.equal("127.0.0.1", json.vars.remote_addr)
        end)
        it("allows with allowed X-Forwarded-For header", function()
          local res = assert(proxy_client:send {
            method  = "GET",
            path    = "/request",
            headers = {
              ["Host"]            = "ip-restriction7.test",
              ["X-Forwarded-For"] = "127.0.0.3"
            }
          })
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.equal("127.0.0.3", json.vars.remote_addr)
        end)
        it("blocks with not allowed X-Forwarded-For header", function()
          local res = assert(proxy_client:send {
            method  = "GET",
            path    = "/status/200",
            headers = {
              ["Host"]            = "ip-restriction7.test",
              ["X-Forwarded-For"] = "127.0.0.4"
            }
          })
          local body = assert.res_status(403, res)
          assert.matches("IP address not allowed", body)
        end)
      end)
    end)

    describe("allow", function()
      it("blocks a request when the IP is not allowed", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction3.test"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("allows a allowed IP", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction4.test"
          }
        })
        assert.res_status(200, res)
      end)

      describe("X-Forwarded-For", function()
        it("blocks without any X-Forwarded-For and not allowed IP", function()
          local res = assert(proxy_client:send {
            method  = "GET",
            path    = "/status/200",
            headers = {
              ["Host"] = "ip-restriction6.test"
            }
          })
          local body = assert.res_status(403, res)
          assert.matches("IP address not allowed", body)
        end)
        it("block with not allowed X-Forwarded-For header", function()
          local res = assert(proxy_client:send {
            method  = "GET",
            path    = "/status/200",
            headers = {
              ["Host"]            = "ip-restriction6.test",
              ["X-Forwarded-For"] = "127.0.0.3"
            }
          })
          local body = assert.res_status(403, res)
          assert.matches("IP address not allowed", body)
        end)
        it("block with not allowed X-Forwarded-For header #grpc", function()
          local ok, err = helpers.proxy_client_grpc(){
            service = "hello.HelloService.SayHello",
            opts = {
              ["-authority"] = "ip-restriction-grpc3.test",
              ["-v"] = true,
            },
          }
          assert.falsy(ok)
          assert.matches("Code: PermissionDenied", err)
        end)
        it("allows with allowed X-Forwarded-For header", function()
          local res = assert(proxy_client:send {
            method  = "GET",
            path    = "/status/200",
            headers = {
              ["Host"]            = "ip-restriction6.test",
              ["X-Forwarded-For"] = "127.0.0.4"
            }
          })
          assert.res_status(200, res)
        end)
        it("allows with allowed X-Forwarded-For header #grpc", function()
          assert.truthy(helpers.proxy_client_grpc(){
            service = "hello.HelloService.SayHello",
            opts = {
              ["-authority"] = "ip-restriction-grpc3.test",
              ["-v"] = true,
              ["-H"] = "'X-Forwarded-For: 127.0.0.4'",
            },
          })
        end)
        it("allows with allowed complex X-Forwarded-For header", function()
          local res = assert(proxy_client:send {
            method  = "GET",
            path    = "/status/200",
            headers = {
              ["Host"]            = "ip-restriction6.test",
              ["X-Forwarded-For"] = "127.0.0.4, 127.0.0.3"
            }
          })
          assert.res_status(200, res)
        end)
      end)
    end)

    it("supports config changes without restarting", function()
      local res = assert(proxy_client:send {
        method  = "GET",
        path    = "/request",
        headers = {
          ["Host"] = "ip-restriction2.test"
        }
      })
      assert.res_status(200, res)

      res = assert(admin_client:send {
        method  = "PATCH",
        path    = "/plugins/" .. plugin.id,
        body    = {
          config = { deny = { "127.0.0.1", "127.0.0.2" } },
        },
        headers = {
          ["Content-Type"] = "application/json"
        }
      })
      assert.res_status(200, res)

      helpers.pwait_until(function()
        res = assert(proxy_client:send {
          method  = "GET",
          path    = "/request",
          headers = {
            ["Host"] = "ip-restriction2.test"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)

      res = assert(admin_client:send {
        method  = "PATCH",
        path    = "/plugins/" .. plugin.id,
        body    = {
          config = { deny = { "127.0.0.2", "127.0.0.3" } },
        },
        headers = {
          ["Content-Type"] = "application/json"
        }
      })
      assert.res_status(200, res)

      helpers.pwait_until(function()
        res = assert(proxy_client:send {
          method  = "GET",
          path    = "/request",
          headers = {
            ["Host"] = "ip-restriction2.test"
          }
        })
        assert.res_status(200, res)
      end)
    end)

    describe("#regression", function()
      it("handles a CIDR entry with 0.0.0.0/0", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction8.test"
          }
        })
        assert.res_status(200, res)
      end)
    end)
  end)

  describe("Plugin: ip-restriction (access) [#" .. strategy .. "]", function()
    local plugin
    local proxy_client
    local admin_client
    local db

    lazy_setup(function()
      local bp
      bp, db = helpers.get_db_utils(strategy, {
        "routes",
        "services",
        "plugins",
      })

      local route1 = bp.routes:insert {
        hosts = { "ip-restriction1.test" },
      }

      local route2 = bp.routes:insert {
        hosts = { "ip-restriction2.test" },
      }

      local route3 = bp.routes:insert {
        hosts = { "ip-restriction3.test" },
      }

      local route4 = bp.routes:insert {
        hosts = { "ip-restriction4.test" },
      }

      local route5 = bp.routes:insert {
        hosts = { "ip-restriction5.test" },
      }

      local route6 = bp.routes:insert {
        hosts = { "ip-restriction6.test" },
      }

      local route7 = bp.routes:insert {
        hosts = { "ip-restriction7.test" },
      }

      local route8 = bp.routes:insert {
        hosts = { "ip-restriction8.test" },
      }

      local route9 = bp.routes:insert {
        hosts = { "ip-restriction9.test" },
      }

      bp.plugins:insert {
        name     = "ip-restriction",
        route = { id = route1.id },
        config   = {
          deny = { "::1", "::2" }
        },
      }

      plugin = assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route2.id },
        config   = {
          deny = { "::2" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route3.id },
        config   = {
          deny = { "fe80::/8" },
        },
      })


      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route4.id },
        config   = {
          allow = { "::2" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route5.id },
        config   = {
          allow = { "::1" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route6.id },
        config   = {
          allow = { "::/0" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route7.id },
        config   = {
          allow = { "::1" },
          deny = { "::1" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route8.id },
        config   = {
          allow = { "::1/128" },
          deny = { "::1" },
        },
      })

      assert(db.plugins:insert {
        name     = "ip-restriction",
        route = { id = route9.id },
        config   = {
          allow = { "::1/128" },
          deny = { "::1/128" },
        },
      })

      assert(helpers.start_kong {
        database          = strategy,
        real_ip_recursive = "on",
        trusted_ips       = "0.0.0.0/0, ::/0",
        nginx_conf        = "spec/fixtures/custom_nginx.template",
      })

      proxy_client = helpers.proxy_client()
      admin_client = helpers.admin_client()
    end)

    lazy_teardown(function()
      if proxy_client and admin_client then
        proxy_client:close()
        admin_client:close()
      end

      helpers.stop_kong()
    end)

    describe("deny", function()
      it("blocks a request when the IPv6 is denied", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction1.test",
            ["X-Real-IP"] = "::1",
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("allows a request when the IPv6 is not denied", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/request",
          headers = {
            ["Host"] = "ip-restriction2.test",
            ["X-Real-IP"] = "::1",
          }
        })
        local body = assert.res_status(200, res)
        local json = cjson.decode(body)
        assert.equal("::1", json.vars.remote_addr)
      end)
      it("blocks the IPv6 with CIDR", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction3.test",
            ["X-Real-IP"] = "fe80::1",
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("blocks an IPv6 on a allowed IPv6 CIDR range", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction8.test",
            ["X-Real-IP"] = "::1",
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("takes precedence over an allowed IPv6", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction7.test",
            ["X-Real-IP"] = "::1",
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("takes precedence over an allowed IPv6 CIDR range", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction9.test"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
    end)

    describe("allow", function()
      it("blocks a request when the IPv6 is not allowed", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction4.test",
            ["X-Real-IP"] = "::1",
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("allows a allowed IPv6", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction5.test",
            ["X-Real-IP"] = "::1",
          }
        })
        local body = assert.res_status(200, res)
        local json = cjson.decode(body)
        assert.equal("::1", json.vars.remote_addr)
      end)
    end)

    it("supports config changes without restarting", function()
      local res = assert(proxy_client:send {
        method  = "GET",
        path    = "/request",
        headers = {
          ["Host"] = "ip-restriction2.test",
          ["X-Real-IP"] = "::1",
        }
      })
      local body = assert.res_status(200, res)
      local json = cjson.decode(body)
      assert.equal("::1", json.vars.remote_addr)

      res = assert(admin_client:send {
        method  = "PATCH",
        path    = "/plugins/" .. plugin.id,
        body    = {
          config = { deny = { "::1", "::2" } },
        },
        headers = {
          ["Content-Type"] = "application/json"
        }
      })
      assert.res_status(200, res)

      helpers.wait_for_all_config_update()

      local res = assert(proxy_client:send {
        method  = "GET",
        path    = "/request",
        headers = {
          ["Host"] = "ip-restriction2.test",
          ["X-Real-IP"] = "::1",
        }
      })
      local body = assert.res_status(403, res)
      assert.matches("IP address not allowed", body)

      res = assert(admin_client:send {
        method  = "PATCH",
        path    = "/plugins/" .. plugin.id,
        body    = {
          config = { deny = { "::2", "::3" } },
        },
        headers = {
          ["Content-Type"] = "application/json"
        }
      })
      assert.res_status(200, res)

      helpers.wait_for_all_config_update()

      local res = assert(proxy_client:send {
        method  = "GET",
        path    = "/request",
        headers = {
          ["Host"] = "ip-restriction2.test",
          ["X-Real-IP"] = "::1",
        }
      })
      local body = assert.res_status(200, res)
      local json = cjson.decode(body)
      assert.equal("::1", json.vars.remote_addr)
    end)

    describe("#regression", function()
      it("handles a CIDR entry with ::/0", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"] = "ip-restriction6.test",
            ["X-Real-IP"] = "::1",
          }
        })
        local body = assert.res_status(200, res)
        local json = cjson.decode(body)
        assert.equal("::1", json.vars.remote_addr)
      end)
    end)
  end)

  describe("Plugin: ip-restriction (access) [#" .. strategy .. "]", function()
    local proxy_client
    local admin_client

    lazy_setup(function()
      local bp
      bp = helpers.get_db_utils(strategy, {
        "routes",
        "services",
        "plugins",
      })

      local route1 = bp.routes:insert {
        hosts = { "ip-restriction1.test" },
      }

      local route2 = bp.routes:insert {
        hosts = { "ip-restriction2.test" },
      }

      bp.plugins:insert {
        name     = "ip-restriction",
        route = { id = route1.id },
        config   = {
          deny = { "::4" }
        },
      }

      bp.plugins:insert {
        name     = "ip-restriction",
        route = { id = route2.id },
        config   = {
          allow = { "::4" }
        },
      }

      assert(helpers.start_kong {
        database          = strategy,
        real_ip_header    = "X-Forwarded-For",
        real_ip_recursive = "on",
        trusted_ips       = "0.0.0.0/0, ::/0",
        nginx_conf        = "spec/fixtures/custom_nginx.template",
      })

      proxy_client = helpers.proxy_client()
      admin_client = helpers.admin_client()
    end)

    lazy_teardown(function()
      if proxy_client and admin_client then
        proxy_client:close()
        admin_client:close()
      end

      helpers.stop_kong()
    end)

    describe("deny", function()
      it("allows with allowed X-Forwarded-For header", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/request",
          headers = {
            ["Host"]            = "ip-restriction1.test",
            ["X-Forwarded-For"] = "::3",
          }
        })
        local body = assert.res_status(200, res)
        local json = cjson.decode(body)
        assert.equal("::3", json.vars.remote_addr)
      end)
      it("blocks with not allowed X-Forwarded-For header", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"]            = "ip-restriction1.test",
            ["X-Forwarded-For"] = "::4"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("blocks with blocked complex X-Forwarded-For header", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"]            = "ip-restriction1.test",
            ["X-Forwarded-For"] = "::4, ::3"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("allows with allowed complex X-Forwarded-For header", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"]            = "ip-restriction1.test",
            ["X-Forwarded-For"] = "::3, ::4"
          }
        })
        local body = assert.res_status(200, res)
        local json = cjson.decode(body)
        assert.equal("::3", json.vars.remote_addr)
      end)
    end)

    describe("allow", function()
      it("block with not allowed X-Forwarded-For header", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"]            = "ip-restriction2.test",
            ["X-Forwarded-For"] = "::3"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
      it("allows with allowed X-Forwarded-For header", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"]            = "ip-restriction2.test",
            ["X-Forwarded-For"] = "::4"
          }
        })
        local body = assert.res_status(200, res)
        local json = cjson.decode(body)
        assert.equal("::4", json.vars.remote_addr)
      end)
      it("allows with allowed complex X-Forwarded-For header", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"]            = "ip-restriction2.test",
            ["X-Forwarded-For"] = "::4, ::3"
          }
        })
        local body = assert.res_status(200, res)
        local json = cjson.decode(body)
        assert.equal("::4", json.vars.remote_addr)
      end)
      it("blocks with blocked complex X-Forwarded-For header", function()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            ["Host"]            = "ip-restriction2.test",
            ["X-Forwarded-For"] = "::3, ::4"
          }
        })
        local body = assert.res_status(403, res)
        assert.matches("IP address not allowed", body)
      end)
    end)
  end)
end
