From 460aa1a5ba4409a2fccb596b2156922532bab5b3 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 6 May 2021 16:41:33 +0300
Subject: [PATCH] Pull request: home: imp client http api, docs

Updates #3075.

Squashed commit of the following:

commit c88fd2e24a19474bce736e5b6af7e094b43be390
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu May 6 16:09:49 2021 +0300

    home: imp code, docs

commit 8ae7d9001927d56394d2177c22fe114d98f01732
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu May 6 15:35:49 2021 +0300

    home: imp client http api, docs
---
 internal/home/clients.go     | 38 ++++++++++++------------
 internal/home/clientshttp.go | 57 ++++++++++++++++++++----------------
 openapi/CHANGELOG.md         |  9 ++++++
 openapi/openapi.yaml         | 10 +++++++
 4 files changed, 70 insertions(+), 44 deletions(-)

diff --git a/internal/home/clients.go b/internal/home/clients.go
index 12f5b32d..8807fa89 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -29,25 +29,25 @@ var webHandlersRegistered = false
 
 // Client contains information about persistent clients.
 type Client struct {
-	IDs                 []string
-	Tags                []string
-	Name                string
-	UseOwnSettings      bool // false: use global settings
-	FilteringEnabled    bool
-	SafeSearchEnabled   bool
-	SafeBrowsingEnabled bool
-	ParentalEnabled     bool
-
-	UseOwnBlockedServices bool // false: use global settings
-	BlockedServices       []string
-
-	Upstreams []string // list of upstream servers to be used for the client's requests
-
-	// Custom upstream config for this client
-	// nil: not yet initialized
-	// not nil, but empty: initialized, no good upstreams
-	// not nil, not empty: Upstreams ready to be used
+	// upstreamConfig is the custom upstream config for this client.  If
+	// it's nil, it has not been initialized yet.  If it's non-nil and
+	// empty, there are no valid upstreams.  If it's non-nil and non-empty,
+	// these upstream must be used.
 	upstreamConfig *proxy.UpstreamConfig
+
+	Name string
+
+	IDs             []string
+	Tags            []string
+	BlockedServices []string
+	Upstreams       []string
+
+	UseOwnSettings        bool
+	FilteringEnabled      bool
+	SafeSearchEnabled     bool
+	SafeBrowsingEnabled   bool
+	ParentalEnabled       bool
+	UseOwnBlockedServices bool
 }
 
 type clientSource uint
@@ -63,9 +63,9 @@ const (
 
 // RuntimeClient information
 type RuntimeClient struct {
+	WhoisInfo *RuntimeClientWhoisInfo
 	Host      string
 	Source    clientSource
-	WhoisInfo *RuntimeClientWhoisInfo
 }
 
 // RuntimeClientWhoisInfo is the filtered WHOIS data for a runtime client.
diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go
index 21df856b..b283d623 100644
--- a/internal/home/clientshttp.go
+++ b/internal/home/clientshttp.go
@@ -7,31 +7,38 @@ import (
 	"net/http"
 )
 
+// clientJSON is a common structure used by several handlers to deal with
+// clients.  Some of the fields are only necessary in one or two handlers and
+// are thus made pointers with an omitempty tag.
+//
+// TODO(a.garipov): Consider using nullbool and an optional string here?  Or
+// split into several structs?
 type clientJSON struct {
-	IDs                 []string `json:"ids"`
-	Tags                []string `json:"tags"`
-	Name                string   `json:"name"`
-	UseGlobalSettings   bool     `json:"use_global_settings"`
-	FilteringEnabled    bool     `json:"filtering_enabled"`
-	ParentalEnabled     bool     `json:"parental_enabled"`
-	SafeSearchEnabled   bool     `json:"safesearch_enabled"`
-	SafeBrowsingEnabled bool     `json:"safebrowsing_enabled"`
+	// Disallowed, if non-nil and false, means that the client's IP is
+	// allowed.  Otherwise, the IP is blocked.
+	Disallowed *bool `json:"disallowed,omitempty"`
 
-	UseGlobalBlockedServices bool     `json:"use_global_blocked_services"`
-	BlockedServices          []string `json:"blocked_services"`
+	// DisallowedRule is the rule due to which the client is disallowed.
+	// If Disallowed is true and this string is empty, the client IP is
+	// disallowed by the "allowed IP list", that is it is not included in
+	// the allowlist.
+	DisallowedRule *string `json:"disallowed_rule,omitempty"`
 
-	Upstreams []string `json:"upstreams"`
+	WhoisInfo *RuntimeClientWhoisInfo `json:"whois_info,omitempty"`
 
-	WhoisInfo *RuntimeClientWhoisInfo `json:"whois_info"`
+	Name string `json:"name"`
 
-	// Disallowed - if true -- client's IP is not disallowed
-	// Otherwise, it is blocked.
-	Disallowed bool `json:"disallowed"`
+	BlockedServices []string `json:"blocked_services"`
+	IDs             []string `json:"ids"`
+	Tags            []string `json:"tags"`
+	Upstreams       []string `json:"upstreams"`
 
-	// 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"`
+	FilteringEnabled         bool `json:"filtering_enabled"`
+	ParentalEnabled          bool `json:"parental_enabled"`
+	SafeBrowsingEnabled      bool `json:"safebrowsing_enabled"`
+	SafeSearchEnabled        bool `json:"safesearch_enabled"`
+	UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
+	UseGlobalSettings        bool `json:"use_global_settings"`
 }
 
 type runtimeClientJSON struct {
@@ -126,8 +133,6 @@ func clientToJSON(c *Client) clientJSON {
 		BlockedServices:          c.BlockedServices,
 
 		Upstreams: c.Upstreams,
-
-		WhoisInfo: &RuntimeClientWhoisInfo{},
 	}
 
 	return cj
@@ -243,7 +248,8 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
 			}
 		} else {
 			cj = clientToJSON(c)
-			cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip)
+			disallowed, rule := clients.dnsServer.IsBlockedIP(ip)
+			cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
 		}
 
 		data = append(data, map[string]clientJSON{
@@ -279,8 +285,8 @@ func (clients *clientsContainer) findRuntime(ip net.IP, idStr string) (cj client
 
 		cj = clientJSON{
 			IDs:            []string{idStr},
-			Disallowed:     disallowed,
-			DisallowedRule: rule,
+			Disallowed:     &disallowed,
+			DisallowedRule: &rule,
 			WhoisInfo:      &RuntimeClientWhoisInfo{},
 		}
 
@@ -288,7 +294,8 @@ func (clients *clientsContainer) findRuntime(ip net.IP, idStr string) (cj client
 	}
 
 	cj = runtimeClientToJSON(idStr, rc)
-	cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip)
+	disallowed, rule := clients.dnsServer.IsBlockedIP(ip)
+	cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
 
 	return cj, true
 }
diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md
index 7ee40a5b..0fd19bb8 100644
--- a/openapi/CHANGELOG.md
+++ b/openapi/CHANGELOG.md
@@ -4,6 +4,15 @@
 
 ## v0.106: API changes
 
+### The field `"supported_tags"` in `GET /control/clients`
+
+* Prefiously undocumented field `"supported_tags"` in the response is now
+  documented.
+
+### The field `"whois_info"` in `GET /control/clients`
+
+* Objects in the `"auto_clients"` array now have the `"whois_info"` field.
+
 ### New response code in `POST /control/login`
 
 * `429` is returned when user is out of login attempts.  It adds the
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 3a0df747..187d764e 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -2234,6 +2234,10 @@
           'type': 'array'
           'items':
             'type': 'string'
+        'tags':
+          'items':
+            'type': 'string'
+          'type': 'array'
     'ClientAuto':
       'type': 'object'
       'description': 'Auto-Client information'
@@ -2250,6 +2254,8 @@
           'type': 'string'
           'description': 'The source of this information'
           'example': 'etc/hosts'
+        'whois_info':
+          '$ref': '#/components/schemas/WhoisInfo'
     'ClientUpdate':
       'type': 'object'
       'description': 'Client update request'
@@ -2384,6 +2390,10 @@
           '$ref': '#/components/schemas/ClientsArray'
         'auto_clients':
           '$ref': '#/components/schemas/ClientsAutoArray'
+        'supported_tags':
+          'items':
+            'type': 'string'
+          'type': 'array'
     'ClientsArray':
       'type': 'array'
       'items':