Explorar o código

[jwt] Add verification config flag for custom JWT Authentication (#2414)

- Defaults to True.
- If set to False, then will not verify the signature of the JWT token (recommended for testing purposes only).
- Add debug level log for user ID and tenant ID.
- Check for 'user' field in token.
Harsh Gupta %!s(int64=4) %!d(string=hai) anos
pai
achega
ddbc93cc89

+ 3 - 0
desktop/conf.dist/hue.ini

@@ -362,6 +362,9 @@
     # Multiple Authentication backends are supported by specifying a comma-separated list in order of priority.
     ## api_auth=rest_framework_simplejwt.authentication.JWTAuthentication,rest_framework.authentication.SessionAuthentication
 
+    # Verify custom JWT.
+    ## verify_custom_jwt=true
+
     # Class which defines extra accessor methods for User objects.
     ## user_aug=desktop.auth.backend.DefaultUserAugmentor
 

+ 3 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -366,6 +366,9 @@
     # Multiple Authentication backends are supported by specifying a comma-separated list in order of priority.
     ## api_auth=rest_framework_simplejwt.authentication.JWTAuthentication,rest_framework.authentication.SessionAuthentication
 
+    # Verify custom JWT.
+    ## verify_custom_jwt=true
+
     # Class which defines extra accessor methods for User objects.
     ## user_aug=desktop.auth.backend.DefaultUserAugmentor
 

+ 14 - 3
desktop/core/src/desktop/auth/api_authentications.py

@@ -21,7 +21,7 @@ import jwt
 from rest_framework import authentication, exceptions
 
 from desktop.auth.backend import find_or_create_user, ensure_has_a_group, rewrite_user
-from desktop.conf import ENABLE_ORGANIZATIONS
+from desktop.conf import ENABLE_ORGANIZATIONS, AUTH
 
 from useradmin.models import User
 
@@ -50,15 +50,26 @@ class JwtAuthentication(authentication.BaseAuthentication):
     LOG.debug('JwtAuthentication: got access token %s' % access_token)
 
     try:
-      payload = jwt.decode(access_token, 'secret', algorithms=["HS256"])
+      payload = jwt.decode(
+        access_token,
+        'secret',
+        algorithms=["RS256"],
+        verify=AUTH.VERIFY_CUSTOM_JWT.get()
+      )
     except jwt.DecodeError:
       raise exceptions.AuthenticationFailed('JwtAuthentication: Invalid token')
     except jwt.ExpiredSignatureError:
       raise exceptions.AuthenticationFailed('JwtAuthentication: Token expired')
     except Exception as e:
       raise exceptions.AuthenticationFailed(e)
+    
+    if payload.get('user') is None:
+      LOG.debug('JwtAuthentication: no user ID in token')
+      return None
+
+    LOG.debug('JwtAuthentication: got user ID %s and tenant ID %s' % (payload.get('user'), payload.get('tenantId')))
 
-    user = find_or_create_user(payload['userId'], is_superuser=False)
+    user = find_or_create_user(payload.get('user'), is_superuser=False)
     ensure_has_a_group(user)
     user = rewrite_user(user)
 

+ 20 - 5
desktop/core/src/desktop/auth/api_authentications_tests.py

@@ -22,6 +22,7 @@ from nose.tools import assert_equal, assert_true, assert_false, assert_raises
 from desktop.auth.backend import rewrite_user
 from desktop.auth.api_authentications import JwtAuthentication
 from desktop.lib.django_test_util import make_logged_in_client
+from desktop.conf import AUTH
 
 from rest_framework import exceptions
 
@@ -36,8 +37,8 @@ else:
 
 class TestJwtAuthentication():
   def setUp(self):
-    self.client = make_logged_in_client(username="test", groupname="default", recreate=True, is_superuser=False)
-    self.user = rewrite_user(User.objects.get(username="test"))
+    self.client = make_logged_in_client(username="test_user", groupname="default", recreate=True, is_superuser=False)
+    self.user = rewrite_user(User.objects.get(username="test_user"))
 
     self.sample_token = "eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsid29ya2xvYWQtYXBwIiwicmFuZ2VyIl0sImV4cCI6MTYyNjI1Njg5MywiaWF0IjoxNjI2MjU2NTkzLCJpc3MiOiJDbG91ZGVyYTEiLCJqdGkiOiJpZDEiLCJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJ1c2VyIjoidGVzdF91c2VyIn0.jvyVDxbWTAik0jbdUcIc9ZANNrJZUCWH-Pg7FloRhg0ZYAETd_AO3p5v_ppoMmVcPD2xBSrngA5J3_A_zPBvQ_hdDlpb0_-mCCJfGhC5tju4bI9EE9Akdn2FrrsqrvQQ8cPyGsIlvoIxrK1De4f74MmUaxfN7Hrrcue1PTY4u4IB9cWQqV9vIcX99Od5PUaNekLIee-I8gweqvfGEEsW7qWUM63nh59_TOB3LLq-YcEuaX1h_oiTATeCssjk_ee9RrJGLNyKmC0WJ4UrEWn8a_T3bwCy8CMe0zV5PSuuvPHy0FvnTo2il5SDjGimxKcbpgNiJdfblslu6i35DlfiWg"
     self.request = MagicMock(
@@ -51,7 +52,7 @@ class TestJwtAuthentication():
     with patch('desktop.auth.api_authentications.jwt.decode') as jwt_decode:
 
       jwt_decode.return_value = {
-        "userId": "test"
+        "user": "test_user"
       }
 
       user, token = JwtAuthentication().authenticate(request=self.request)
@@ -65,7 +66,7 @@ class TestJwtAuthentication():
     with patch('desktop.auth.api_authentications.jwt.decode') as jwt_decode:
 
       jwt_decode.return_value = {
-        "userId": "test_new_user"
+        "user": "test_new_user"
       }
 
       assert_false(User.objects.filter(username="test_new_user").exists())
@@ -93,9 +94,23 @@ class TestJwtAuthentication():
   def test_check_user_token_storage(self):
     with patch('desktop.auth.api_authentications.jwt.decode') as jwt_decode:
       jwt_decode.return_value = {
-        "userId": "test"
+        "user": "test_user"
       }
       user, token = JwtAuthentication().authenticate(request=self.request)
 
       assert_true('jwt_access_token' in user.profile.data)
       assert_equal(user.profile.data['jwt_access_token'], self.sample_token)
+
+  def test_check_token_verification_flag(self):
+
+    # When verification flag is True for old sample token
+    assert_raises(exceptions.AuthenticationFailed, JwtAuthentication().authenticate, self.request)
+
+    # When verification flag is False
+    reset = AUTH.VERIFY_CUSTOM_JWT.set_for_testing(False)
+    try:
+      user, token = JwtAuthentication().authenticate(request=self.request)
+
+      assert_equal(user, self.user)
+    finally:
+      reset()

+ 6 - 0
desktop/core/src/desktop/conf.py

@@ -1088,6 +1088,12 @@ AUTH = ConfigSection(
           "Custom Authentication backends for the REST API." +
           "Multiple Authentication backends are supported by specifying a comma-separated list in order of priority.")
     ),
+    VERIFY_CUSTOM_JWT=Config(
+        key="verify_custom_jwt",
+        default=True,
+        type=coerce_bool,
+        help=_("Verify custom JWT.")
+    ),
     USER_AUGMENTOR=Config("user_augmentor",
                    default="desktop.auth.backend.DefaultUserAugmentor",
                    help=_("Class which defines extra accessor methods for User objects.")),