summaryrefslogtreecommitdiff
path: root/src/services/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/services/api')
-rw-r--r--src/services/api/graphql/__init__.py0
-rw-r--r--src/services/api/graphql/bindings.py14
-rw-r--r--src/services/api/graphql/generate/composite_function.py11
-rw-r--r--src/services/api/graphql/generate/config_session_function.py28
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_composite.py165
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_config_session.py165
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_op_mode.py (renamed from src/services/api/graphql/utils/schema_from_op_mode.py)83
-rw-r--r--src/services/api/graphql/graphql/auth_token_mutation.py49
-rw-r--r--src/services/api/graphql/graphql/directives.py65
-rw-r--r--src/services/api/graphql/graphql/mutations.py91
-rw-r--r--src/services/api/graphql/graphql/queries.py85
-rw-r--r--src/services/api/graphql/graphql/schema/auth_token.graphql19
-rw-r--r--src/services/api/graphql/graphql/schema/config_file.graphql29
-rw-r--r--src/services/api/graphql/graphql/schema/dhcp_server.graphql36
-rw-r--r--src/services/api/graphql/graphql/schema/firewall_group.graphql101
-rw-r--r--src/services/api/graphql/graphql/schema/image.graphql31
-rw-r--r--src/services/api/graphql/graphql/schema/interface_ethernet.graphql19
-rw-r--r--src/services/api/graphql/graphql/schema/schema.graphql32
-rw-r--r--src/services/api/graphql/graphql/schema/show.graphql15
-rw-r--r--src/services/api/graphql/graphql/schema/show_config.graphql21
-rw-r--r--src/services/api/graphql/graphql/schema/system_status.graphql18
-rw-r--r--src/services/api/graphql/libs/key_auth.py (renamed from src/services/api/graphql/key_auth.py)2
-rw-r--r--src/services/api/graphql/libs/op_mode.py (renamed from src/services/api/graphql/utils/util.py)37
-rw-r--r--src/services/api/graphql/libs/token_auth.py71
-rwxr-xr-xsrc/services/api/graphql/session/composite/system_status.py2
-rw-r--r--src/services/api/graphql/session/errors/op_mode_errors.py6
-rw-r--r--src/services/api/graphql/session/session.py49
27 files changed, 749 insertions, 495 deletions
diff --git a/src/services/api/graphql/__init__.py b/src/services/api/graphql/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/services/api/graphql/__init__.py
diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py
index 0b1260912..aa1ba0eb0 100644
--- a/src/services/api/graphql/bindings.py
+++ b/src/services/api/graphql/bindings.py
@@ -18,16 +18,26 @@ from . graphql.queries import query
from . graphql.mutations import mutation
from . graphql.directives import directives_dict
from . graphql.errors import op_mode_error
-from . utils.schema_from_op_mode import generate_op_mode_definitions
+from . graphql.auth_token_mutation import auth_token_mutation
+from . generate.schema_from_op_mode import generate_op_mode_definitions
+from . generate.schema_from_config_session import generate_config_session_definitions
+from . generate.schema_from_composite import generate_composite_definitions
+from . libs.token_auth import init_secret
+from . import state
from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers
def generate_schema():
api_schema_dir = vyos.defaults.directories['api_schema']
generate_op_mode_definitions()
+ generate_config_session_definitions()
+ generate_composite_definitions()
+
+ if state.settings['app'].state.vyos_auth_type == 'token':
+ init_secret()
type_defs = load_schema_from_path(api_schema_dir)
- schema = make_executable_schema(type_defs, query, op_mode_error, mutation, snake_case_fallback_resolvers, directives=directives_dict)
+ schema = make_executable_schema(type_defs, query, op_mode_error, mutation, auth_token_mutation, snake_case_fallback_resolvers, directives=directives_dict)
return schema
diff --git a/src/services/api/graphql/generate/composite_function.py b/src/services/api/graphql/generate/composite_function.py
new file mode 100644
index 000000000..bc9d80fbb
--- /dev/null
+++ b/src/services/api/graphql/generate/composite_function.py
@@ -0,0 +1,11 @@
+# typing information for composite functions: those that invoke several
+# elementary requests, and return the result as a single dict
+import typing
+
+def system_status():
+ pass
+
+queries = {'system_status': system_status}
+
+mutations = {}
+
diff --git a/src/services/api/graphql/generate/config_session_function.py b/src/services/api/graphql/generate/config_session_function.py
new file mode 100644
index 000000000..fc0dd7a87
--- /dev/null
+++ b/src/services/api/graphql/generate/config_session_function.py
@@ -0,0 +1,28 @@
+# typing information for native configsession functions; used to generate
+# schema definition files
+import typing
+
+def show_config(path: list[str], configFormat: typing.Optional[str]):
+ pass
+
+def show(path: list[str]):
+ pass
+
+queries = {'show_config': show_config,
+ 'show': show}
+
+def save_config_file(fileName: typing.Optional[str]):
+ pass
+def load_config_file(fileName: str):
+ pass
+def add_system_image(location: str):
+ pass
+def delete_system_image(name: str):
+ pass
+
+mutations = {'save_config_file': save_config_file,
+ 'load_config_file': load_config_file,
+ 'add_system_image': add_system_image,
+ 'delete_system_image': delete_system_image}
+
+
diff --git a/src/services/api/graphql/generate/schema_from_composite.py b/src/services/api/graphql/generate/schema_from_composite.py
new file mode 100755
index 000000000..61a08cb2f
--- /dev/null
+++ b/src/services/api/graphql/generate/schema_from_composite.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+# A utility to generate GraphQL schema defintions from typing information of
+# composite functions comprising several requests.
+
+import os
+import sys
+import json
+from inspect import signature, getmembers, isfunction, isclass, getmro
+from jinja2 import Template
+
+from vyos.defaults import directories
+if __package__ is None or __package__ == '':
+ sys.path.append("/usr/libexec/vyos/services/api")
+ from graphql.libs.op_mode import snake_to_pascal_case, map_type_name
+ from composite_function import queries, mutations
+ from vyos.config import Config
+ from vyos.configdict import dict_merge
+ from vyos.xml import defaults
+else:
+ from .. libs.op_mode import snake_to_pascal_case, map_type_name
+ from . composite_function import queries, mutations
+ from .. import state
+
+SCHEMA_PATH = directories['api_schema']
+
+if __package__ is None or __package__ == '':
+ # allow running stand-alone
+ conf = Config()
+ base = ['service', 'https', 'api']
+ graphql_dict = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
+ if 'graphql' not in graphql_dict:
+ exit("graphql is not configured")
+
+ graphql_dict = dict_merge(defaults(base), graphql_dict)
+ auth_type = graphql_dict['graphql']['authentication']['type']
+else:
+ auth_type = state.settings['app'].state.vyos_auth_type
+
+schema_data: dict = {'auth_type': auth_type,
+ 'schema_name': '',
+ 'schema_fields': []}
+
+query_template = """
+{%- if auth_type == 'key' %}
+input {{ schema_name }}Input {
+ key: String!
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
+
+type {{ schema_name }} {
+ result: Generic
+}
+
+type {{ schema_name }}Result {
+ data: {{ schema_name }}
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Query {
+{%- if auth_type == 'key' or schema_fields %}
+ {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositequery
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @compositequery
+{%- endif %}
+}
+"""
+
+mutation_template = """
+{%- if auth_type == 'key' %}
+input {{ schema_name }}Input {
+ key: String!
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
+
+type {{ schema_name }} {
+ result: Generic
+}
+
+type {{ schema_name }}Result {
+ data: {{ schema_name }}
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+{%- if auth_type == 'key' or schema_fields %}
+ {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositemutation
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @compositemutation
+{%- endif %}
+}
+"""
+
+def create_schema(func_name: str, func: callable, template: str) -> str:
+ sig = signature(func)
+
+ field_dict = {}
+ for k in sig.parameters:
+ field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation)
+
+ schema_fields = []
+ for k,v in field_dict.items():
+ schema_fields.append(k+': '+v)
+
+ schema_data['schema_name'] = snake_to_pascal_case(func_name)
+ schema_data['schema_fields'] = schema_fields
+
+ j2_template = Template(template)
+ res = j2_template.render(schema_data)
+
+ return res
+
+def generate_composite_definitions():
+ results = []
+ for name,func in queries.items():
+ res = create_schema(name, func, query_template)
+ results.append(res)
+
+ for name,func in mutations.items():
+ res = create_schema(name, func, mutation_template)
+ results.append(res)
+
+ out = '\n'.join(results)
+ with open(f'{SCHEMA_PATH}/composite.graphql', 'w') as f:
+ f.write(out)
+
+if __name__ == '__main__':
+ generate_composite_definitions()
diff --git a/src/services/api/graphql/generate/schema_from_config_session.py b/src/services/api/graphql/generate/schema_from_config_session.py
new file mode 100755
index 000000000..49bf2440e
--- /dev/null
+++ b/src/services/api/graphql/generate/schema_from_config_session.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+# A utility to generate GraphQL schema defintions from typing information of
+# (wrappers of) native configsession functions.
+
+import os
+import sys
+import json
+from inspect import signature, getmembers, isfunction, isclass, getmro
+from jinja2 import Template
+
+from vyos.defaults import directories
+if __package__ is None or __package__ == '':
+ sys.path.append("/usr/libexec/vyos/services/api")
+ from graphql.libs.op_mode import snake_to_pascal_case, map_type_name
+ from config_session_function import queries, mutations
+ from vyos.config import Config
+ from vyos.configdict import dict_merge
+ from vyos.xml import defaults
+else:
+ from .. libs.op_mode import snake_to_pascal_case, map_type_name
+ from . config_session_function import queries, mutations
+ from .. import state
+
+SCHEMA_PATH = directories['api_schema']
+
+if __package__ is None or __package__ == '':
+ # allow running stand-alone
+ conf = Config()
+ base = ['service', 'https', 'api']
+ graphql_dict = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
+ if 'graphql' not in graphql_dict:
+ exit("graphql is not configured")
+
+ graphql_dict = dict_merge(defaults(base), graphql_dict)
+ auth_type = graphql_dict['graphql']['authentication']['type']
+else:
+ auth_type = state.settings['app'].state.vyos_auth_type
+
+schema_data: dict = {'auth_type': auth_type,
+ 'schema_name': '',
+ 'schema_fields': []}
+
+query_template = """
+{%- if auth_type == 'key' %}
+input {{ schema_name }}Input {
+ key: String!
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
+
+type {{ schema_name }} {
+ result: Generic
+}
+
+type {{ schema_name }}Result {
+ data: {{ schema_name }}
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Query {
+{%- if auth_type == 'key' or schema_fields %}
+ {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionquery
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @configsessionquery
+{%- endif %}
+}
+"""
+
+mutation_template = """
+{%- if auth_type == 'key' %}
+input {{ schema_name }}Input {
+ key: String!
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
+
+type {{ schema_name }} {
+ result: Generic
+}
+
+type {{ schema_name }}Result {
+ data: {{ schema_name }}
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+{%- if auth_type == 'key' or schema_fields %}
+ {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionmutation
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @configsessionmutation
+{%- endif %}
+}
+"""
+
+def create_schema(func_name: str, func: callable, template: str) -> str:
+ sig = signature(func)
+
+ field_dict = {}
+ for k in sig.parameters:
+ field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation)
+
+ schema_fields = []
+ for k,v in field_dict.items():
+ schema_fields.append(k+': '+v)
+
+ schema_data['schema_name'] = snake_to_pascal_case(func_name)
+ schema_data['schema_fields'] = schema_fields
+
+ j2_template = Template(template)
+ res = j2_template.render(schema_data)
+
+ return res
+
+def generate_config_session_definitions():
+ results = []
+ for name,func in queries.items():
+ res = create_schema(name, func, query_template)
+ results.append(res)
+
+ for name,func in mutations.items():
+ res = create_schema(name, func, mutation_template)
+ results.append(res)
+
+ out = '\n'.join(results)
+ with open(f'{SCHEMA_PATH}/configsession.graphql', 'w') as f:
+ f.write(out)
+
+if __name__ == '__main__':
+ generate_config_session_definitions()
diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/generate/schema_from_op_mode.py
index 379d15250..fc63b0100 100755
--- a/src/services/api/graphql/utils/schema_from_op_mode.py
+++ b/src/services/api/graphql/generate/schema_from_op_mode.py
@@ -19,16 +19,24 @@
# scripts.
import os
+import sys
import json
-import typing
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__ == '':
- from util import load_as_module, is_op_mode_function_name, is_show_function_name
+ sys.path.append("/usr/libexec/vyos/services/api")
+ 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 . util 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
OP_MODE_PATH = directories['op_mode']
SCHEMA_PATH = directories['api_schema']
@@ -37,16 +45,40 @@ DATA_DIR = directories['data']
op_mode_include_file = os.path.join(DATA_DIR, 'op-mode-standardized.json')
op_mode_error_schema = 'op_mode_error.graphql'
-schema_data: dict = {'schema_name': '',
+if __package__ is None or __package__ == '':
+ # allow running stand-alone
+ conf = Config()
+ base = ['service', 'https', 'api']
+ graphql_dict = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
+ if 'graphql' not in graphql_dict:
+ exit("graphql is not configured")
+
+ graphql_dict = dict_merge(defaults(base), graphql_dict)
+ auth_type = graphql_dict['graphql']['authentication']['type']
+else:
+ auth_type = state.settings['app'].state.vyos_auth_type
+
+schema_data: dict = {'auth_type': auth_type,
+ 'schema_name': '',
'schema_fields': []}
query_template = """
+{%- if auth_type == 'key' %}
input {{ schema_name }}Input {
key: String!
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -60,17 +92,29 @@ type {{ schema_name }}Result {
}
extend type Query {
+{%- if auth_type == 'key' or schema_fields %}
{{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @genopquery
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @genopquery
+{%- endif %}
}
"""
mutation_template = """
+{%- if auth_type == 'key' %}
input {{ schema_name }}Input {
key: String!
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -84,7 +128,11 @@ type {{ schema_name }}Result {
}
extend type Mutation {
+{%- if auth_type == 'key' or schema_fields %}
{{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @genopmutation
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @genopquery
+{%- endif %}
}
"""
@@ -103,35 +151,12 @@ type {{ name }} implements OpModeError {
{%- endfor %}
"""
-def _snake_to_pascal_case(name: str) -> str:
- res = ''.join(map(str.title, name.split('_')))
- return res
-
-def _map_type_name(type_name: type, optional: bool = False) -> str:
- if type_name == str:
- return 'String!' if not optional else 'String = null'
- if type_name == int:
- return 'Int!' if not optional else 'Int = null'
- if type_name == bool:
- return 'Boolean!' if not optional else 'Boolean = false'
- if typing.get_origin(type_name) == list:
- if not optional:
- return f'[{_map_type_name(typing.get_args(type_name)[0])}]!'
- return f'[{_map_type_name(typing.get_args(type_name)[0])}]'
- # typing.Optional is typing.Union[_, NoneType]
- if (typing.get_origin(type_name) is typing.Union and
- typing.get_args(type_name)[1] == type(None)):
- return f'{_map_type_name(typing.get_args(type_name)[0], optional=True)}'
-
- # scalar 'Generic' is defined in schema.graphql
- return 'Generic'
-
def create_schema(func_name: str, base_name: str, func: callable) -> str:
sig = signature(func)
field_dict = {}
for k in sig.parameters:
- field_dict[sig.parameters[k].name] = _map_type_name(sig.parameters[k].annotation)
+ field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation)
# It is assumed that if one is generating a schema for a 'show_*'
# function, that 'get_raw_data' is present and 'raw' is desired.
@@ -142,7 +167,7 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str:
for k,v in field_dict.items():
schema_fields.append(k+': '+v)
- schema_data['schema_name'] = _snake_to_pascal_case(func_name + '_' + base_name)
+ schema_data['schema_name'] = snake_to_pascal_case(func_name + '_' + base_name)
schema_data['schema_fields'] = schema_fields
if is_show_function_name(func_name):
diff --git a/src/services/api/graphql/graphql/auth_token_mutation.py b/src/services/api/graphql/graphql/auth_token_mutation.py
new file mode 100644
index 000000000..21ac40094
--- /dev/null
+++ b/src/services/api/graphql/graphql/auth_token_mutation.py
@@ -0,0 +1,49 @@
+# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import jwt
+import datetime
+from typing import Any, Dict
+from ariadne import ObjectType, UnionType
+from graphql import GraphQLResolveInfo
+
+from .. libs.token_auth import generate_token
+from .. import state
+
+auth_token_mutation = ObjectType("Mutation")
+
+@auth_token_mutation.field('AuthToken')
+def auth_token_resolver(obj: Any, info: GraphQLResolveInfo, data: Dict):
+ # non-nullable fields
+ user = data['username']
+ passwd = data['password']
+
+ secret = state.settings['secret']
+ exp_interval = int(state.settings['app'].state.vyos_token_exp)
+ expiration = (datetime.datetime.now(tz=datetime.timezone.utc) +
+ datetime.timedelta(seconds=exp_interval))
+
+ res = generate_token(user, passwd, secret, expiration)
+ if res:
+ data['result'] = res
+ return {
+ "success": True,
+ "data": data
+ }
+
+ return {
+ "success": False,
+ "errors": ['token generation failed']
+ }
diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py
index d8ceefae6..a7919854a 100644
--- a/src/services/api/graphql/graphql/directives.py
+++ b/src/services/api/graphql/graphql/directives.py
@@ -31,76 +31,57 @@ class VyosDirective(SchemaDirectiveVisitor):
field.resolve = func
return field
-
-class ConfigureDirective(VyosDirective):
+class ConfigSessionQueryDirective(VyosDirective):
"""
- Class providing implementation of 'configure' directive in schema.
+ Class providing implementation of 'configsessionquery' directive in schema.
"""
def visit_field_definition(self, field, object_type):
super().visit_field_definition(field, object_type,
- make_resolver=make_configure_resolver)
+ make_resolver=make_config_session_query_resolver)
-class ShowConfigDirective(VyosDirective):
- """
- Class providing implementation of 'show' directive in schema.
+class ConfigSessionMutationDirective(VyosDirective):
"""
- def visit_field_definition(self, field, object_type):
- super().visit_field_definition(field, object_type,
- make_resolver=make_show_config_resolver)
-
-class SystemStatusDirective(VyosDirective):
- """
- Class providing implementation of 'system_status' directive in schema.
+ Class providing implementation of 'configsessionmutation' directive in schema.
"""
def visit_field_definition(self, field, object_type):
super().visit_field_definition(field, object_type,
- make_resolver=make_system_status_resolver)
+ make_resolver=make_config_session_mutation_resolver)
-class ConfigFileDirective(VyosDirective):
- """
- Class providing implementation of 'configfile' directive in schema.
- """
- def visit_field_definition(self, field, object_type):
- super().visit_field_definition(field, object_type,
- make_resolver=make_config_file_resolver)
-
-class ShowDirective(VyosDirective):
+class GenOpQueryDirective(VyosDirective):
"""
- Class providing implementation of 'show' directive in schema.
+ Class providing implementation of 'genopquery' directive in schema.
"""
def visit_field_definition(self, field, object_type):
super().visit_field_definition(field, object_type,
- make_resolver=make_show_resolver)
+ make_resolver=make_gen_op_query_resolver)
-class ImageDirective(VyosDirective):
+class GenOpMutationDirective(VyosDirective):
"""
- Class providing implementation of 'image' directive in schema.
+ Class providing implementation of 'genopmutation' directive in schema.
"""
def visit_field_definition(self, field, object_type):
super().visit_field_definition(field, object_type,
- make_resolver=make_image_resolver)
+ make_resolver=make_gen_op_mutation_resolver)
-class GenOpQueryDirective(VyosDirective):
+class CompositeQueryDirective(VyosDirective):
"""
- Class providing implementation of 'genopquery' directive in schema.
+ Class providing implementation of 'system_status' directive in schema.
"""
def visit_field_definition(self, field, object_type):
super().visit_field_definition(field, object_type,
- make_resolver=make_gen_op_query_resolver)
+ make_resolver=make_composite_query_resolver)
-class GenOpMutationDirective(VyosDirective):
+class CompositeMutationDirective(VyosDirective):
"""
- Class providing implementation of 'genopmutation' directive in schema.
+ Class providing implementation of 'system_status' directive in schema.
"""
def visit_field_definition(self, field, object_type):
super().visit_field_definition(field, object_type,
- make_resolver=make_gen_op_mutation_resolver)
+ make_resolver=make_composite_mutation_resolver)
-directives_dict = {"configure": ConfigureDirective,
- "showconfig": ShowConfigDirective,
- "systemstatus": SystemStatusDirective,
- "configfile": ConfigFileDirective,
- "show": ShowDirective,
- "image": ImageDirective,
+directives_dict = {"configsessionquery": ConfigSessionQueryDirective,
+ "configsessionmutation": ConfigSessionMutationDirective,
"genopquery": GenOpQueryDirective,
- "genopmutation": GenOpMutationDirective}
+ "genopmutation": GenOpMutationDirective,
+ "compositequery": CompositeQueryDirective,
+ "compositemutation": CompositeMutationDirective}
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index 5ccc9b0b6..87ea59c43 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -14,13 +14,13 @@
# 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
from .. import state
-from .. import key_auth
+from .. libs import key_auth
from api.graphql.session.session import Session
from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code
from vyos.opmode import Error as OpModeError
@@ -42,32 +42,52 @@ 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:
- if 'data' not in kwargs:
- return {
- "success": False,
- "errors": ['missing data']
- }
-
- data = kwargs['data']
- key = data['key']
-
- auth = key_auth.auth_required(key)
- if auth is None:
- return {
- "success": False,
- "errors": ['invalid API key']
- }
-
- # We are finished with the 'key' entry, and may remove so as to
- # pass the rest of data (if any) to function.
- del data['key']
+ auth_type = state.settings['app'].state.vyos_auth_type
+
+ if auth_type == 'key':
+ data = kwargs['data']
+ key = data['key']
+
+ auth = key_auth.auth_required(key)
+ if auth is None:
+ return {
+ "success": False,
+ "errors": ['invalid API key']
+ }
+
+ # We are finished with the 'key' entry, and may remove so as to
+ # pass the rest of data (if any) to function.
+ del data['key']
+
+ elif auth_type == 'token':
+ data = kwargs['data']
+ 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']
+ }
+ else:
+ # AtrributeError will have already been raised if no
+ # vyos_auth_type; validation and defaultValue ensure it is
+ # one of the previous cases, so this is never reached.
+ pass
session = state.settings['app'].state.vyos_session
@@ -106,24 +126,13 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
return func_impl
-def make_prefix_resolver(mutation_name, prefix=[]):
- for pre in prefix:
- Pre = pre.capitalize()
- if Pre in mutation_name:
- class_name = mutation_name.replace(Pre, '', 1)
- return make_mutation_resolver(mutation_name, class_name, pre)
- raise Exception
-
-def make_configure_resolver(mutation_name):
- class_name = mutation_name
- return make_mutation_resolver(mutation_name, class_name, 'configure')
-
-def make_config_file_resolver(mutation_name):
- return make_prefix_resolver(mutation_name, prefix=['save', 'load'])
-
-def make_image_resolver(mutation_name):
- return make_prefix_resolver(mutation_name, prefix=['add', 'delete'])
+def make_config_session_mutation_resolver(mutation_name):
+ return make_mutation_resolver(mutation_name, mutation_name,
+ convert_camel_case_to_snake(mutation_name))
def make_gen_op_mutation_resolver(mutation_name):
- class_name = mutation_name
- return make_mutation_resolver(mutation_name, class_name, 'gen_op_mutation')
+ return make_mutation_resolver(mutation_name, mutation_name, 'gen_op_mutation')
+
+def make_composite_mutation_resolver(mutation_name):
+ return make_mutation_resolver(mutation_name, mutation_name,
+ convert_camel_case_to_snake(mutation_name))
diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py
index b46914dcc..1ad586428 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -14,13 +14,13 @@
# 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
from .. import state
-from .. import key_auth
+from .. libs import key_auth
from api.graphql.session.session import Session
from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code
from vyos.opmode import Error as OpModeError
@@ -42,32 +42,52 @@ 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:
- if 'data' not in kwargs:
- return {
- "success": False,
- "errors": ['missing data']
- }
-
- data = kwargs['data']
- key = data['key']
-
- auth = key_auth.auth_required(key)
- if auth is None:
- return {
- "success": False,
- "errors": ['invalid API key']
- }
-
- # We are finished with the 'key' entry, and may remove so as to
- # pass the rest of data (if any) to function.
- del data['key']
+ auth_type = state.settings['app'].state.vyos_auth_type
+
+ if auth_type == 'key':
+ data = kwargs['data']
+ key = data['key']
+
+ auth = key_auth.auth_required(key)
+ if auth is None:
+ return {
+ "success": False,
+ "errors": ['invalid API key']
+ }
+
+ # We are finished with the 'key' entry, and may remove so as to
+ # pass the rest of data (if any) to function.
+ del data['key']
+
+ elif auth_type == 'token':
+ data = kwargs['data']
+ 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']
+ }
+ else:
+ # AtrributeError will have already been raised if no
+ # vyos_auth_type; validation and defaultValue ensure it is
+ # one of the previous cases, so this is never reached.
+ pass
session = state.settings['app'].state.vyos_session
@@ -106,18 +126,13 @@ def make_query_resolver(query_name, class_name, session_func):
return func_impl
-def make_show_config_resolver(query_name):
- class_name = query_name
- return make_query_resolver(query_name, class_name, 'show_config')
-
-def make_system_status_resolver(query_name):
- class_name = query_name
- return make_query_resolver(query_name, class_name, 'system_status')
-
-def make_show_resolver(query_name):
- class_name = query_name
- return make_query_resolver(query_name, class_name, 'show')
+def make_config_session_query_resolver(query_name):
+ return make_query_resolver(query_name, query_name,
+ convert_camel_case_to_snake(query_name))
def make_gen_op_query_resolver(query_name):
- class_name = query_name
- return make_query_resolver(query_name, class_name, 'gen_op_query')
+ return make_query_resolver(query_name, query_name, 'gen_op_query')
+
+def make_composite_query_resolver(query_name):
+ return make_query_resolver(query_name, query_name,
+ convert_camel_case_to_snake(query_name))
diff --git a/src/services/api/graphql/graphql/schema/auth_token.graphql b/src/services/api/graphql/graphql/schema/auth_token.graphql
new file mode 100644
index 000000000..af53a293a
--- /dev/null
+++ b/src/services/api/graphql/graphql/schema/auth_token.graphql
@@ -0,0 +1,19 @@
+
+input AuthTokenInput {
+ username: String!
+ password: String!
+}
+
+type AuthToken {
+ result: Generic
+}
+
+type AuthTokenResult {
+ data: AuthToken
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+ AuthToken(data: AuthTokenInput) : AuthTokenResult
+}
diff --git a/src/services/api/graphql/graphql/schema/config_file.graphql b/src/services/api/graphql/graphql/schema/config_file.graphql
deleted file mode 100644
index a7263114b..000000000
--- a/src/services/api/graphql/graphql/schema/config_file.graphql
+++ /dev/null
@@ -1,29 +0,0 @@
-input SaveConfigFileInput {
- key: String!
- fileName: String
-}
-
-type SaveConfigFile {
- fileName: String
-}
-
-type SaveConfigFileResult {
- data: SaveConfigFile
- success: Boolean!
- errors: [String]
-}
-
-input LoadConfigFileInput {
- key: String!
- fileName: String!
-}
-
-type LoadConfigFile {
- fileName: String!
-}
-
-type LoadConfigFileResult {
- data: LoadConfigFile
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/dhcp_server.graphql b/src/services/api/graphql/graphql/schema/dhcp_server.graphql
deleted file mode 100644
index 345c349ac..000000000
--- a/src/services/api/graphql/graphql/schema/dhcp_server.graphql
+++ /dev/null
@@ -1,36 +0,0 @@
-input DhcpServerConfigInput {
- key: String!
- sharedNetworkName: String
- subnet: String
- defaultRouter: String
- nameServer: String
- domainName: String
- lease: Int
- range: Int
- start: String
- stop: String
- dnsForwardingAllowFrom: String
- dnsForwardingCacheSize: Int
- dnsForwardingListenAddress: String
-}
-
-type DhcpServerConfig {
- sharedNetworkName: String
- subnet: String
- defaultRouter: String
- nameServer: String
- domainName: String
- lease: Int
- range: Int
- start: String
- stop: String
- dnsForwardingAllowFrom: String
- dnsForwardingCacheSize: Int
- dnsForwardingListenAddress: String
-}
-
-type CreateDhcpServerResult {
- data: DhcpServerConfig
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/firewall_group.graphql b/src/services/api/graphql/graphql/schema/firewall_group.graphql
deleted file mode 100644
index 9454d2997..000000000
--- a/src/services/api/graphql/graphql/schema/firewall_group.graphql
+++ /dev/null
@@ -1,101 +0,0 @@
-input CreateFirewallAddressGroupInput {
- key: String!
- name: String!
- address: [String]
-}
-
-type CreateFirewallAddressGroup {
- name: String!
- address: [String]
-}
-
-type CreateFirewallAddressGroupResult {
- data: CreateFirewallAddressGroup
- success: Boolean!
- errors: [String]
-}
-
-input UpdateFirewallAddressGroupMembersInput {
- key: String!
- name: String!
- address: [String!]!
-}
-
-type UpdateFirewallAddressGroupMembers {
- name: String!
- address: [String!]!
-}
-
-type UpdateFirewallAddressGroupMembersResult {
- data: UpdateFirewallAddressGroupMembers
- success: Boolean!
- errors: [String]
-}
-
-input RemoveFirewallAddressGroupMembersInput {
- key: String!
- name: String!
- address: [String!]!
-}
-
-type RemoveFirewallAddressGroupMembers {
- name: String!
- address: [String!]!
-}
-
-type RemoveFirewallAddressGroupMembersResult {
- data: RemoveFirewallAddressGroupMembers
- success: Boolean!
- errors: [String]
-}
-
-input CreateFirewallAddressIpv6GroupInput {
- key: String!
- name: String!
- address: [String]
-}
-
-type CreateFirewallAddressIpv6Group {
- name: String!
- address: [String]
-}
-
-type CreateFirewallAddressIpv6GroupResult {
- data: CreateFirewallAddressIpv6Group
- success: Boolean!
- errors: [String]
-}
-
-input UpdateFirewallAddressIpv6GroupMembersInput {
- key: String!
- name: String!
- address: [String!]!
-}
-
-type UpdateFirewallAddressIpv6GroupMembers {
- name: String!
- address: [String!]!
-}
-
-type UpdateFirewallAddressIpv6GroupMembersResult {
- data: UpdateFirewallAddressIpv6GroupMembers
- success: Boolean!
- errors: [String]
-}
-
-input RemoveFirewallAddressIpv6GroupMembersInput {
- key: String!
- name: String!
- address: [String!]!
-}
-
-type RemoveFirewallAddressIpv6GroupMembers {
- name: String!
- address: [String!]!
-}
-
-type RemoveFirewallAddressIpv6GroupMembersResult {
- data: RemoveFirewallAddressIpv6GroupMembers
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/image.graphql b/src/services/api/graphql/graphql/schema/image.graphql
deleted file mode 100644
index 485033875..000000000
--- a/src/services/api/graphql/graphql/schema/image.graphql
+++ /dev/null
@@ -1,31 +0,0 @@
-input AddSystemImageInput {
- key: String!
- location: String!
-}
-
-type AddSystemImage {
- location: String
- result: String
-}
-
-type AddSystemImageResult {
- data: AddSystemImage
- success: Boolean!
- errors: [String]
-}
-
-input DeleteSystemImageInput {
- key: String!
- name: String!
-}
-
-type DeleteSystemImage {
- name: String
- result: String
-}
-
-type DeleteSystemImageResult {
- data: DeleteSystemImage
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/interface_ethernet.graphql b/src/services/api/graphql/graphql/schema/interface_ethernet.graphql
deleted file mode 100644
index 8a17d919f..000000000
--- a/src/services/api/graphql/graphql/schema/interface_ethernet.graphql
+++ /dev/null
@@ -1,19 +0,0 @@
-input InterfaceEthernetConfigInput {
- key: String!
- interface: String
- address: String
- replace: Boolean = true
- description: String
-}
-
-type InterfaceEthernetConfig {
- interface: String
- address: String
- description: String
-}
-
-type CreateInterfaceEthernetResult {
- data: InterfaceEthernetConfig
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql
index 624be2620..62b0d30bb 100644
--- a/src/services/api/graphql/graphql/schema/schema.graphql
+++ b/src/services/api/graphql/graphql/schema/schema.graphql
@@ -3,34 +3,14 @@ schema {
mutation: Mutation
}
-directive @configure on FIELD_DEFINITION
-directive @configfile on FIELD_DEFINITION
-directive @show on FIELD_DEFINITION
-directive @showconfig on FIELD_DEFINITION
-directive @systemstatus on FIELD_DEFINITION
-directive @image on FIELD_DEFINITION
+directive @compositequery on FIELD_DEFINITION
+directive @compositemutation on FIELD_DEFINITION
+directive @configsessionquery on FIELD_DEFINITION
+directive @configsessionmutation on FIELD_DEFINITION
directive @genopquery on FIELD_DEFINITION
directive @genopmutation on FIELD_DEFINITION
scalar Generic
-type Query {
- Show(data: ShowInput) : ShowResult @show
- ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig
- SystemStatus(data: SystemStatusInput) : SystemStatusResult @systemstatus
-}
-
-type Mutation {
- CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure
- CreateInterfaceEthernet(data: InterfaceEthernetConfigInput) : CreateInterfaceEthernetResult @configure
- CreateFirewallAddressGroup(data: CreateFirewallAddressGroupInput) : CreateFirewallAddressGroupResult @configure
- UpdateFirewallAddressGroupMembers(data: UpdateFirewallAddressGroupMembersInput) : UpdateFirewallAddressGroupMembersResult @configure
- RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure
- CreateFirewallAddressIpv6Group(data: CreateFirewallAddressIpv6GroupInput) : CreateFirewallAddressIpv6GroupResult @configure
- UpdateFirewallAddressIpv6GroupMembers(data: UpdateFirewallAddressIpv6GroupMembersInput) : UpdateFirewallAddressIpv6GroupMembersResult @configure
- RemoveFirewallAddressIpv6GroupMembers(data: RemoveFirewallAddressIpv6GroupMembersInput) : RemoveFirewallAddressIpv6GroupMembersResult @configure
- SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile
- LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile
- AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @image
- DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @image
-}
+type Query
+type Mutation
diff --git a/src/services/api/graphql/graphql/schema/show.graphql b/src/services/api/graphql/graphql/schema/show.graphql
deleted file mode 100644
index 278ed536b..000000000
--- a/src/services/api/graphql/graphql/schema/show.graphql
+++ /dev/null
@@ -1,15 +0,0 @@
-input ShowInput {
- key: String!
- path: [String!]!
-}
-
-type Show {
- path: [String]
- result: String
-}
-
-type ShowResult {
- data: Show
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/show_config.graphql b/src/services/api/graphql/graphql/schema/show_config.graphql
deleted file mode 100644
index 5a1fe43da..000000000
--- a/src/services/api/graphql/graphql/schema/show_config.graphql
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
-Use 'scalar Generic' for show config output, to avoid attempts to
-JSON-serialize in case of JSON output.
-"""
-
-input ShowConfigInput {
- key: String!
- path: [String!]!
- configFormat: String
-}
-
-type ShowConfig {
- path: [String]
- result: Generic
-}
-
-type ShowConfigResult {
- data: ShowConfig
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/system_status.graphql b/src/services/api/graphql/graphql/schema/system_status.graphql
deleted file mode 100644
index be8d87535..000000000
--- a/src/services/api/graphql/graphql/schema/system_status.graphql
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
-Use 'scalar Generic' for system status output, to avoid attempts to
-JSON-serialize in case of JSON output.
-"""
-
-input SystemStatusInput {
- key: String!
-}
-
-type SystemStatus {
- result: Generic
-}
-
-type SystemStatusResult {
- data: SystemStatus
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/key_auth.py b/src/services/api/graphql/libs/key_auth.py
index f756ed6d8..2db0f7d48 100644
--- a/src/services/api/graphql/key_auth.py
+++ b/src/services/api/graphql/libs/key_auth.py
@@ -1,5 +1,5 @@
-from . import state
+from .. import state
def check_auth(key_list, key):
if not key_list:
diff --git a/src/services/api/graphql/utils/util.py b/src/services/api/graphql/libs/op_mode.py
index 073126853..6939ed5d6 100644
--- a/src/services/api/graphql/utils/util.py
+++ b/src/services/api/graphql/libs/op_mode.py
@@ -15,15 +15,14 @@
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)
@@ -74,3 +73,29 @@ def split_compound_op_mode_name(name: str, files: list):
pair = (pair[0], f[0])
return pair
return (name, '')
+
+def snake_to_pascal_case(name: str) -> str:
+ res = ''.join(map(str.title, name.split('_')))
+ return res
+
+def map_type_name(type_name: type, optional: bool = False) -> str:
+ if type_name == str:
+ return 'String!' if not optional else 'String = null'
+ if type_name == int:
+ return 'Int!' if not optional else 'Int = null'
+ if type_name == bool:
+ return 'Boolean!' if not optional else 'Boolean = false'
+ if typing.get_origin(type_name) == list:
+ if not optional:
+ return f'[{map_type_name(typing.get_args(type_name)[0])}]!'
+ return f'[{map_type_name(typing.get_args(type_name)[0])}]'
+ # typing.Optional is typing.Union[_, NoneType]
+ if (typing.get_origin(type_name) is typing.Union and
+ typing.get_args(type_name)[1] == type(None)):
+ return f'{map_type_name(typing.get_args(type_name)[0], optional=True)}'
+
+ # 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
new file mode 100644
index 000000000..2100eba7f
--- /dev/null
+++ b/src/services/api/graphql/libs/token_auth.py
@@ -0,0 +1,71 @@
+import jwt
+import uuid
+import pam
+from secrets import token_hex
+
+from .. import state
+
+def _check_passwd_pam(username: str, passwd: str) -> bool:
+ if pam.authenticate(username, passwd):
+ return True
+ return False
+
+def init_secret():
+ length = int(state.settings['app'].state.vyos_secret_len)
+ secret = token_hex(length)
+ state.settings['secret'] = secret
+
+def generate_token(user: str, passwd: str, secret: str, exp: int) -> dict:
+ if user is None or passwd is None:
+ return {}
+ if _check_passwd_pam(user, passwd):
+ app = state.settings['app']
+ try:
+ users = app.state.vyos_token_users
+ except AttributeError:
+ app.state.vyos_token_users = {}
+ users = app.state.vyos_token_users
+ user_id = uuid.uuid1().hex
+ 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']
+ }
+ token = jwt.encode(payload=payload_data, key=secret, algorithm="HS256")
+
+ users |= {user_id: user}
+ return {'token': token}
+
+def get_user_context(request):
+ context = {}
+ context['request'] = request
+ context['user'] = None
+ if 'Authorization' in request.headers:
+ auth = request.headers['Authorization']
+ scheme, token = auth.split()
+ if scheme.lower() != 'bearer':
+ return context
+
+ try:
+ secret = state.settings.get('secret')
+ payload = jwt.decode(token, secret, algorithms=["HS256"])
+ 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:
+ users = state.settings['app'].state.vyos_token_users
+ except AttributeError:
+ return context
+
+ user = users.get(user_id)
+ if user is not None:
+ context['user'] = user
+
+ return context
diff --git a/src/services/api/graphql/session/composite/system_status.py b/src/services/api/graphql/session/composite/system_status.py
index 3c1a3d45b..d809f32e3 100755
--- a/src/services/api/graphql/session/composite/system_status.py
+++ b/src/services/api/graphql/session/composite/system_status.py
@@ -23,7 +23,7 @@ import importlib.util
from vyos.defaults import directories
-from api.graphql.utils.util import load_op_mode_as_module
+from api.graphql.libs.op_mode import load_op_mode_as_module
def get_system_version() -> dict:
show_version = load_op_mode_as_module('version.py')
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 93e1c328e..0b77b1433 100644
--- a/src/services/api/graphql/session/session.py
+++ b/src/services/api/graphql/session/session.py
@@ -24,7 +24,8 @@ from vyos.defaults import directories
from vyos.template import render
from vyos.opmode import Error as OpModeError
-from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name
+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')
@@ -45,40 +46,6 @@ class Session:
except Exception:
self._op_mode_list = None
- def configure(self):
- session = self._session
- data = self._data
- func_base_name = self._name
-
- tmpl_file = f'{func_base_name}.tmpl'
- cmd_file = f'/tmp/{func_base_name}.cmds'
- tmpl_dir = directories['api_templates']
-
- try:
- render(cmd_file, tmpl_file, data, location=tmpl_dir)
- commands = []
- with open(cmd_file) as f:
- lines = f.readlines()
- for line in lines:
- commands.append(line.split())
- for cmd in commands:
- if cmd[0] == 'set':
- session.set(cmd[1:])
- elif cmd[0] == 'delete':
- session.delete(cmd[1:])
- else:
- raise ValueError('Operation must be "set" or "delete"')
- session.commit()
- except Exception as error:
- raise error
-
- def delete_path_if_childless(self, path):
- session = self._session
- config = Config(session.get_session_env())
- if not config.list_nodes(path):
- session.delete(path)
- session.commit()
-
def show_config(self):
session = self._session
data = self._data
@@ -87,14 +54,14 @@ class Session:
try:
out = session.show_config(data['path'])
if data.get('config_format', '') == 'json':
- config_tree = vyos.configtree.ConfigTree(out)
+ config_tree = ConfigTree(out)
out = json.loads(config_tree.to_json())
except Exception as error:
raise error
return out
- def save(self):
+ def save_config_file(self):
session = self._session
data = self._data
if 'file_name' not in data or not data['file_name']:
@@ -105,7 +72,7 @@ class Session:
except Exception as error:
raise error
- def load(self):
+ def load_config_file(self):
session = self._session
data = self._data
@@ -127,7 +94,7 @@ class Session:
return out
- def add(self):
+ def add_system_image(self):
session = self._session
data = self._data
@@ -138,7 +105,7 @@ class Session:
return res
- def delete(self):
+ def delete_system_image(self):
session = self._session
data = self._data
@@ -183,6 +150,8 @@ class Session:
except OpModeError as e:
raise e
+ res = normalize_output(res)
+
return res
def gen_op_mutation(self):