mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-12-21 14:24:26 +03:00
all: clients search
This commit is contained in:
parent
dedbadafc4
commit
2dd1c94123
8 changed files with 207 additions and 14 deletions
|
@ -18,6 +18,14 @@ See also the [v0.107.56 GitHub milestone][ms-v0.107.56].
|
|||
NOTE: Add new changes BELOW THIS COMMENT.
|
||||
-->
|
||||
|
||||
### Added
|
||||
|
||||
- The new HTTP API `POST /clients/search` that finds clients by their IP addresses, CIDRs, MAC addresses, or ClientIDs. See `openapi/openapi.yaml` for the full description.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- The `GET /clients/find` HTTP API is deprecated. Use the new `POST /clients/search` API.
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
|
|
|
@ -46,7 +46,7 @@ export const getStats = () => async (dispatch: any) => {
|
|||
const normalizedTopClients = normalizeTopStats(stats.top_clients);
|
||||
|
||||
const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name');
|
||||
const clients = await apiClient.findClients(clientsParams);
|
||||
const clients = await apiClient.searchClients(clientsParams);
|
||||
const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name');
|
||||
|
||||
const normalizedStats = {
|
||||
|
|
|
@ -415,7 +415,7 @@ class Api {
|
|||
// Per-client settings
|
||||
GET_CLIENTS = { path: 'clients', method: 'GET' };
|
||||
|
||||
FIND_CLIENTS = { path: 'clients/find', method: 'GET' };
|
||||
SEARCH_CLIENTS = { path: 'clients/search', method: 'POST' };
|
||||
|
||||
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
|
||||
|
||||
|
@ -453,11 +453,12 @@ class Api {
|
|||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
findClients(params: any) {
|
||||
const { path, method } = this.FIND_CLIENTS;
|
||||
const url = getPathWithQueryString(path, params);
|
||||
|
||||
return this.makeRequest(url, method);
|
||||
searchClients(config: any) {
|
||||
const { path, method } = this.SEARCH_CLIENTS;
|
||||
const parameters = {
|
||||
data: config,
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// DNS access settings
|
||||
|
|
|
@ -451,13 +451,10 @@ export const getParamsForClientsSearch = (data: any, param: any, additionalParam
|
|||
clients.add(e[additionalParam]);
|
||||
}
|
||||
});
|
||||
const params = {};
|
||||
const ids = Array.from(clients.values());
|
||||
ids.forEach((id, i) => {
|
||||
params[`ip${i}`] = id;
|
||||
});
|
||||
|
||||
return params;
|
||||
return {
|
||||
clients: Array.from(clients).map(id => ({ id })),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -424,6 +424,8 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
|
|||
}
|
||||
|
||||
// handleFindClient is the handler for GET /control/clients/find HTTP API.
|
||||
//
|
||||
// Deprecated: Remove it when migration to the new API is over.
|
||||
func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
data := []map[string]*clientJSON{}
|
||||
|
@ -452,6 +454,51 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
|
|||
aghhttp.WriteJSONResponseOK(w, r, data)
|
||||
}
|
||||
|
||||
// searchQueryJSON is a request to the POST /control/clients/search HTTP API.
|
||||
//
|
||||
// TODO(s.chzhen): Add UIDs.
|
||||
type searchQueryJSON struct {
|
||||
Clients []searchClientJSON `json:"clients"`
|
||||
}
|
||||
|
||||
// searchClientJSON is a part of [searchQueryJSON] that contains a string
|
||||
// representation of the client's IP address, CIDR, MAC address, or ClientID.
|
||||
type searchClientJSON struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// handleSearchClient is the handler for the POST /control/clients/search HTTP API.
|
||||
func (clients *clientsContainer) handleSearchClient(w http.ResponseWriter, r *http.Request) {
|
||||
q := searchQueryJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&q)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "failed to process request body: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
data := []map[string]*clientJSON{}
|
||||
for _, c := range q.Clients {
|
||||
idStr := c.ID
|
||||
ip, _ := netip.ParseAddr(idStr)
|
||||
c, ok := clients.storage.Find(idStr)
|
||||
var cj *clientJSON
|
||||
if !ok {
|
||||
cj = clients.findRuntime(ip, idStr)
|
||||
} else {
|
||||
cj = clientToJSON(c)
|
||||
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, idStr)
|
||||
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
||||
}
|
||||
|
||||
data = append(data, map[string]*clientJSON{
|
||||
idStr: cj,
|
||||
})
|
||||
}
|
||||
|
||||
aghhttp.WriteJSONResponseOK(w, r, data)
|
||||
}
|
||||
|
||||
// findRuntime looks up the IP in runtime and temporary storages, like
|
||||
// /etc/hosts tables, DHCP leases, or blocklists. cj is guaranteed to be
|
||||
// non-nil.
|
||||
|
@ -493,5 +540,8 @@ func (clients *clientsContainer) registerWebHandlers() {
|
|||
httpRegister(http.MethodPost, "/control/clients/add", clients.handleAddClient)
|
||||
httpRegister(http.MethodPost, "/control/clients/delete", clients.handleDelClient)
|
||||
httpRegister(http.MethodPost, "/control/clients/update", clients.handleUpdateClient)
|
||||
httpRegister(http.MethodPost, "/control/clients/search", clients.handleSearchClient)
|
||||
|
||||
// Deprecated handler.
|
||||
httpRegister(http.MethodGet, "/control/clients/find", clients.handleFindClient)
|
||||
}
|
||||
|
|
|
@ -408,3 +408,77 @@ func TestClientsContainer_HandleFindClient(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsContainer_HandleSearchClient(t *testing.T) {
|
||||
clients := newClientsContainer(t)
|
||||
clients.clientChecker = &testBlockedClientChecker{
|
||||
onIsBlockedClient: func(ip netip.Addr, clientID string) (ok bool, rule string) {
|
||||
return false, ""
|
||||
},
|
||||
}
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
|
||||
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
||||
err := clients.storage.Add(ctx, clientOne)
|
||||
require.NoError(t, err)
|
||||
|
||||
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||
err = clients.storage.Add(ctx, clientTwo)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
query *searchQueryJSON
|
||||
wantCode int
|
||||
wantClient []*client.Persistent
|
||||
}{{
|
||||
name: "single",
|
||||
query: &searchQueryJSON{
|
||||
Clients: []searchClientJSON{{
|
||||
ID: testClientIP1,
|
||||
}},
|
||||
},
|
||||
wantCode: http.StatusOK,
|
||||
wantClient: []*client.Persistent{clientOne},
|
||||
}, {
|
||||
name: "multiple",
|
||||
query: &searchQueryJSON{
|
||||
Clients: []searchClientJSON{{
|
||||
ID: testClientIP1,
|
||||
}, {
|
||||
ID: testClientIP2,
|
||||
}},
|
||||
},
|
||||
wantCode: http.StatusOK,
|
||||
wantClient: []*client.Persistent{clientOne, clientTwo},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var body []byte
|
||||
body, err = json.Marshal(tc.query)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
clients.handleSearchClient(rw, r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.wantCode, rw.Code)
|
||||
|
||||
body, err = io.ReadAll(rw.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
clientData := []map[string]*clientJSON{}
|
||||
err = json.Unmarshal(body, &clientData)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertPersistentClientsData(t, clients, clientData, tc.wantClient)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,33 @@
|
|||
|
||||
## v0.108.0: API changes
|
||||
|
||||
## v0.107.55: API changes
|
||||
## v0.107.56: API changes
|
||||
|
||||
### Deprecated client APIs
|
||||
|
||||
* The `GET /control/clients/find` HTTP API; use the new `POST
|
||||
/control/clients/search` API instead.
|
||||
|
||||
### New client APIs
|
||||
|
||||
* The new `POST /control/clients/search` HTTP API allows config updates.
|
||||
|
||||
These APIs accept and return a JSON object with the following format:
|
||||
|
||||
```json
|
||||
{
|
||||
"clients": [
|
||||
{
|
||||
"id": "192.0.2.1"
|
||||
},
|
||||
{
|
||||
"id": "test"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## v0.107.53: API changes
|
||||
|
||||
### The new field `"ecosia"` in `SafeSearchConfig`
|
||||
|
||||
|
|
|
@ -934,6 +934,9 @@
|
|||
'description': 'OK.'
|
||||
'/clients/find':
|
||||
'get':
|
||||
'deprecated': true
|
||||
'description': >
|
||||
Deprecated: Use `POST /clients/search` instead.
|
||||
'tags':
|
||||
- 'clients'
|
||||
'operationId': 'clientsFind'
|
||||
|
@ -957,6 +960,26 @@
|
|||
'application/json':
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/ClientsFindResponse'
|
||||
'/clients/search':
|
||||
'post':
|
||||
'tags':
|
||||
- 'clients'
|
||||
'operationId': 'clientsSearch'
|
||||
'summary': >
|
||||
Get information about clients by their IP addresses, CIDRs, MAC addresses, or ClientIDs.
|
||||
'requestBody':
|
||||
'content':
|
||||
'application/json':
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/ClientsSearchRequest'
|
||||
'required': true
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK.'
|
||||
'content':
|
||||
'application/json':
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/ClientsFindResponse'
|
||||
'/access/list':
|
||||
'get':
|
||||
'operationId': 'accessList'
|
||||
|
@ -2749,6 +2772,20 @@
|
|||
'properties':
|
||||
'name':
|
||||
'type': 'string'
|
||||
'ClientsSearchRequest':
|
||||
'type': 'object'
|
||||
'description': 'Client search request'
|
||||
'properties':
|
||||
'clients':
|
||||
'type': 'array'
|
||||
'items':
|
||||
'$ref': '#/components/schemas/ClientsIDEntry'
|
||||
'ClientsIDEntry':
|
||||
'type': 'object'
|
||||
'properties':
|
||||
'id':
|
||||
'type': 'string'
|
||||
'description': 'Client IP address, CIDR, MAC address, or ClientID'
|
||||
'ClientsFindResponse':
|
||||
'type': 'array'
|
||||
'description': 'Client search results.'
|
||||
|
|
Loading…
Reference in a new issue