summaryrefslogtreecommitdiff
path: root/src/services/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/services/api')
-rw-r--r--src/services/api/graphql/generate/config_session_function.py6
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_op_mode.py6
-rw-r--r--src/services/api/graphql/graphql/auth_token_mutation.py14
-rw-r--r--src/services/api/graphql/graphql/mutations.py25
-rw-r--r--src/services/api/graphql/graphql/queries.py25
-rw-r--r--src/services/api/graphql/libs/op_mode.py14
-rw-r--r--src/services/api/graphql/libs/token_auth.py10
-rw-r--r--src/services/api/graphql/session/errors/op_mode_errors.py12
-rw-r--r--src/services/api/graphql/session/session.py35
9 files changed, 95 insertions, 52 deletions
diff --git a/src/services/api/graphql/generate/config_session_function.py b/src/services/api/graphql/generate/config_session_function.py
index fc0dd7a87..20fc7cc1d 100644
--- a/src/services/api/graphql/generate/config_session_function.py
+++ b/src/services/api/graphql/generate/config_session_function.py
@@ -8,8 +8,12 @@ def show_config(path: list[str], configFormat: typing.Optional[str]):
def show(path: list[str]):
pass
+def show_user_info(user: str):
+ pass
+
queries = {'show_config': show_config,
- 'show': show}
+ 'show': show,
+ 'show_user_info': show_user_info}
def save_config_file(fileName: typing.Optional[str]):
pass
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..b320a529e 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,17 @@ from inspect import signature, getmembers, isfunction, isclass, getmro
from jinja2 import Template
from vyos.defaults import directories
+from vyos.opmode import _is_op_mode_function_name as is_op_mode_function_name
+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_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_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/auth_token_mutation.py b/src/services/api/graphql/graphql/auth_token_mutation.py
index 21ac40094..603a13758 100644
--- a/src/services/api/graphql/graphql/auth_token_mutation.py
+++ b/src/services/api/graphql/graphql/auth_token_mutation.py
@@ -20,6 +20,7 @@ from ariadne import ObjectType, UnionType
from graphql import GraphQLResolveInfo
from .. libs.token_auth import generate_token
+from .. session.session import get_user_info
from .. import state
auth_token_mutation = ObjectType("Mutation")
@@ -36,13 +37,24 @@ def auth_token_resolver(obj: Any, info: GraphQLResolveInfo, data: Dict):
datetime.timedelta(seconds=exp_interval))
res = generate_token(user, passwd, secret, expiration)
- if res:
+ try:
+ res |= get_user_info(user)
+ except ValueError:
+ # non-existent user already caught
+ pass
+ if 'token' in res:
data['result'] = res
return {
"success": True,
"data": data
}
+ if 'errors' in res:
+ return {
+ "success": False,
+ "errors": res['errors']
+ }
+
return {
"success": False,
"errors": ['token generation failed']
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index 2778feb69..8254e22b1 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -14,8 +14,8 @@
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from importlib import import_module
-from typing import Any, Dict
-from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake
+from typing import Any, Dict, Optional
+from ariadne import ObjectType, convert_camel_case_to_snake
from graphql import GraphQLResolveInfo
from makefun import with_signature
@@ -42,10 +42,9 @@ 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
@with_signature(func_sig, func_name=resolver_name)
async def func_impl(*args, **kwargs):
try:
@@ -67,20 +66,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..daccc19b2 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -14,8 +14,8 @@
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from importlib import import_module
-from typing import Any, Dict
-from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake
+from typing import Any, Dict, Optional
+from ariadne import ObjectType, convert_camel_case_to_snake
from graphql import GraphQLResolveInfo
from makefun import with_signature
@@ -42,10 +42,9 @@ 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
@with_signature(func_sig, func_name=resolver_name)
async def func_impl(*args, **kwargs):
try:
@@ -67,20 +66,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 97a26520e..c553bbd67 100644
--- a/src/services/api/graphql/libs/op_mode.py
+++ b/src/services/api/graphql/libs/op_mode.py
@@ -21,24 +21,14 @@ from typing import Union
from humps import decamelize
from vyos.defaults import directories
+from vyos.util import load_as_module
from vyos.opmode import _normalize_field_names
-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
-
def load_op_mode_as_module(name: str):
path = os.path.join(directories['op_mode'], name)
name = os.path.splitext(name)[0].replace('-', '_')
return load_as_module(name, path)
-def is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart)", name):
- return True
- return False
-
def is_show_function_name(name):
if re.match(r"^show", name):
return True
@@ -89,7 +79,7 @@ def map_type_name(type_name: type, optional: bool = False) -> str:
if type_name == int:
return 'Int!' if not optional else 'Int = null'
if type_name == bool:
- return 'Boolean!' if not optional else 'Boolean = false'
+ return 'Boolean = false'
if typing.get_origin(type_name) == list:
if not optional:
return f'[{map_type_name(typing.get_args(type_name)[0])}]!'
diff --git a/src/services/api/graphql/libs/token_auth.py b/src/services/api/graphql/libs/token_auth.py
index 3ecd8b855..8585485c9 100644
--- a/src/services/api/graphql/libs/token_auth.py
+++ b/src/services/api/graphql/libs/token_auth.py
@@ -29,14 +29,13 @@ def generate_token(user: str, passwd: str, secret: str, exp: int) -> dict:
payload_data = {'iss': user, 'sub': user_id, 'exp': exp}
secret = state.settings.get('secret')
if secret is None:
- return {
- "success": False,
- "errors": ['failed secret generation']
- }
+ return {"errors": ['missing secret']}
token = jwt.encode(payload=payload_data, key=secret, algorithm="HS256")
users |= {user_id: user}
return {'token': token}
+ else:
+ return {"errors": ['failed pam authentication']}
def get_user_context(request):
context = {}
@@ -54,6 +53,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..18d555f2d 100644
--- a/src/services/api/graphql/session/errors/op_mode_errors.py
+++ b/src/services/api/graphql/session/errors/op_mode_errors.py
@@ -1,13 +1,17 @@
-
-
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",
+ "InsufficientResources": "insufficient system resources",
+ "IncorrectValue": "argument value is incorrect",
+ "UnsupportedOperation": "operation is not supported (yet)",
}
op_mode_err_code = {
"UnconfiguredSubsystem": 2000,
"DataUnavailable": 2001,
- "PermissionDenied": 1003
+ "InsufficientResources": 2002,
+ "PermissionDenied": 1003,
+ "IncorrectValue": 1002,
+ "UnsupportedOperation": 1004,
}
diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py
index 0b77b1433..3c5a062b6 100644
--- a/src/services/api/graphql/session/session.py
+++ b/src/services/api/graphql/session/session.py
@@ -29,6 +29,28 @@ from api.graphql.libs.op_mode import normalize_output
op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json')
+def get_config_dict(path=[], effective=False, key_mangling=None,
+ get_first_key=False, no_multi_convert=False,
+ no_tag_node_value_mangle=False):
+ config = Config()
+ return config.get_config_dict(path=path, effective=effective,
+ key_mangling=key_mangling,
+ get_first_key=get_first_key,
+ no_multi_convert=no_multi_convert,
+ no_tag_node_value_mangle=no_tag_node_value_mangle)
+
+def get_user_info(user):
+ user_info = {}
+ info = get_config_dict(['system', 'login', 'user', user],
+ get_first_key=True)
+ if not info:
+ raise ValueError("No such user")
+
+ user_info['user'] = user
+ user_info['full_name'] = info.get('full-name', '')
+
+ return user_info
+
class Session:
"""
Wrapper for calling configsession functions based on GraphQL requests.
@@ -116,6 +138,19 @@ class Session:
return res
+ def show_user_info(self):
+ session = self._session
+ data = self._data
+
+ user_info = {}
+ user = data['user']
+ try:
+ user_info = get_user_info(user)
+ except Exception as error:
+ raise error
+
+ return user_info
+
def system_status(self):
import api.graphql.session.composite.system_status as system_status