From dd3027afe76aa9d10eafade314a92ec3d4400f29 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 24 Jul 2020 14:30:29 +0300 Subject: [PATCH 1/3] + GET /control/clients/find: add "disallowed" property --- AGHTechDoc.md | 9 +++++- dnsforward/access.go | 14 +++++----- dnsforward/dnsforward.go | 5 ++++ dnsforward/dnsforward_test.go | 24 ++++++++++------ dnsforward/filter.go | 3 +- home/clients.go | 2 ++ home/clients_http.go | 29 +++++++++++++------ home/dns.go | 1 + openapi/openapi.yaml | 52 ++++++++++++++++++++++++++++++++++- 9 files changed, 113 insertions(+), 26 deletions(-) diff --git a/AGHTechDoc.md b/AGHTechDoc.md index e5e582ec..196515a5 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -940,7 +940,7 @@ Error response (Client not found): ### API: Find clients by IP This method returns the list of clients (manual and auto-clients) matching the IP list. -For auto-clients only `name`, `ids` and `whois_info` fields are set. Other fields are empty. +For auto-clients only `name`, `ids`, `whois_info`, `disallowed` fields are set. Other fields are empty. Request: @@ -966,11 +966,18 @@ Response: key: "value" ... } + + "disallowed": "..." } } ... ] +`disallowed`: +* "": IP is allowed +* not "", e.g. "127.0.0.0/24" - IP is disallowed by "disallowed IP list", and the string contains the matched rule (IP or CIDR) +* "not-in-allowed-list" - IP is disallowed by "allowed IP list" + ## DNS general settings diff --git a/dnsforward/access.go b/dnsforward/access.go index eadd1141..1a56dc84 100644 --- a/dnsforward/access.go +++ b/dnsforward/access.go @@ -80,43 +80,43 @@ func processIPCIDRArray(dst *map[string]bool, dstIPNet *[]net.IPNet, src []strin } // IsBlockedIP - return TRUE if this client should be blocked -func (a *accessCtx) IsBlockedIP(ip string) bool { +func (a *accessCtx) IsBlockedIP(ip string) (bool, string) { a.lock.Lock() defer a.lock.Unlock() if len(a.allowedClients) != 0 || len(a.allowedClientsIPNet) != 0 { _, ok := a.allowedClients[ip] if ok { - return false + return false, "" } if len(a.allowedClientsIPNet) != 0 { ipAddr := net.ParseIP(ip) for _, ipnet := range a.allowedClientsIPNet { if ipnet.Contains(ipAddr) { - return false + return false, "" } } } - return true + return true, "not-in-allowed-list" } _, ok := a.disallowedClients[ip] if ok { - return true + return true, ip } if len(a.disallowedClientsIPNet) != 0 { ipAddr := net.ParseIP(ip) for _, ipnet := range a.disallowedClientsIPNet { if ipnet.Contains(ipAddr) { - return true + return true, ipnet.String() } } } - return false + return false, "" } // IsBlockedDomain - return TRUE if this domain should be blocked diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index 39d09a39..6815a7cd 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -298,3 +298,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { p.ServeHTTP(w, r) } } + +// IsBlockedIP - return TRUE if this client should be blocked +func (s *Server) IsBlockedIP(ip string) (bool, string) { + return s.access.IsBlockedIP(ip) +} diff --git a/dnsforward/dnsforward_test.go b/dnsforward/dnsforward_test.go index a3eb94f1..6099fed0 100644 --- a/dnsforward/dnsforward_test.go +++ b/dnsforward/dnsforward_test.go @@ -878,20 +878,28 @@ func TestIsBlockedIPAllowed(t *testing.T) { a := &accessCtx{} assert.True(t, a.Init([]string{"1.1.1.1", "2.2.0.0/16"}, nil, nil) == nil) - assert.True(t, !a.IsBlockedIP("1.1.1.1")) - assert.True(t, a.IsBlockedIP("1.1.1.2")) - assert.True(t, !a.IsBlockedIP("2.2.1.1")) - assert.True(t, a.IsBlockedIP("2.3.1.1")) + disallowed, _ := a.IsBlockedIP("1.1.1.1") + assert.False(t, disallowed) + disallowed, _ = a.IsBlockedIP("1.1.1.2") + assert.True(t, disallowed) + disallowed, _ = a.IsBlockedIP("2.2.1.1") + assert.False(t, disallowed) + disallowed, _ = a.IsBlockedIP("2.3.1.1") + assert.True(t, disallowed) } func TestIsBlockedIPDisallowed(t *testing.T) { a := &accessCtx{} assert.True(t, a.Init(nil, []string{"1.1.1.1", "2.2.0.0/16"}, nil) == nil) - assert.True(t, a.IsBlockedIP("1.1.1.1")) - assert.True(t, !a.IsBlockedIP("1.1.1.2")) - assert.True(t, a.IsBlockedIP("2.2.1.1")) - assert.True(t, !a.IsBlockedIP("2.3.1.1")) + disallowed, _ := a.IsBlockedIP("1.1.1.1") + assert.True(t, disallowed) + disallowed, _ = a.IsBlockedIP("1.1.1.2") + assert.False(t, disallowed) + disallowed, _ = a.IsBlockedIP("2.2.1.1") + assert.True(t, disallowed) + disallowed, _ = a.IsBlockedIP("2.3.1.1") + assert.False(t, disallowed) } func TestIsBlockedIPBlockedDomain(t *testing.T) { diff --git a/dnsforward/filter.go b/dnsforward/filter.go index 068c5112..e4900acb 100644 --- a/dnsforward/filter.go +++ b/dnsforward/filter.go @@ -12,7 +12,8 @@ import ( func (s *Server) beforeRequestHandler(_ *proxy.Proxy, d *proxy.DNSContext) (bool, error) { ip := ipFromAddr(d.Addr) - if s.access.IsBlockedIP(ip) { + disallowed, _ := s.access.IsBlockedIP(ip) + if disallowed { log.Tracef("Client IP %s is blocked by settings", ip) return false, nil } diff --git a/home/clients.go b/home/clients.go index 1e6ae413..81ee9bec 100644 --- a/home/clients.go +++ b/home/clients.go @@ -80,6 +80,8 @@ type clientsContainer struct { // dhcpServer is used for looking up clients IP addresses by MAC addresses dhcpServer *dhcpd.Server + DNSServer *dnsforward.Server + autoHosts *util.AutoHosts // get entries from system hosts-files testing bool // if TRUE, this object is used for internal tests diff --git a/home/clients_http.go b/home/clients_http.go index c48e4937..40130d42 100644 --- a/home/clients_http.go +++ b/home/clients_http.go @@ -21,6 +21,13 @@ type clientJSON struct { BlockedServices []string `json:"blocked_services"` Upstreams []string `json:"upstreams"` + + WhoisInfo map[string]interface{} `json:"whois_info"` + + // * "": IP is allowed + // * not "", e.g. "127.0.0.0/24" - IP is disallowed by "disallowed IP list", and the string contains the matched rule (IP or CIDR) + // * "not-in-allowed-list" - IP is disallowed by "allowed IP list" + Disallowed string `json:"disallowed"` } type clientHostJSON struct { @@ -123,15 +130,9 @@ func clientToJSON(c *Client) clientJSON { return cj } -type clientHostJSONWithID struct { - IDs []string `json:"ids"` - Name string `json:"name"` - WhoisInfo map[string]interface{} `json:"whois_info"` -} - // Convert ClientHost object to JSON -func clientHostToJSON(ip string, ch ClientHost) clientHostJSONWithID { - cj := clientHostJSONWithID{ +func clientHostToJSON(ip string, ch ClientHost) clientJSON { + cj := clientJSON{ Name: ch.Host, IDs: []string{ip}, } @@ -255,9 +256,21 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http continue // a client with this IP isn't found } cj := clientHostToJSON(ip, ch) + + disallowed, disallowedRule := clients.DNSServer.IsBlockedIP(ip) + if disallowed { + cj.Disallowed = disallowedRule + } + el[ip] = cj } else { cj := clientToJSON(&c) + + disallowed, disallowedRule := clients.DNSServer.IsBlockedIP(ip) + if disallowed { + cj.Disallowed = disallowedRule + } + el[ip] = cj } diff --git a/home/dns.go b/home/dns.go index 2d647f94..e44c1ff1 100644 --- a/home/dns.go +++ b/home/dns.go @@ -70,6 +70,7 @@ func initDNSServer() error { p.DHCPServer = Context.dhcpServer } Context.dnsServer = dnsforward.NewServer(p) + Context.clients.DNSServer = Context.dnsServer dnsConfig := generateServerConfig() err = Context.dnsServer.Prepare(&dnsConfig) if err != nil { diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 57d63c70..b9be01c3 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1757,7 +1757,57 @@ components: properties: 1.2.3.4: items: - $ref: "#/components/schemas/Client" + $ref: "#/components/schemas/ClientFindSubEntry" + + ClientFindSubEntry: + type: object + properties: + name: + type: string + description: Name + example: localhost + ids: + type: array + description: IP, CIDR or MAC address + items: + type: string + use_global_settings: + type: boolean + filtering_enabled: + type: boolean + parental_enabled: + type: boolean + safebrowsing_enabled: + type: boolean + safesearch_enabled: + type: boolean + use_global_blocked_services: + type: boolean + blocked_services: + type: array + items: + type: string + upstreams: + type: array + items: + type: string + whois_info: + type: array + items: + $ref: "#/components/schemas/WhoisInfo" + disallowed: + description: > + * "": IP is allowed + * not "", e.g. "127.0.0.0/24" - IP is disallowed by "disallowed IP list", and the string contains the matched rule (IP or CIDR) + * "not-in-allowed-list" - IP is disallowed by "allowed IP list" + type: string + + WhoisInfo: + type: object + properties: + key: + type: string + Clients: type: object properties: From 2c12d5f860cef4504e5cca17741d00b944e30699 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Tue, 8 Sep 2020 13:56:45 +0300 Subject: [PATCH 2/3] Improve the clients/find API response --- AGHTechDoc.md | 12 +++--- dnsforward/access.go | 5 ++- dnsforward/access_test.go | 73 +++++++++++++++++++++++++++++++++++ dnsforward/dnsforward_test.go | 52 ------------------------- home/clients.go | 3 +- home/clients_http.go | 26 ++++++------- home/dns.go | 2 +- openapi/openapi.yaml | 10 +++-- 8 files changed, 103 insertions(+), 80 deletions(-) create mode 100644 dnsforward/access_test.go diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 196515a5..0ac594ef 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -940,7 +940,7 @@ Error response (Client not found): ### API: Find clients by IP This method returns the list of clients (manual and auto-clients) matching the IP list. -For auto-clients only `name`, `ids`, `whois_info`, `disallowed` fields are set. Other fields are empty. +For auto-clients only `name`, `ids`, `whois_info`, `disallowed`, and `disallowed_rule` fields are set. Other fields are empty. Request: @@ -967,17 +967,15 @@ Response: ... } - "disallowed": "..." + "disallowed": false, + "disallowed_rule": "..." } } ... ] -`disallowed`: -* "": IP is allowed -* not "", e.g. "127.0.0.0/24" - IP is disallowed by "disallowed IP list", and the string contains the matched rule (IP or CIDR) -* "not-in-allowed-list" - IP is disallowed by "allowed IP list" - +* `disallowed` - whether the client's IP is blocked or not. +* `disallowed_rule` - the rule due to which the client is disallowed. If `disallowed` is `true`, and this string is empty - it means that the client IP is disallowed by the "allowed IP list", i.e. it is not included in allowed. ## DNS general settings diff --git a/dnsforward/access.go b/dnsforward/access.go index 1a56dc84..dbe60de5 100644 --- a/dnsforward/access.go +++ b/dnsforward/access.go @@ -80,6 +80,9 @@ func processIPCIDRArray(dst *map[string]bool, dstIPNet *[]net.IPNet, src []strin } // IsBlockedIP - return TRUE if this client should be blocked +// Returns the item from the "disallowedClients" list that lead to blocking IP. +// If it returns TRUE and an empty string, it means that the "allowedClients" is not empty, +// but the ip does not belong to it. func (a *accessCtx) IsBlockedIP(ip string) (bool, string) { a.lock.Lock() defer a.lock.Unlock() @@ -99,7 +102,7 @@ func (a *accessCtx) IsBlockedIP(ip string) (bool, string) { } } - return true, "not-in-allowed-list" + return true, "" } _, ok := a.disallowedClients[ip] diff --git a/dnsforward/access_test.go b/dnsforward/access_test.go new file mode 100644 index 00000000..b760d554 --- /dev/null +++ b/dnsforward/access_test.go @@ -0,0 +1,73 @@ +package dnsforward + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsBlockedIPAllowed(t *testing.T) { + a := &accessCtx{} + assert.True(t, a.Init([]string{"1.1.1.1", "2.2.0.0/16"}, nil, nil) == nil) + + disallowed, disallowedRule := a.IsBlockedIP("1.1.1.1") + assert.False(t, disallowed) + assert.Equal(t, "", disallowedRule) + + disallowed, disallowedRule = a.IsBlockedIP("1.1.1.2") + assert.True(t, disallowed) + assert.Equal(t, "", disallowedRule) + + disallowed, disallowedRule = a.IsBlockedIP("2.2.1.1") + assert.False(t, disallowed) + assert.Equal(t, "", disallowedRule) + + disallowed, disallowedRule = a.IsBlockedIP("2.3.1.1") + assert.True(t, disallowed) + assert.Equal(t, "", disallowedRule) +} + +func TestIsBlockedIPDisallowed(t *testing.T) { + a := &accessCtx{} + assert.True(t, a.Init(nil, []string{"1.1.1.1", "2.2.0.0/16"}, nil) == nil) + + disallowed, disallowedRule := a.IsBlockedIP("1.1.1.1") + assert.True(t, disallowed) + assert.Equal(t, "1.1.1.1", disallowedRule) + + disallowed, disallowedRule = a.IsBlockedIP("1.1.1.2") + assert.False(t, disallowed) + assert.Equal(t, "", disallowedRule) + + disallowed, disallowedRule = a.IsBlockedIP("2.2.1.1") + assert.True(t, disallowed) + assert.Equal(t, "2.2.0.0/16", disallowedRule) + + disallowed, disallowedRule = a.IsBlockedIP("2.3.1.1") + assert.False(t, disallowed) + assert.Equal(t, "", disallowedRule) +} + +func TestIsBlockedIPBlockedDomain(t *testing.T) { + a := &accessCtx{} + assert.True(t, a.Init(nil, nil, []string{"host1", + "host2", + "*.host.com", + "||host3.com^", + }) == nil) + + // match by "host2.com" + assert.True(t, a.IsBlockedDomain("host1")) + assert.True(t, a.IsBlockedDomain("host2")) + assert.True(t, !a.IsBlockedDomain("host3")) + + // match by wildcard "*.host.com" + assert.True(t, !a.IsBlockedDomain("host.com")) + assert.True(t, a.IsBlockedDomain("asdf.host.com")) + assert.True(t, a.IsBlockedDomain("qwer.asdf.host.com")) + assert.True(t, !a.IsBlockedDomain("asdf.zhost.com")) + + // match by wildcard "||host3.com^" + assert.True(t, a.IsBlockedDomain("host3.com")) + assert.True(t, a.IsBlockedDomain("asdf.host3.com")) +} diff --git a/dnsforward/dnsforward_test.go b/dnsforward/dnsforward_test.go index 6099fed0..605b52a9 100644 --- a/dnsforward/dnsforward_test.go +++ b/dnsforward/dnsforward_test.go @@ -874,58 +874,6 @@ func publicKey(priv interface{}) interface{} { } } -func TestIsBlockedIPAllowed(t *testing.T) { - a := &accessCtx{} - assert.True(t, a.Init([]string{"1.1.1.1", "2.2.0.0/16"}, nil, nil) == nil) - - disallowed, _ := a.IsBlockedIP("1.1.1.1") - assert.False(t, disallowed) - disallowed, _ = a.IsBlockedIP("1.1.1.2") - assert.True(t, disallowed) - disallowed, _ = a.IsBlockedIP("2.2.1.1") - assert.False(t, disallowed) - disallowed, _ = a.IsBlockedIP("2.3.1.1") - assert.True(t, disallowed) -} - -func TestIsBlockedIPDisallowed(t *testing.T) { - a := &accessCtx{} - assert.True(t, a.Init(nil, []string{"1.1.1.1", "2.2.0.0/16"}, nil) == nil) - - disallowed, _ := a.IsBlockedIP("1.1.1.1") - assert.True(t, disallowed) - disallowed, _ = a.IsBlockedIP("1.1.1.2") - assert.False(t, disallowed) - disallowed, _ = a.IsBlockedIP("2.2.1.1") - assert.True(t, disallowed) - disallowed, _ = a.IsBlockedIP("2.3.1.1") - assert.False(t, disallowed) -} - -func TestIsBlockedIPBlockedDomain(t *testing.T) { - a := &accessCtx{} - assert.True(t, a.Init(nil, nil, []string{"host1", - "host2", - "*.host.com", - "||host3.com^", - }) == nil) - - // match by "host2.com" - assert.True(t, a.IsBlockedDomain("host1")) - assert.True(t, a.IsBlockedDomain("host2")) - assert.True(t, !a.IsBlockedDomain("host3")) - - // match by wildcard "*.host.com" - assert.True(t, !a.IsBlockedDomain("host.com")) - assert.True(t, a.IsBlockedDomain("asdf.host.com")) - assert.True(t, a.IsBlockedDomain("qwer.asdf.host.com")) - assert.True(t, !a.IsBlockedDomain("asdf.zhost.com")) - - // match by wildcard "||host3.com^" - assert.True(t, a.IsBlockedDomain("host3.com")) - assert.True(t, a.IsBlockedDomain("asdf.host3.com")) -} - func TestValidateUpstream(t *testing.T) { invalidUpstreams := []string{"1.2.3.4.5", "123.3.7m", diff --git a/home/clients.go b/home/clients.go index 81ee9bec..6a1de852 100644 --- a/home/clients.go +++ b/home/clients.go @@ -80,7 +80,8 @@ type clientsContainer struct { // dhcpServer is used for looking up clients IP addresses by MAC addresses dhcpServer *dhcpd.Server - DNSServer *dnsforward.Server + // dnsServer is used for checking clients IP status access list status + dnsServer *dnsforward.Server autoHosts *util.AutoHosts // get entries from system hosts-files diff --git a/home/clients_http.go b/home/clients_http.go index 40130d42..42fa6f2a 100644 --- a/home/clients_http.go +++ b/home/clients_http.go @@ -24,10 +24,14 @@ type clientJSON struct { WhoisInfo map[string]interface{} `json:"whois_info"` - // * "": IP is allowed - // * not "", e.g. "127.0.0.0/24" - IP is disallowed by "disallowed IP list", and the string contains the matched rule (IP or CIDR) - // * "not-in-allowed-list" - IP is disallowed by "allowed IP list" - Disallowed string `json:"disallowed"` + // Disallowed - if true -- client's IP is not disallowed + // Otherwise, it is blocked. + Disallowed bool `json:"disallowed"` + + // DisallowedRule - the rule due to which the client is disallowed + // If Disallowed is true, and this string is empty - it means that the client IP + // is disallowed by the "allowed IP list", i.e. it is not included in allowed. + DisallowedRule string `json:"disallowed_rule"` } type clientHostJSON struct { @@ -45,7 +49,7 @@ type clientListJSON struct { } // respond with information about configured clients -func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http.Request) { +func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, _ *http.Request) { data := clientListJSON{} clients.lock.Lock() @@ -257,20 +261,12 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http } cj := clientHostToJSON(ip, ch) - disallowed, disallowedRule := clients.DNSServer.IsBlockedIP(ip) - if disallowed { - cj.Disallowed = disallowedRule - } - + cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) el[ip] = cj } else { cj := clientToJSON(&c) - disallowed, disallowedRule := clients.DNSServer.IsBlockedIP(ip) - if disallowed { - cj.Disallowed = disallowedRule - } - + cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) el[ip] = cj } diff --git a/home/dns.go b/home/dns.go index e44c1ff1..c28ebd1f 100644 --- a/home/dns.go +++ b/home/dns.go @@ -70,7 +70,7 @@ func initDNSServer() error { p.DHCPServer = Context.dhcpServer } Context.dnsServer = dnsforward.NewServer(p) - Context.clients.DNSServer = Context.dnsServer + Context.clients.dnsServer = Context.dnsServer dnsConfig := generateServerConfig() err = Context.dnsServer.Prepare(&dnsConfig) if err != nil { diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index b9be01c3..52791130 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1796,11 +1796,15 @@ components: items: $ref: "#/components/schemas/WhoisInfo" disallowed: + type: boolean description: > - * "": IP is allowed - * not "", e.g. "127.0.0.0/24" - IP is disallowed by "disallowed IP list", and the string contains the matched rule (IP or CIDR) - * "not-in-allowed-list" - IP is disallowed by "allowed IP list" + Whether the client's IP is blocked or not. + disallowed_rule: type: string + description: > + The rule due to which the client is disallowed. + If `disallowed` is `true`, and this string is empty - it means that + the client IP is disallowed by the "allowed IP list", i.e. it is not included in allowed list. WhoisInfo: type: object From c72cd58f69b71e4981d8b182b2f7d53ea5e30868 Mon Sep 17 00:00:00 2001 From: Artem Baskal Date: Thu, 24 Sep 2020 15:48:37 +0300 Subject: [PATCH 3/3] + client: Move the client access check to the server-side Squashed commit of the following: commit 1aab0f62e94ce665a1b996552fac41dc4e769b4d Merge: cdf5eb6e c1f5fdae Author: ArtemBaskal Date: Thu Sep 24 15:36:05 2020 +0300 Merge branch '1920-client-find' into feature/1925 commit cdf5eb6ea67a665d21a3155d8cf89bba9a5a9948 Merge: b6c20b1c 10f67bd3 Author: ArtemBaskal Date: Wed Sep 23 20:28:51 2020 +0300 Merge branch 'master' into feature/1925 commit b6c20b1c7359a0e5902405b0551712f936848a80 Merge: 97d388ef 96512433 Author: ArtemBaskal Date: Tue Sep 15 10:44:25 2020 +0300 Merge branch 'master' into feature/1925 commit 97d388ef6571d590f21da00f86d889e881ca0c3d Author: ArtemBaskal Date: Tue Sep 15 10:30:50 2020 +0300 Extract buttons commit ca45fde11fc2b2812ff2b84dbd67aff0b5341be1 Author: ArtemBaskal Date: Thu Sep 10 12:46:09 2020 +0300 Handle errors in updateLogs commit f15e03c2e5a7115db984f70f72b0ddd870ece73d Author: ArtemBaskal Date: Thu Sep 10 12:39:34 2020 +0300 Update mobile block status on click commit 033b28db3b324f6d529ac1a0ef657886cdbe02bd Author: ArtemBaskal Date: Wed Sep 9 20:53:42 2020 +0300 Fix mobile block buttons, auto open page on web serve start commit 2730937b23309167a066b9154728ac53ffe81a49 Author: ArtemBaskal Date: Wed Sep 9 13:58:37 2020 +0300 Disable allow this client button when isNotInAllowedList is true commit 818cf869d63654c184762ad2701c4429a3e3011e Author: ArtemBaskal Date: Wed Sep 9 13:06:01 2020 +0300 Update client block state on option click commit a072b8983757f419645c0207ea78e6e867c440cb Author: ArtemBaskal Date: Tue Sep 8 20:17:16 2020 +0300 Adapt to api changes commit 28ab2bd8b3f14f60bc822b5a69fa1801db67d816 Author: ArtemBaskal Date: Tue Sep 8 14:12:20 2020 +0300 Change query log block confirm messages commit 9b0b6f6f9b1ec168fa71dbedd036152da59006e3 Author: ArtemBaskal Date: Tue Sep 8 12:00:46 2020 +0300 Refactor inner work with disallowed commit 05f76154b8f489738d032fdaa835edb371ce70c7 Author: ArtemBaskal Date: Mon Sep 7 16:11:37 2020 +0300 + client: Move the client access check to the server-side --- client/src/__locales/en.json | 1 + client/src/__tests__/helpers.test.js | 135 +----------------- client/src/actions/access.js | 20 ++- client/src/actions/queryLogs.js | 37 ++++- client/src/components/Dashboard/Clients.js | 66 ++++----- .../src/components/Logs/Cells/ClientCell.js | 54 ++++--- .../components/Logs/Cells/helpers/index.js | 17 ++- client/src/components/Logs/Cells/index.js | 66 +++++---- client/src/components/Logs/Logs.css | 19 ++- client/src/helpers/constants.js | 6 - client/src/helpers/helpers.js | 132 ++--------------- client/src/reducers/dhcp.js | 1 - client/webpack.dev.js | 1 + 13 files changed, 175 insertions(+), 380 deletions(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index b6235a66..26e7df08 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -582,5 +582,6 @@ "click_to_view_queries": "Click to view queries", "port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction on how to resolve this.", "adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.", + "client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.", "experimental": "Experimental" } diff --git a/client/src/__tests__/helpers.test.js b/client/src/__tests__/helpers.test.js index bb371be4..309b40e6 100644 --- a/client/src/__tests__/helpers.test.js +++ b/client/src/__tests__/helpers.test.js @@ -1,136 +1,5 @@ -import { - countClientsStatistics, findAddressType, getIpMatchListStatus, sortIp, -} from '../helpers/helpers'; -import { ADDRESS_TYPES, IP_MATCH_LIST_STATUS } from '../helpers/constants'; - -describe('getIpMatchListStatus', () => { - describe('IPv4', () => { - test('should return EXACT on find the exact ip match', () => { - const list = `127.0.0.2 -2001:db8:11a3:9d7:0:0:0:0 -192.168.0.1/8 -127.0.0.1 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.1', list)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - }); - - test('should return CIDR on find the cidr match', () => { - const list = `127.0.0.2 -2001:db8:11a3:9d7:0:0:0:0 -192.168.0.1/8 -127.0.0.0/24 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.1', list)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - }); - - test('should return NOT_FOUND if the ip is not in the list', () => { - const list = `127.0.0.1 -2001:db8:11a3:9d7:0:0:0:0 -192.168.0.1/8 -127.0.0.2 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.4', list)) - .toEqual(IP_MATCH_LIST_STATUS.NOT_FOUND); - }); - - test('should return the first EXACT or CIDR match in the list', () => { - const list1 = `2001:db8:11a3:9d7:0:0:0:0 -127.0.0.1 -127.0.0.8/24 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.1', list1)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - - const list2 = `2001:db8:11a3:9d7:ffff:ffff:ffff:ffff -2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -127.0.0.0/24 -127.0.0.1 -127.0.0.8/24 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.1', list2)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - }); - }); - - describe('IPv6', () => { - test('should return EXACT on find the exact ip match', () => { - const list = `127.0.0.0 -2001:db8:11a3:9d7:0:0:0:0 -2001:db8:11a3:9d7:ffff:ffff:ffff:ffff -127.0.0.1`; - expect(getIpMatchListStatus('2001:db8:11a3:9d7:0:0:0:0', list)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - }); - - test('should return EXACT on find the exact ip match of short and long notation', () => { - const list = `127.0.0.0 -192.168.0.1/8 -2001:db8:: -127.0.0.2`; - expect(getIpMatchListStatus('2001:db8:0:0:0:0:0:0', list)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - }); - - test('should return CIDR on find the cidr match', () => { - const list1 = `2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -127.0.0.1 -127.0.0.2`; - expect(getIpMatchListStatus('2001:db8:11a3:9d7:0:0:0:0', list1)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - - const list2 = `2001:0db8::/16 -127.0.0.0 -2001:db8:11a3:9d7:0:0:0:0 -2001:db8:: -2001:db8:11a3:9d7:ffff:ffff:ffff:ffff -127.0.0.1`; - expect(getIpMatchListStatus('2001:db1::', list2)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - }); - - test('should return NOT_FOUND if the ip is not in the list', () => { - const list = `2001:db8:11a3:9d7:0:0:0:0 -2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -127.0.0.1 -127.0.0.2`; - expect(getIpMatchListStatus('::', list)) - .toEqual(IP_MATCH_LIST_STATUS.NOT_FOUND); - }); - - test('should return the first EXACT or CIDR match in the list', () => { - const list1 = `2001:db8:11a3:9d7:0:0:0:0 -2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -127.0.0.3`; - expect(getIpMatchListStatus('2001:db8:11a3:9d7:0:0:0:0', list1)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - - const list2 = `2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -2001:db8:11a3:9d7:0:0:0:0 -127.0.0.3`; - expect(getIpMatchListStatus('2001:db8:11a3:9d7:0:0:0:0', list2)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - }); - }); - - describe('Empty list or IP', () => { - test('should return NOT_FOUND on empty ip', () => { - const list = `127.0.0.0 -2001:db8:11a3:9d7:0:0:0:0 -2001:db8:11a3:9d7:ffff:ffff:ffff:ffff -127.0.0.1`; - expect(getIpMatchListStatus('', list)) - .toEqual(IP_MATCH_LIST_STATUS.NOT_FOUND); - }); - - test('should return NOT_FOUND on empty list', () => { - const list = ''; - expect(getIpMatchListStatus('127.0.0.1', list)) - .toEqual(IP_MATCH_LIST_STATUS.NOT_FOUND); - }); - }); -}); +import { sortIp, countClientsStatistics, findAddressType } from '../helpers/helpers'; +import { ADDRESS_TYPES } from '../helpers/constants'; describe('sortIp', () => { describe('ipv4', () => { diff --git a/client/src/actions/access.js b/client/src/actions/access.js index e04daac0..17acb5e8 100644 --- a/client/src/actions/access.js +++ b/client/src/actions/access.js @@ -3,7 +3,6 @@ import i18next from 'i18next'; import apiClient from '../api/Api'; import { addErrorToast, addSuccessToast } from './toasts'; -import { BLOCK_ACTIONS } from '../helpers/constants'; import { splitByNewLine } from '../helpers/helpers'; export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST'); @@ -49,19 +48,16 @@ export const toggleClientBlockRequest = createAction('TOGGLE_CLIENT_BLOCK_REQUES export const toggleClientBlockFailure = createAction('TOGGLE_CLIENT_BLOCK_FAILURE'); export const toggleClientBlockSuccess = createAction('TOGGLE_CLIENT_BLOCK_SUCCESS'); -export const toggleClientBlock = (type, ip) => async (dispatch) => { +export const toggleClientBlock = (ip, disallowed, disallowed_rule) => async (dispatch) => { dispatch(toggleClientBlockRequest()); try { const { - allowed_clients, disallowed_clients, blocked_hosts, + allowed_clients, blocked_hosts, disallowed_clients = [], } = await apiClient.getAccessList(); - let updatedDisallowedClients = disallowed_clients || []; - if (type === BLOCK_ACTIONS.UNBLOCK && updatedDisallowedClients.includes(ip)) { - updatedDisallowedClients = updatedDisallowedClients.filter((client) => client !== ip); - } else if (type === BLOCK_ACTIONS.BLOCK && !updatedDisallowedClients.includes(ip)) { - updatedDisallowedClients.push(ip); - } + const updatedDisallowedClients = disallowed + ? disallowed_clients.filter((client) => client !== disallowed_rule) + : disallowed_clients.concat(ip); const values = { allowed_clients, @@ -72,9 +68,9 @@ export const toggleClientBlock = (type, ip) => async (dispatch) => { await apiClient.setAccessList(values); dispatch(toggleClientBlockSuccess(values)); - if (type === BLOCK_ACTIONS.UNBLOCK) { - dispatch(addSuccessToast(i18next.t('client_unblocked', { ip }))); - } else if (type === BLOCK_ACTIONS.BLOCK) { + if (disallowed) { + dispatch(addSuccessToast(i18next.t('client_unblocked', { ip: disallowed_rule }))); + } else { dispatch(addSuccessToast(i18next.t('client_blocked', { ip }))); } } catch (error) { diff --git a/client/src/actions/queryLogs.js b/client/src/actions/queryLogs.js index bfb9a609..1c653fb3 100644 --- a/client/src/actions/queryLogs.js +++ b/client/src/actions/queryLogs.js @@ -7,6 +7,17 @@ import { } from '../helpers/constants'; import { addErrorToast, addSuccessToast } from './toasts'; +const enrichWithClientInfo = async (logs) => { + const clientsParams = getParamsForClientsSearch(logs, 'client'); + + if (Object.keys(clientsParams).length > 0) { + const clients = await apiClient.findClients(clientsParams); + return addClientInfo(logs, clients, 'client'); + } + + return logs; +}; + const getLogsWithParams = async (config) => { const { older_than, filter, ...values } = config; const rawLogs = await apiClient.getQueryLog({ @@ -14,13 +25,8 @@ const getLogsWithParams = async (config) => { older_than, }); const { data, oldest } = rawLogs; - let logs = normalizeLogs(data); - const clientsParams = getParamsForClientsSearch(logs, 'client'); - - if (Object.keys(clientsParams).length > 0) { - const clients = await apiClient.findClients(clientsParams); - logs = addClientInfo(logs, clients, 'client'); - } + const normalizedLogs = normalizeLogs(data); + const logs = await enrichWithClientInfo(normalizedLogs); return { logs, @@ -82,6 +88,23 @@ export const getLogsRequest = createAction('GET_LOGS_REQUEST'); export const getLogsFailure = createAction('GET_LOGS_FAILURE'); export const getLogsSuccess = createAction('GET_LOGS_SUCCESS'); +export const updateLogs = () => async (dispatch, getState) => { + try { + const { logs, oldest, older_than } = getState().queryLogs; + + const enrichedLogs = await enrichWithClientInfo(logs); + + dispatch(getLogsSuccess({ + logs: enrichedLogs, + oldest, + older_than, + })); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getLogsFailure(error)); + } +}; + export const getLogs = () => async (dispatch, getState) => { dispatch(getLogsRequest()); try { diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js index 2f25bda3..ccac7baf 100644 --- a/client/src/components/Dashboard/Clients.js +++ b/client/src/components/Dashboard/Clients.js @@ -8,10 +8,11 @@ import classNames from 'classnames'; import Card from '../ui/Card'; import Cell from '../ui/Cell'; -import { getPercent, getIpMatchListStatus, sortIp } from '../../helpers/helpers'; -import { BLOCK_ACTIONS, IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants'; +import { getPercent, sortIp } from '../../helpers/helpers'; +import { BLOCK_ACTIONS, STATUS_COLORS } from '../../helpers/constants'; import { toggleClientBlock } from '../../actions/access'; import { renderFormattedClientCell } from '../../helpers/renderFormattedClientCell'; +import { getStats } from '../../actions/stats'; const getClientsPercentColor = (percent) => { if (percent > 50) { @@ -33,59 +34,51 @@ const CountCell = (row) => { return ; }; -const renderBlockingButton = (ip) => { +const renderBlockingButton = (ip, disallowed, disallowed_rule) => { const dispatch = useDispatch(); const { t } = useTranslation(); const processingSet = useSelector((state) => state.access.processingSet); - const disallowed_clients = useSelector( - (state) => state.access.disallowed_clients, shallowEqual, - ); - - const ipMatchListStatus = getIpMatchListStatus(ip, disallowed_clients); - - if (ipMatchListStatus === IP_MATCH_LIST_STATUS.CIDR) { - return null; - } - - const isNotFound = ipMatchListStatus === IP_MATCH_LIST_STATUS.NOT_FOUND; - const type = isNotFound ? BLOCK_ACTIONS.BLOCK : BLOCK_ACTIONS.UNBLOCK; - const text = type; const buttonClass = classNames('button-action button-action--main', { - 'button-action--unblock': !isNotFound, + 'button-action--unblock': disallowed, }); - const toggleClientStatus = (type, ip) => { - const confirmMessage = type === BLOCK_ACTIONS.BLOCK - ? `${t('adg_will_drop_dns_queries')} ${t('client_confirm_block', { ip })}` - : t('client_confirm_unblock', { ip }); + const toggleClientStatus = async (ip, disallowed, disallowed_rule) => { + const confirmMessage = disallowed + ? t('client_confirm_unblock', { ip: disallowed_rule }) + : `${t('adg_will_drop_dns_queries')} ${t('client_confirm_block', { ip })}`; if (window.confirm(confirmMessage)) { - dispatch(toggleClientBlock(type, ip)); + await dispatch(toggleClientBlock(ip, disallowed, disallowed_rule)); + await dispatch(getStats()); } }; - const onClick = () => toggleClientStatus(type, ip); + const onClick = () => toggleClientStatus(ip, disallowed, disallowed_rule); + const text = disallowed ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; + + const isNotInAllowedList = disallowed && disallowed_rule === ''; return
- -
; + + ; }; const ClientCell = (row) => { - const { value, original: { info } } = row; + const { value, original: { info, info: { disallowed, disallowed_rule } } } = row; return <>
{renderFormattedClientCell(value, info, true)} - {renderBlockingButton(value)} + {renderBlockingButton(value, disallowed, disallowed_rule)}
; }; @@ -96,7 +89,6 @@ const Clients = ({ }) => { const { t } = useTranslation(); const topClients = useSelector((state) => state.stats.topClients, shallowEqual); - const disallowedClients = useSelector((state) => state.access.disallowed_clients, shallowEqual); return ; diff --git a/client/src/components/Logs/Cells/ClientCell.js b/client/src/components/Logs/Cells/ClientCell.js index a468bfcb..56a3440c 100644 --- a/client/src/components/Logs/Cells/ClientCell.js +++ b/client/src/components/Logs/Cells/ClientCell.js @@ -11,12 +11,16 @@ import IconTooltip from './IconTooltip'; import { renderFormattedClientCell } from '../../../helpers/renderFormattedClientCell'; import { toggleClientBlock } from '../../../actions/access'; import { getBlockClientInfo } from './helpers'; +import { getStats } from '../../../actions/stats'; +import { updateLogs } from '../../../actions/queryLogs'; const ClientCell = ({ client, domain, info, - info: { name, whois_info }, + info: { + name, whois_info, disallowed, disallowed_rule, + }, reason, }) => { const { t } = useTranslation(); @@ -26,11 +30,6 @@ const ClientCell = ({ const isDetailed = useSelector((state) => state.queryLogs.isDetailed); const [isOptionsOpened, setOptionsOpened] = useState(false); - const disallowed_clients = useSelector( - (state) => state.access.disallowed_clients, - shallowEqual, - ); - const autoClient = autoClients.find((autoClient) => autoClient.name === client); const source = autoClient?.source; const whoisAvailable = whois_info && Object.keys(whois_info).length > 0; @@ -66,43 +65,50 @@ const ClientCell = ({ const { confirmMessage, buttonKey: blockingClientKey, - type, - } = getBlockClientInfo(client, disallowed_clients); + isNotInAllowedList, + } = getBlockClientInfo(client, disallowed, disallowed_rule); const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only'; const clientNameBlockingFor = getBlockingClientName(clients, client); - const BUTTON_OPTIONS_TO_ACTION_MAP = { - [blockingForClientKey]: () => { - dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor)); + const BUTTON_OPTIONS = [ + { + name: blockingForClientKey, + onClick: () => { + dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor)); + }, }, - [blockingClientKey]: () => { - const message = `${type === BLOCK_ACTIONS.BLOCK ? t('adg_will_drop_dns_queries') : ''} ${t(confirmMessage, { ip: client })}`; - if (window.confirm(message)) { - dispatch(toggleClientBlock(type, client)); - } + { + name: blockingClientKey, + onClick: async () => { + if (window.confirm(confirmMessage)) { + await dispatch(toggleClientBlock(client, disallowed, disallowed_rule)); + await dispatch(updateLogs()); + } + }, + disabled: isNotInAllowedList, }, - }; + ]; const onClick = async () => { await dispatch(toggleBlocking(buttonType, domain)); + await dispatch(getStats()); }; - const getOptions = (optionToActionMap) => { - const options = Object.entries(optionToActionMap); + const getOptions = (options) => { if (options.length === 0) { return null; } - return <>{options - .map(([name, onClick]) =>
{options.map(({ name, onClick, disabled }) =>
)}; + )}; }; - const content = getOptions(BUTTON_OPTIONS_TO_ACTION_MAP); + const content = getOptions(BUTTON_OPTIONS); const buttonClass = classNames('button-action button-action--main', { 'button-action--unblock': isFiltered, @@ -168,6 +174,8 @@ ClientCell.propTypes = { city: propTypes.string, orgname: propTypes.string, }), + disallowed: propTypes.bool.isRequired, + disallowed_rule: propTypes.string.isRequired, }), ]), reason: propTypes.string.isRequired, diff --git a/client/src/components/Logs/Cells/helpers/index.js b/client/src/components/Logs/Cells/helpers/index.js index 61e7ff5c..b843ed99 100644 --- a/client/src/components/Logs/Cells/helpers/index.js +++ b/client/src/components/Logs/Cells/helpers/index.js @@ -1,19 +1,18 @@ -import { getIpMatchListStatus } from '../../../../helpers/helpers'; -import { BLOCK_ACTIONS, IP_MATCH_LIST_STATUS } from '../../../../helpers/constants'; +import i18next from 'i18next'; export const BUTTON_PREFIX = 'btn_'; -export const getBlockClientInfo = (client, disallowed_clients) => { - const ipMatchListStatus = getIpMatchListStatus(client, disallowed_clients); +export const getBlockClientInfo = (ip, disallowed, disallowed_rule) => { + const confirmMessage = disallowed + ? i18next.t('client_confirm_unblock', { ip: disallowed_rule }) + : `${i18next.t('adg_will_drop_dns_queries')} ${i18next.t('client_confirm_block', { ip })}`; - const isNotFound = ipMatchListStatus === IP_MATCH_LIST_STATUS.NOT_FOUND; - const type = isNotFound ? BLOCK_ACTIONS.BLOCK : BLOCK_ACTIONS.UNBLOCK; + const buttonKey = i18next.t(disallowed ? 'allow_this_client' : 'disallow_this_client'); + const isNotInAllowedList = disallowed && disallowed_rule === ''; - const confirmMessage = isNotFound ? 'client_confirm_block' : 'client_confirm_unblock'; - const buttonKey = isNotFound ? 'disallow_this_client' : 'allow_this_client'; return { confirmMessage, buttonKey, - type, + isNotInAllowedList, }; }; diff --git a/client/src/components/Logs/Cells/index.js b/client/src/components/Logs/Cells/index.js index 4f96a5e9..2e2635d9 100644 --- a/client/src/components/Logs/Cells/index.js +++ b/client/src/components/Logs/Cells/index.js @@ -32,6 +32,7 @@ import ClientCell from './ClientCell'; import '../Logs.css'; import { toggleClientBlock } from '../../../actions/access'; import { getBlockClientInfo, BUTTON_PREFIX } from './helpers'; +import { updateLogs } from '../../../actions/queryLogs'; const Row = memo(({ style, @@ -49,21 +50,19 @@ const Row = memo(({ const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual); const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual); - const disallowed_clients = useSelector( - (state) => state.access.disallowed_clients, - shallowEqual, - ); - const clients = useSelector((state) => state.dashboard.clients); const onClick = () => { - if (!isSmallScreen) { return; } + if (!isSmallScreen) { + return; + } const { answer_dnssec, client, domain, elapsedMs, info, + info: { disallowed, disallowed_rule }, reason, response, time, @@ -94,7 +93,7 @@ const Row = memo(({ const isFiltered = checkFiltered(reason); const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST - || reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE; + || reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE; const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; const onToggleBlock = () => { @@ -113,8 +112,8 @@ const Row = memo(({ const { confirmMessage, buttonKey: blockingClientKey, - type: blockType, - } = getBlockClientInfo(client, disallowed_clients); + isNotInAllowedList, + } = getBlockClientInfo(client, disallowed, disallowed_rule); const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only'; const clientNameBlockingFor = getBlockingClientName(clients, client); @@ -123,13 +122,33 @@ const Row = memo(({ dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor)); }; - const onBlockingClientClick = () => { - const message = `${blockType === BLOCK_ACTIONS.BLOCK ? t('adg_will_drop_dns_queries') : ''} ${t(confirmMessage, { ip: client })}`; - if (window.confirm(message)) { - dispatch(toggleClientBlock(blockType, client)); + const onBlockingClientClick = async () => { + if (window.confirm(confirmMessage)) { + await dispatch(toggleClientBlock(client, disallowed, disallowed_rule)); + await dispatch(updateLogs()); + setModalOpened(false); } }; + const blockButton = ; + + const blockForClientButton = ; + + const blockClientButton = ; + const detailedData = { time_table_header: formatTime(time, LONG_TIME_FORMAT), date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS), @@ -144,12 +163,12 @@ const Row = memo(({ table_name: tracker?.name, category_label: hasTracker && captitalizeWords(tracker.category), tracker_source: hasTracker && sourceData - && {sourceData.name} - , + && {sourceData.name} + , response_details: 'title', install_settings_dns: upstream, elapsed: formattedElapsedMs, @@ -166,12 +185,9 @@ const Row = memo(({ source_label: source, validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false, original_response: originalResponse?.join('\n'), - [BUTTON_PREFIX + buttonType]:
{t(buttonType)}
, - [BUTTON_PREFIX + blockingForClientKey]:
{t(blockingForClientKey)}
, - [BUTTON_PREFIX + blockingClientKey]:
{t(blockingClientKey)}
, + [BUTTON_PREFIX + buttonType]: blockButton, + [BUTTON_PREFIX + blockingForClientKey]: blockForClientButton, + [BUTTON_PREFIX + blockingClientKey]: blockClientButton, }; setDetailedDataCurrent(processContent(detailedData)); diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css index b230ab8b..901c3ab0 100644 --- a/client/src/components/Logs/Logs.css +++ b/client/src/components/Logs/Logs.css @@ -96,7 +96,7 @@ } .bg--danger { - color: var(--danger); + color: var(--danger) !important; } .form-control--search { @@ -293,7 +293,20 @@ background: var(--btn-unblock-disabled); } -.button-action--arrow-option:hover { +.button-action--arrow-option { + background: transparent; + border: 0; + display: block; + width: 100%; + text-align: left; + color: inherit; +} + +.button-action--arrow-option:disabled { + display: none; +} + +.tooltip-custom__container .button-action--arrow-option:not(:disabled):hover { cursor: pointer; background: var(--gray-f3); overflow: hidden; @@ -327,7 +340,7 @@ } .logs__row--red { - background-color: var(--red); + background-color: var(--red) !important; } .logs__row--white { diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 08fd1529..70f9e317 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -479,12 +479,6 @@ export const DNS_REQUEST_OPTIONS = { LOAD_BALANCING: '', }; -export const IP_MATCH_LIST_STATUS = { - NOT_FOUND: 'NOT_FOUND', // not found in the list - EXACT: 'EXACT', // found exact match (including the match of short and long forms) - CIDR: 'CIDR', // the ip is in the specified CIDR range -}; - export const DHCP_FORM_NAMES = { DHCPv4: 'dhcpv4', DHCPv6: 'dhcpv6', diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index b0bbea5a..934268ac 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -25,7 +25,6 @@ import { DHCP_VALUES_PLACEHOLDERS, FILTERED, FILTERED_STATUS, - IP_MATCH_LIST_STATUS, SERVICES_ID_NAME_MAP, STANDARD_DNS_PORT, STANDARD_HTTPS_PORT, @@ -133,16 +132,14 @@ export const normalizeTopStats = (stats) => ( })) ); -export const addClientInfo = (data, clients, param) => ( - data.map((row) => { - const clientIp = row[param]; - const info = clients.find((item) => item[clientIp]) || ''; - return { - ...row, - info: info?.[clientIp] ?? '', - }; - }) -); +export const addClientInfo = (data, clients, param) => data.map((row) => { + const clientIp = row[param]; + const info = clients.find((item) => item[clientIp]) || ''; + return { + ...row, + info: info?.[clientIp] ?? '', + }; +}); export const normalizeFilters = (filters) => ( filters ? filters.map((filter) => { @@ -530,75 +527,6 @@ export const isIpInCidr = (ip, cidr) => { } }; -/** - * The purpose of this method is to quickly check - * if this IP can possibly be in the specified CIDR range. - * - * @param ip {string} - * @param listItem {string} - * @returns {boolean} - */ -const isIpQuickMatchCIDR = (ip, listItem) => { - const ipv6 = ip.indexOf(':') !== -1; - const cidrIpv6 = listItem.indexOf(':') !== -1; - if (ipv6 !== cidrIpv6) { - // CIDR is for a different IP type - return false; - } - - if (cidrIpv6) { - // We don't do quick check for IPv6 addresses - return true; - } - - const idx = listItem.indexOf('/'); - if (idx === -1) { - // Not a CIDR, return false immediately - return false; - } - - const cidrIp = listItem.substring(0, idx); - const cidrRange = parseInt(listItem.substring(idx + 1), 10); - if (Number.isNaN(cidrRange)) { - // Not a valid CIDR - return false; - } - - const parts = cidrIp.split('.'); - if (parts.length !== 4) { - // Invalid IP, return immediately - return false; - } - - // Now depending on the range we check if the IP can possibly be in that range - if (cidrRange < 8) { - // Use the slow approach - return true; - } - - if (cidrRange < 16) { - // Check the first part - // Example: 0.0.0.0/8 matches 0.*.*.* - return ip.indexOf(`${parts[0]}.`) === 0; - } - - if (cidrRange < 24) { - // Check the first two parts - // Example: 0.0.0.0/16 matches 0.0.*.* - return ip.indexOf(`${parts[0]}.${parts[1]}.`) === 0; - } - - if (cidrRange <= 32) { - // Check the first two parts - // Example: 0.0.0.0/16 matches 0.0.*.* - return ip.indexOf(`${parts[0]}.${parts[1]}.${parts[2]}.`) === 0; - } - - // range for IPv4 CIDR cannot be more than 32 - // no need to check further, this CIDR is invalid - return false; -}; - /** * * @param ipOrCidr @@ -622,50 +550,6 @@ export const findAddressType = (address) => { } }; -/** - * @param ip {string} - * @param list {string} - * @returns {'EXACT' | 'CIDR' | 'NOT_FOND'} - */ -export const getIpMatchListStatus = (ip, list) => { - if (!ip || !list) { - return IP_MATCH_LIST_STATUS.NOT_FOUND; - } - - const listArr = list.trim() - .split('\n'); - - try { - for (let i = 0; i < listArr.length; i += 1) { - const listItem = listArr[i]; - - if (ip === listItem.trim()) { - return IP_MATCH_LIST_STATUS.EXACT; - } - - // Using ipaddr.js is quite slow so we first do a quick check - // to see if it's possible that this IP may be in the specified CIDR range - if (isIpQuickMatchCIDR(ip, listItem)) { - const parsedIp = ipaddr.parse(ip); - const isItemAnIp = ipaddr.isValid(listItem); - const parsedItem = isItemAnIp ? ipaddr.parse(listItem) : ipaddr.parseCIDR(listItem); - - if (isItemAnIp && parsedIp.toString() === parsedItem.toString()) { - return IP_MATCH_LIST_STATUS.EXACT; - } - - if (!isItemAnIp && isIpMatchCidr(parsedIp, parsedItem)) { - return IP_MATCH_LIST_STATUS.CIDR; - } - } - } - return IP_MATCH_LIST_STATUS.NOT_FOUND; - } catch (e) { - console.error(e); - return IP_MATCH_LIST_STATUS.NOT_FOUND; - } -}; - /** * @param ids {string[]} * @returns {Object} diff --git a/client/src/reducers/dhcp.js b/client/src/reducers/dhcp.js index 5f9f0f58..d6e2868a 100644 --- a/client/src/reducers/dhcp.js +++ b/client/src/reducers/dhcp.js @@ -2,7 +2,6 @@ import { handleActions } from 'redux-actions'; import * as actions from '../actions'; import { enrichWithConcatenatedIpAddresses } from '../helpers/helpers'; -// todo: figure out if we cat get rid of redux-form state duplication const dhcp = handleActions( { [actions.getDhcpStatusRequest]: (state) => ({ diff --git a/client/webpack.dev.js b/client/webpack.dev.js index afcc3506..3d34cb28 100644 --- a/client/webpack.dev.js +++ b/client/webpack.dev.js @@ -44,6 +44,7 @@ const getDevServerConfig = (proxyUrl = BASE_URL) => { proxy: { [proxyUrl]: `http://${devServerHost}:${port}`, }, + open: true, }; };