summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/services/api/graphql/graphql/mutations.py62
-rw-r--r--src/services/api/graphql/graphql/queries.py62
-rw-r--r--src/services/api/graphql/libs/token_auth.py29
-rwxr-xr-xsrc/services/vyos-http-api-server5
4 files changed, 116 insertions, 42 deletions
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index f0c8b438f..2778feb69 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -42,32 +42,54 @@ 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: Dict = {})'
@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':
+ # 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']
+
+ info = kwargs['info']
+ user = info.context.get('user')
+ if user is None:
+ 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
diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py
index 13eb59ae4..9c8a4f064 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -42,32 +42,54 @@ 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: Dict = {})'
@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':
+ # 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']
+
+ info = kwargs['info']
+ user = info.context.get('user')
+ if user is None:
+ 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
diff --git a/src/services/api/graphql/libs/token_auth.py b/src/services/api/graphql/libs/token_auth.py
index c53e354b1..2d63a1cc7 100644
--- a/src/services/api/graphql/libs/token_auth.py
+++ b/src/services/api/graphql/libs/token_auth.py
@@ -36,3 +36,32 @@ def generate_token(user: str, passwd: str, secret: str) -> dict:
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.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/vyos-http-api-server b/src/services/vyos-http-api-server
index 7a35546e5..840041b73 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -647,6 +647,7 @@ def reset_op(data: ResetModel):
###
def graphql_init(fast_api_app):
+ from api.graphql.libs.token_auth import get_user_context
api.graphql.state.init()
api.graphql.state.settings['app'] = app
@@ -658,9 +659,9 @@ def graphql_init(fast_api_app):
if app.state.vyos_origins:
origins = app.state.vyos_origins
- app.add_route('/graphql', CORSMiddleware(GraphQL(schema, debug=True, introspection=in_spec), allow_origins=origins, allow_methods=("GET", "POST", "OPTIONS")))
+ app.add_route('/graphql', CORSMiddleware(GraphQL(schema, context_value=get_user_context, debug=True, introspection=in_spec), allow_origins=origins, allow_methods=("GET", "POST", "OPTIONS")))
else:
- app.add_route('/graphql', GraphQL(schema, debug=True, introspection=in_spec))
+ app.add_route('/graphql', GraphQL(schema, context_value=get_user_context, debug=True, introspection=in_spec))
###