/*****************************************************************************\ * openapi.c - Slurm REST API openapi handlers ***************************************************************************** * Copyright (C) 2019-2020 SchedMD LLC. * Written by Nathan Rini * * This file is part of Slurm, a resource management program. * For details, see . * Please also read the included file: DISCLAIMER. * * Slurm is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * In addition, as a special exception, the copyright holders give permission * to link the code of portions of this program with the OpenSSL library under * certain conditions as described in each individual source file, and * distribute linked combinations including the two. You must obey the GNU * General Public License in all respects for all of the code used other than * OpenSSL. If you modify file(s) with this exception, you may extend this * exception to your version of the file(s), but you are not obligated to do * so. If you do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source files in * the program, then also delete it here. * * Slurm is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with Slurm; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. \*****************************************************************************/ #include "config.h" #include #include "slurm/slurm.h" #include "src/common/data.h" #include "src/common/list.h" #include "src/common/log.h" #include "src/common/xassert.h" #include "src/common/xmalloc.h" #include "src/common/xstring.h" #include "src/slurmrestd/http.h" #include "src/slurmrestd/openapi.h" #include "src/slurmrestd/ref.h" #include "src/slurmrestd/xjson.h" decl_static_data(openapi_json); #define MAGIC_PATH 0x1111beef static pthread_rwlock_t paths_lock = PTHREAD_RWLOCK_INITIALIZER; static List paths = NULL; static int path_tag_counter = 0; static const data_t *spec = NULL; typedef enum { OPENAPI_TYPE_UNKNOWN = 0, OPENAPI_TYPE_INTEGER, OPENAPI_TYPE_NUMBER, OPENAPI_TYPE_STRING, OPENAPI_TYPE_BOOL, OPENAPI_TYPE_OBJECT, OPENAPI_TYPE_ARRAY, OPENAPI_TYPE_MAX } parameter_type_t; /* * Parse OAS type. * IN str string to parse * RET parameter_type_t or OPENAPI_TYPE_UNKNOWN if unknown */ static parameter_type_t _get_parameter_type(const char *str) { if (!str) return OPENAPI_TYPE_UNKNOWN; if (!xstrcasecmp(str, "integer")) return OPENAPI_TYPE_INTEGER; if (!xstrcasecmp(str, "number")) return OPENAPI_TYPE_NUMBER; if (!xstrcasecmp(str, "string")) return OPENAPI_TYPE_STRING; if (!xstrcasecmp(str, "boolean") || !xstrcasecmp(str, "bool")) return OPENAPI_TYPE_BOOL; if (!xstrcasecmp(str, "object")) return OPENAPI_TYPE_OBJECT; if (!xstrcasecmp(str, "array")) return OPENAPI_TYPE_ARRAY; return OPENAPI_TYPE_UNKNOWN; } static const char *_get_parameter_type_string(parameter_type_t type) { switch (type) { case OPENAPI_TYPE_UNKNOWN: return "unknown"; case OPENAPI_TYPE_INTEGER: return "integer"; case OPENAPI_TYPE_NUMBER: return "number"; case OPENAPI_TYPE_STRING: return "string"; case OPENAPI_TYPE_BOOL: return "boolean"; case OPENAPI_TYPE_OBJECT: return "object"; case OPENAPI_TYPE_ARRAY: return "array"; default: xassert(false); return "unknown"; } } typedef enum { OPENAPI_PATH_ENTRY_UNKNOWN = 0, OPENAPI_PATH_ENTRY_MATCH_STRING, OPENAPI_PATH_ENTRY_MATCH_PARAMETER, OPENAPI_PATH_ENTRY_MAX } entry_type_t; static const char *_get_entry_type_string(entry_type_t type) { switch (type) { case OPENAPI_PATH_ENTRY_MATCH_STRING: return "string"; case OPENAPI_PATH_ENTRY_MATCH_PARAMETER: return "parameter"; default: return "invalid"; } } /* * This is a simplified entry since OAS allows combos of * parameters but we will only honor having a single parameter * as an dir entry for now */ typedef struct { char *entry; char *name; entry_type_t type; parameter_type_t parameter; } entry_t; typedef struct { entry_t *entries; http_request_method_t method; } entry_method_t; typedef struct { entry_method_t *methods; int tag; } path_t; static entry_t *_parse_openapi_path(const char *str_path) { char *save_ptr = NULL; char *buffer = xstrdup(str_path); char *token = strtok_r(buffer, "/", &save_ptr); entry_t *entries = NULL; entry_t *entry = NULL; int count = 0; /* find max bound on number of entries */ for (const char *i = str_path; *i; i++) if (*i == '/') count++; if (count > 1024) fatal_abort("%s: url %s is way too long", str_path, __func__); entry = entries = xcalloc((count + 1), sizeof(entry_t)); while (token) { const size_t slen = strlen(token); /* ignore // entries */ if (slen <= 0) goto again; entry->entry = xstrdup(token); if (!xstrcmp(token, ".") || !xstrcmp(token, "..")) { /* * there should not be a .. or . in a path * definition, it just doesn't make any sense */ error("%s: invalid %s at entry", __func__, token); goto fail; } else if (slen > 3 && token[0] == '{' && token[slen - 1] == '}') { entry->type = OPENAPI_PATH_ENTRY_MATCH_PARAMETER; entry->name = xstrndup(token + 1, slen - 2); debug5("%s: parameter %s at entry %s", __func__, entry->name, token); } else { /* not a variable */ entry->type = OPENAPI_PATH_ENTRY_MATCH_STRING; entry->name = NULL; debug5("%s: string match entry %s", __func__, token); } entry++; xassert(entry <= entries + count); again: token = strtok_r(NULL, "/", &save_ptr); } /* last is always NULL */ xassert(!entry->type); xfree(buffer); return entries; fail: xfree(entries); xfree(buffer); return NULL; } typedef struct { const char *str_path; const data_t *found; } match_path_string_t; static data_for_each_cmd_t _match_path_string(const char *key, const data_t *data, void *arg) { match_path_string_t *args = arg; if (!xstrcasecmp(args->str_path, key)) { args->found = data; return DATA_FOR_EACH_STOP; } return DATA_FOR_EACH_CONT; } static const data_t *_find_spec_path(const char *str_path) { const data_t *path_list = data_resolve_dict_path(spec, "/paths"); match_path_string_t args = { .str_path = str_path }; if (!path_list) return NULL; if (data_get_type(path_list) != DATA_TYPE_DICT) return NULL; if (data_dict_for_each_const(path_list, _match_path_string, &args) < 0) return NULL; return args.found; } typedef struct { entry_method_t *method; entry_t *entries; path_t *path; } populate_methods_t; static data_for_each_cmd_t _populate_parameters(const data_t *data, void *arg) { populate_methods_t *args = arg; entry_t *entry; const char *key = NULL; const data_t *dname = data_key_get_const(data, "name"); if (!dname || !(key = data_get_string(dname)) || !key[0]) { /* parameter doesn't have a name! */ return DATA_FOR_EACH_FAIL; } for (entry = args->entries; entry->type; entry++) if (entry->type == OPENAPI_PATH_ENTRY_MATCH_PARAMETER && !xstrcasecmp(entry->name, key)) { char *buffer = NULL; if (!data_retrieve_dict_path_string(data, "schema/type", &buffer)) { entry->parameter = _get_parameter_type(buffer); if (entry->parameter == OPENAPI_TYPE_UNKNOWN) fatal("%s: invalid type for %s", __func__, key); } else fatal("%s: missing schema type for %s", __func__, key); xfree(buffer); return DATA_FOR_EACH_CONT; } return DATA_FOR_EACH_CONT; } static data_for_each_cmd_t _populate_methods(const char *key, const data_t *data, void *arg) { populate_methods_t *args = arg; populate_methods_t nargs = *args; entry_method_t *method = args->method; const data_t *para; int count = 0; entry_t *entry; if (data_get_type(data) != DATA_TYPE_DICT) return DATA_FOR_EACH_FAIL; if ((method->method = get_http_method(key)) == HTTP_REQUEST_INVALID) return DATA_FOR_EACH_FAIL; for (entry = args->entries; entry->type; entry++) count++; xassert(!method->entries); method->entries = xcalloc((count + 1), sizeof(entry_t)); /* count is already bounded */ memcpy(method->entries, args->entries, (count * sizeof(entry_t))); /* clone over any strings */ for (entry = method->entries; entry->type; entry++) { entry->entry = xstrdup(entry->entry); entry->name = xstrdup(entry->name); } /* point to new entries clone */ nargs.entries = method->entries; para = data_key_get_const(data, "parameters"); if (!para) return DATA_FOR_EACH_CONT; if (data_get_type(para) != DATA_TYPE_LIST) return DATA_FOR_EACH_FAIL; if (data_list_for_each_const(para, _populate_parameters, &nargs) < 0) return DATA_FOR_EACH_FAIL; /* increment to next method entry */ args->method++; if (get_log_level() >= LOG_LEVEL_DEBUG5) for (entry = method->entries; entry->type; entry++) { debug5("%s: add method:%s for path tag:%d entry:%s name:%s parameter:%s entry_type:%s", __func__, key, args->path->tag, entry->entry, entry->name, _get_parameter_type_string(entry->parameter), _get_entry_type_string(entry->type)); } return DATA_FOR_EACH_CONT; } extern int register_path_tag(const char *str_path) { path_t *path = NULL; entry_t *entries = _parse_openapi_path(str_path); const data_t *spec_entry; populate_methods_t args = {0}; if (!entries) return -1; spec_entry = _find_spec_path(str_path); if (!spec_entry) return -1; if (data_get_type(spec_entry) != DATA_TYPE_DICT) return -1; path = xmalloc(sizeof(*path)); path->tag = path_tag_counter++; path->methods = xcalloc((data_get_dict_length(spec_entry) + 1), sizeof(*path->methods)); args.method = path->methods; args.entries = entries; args.path = path; if (data_dict_for_each_const(spec_entry, _populate_methods, &args) < 0) fatal_abort("%s: failed", __func__); for (entry_t *entry = entries; entry->type; entry++) { xfree(entry->entry); xfree(entry->name); } xfree(entries); list_append(paths, path); return path->tag; } static int _rm_path_by_tag(void *x, void *tptr) { path_t *path = (path_t *)x; const int tag = *(int*)tptr; if (path->tag != tag) return 0; debug5("%s: removing tag %d", __func__, path->tag); return 1; } extern void unregister_path_tag(int tag) { slurm_rwlock_wrlock(&paths_lock); if (paths) list_delete_all(paths, _rm_path_by_tag, &tag); slurm_rwlock_unlock(&paths_lock); } typedef struct { bool matched; const data_t *dpath; path_t *path; data_t *params; http_request_method_t method; entry_t *entry; } match_path_from_data_t; /* * Check if the entry matches based on the OAS type * and if it does, then add that matched parameter */ static bool _match_param(const data_t *data, match_path_from_data_t *args) { bool matched = false; entry_t *entry = args->entry; data_t *params = args->params; data_t *match = data_new(); data_copy(match, data); switch (entry->parameter) { case OPENAPI_TYPE_NUMBER: { if (data_convert_type(match, DATA_TYPE_FLOAT) == DATA_TYPE_FLOAT) { data_set_float(data_key_set(params, entry->name), data_get_float(match)); matched = true; } break; } case OPENAPI_TYPE_INTEGER: { if (data_convert_type(match, DATA_TYPE_INT_64) == DATA_TYPE_INT_64) { data_set_int(data_key_set(params, entry->name), data_get_int(match)); matched = true; } break; } default: /* assume string */ debug("%s: unknown parameter type %s", __func__, _get_parameter_type_string(entry->parameter)); /* fall through */ case OPENAPI_TYPE_STRING: { if (data_convert_type(match, DATA_TYPE_STRING) == DATA_TYPE_STRING) { data_set_string(data_key_set(params, entry->name), data_get_string(match)); matched = true; } break; } } if (get_log_level() >= LOG_LEVEL_DEBUG5) { char *str = NULL; data_get_string_converted(data, &str); debug5("%s: parameter %s[%s]->%s[%s] result=%s", __func__, entry->name, _get_parameter_type_string(entry->parameter), str, data_type_to_string(data_get_type(data)), (matched ? "matched" : "failed")); xfree(str); } FREE_NULL_DATA(match); return matched; } static data_for_each_cmd_t _match_path(const data_t *data, void *y) { match_path_from_data_t *args = y; entry_t *entry = args->entry; args->matched = false; if (!entry->type) return DATA_FOR_EACH_FAIL; if (entry->type == OPENAPI_PATH_ENTRY_MATCH_STRING) { bool match; if (data_get_type(data) != DATA_TYPE_STRING) return DATA_FOR_EACH_FAIL; match = !xstrcmp(data_get_string(data), entry->entry); debug5("%s: string attempt match %s to %s: %s", __func__, entry->entry, data_get_string(data), (match ? "SUCCESS" : "FAILURE")); if (!match) return DATA_FOR_EACH_FAIL; } else if (entry->type == OPENAPI_PATH_ENTRY_MATCH_PARAMETER) { if (!_match_param(data, args)) return DATA_FOR_EACH_FAIL; } else fatal_abort("%s: unknown OAS path entry match type", __func__); args->entry++; args->matched = true; return DATA_FOR_EACH_CONT; } static int _match_path_from_data(void *x, void *key) { match_path_from_data_t *args = key; path_t *path = x; entry_method_t *method; args->path = path; for (method = path->methods; method->entries; method++) { args->entry = method->entries; data_list_for_each_const(args->dpath, _match_path, args); if (args->matched) break; } if (get_log_level() >= LOG_LEVEL_DEBUG5) { char *str_path = dump_json(args->dpath, DUMP_JSON_FLAGS_COMPACT); if (args->matched) debug5("%s: match successful for tag %d to %s(0x%"PRIXPTR")", __func__, args->path->tag, str_path, (uintptr_t) args->dpath); else debug5("%s: match failed for tag %d to %s(0x%"PRIXPTR")", __func__, args->path->tag, str_path, (uintptr_t) args->dpath); xfree(str_path); } return (args->matched ? 1 : 0); } extern int find_path_tag(const data_t *dpath, data_t *params, http_request_method_t method) { path_t *path; match_path_from_data_t args = { .params = params, .dpath = dpath }; int tag = -1; xassert(data_get_type(params) == DATA_TYPE_DICT); slurm_rwlock_rdlock(&paths_lock); path = list_find_first(paths, _match_path_from_data, &args); if (path) tag = path->tag; slurm_rwlock_unlock(&paths_lock); return tag; } extern const data_t *get_openapi_specification(void) { return spec; } static void _list_delete_path_t(void *x) { entry_method_t *method; if (!x) return; path_t *path = x; xassert(path->tag != -1); method = path->methods; while (method->method) { entry_t *entry = method->entries; debug5("%s: remove path tag:%d method:%s", __func__, path->tag, get_http_method_string(method->method)); while (entry->type) { debug5("%s: remove path tag:%d method:%s entry:%s name:%s", __func__, path->tag, get_http_method_string(method->method), entry->entry, entry->name); xfree(entry->entry); xfree(entry->name); xassert(!(entry->parameter = 0)); /* set value */ xassert(!(entry->type = 0)); /* set value */ entry++; } xfree(method->entries); xassert(!(method->method = 0)); /* set value */ method++; } xfree(path->methods); xassert((path->tag = -1)); /* set value */ xfree(path); } extern int init_openapi(void) { slurm_rwlock_wrlock(&paths_lock); if (spec) fatal_abort("%s called twice", __func__); paths = list_create(_list_delete_path_t); static_ref_json_to_data_t(spec, openapi_json); slurm_rwlock_unlock(&paths_lock); return SLURM_SUCCESS; } extern void destroy_openapi(void) { slurm_rwlock_wrlock(&paths_lock); FREE_NULL_LIST(paths); /* const_cast spec since this is only place that frees it */ data_t *dspec = (data_t *) spec; FREE_NULL_DATA(dspec); spec = NULL; slurm_rwlock_unlock(&paths_lock); }