summaryrefslogtreecommitdiff
path: root/src/services/api/graphql
diff options
context:
space:
mode:
Diffstat (limited to 'src/services/api/graphql')
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_op_mode.py5
-rw-r--r--src/services/api/graphql/graphql/mutations.py22
-rw-r--r--src/services/api/graphql/graphql/queries.py22
-rw-r--r--src/services/api/graphql/libs/op_mode.py13
-rw-r--r--src/services/api/graphql/libs/token_auth.py3
-rw-r--r--src/services/api/graphql/session/errors/op_mode_errors.py6
-rw-r--r--src/services/api/graphql/session/session.py3
7 files changed, 40 insertions, 34 deletions
diff --git a/src/services/api/graphql/generate/schema_from_op_mode.py b/src/services/api/graphql/generate/schema_from_op_mode.py
index 1fd198a37..fc63b0100 100755
--- a/src/services/api/graphql/generate/schema_from_op_mode.py
+++ b/src/services/api/graphql/generate/schema_from_op_mode.py
@@ -25,15 +25,16 @@ from inspect import signature, getmembers, isfunction, isclass, getmro
from jinja2 import Template
from vyos.defaults import directories
+from vyos.util import load_as_module
if __package__ is None or __package__ == '':
sys.path.append("/usr/libexec/vyos/services/api")
- from graphql.libs.op_mode import load_as_module, is_op_mode_function_name, is_show_function_name
+ from graphql.libs.op_mode import is_op_mode_function_name, is_show_function_name
from graphql.libs.op_mode import snake_to_pascal_case, map_type_name
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.xml import defaults
else:
- from .. libs.op_mode import load_as_module, is_op_mode_function_name, is_show_function_name
+ from .. libs.op_mode import is_op_mode_function_name, is_show_function_name
from .. libs.op_mode import snake_to_pascal_case, map_type_name
from .. import state
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index 2778feb69..87ea59c43 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -14,7 +14,7 @@
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from importlib import import_module
-from typing import Any, Dict
+from typing import Any, Dict, Optional
from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake
from graphql import GraphQLResolveInfo
from makefun import with_signature
@@ -42,7 +42,7 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
func_base_name = convert_camel_case_to_snake(class_name)
resolver_name = f'resolve_{func_base_name}'
- func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Dict = {})'
+ func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Optional[Dict]=None)'
@mutation.field(mutation_name)
@convert_kwargs_to_snake_case
@@ -67,20 +67,18 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
del data['key']
elif auth_type == 'token':
- # there is a subtlety here: with the removal of the key entry,
- # some requests will now have empty input, hence no data arg, so
- # make it optional in the func_sig. However, it can not be None,
- # as the makefun package provides accurate TypeError exceptions;
- # hence set it to {}, but now it is a mutable default argument,
- # so clear the key 'result', which is added at the end of
- # this function.
data = kwargs['data']
- if 'result' in data:
- del data['result']
-
+ if data is None:
+ data = {}
info = kwargs['info']
user = info.context.get('user')
if user is None:
+ error = info.context.get('error')
+ if error is not None:
+ return {
+ "success": False,
+ "errors": [error]
+ }
return {
"success": False,
"errors": ['not authenticated']
diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py
index 9c8a4f064..1ad586428 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -14,7 +14,7 @@
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from importlib import import_module
-from typing import Any, Dict
+from typing import Any, Dict, Optional
from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake
from graphql import GraphQLResolveInfo
from makefun import with_signature
@@ -42,7 +42,7 @@ def make_query_resolver(query_name, class_name, session_func):
func_base_name = convert_camel_case_to_snake(class_name)
resolver_name = f'resolve_{func_base_name}'
- func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Dict = {})'
+ func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Optional[Dict]=None)'
@query.field(query_name)
@convert_kwargs_to_snake_case
@@ -67,20 +67,18 @@ def make_query_resolver(query_name, class_name, session_func):
del data['key']
elif auth_type == 'token':
- # there is a subtlety here: with the removal of the key entry,
- # some requests will now have empty input, hence no data arg, so
- # make it optional in the func_sig. However, it can not be None,
- # as the makefun package provides accurate TypeError exceptions;
- # hence set it to {}, but now it is a mutable default argument,
- # so clear the key 'result', which is added at the end of
- # this function.
data = kwargs['data']
- if 'result' in data:
- del data['result']
-
+ if data is None:
+ data = {}
info = kwargs['info']
user = info.context.get('user')
if user is None:
+ error = info.context.get('error')
+ if error is not None:
+ return {
+ "success": False,
+ "errors": [error]
+ }
return {
"success": False,
"errors": ['not authenticated']
diff --git a/src/services/api/graphql/libs/op_mode.py b/src/services/api/graphql/libs/op_mode.py
index da2bcdb5b..6939ed5d6 100644
--- a/src/services/api/graphql/libs/op_mode.py
+++ b/src/services/api/graphql/libs/op_mode.py
@@ -17,14 +17,12 @@ import os
import re
import typing
import importlib.util
+from typing import Union
+from humps import decamelize
from vyos.defaults import directories
-
-def load_as_module(name: str, path: str):
- spec = importlib.util.spec_from_file_location(name, path)
- mod = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(mod)
- return mod
+from vyos.util import load_as_module
+from vyos.opmode import _normalize_field_names
def load_op_mode_as_module(name: str):
path = os.path.join(directories['op_mode'], name)
@@ -98,3 +96,6 @@ def map_type_name(type_name: type, optional: bool = False) -> str:
# scalar 'Generic' is defined in schema.graphql
return 'Generic'
+
+def normalize_output(result: Union[dict, list]) -> Union[dict, list]:
+ return _normalize_field_names(decamelize(result))
diff --git a/src/services/api/graphql/libs/token_auth.py b/src/services/api/graphql/libs/token_auth.py
index 3ecd8b855..2100eba7f 100644
--- a/src/services/api/graphql/libs/token_auth.py
+++ b/src/services/api/graphql/libs/token_auth.py
@@ -54,6 +54,9 @@ def get_user_context(request):
user_id: str = payload.get('sub')
if user_id is None:
return context
+ except jwt.exceptions.ExpiredSignatureError:
+ context['error'] = 'expired token'
+ return context
except jwt.PyJWTError:
return context
try:
diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py
index 7ba75455d..7bc1d1d81 100644
--- a/src/services/api/graphql/session/errors/op_mode_errors.py
+++ b/src/services/api/graphql/session/errors/op_mode_errors.py
@@ -3,11 +3,13 @@
op_mode_err_msg = {
"UnconfiguredSubsystem": "subsystem is not configured or not running",
"DataUnavailable": "data currently unavailable",
- "PermissionDenied": "client does not have permission"
+ "PermissionDenied": "client does not have permission",
+ "IncorrectValue": "argument value is incorrect"
}
op_mode_err_code = {
"UnconfiguredSubsystem": 2000,
"DataUnavailable": 2001,
- "PermissionDenied": 1003
+ "PermissionDenied": 1003,
+ "IncorrectValue": 1002
}
diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py
index c2c1db1df..0b77b1433 100644
--- a/src/services/api/graphql/session/session.py
+++ b/src/services/api/graphql/session/session.py
@@ -25,6 +25,7 @@ from vyos.template import render
from vyos.opmode import Error as OpModeError
from api.graphql.libs.op_mode import load_op_mode_as_module, split_compound_op_mode_name
+from api.graphql.libs.op_mode import normalize_output
op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json')
@@ -149,6 +150,8 @@ class Session:
except OpModeError as e:
raise e
+ res = normalize_output(res)
+
return res
def gen_op_mutation(self):