summaryrefslogtreecommitdiff
path: root/src/services/api/graphql
diff options
context:
space:
mode:
authorJohn Estabrook <jestabro@vyos.io>2022-10-23 11:08:06 -0500
committerJohn Estabrook <jestabro@vyos.io>2022-10-25 10:35:48 -0500
commit28676844e3f4317786e457fcd8651939a05c88ff (patch)
tree1031b5079f076584a9c9a40833d4452bf33106d4 /src/services/api/graphql
parentaf56ddf4615974c6b5f5886520d6abb0781cea80 (diff)
downloadvyos-1x-28676844e3f4317786e457fcd8651939a05c88ff.tar.gz
vyos-1x-28676844e3f4317786e457fcd8651939a05c88ff.zip
graphql: T4574: add context to read token in queries/mutations
Diffstat (limited to 'src/services/api/graphql')
-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
3 files changed, 113 insertions, 40 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