| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "web_api.h" |
| 4 | |
| 5 | void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key) { |
| 6 | buffer_json_member_add_object(wb, key); |
| 7 | rrdlabels_to_buffer_json_members(host->rrdlabels, wb); |
| 8 | buffer_json_object_close(wb); |
| 9 | } |
| 10 | |
| 11 | void web_client_ensure_proper_authorization(struct web_client *w) { |
| 12 | internal_fatal(w->user_auth.method != USER_AUTH_METHOD_NONE && !(w->user_auth.access & HTTP_ACCESS_SIGNED_ID), |
| 13 | "signed-in permission should be set, but is missing"); |
| 14 | |
| 15 | internal_fatal(w->user_auth.method == USER_AUTH_METHOD_NONE && (w->user_auth.access & HTTP_ACCESS_SIGNED_ID), |
| 16 | "signed-in permission is set, but it shouldn't"); |
| 17 | |
| 18 | #ifdef NETDATA_GOD_MODE |
| 19 | web_client_set_permissions(w, HTTP_ACCESS_ALL, HTTP_USER_ROLE_ADMIN, USER_AUTH_METHOD_GOD); |
| 20 | #else |
| 21 | if(w->user_auth.method == USER_AUTH_METHOD_NONE) { |
| 22 | web_client_set_permissions( |
| 23 | w, |
| 24 | (netdata_is_protected_by_bearer) ? HTTP_ACCESS_NONE : HTTP_ACCESS_ANONYMOUS_DATA, |
| 25 | (netdata_is_protected_by_bearer) ? HTTP_USER_ROLE_NONE : HTTP_USER_ROLE_ANY, |
| 26 | USER_AUTH_METHOD_NONE); |
| 27 | } |
| 28 | #endif |
| 29 | } |
| 30 | |
| 31 | int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_path_endpoint, struct web_api_command *api_commands) { |
| 32 | buffer_no_cacheable(w->response.data); |
| 33 | |
| 34 | web_client_ensure_proper_authorization(w); |
| 35 | |
| 36 | if(unlikely(!url_path_endpoint || !*url_path_endpoint)) { |
| 37 | buffer_flush(w->response.data); |
| 38 | buffer_sprintf(w->response.data, "Which API command?"); |
| 39 | return HTTP_RESP_BAD_REQUEST; |
| 40 | } |
| 41 | |
| 42 | char *api_command = strchr(url_path_endpoint, '/'); |
| 43 | if (likely(api_command == NULL)) // only config command supports subpaths for now |
| 44 | api_command = url_path_endpoint; |
| 45 | else { |
| 46 | size_t api_command_len = api_command - url_path_endpoint; |
| 47 | api_command = callocz(1, api_command_len + 1); |
| 48 | memcpy(api_command, url_path_endpoint, api_command_len); |
| 49 | } |
| 50 | |
| 51 | uint32_t hash = simple_hash(api_command); |
| 52 | |
| 53 | for(int i = 0; api_commands[i].api ; i++) { |
| 54 | if(unlikely(hash == api_commands[i].hash && !strcmp(api_command, api_commands[i].api))) { |
| 55 | if(unlikely(!api_commands[i].allow_subpaths && api_command != url_path_endpoint)) { |
| 56 | buffer_flush(w->response.data); |
| 57 | buffer_sprintf(w->response.data, "API command '%s' does not support subpaths.", api_command); |
| 58 | freez(api_command); |
| 59 | return HTTP_RESP_BAD_REQUEST; |
| 60 | } |
| 61 | |
| 62 | if (api_command != url_path_endpoint) |
| 63 | freez(api_command); |
| 64 | |
| 65 | bool acl_allows = ((w->acl & api_commands[i].acl) == api_commands[i].acl) || (api_commands[i].acl & HTTP_ACL_NOCHECK); |
| 66 | if(!acl_allows) |
| 67 | return web_client_permission_denied_acl(w); |
| 68 | |
| 69 | bool permissions_allows = |
| 70 | http_access_user_has_enough_access_level_for_endpoint(w->user_auth.access, api_commands[i].access); |
| 71 | if(!permissions_allows) |
| 72 | return web_client_permission_denied(w); |
| 73 | |
| 74 | char *query_string = (char *)buffer_tostring(w->url_query_string_decoded); |
| 75 | |
| 76 | if(*query_string == '?') |
| 77 | query_string = &query_string[1]; |
| 78 | |
| 79 | return api_commands[i].callback(host, w, query_string); |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | if (api_command != url_path_endpoint) |
| 84 | freez(api_command); |
| 85 | |
| 86 | buffer_flush(w->response.data); |
| 87 | buffer_strcat(w->response.data, "Unsupported API command: "); |
| 88 | buffer_strcat_htmlescape(w->response.data, url_path_endpoint); |
| 89 | return HTTP_RESP_NOT_FOUND; |
| 90 | } |
| 91 | |
| 92 | RRDCONTEXT_TO_JSON_OPTIONS rrdcontext_to_json_parse_options(char *o) { |
| 93 | RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE; |
| 94 | char *tok; |
| 95 | |
| 96 | while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) { |
| 97 | if(!*tok) continue; |
| 98 | |
| 99 | if(!strcmp(tok, "full") || !strcmp(tok, "all")) |
| 100 | options |= RRDCONTEXT_OPTIONS_ALL; |
| 101 | else if(!strcmp(tok, "charts") || !strcmp(tok, "instances")) |
| 102 | options |= RRDCONTEXT_OPTION_SHOW_INSTANCES; |
| 103 | else if(!strcmp(tok, "dimensions") || !strcmp(tok, "metrics")) |
| 104 | options |= RRDCONTEXT_OPTION_SHOW_METRICS; |
| 105 | else if(!strcmp(tok, "queue")) |
| 106 | options |= RRDCONTEXT_OPTION_SHOW_QUEUED; |
| 107 | else if(!strcmp(tok, "flags")) |
| 108 | options |= RRDCONTEXT_OPTION_SHOW_FLAGS; |
| 109 | else if(!strcmp(tok, "uuids")) |
| 110 | options |= RRDCONTEXT_OPTION_SHOW_UUIDS; |
| 111 | else if(!strcmp(tok, "deleted")) |
| 112 | options |= RRDCONTEXT_OPTION_SHOW_DELETED; |
| 113 | else if(!strcmp(tok, "labels")) |
| 114 | options |= RRDCONTEXT_OPTION_SHOW_LABELS; |
| 115 | else if(!strcmp(tok, "deepscan")) |
| 116 | options |= RRDCONTEXT_OPTION_DEEPSCAN; |
| 117 | else if(!strcmp(tok, "hidden")) |
| 118 | options |= RRDCONTEXT_OPTION_SHOW_HIDDEN; |
| 119 | else if(!strcmp(tok, "rfc3339")) |
| 120 | options |= RRDCONTEXT_OPTION_RFC3339; |
| 121 | } |
| 122 | |
| 123 | return options; |
| 124 | } |
| 125 | |
| 126 | |
| 127 | bool web_client_interrupt_callback(void *data) { |
| 128 | struct web_client *w = data; |
| 129 | |
| 130 | bool ret; |
| 131 | if(w->interrupt.callback) |
| 132 | ret = w->interrupt.callback(w, w->interrupt.callback_data); |
| 133 | else |
| 134 | ret = is_socket_closed(w->fd); |
| 135 | |
| 136 | return ret; |
| 137 | } |
| 138 | |
| 139 | void nd_web_api_init(void) { |
| 140 | contexts_alert_statuses_init(); |
| 141 | rrdr_options_init(); |
| 142 | contexts_options_init(); |
| 143 | datasource_formats_init(); |
| 144 | time_grouping_init(); |
| 145 | } |
| 146 | |
| 147 | void web_client_progress_functions_update(nd_uuid_t *transaction, void *data, size_t done, size_t all) { |
| 148 | // handle progress updates from the plugin |
| 149 | // data parameter is no longer used - transaction is provided directly |
| 150 | (void)data; |
| 151 | query_progress_functions_update(transaction, done, all); |
| 152 | } |
| 153 |