Enable the PKCE workflow for OAuth 2 authentication. #8941
parent
1195f14327
commit
13ade4c0b2
|
|
@ -48,6 +48,8 @@ and modify the values for the following parameters:
|
|||
Useful for checking AzureAD_ *wids* or *groups*, GitLab_ *owner*, *maintainer* and *reporter* claims."
|
||||
"OAUTH2_SSL_CERT_VERIFICATION", "Set this variable to False to disable SSL certificate verification for OAuth2 provider.
|
||||
This may need to set False, in case of self-signed certificates."
|
||||
"OAUTH2_CHALLENGE_METHOD", "Enable PKCE workflow. PKCE method name, only *S256* is supported"
|
||||
"OAUTH2_RESPONSE_TYPE", "Enable PKCE workflow. Mandatory with OAUTH2_CHALLENGE_METHOD, must be set to *code*"
|
||||
|
||||
Redirect URL
|
||||
============
|
||||
|
|
@ -65,12 +67,19 @@ the PostgreSQL server password.
|
|||
To accomplish this, set the configuration parameter MASTER_PASSWORD to *True*, so upon setting the master password,
|
||||
it will be used as an encryption key while storing the password. If it is False, the server password can not be stored.
|
||||
|
||||
|
||||
Login Page
|
||||
============
|
||||
==========
|
||||
|
||||
After configuration, on restart, you can see the login page with the Oauth2 login button(s).
|
||||
|
||||
.. image:: images/oauth2_login.png
|
||||
:alt: Oauth2 login
|
||||
:align: center
|
||||
|
||||
PKCE Workflow
|
||||
=============
|
||||
|
||||
Ref: https://oauth.net/2/pkce
|
||||
|
||||
To enable PKCE workflow, set the configuration parameters OAUTH2_CHALLENGE_METHOD to *S256* and OAUTH2_RESPONSE_TYPE to *code*.
|
||||
Both parameters are mandatory to enable PKCE workflow.
|
||||
|
|
|
|||
|
|
@ -109,6 +109,26 @@ class OAuth2Authentication(BaseAuthentication):
|
|||
OAuth2Authentication.oauth2_config[
|
||||
oauth2_config['OAUTH2_NAME']] = oauth2_config
|
||||
|
||||
# Build client_kwargs with defaults
|
||||
client_kwargs = {
|
||||
'scope': oauth2_config.get(
|
||||
'OAUTH2_SCOPE', 'email profile'),
|
||||
'verify': oauth2_config.get(
|
||||
'OAUTH2_SSL_CERT_VERIFICATION', True)
|
||||
}
|
||||
|
||||
# Override with PKCE parameters if provided
|
||||
if 'OAUTH2_CHALLENGE_METHOD' in oauth2_config and \
|
||||
'OAUTH2_RESPONSE_TYPE' in oauth2_config:
|
||||
# Merge PKCE kwargs with defaults
|
||||
pkce_kwargs = {
|
||||
'code_challenge_method': oauth2_config[
|
||||
'OAUTH2_CHALLENGE_METHOD'],
|
||||
'response_type': oauth2_config[
|
||||
'OAUTH2_RESPONSE_TYPE']
|
||||
}
|
||||
client_kwargs.update(pkce_kwargs)
|
||||
|
||||
OAuth2Authentication.oauth2_clients[
|
||||
oauth2_config['OAUTH2_NAME']
|
||||
] = OAuth2Authentication.oauth_obj.register(
|
||||
|
|
@ -118,10 +138,7 @@ class OAuth2Authentication(BaseAuthentication):
|
|||
access_token_url=oauth2_config['OAUTH2_TOKEN_URL'],
|
||||
authorize_url=oauth2_config['OAUTH2_AUTHORIZATION_URL'],
|
||||
api_base_url=oauth2_config['OAUTH2_API_BASE_URL'],
|
||||
client_kwargs={'scope': oauth2_config.get(
|
||||
'OAUTH2_SCOPE', 'email profile'),
|
||||
'verify': oauth2_config.get(
|
||||
'OAUTH2_SSL_CERT_VERIFICATION', True)},
|
||||
client_kwargs=client_kwargs,
|
||||
server_metadata_url=oauth2_config.get(
|
||||
'OAUTH2_SERVER_METADATA_URL', None)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from regression.python_test_utils import test_utils as utils
|
|||
from pgadmin.authenticate.registry import AuthSourceRegistry
|
||||
from unittest.mock import patch, MagicMock
|
||||
from pgadmin.authenticate import AuthSourceManager
|
||||
from pgadmin.utils.constants import OAUTH2, LDAP, INTERNAL
|
||||
from pgadmin.utils.constants import OAUTH2, INTERNAL
|
||||
|
||||
|
||||
class Oauth2LoginMockTestCase(BaseTestGenerator):
|
||||
|
|
@ -33,18 +33,23 @@ class Oauth2LoginMockTestCase(BaseTestGenerator):
|
|||
oauth2_provider='github',
|
||||
flag=2
|
||||
)),
|
||||
('Oauth2 Authentication', dict(
|
||||
('Oauth2 Additional Claims Authentication', dict(
|
||||
auth_source=['oauth2'],
|
||||
oauth2_provider='auth-with-additional-claim-check',
|
||||
flag=3
|
||||
)),
|
||||
('Oauth2 PKCE Support', dict(
|
||||
auth_source=['oauth2'],
|
||||
oauth2_provider='keycloak-pkce',
|
||||
flag=4
|
||||
)),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""
|
||||
We need to logout the test client as we are testing
|
||||
spnego/kerberos login scenarios.
|
||||
OAuth2 login scenarios.
|
||||
"""
|
||||
cls.tester.logout()
|
||||
|
||||
|
|
@ -63,7 +68,7 @@ class Oauth2LoginMockTestCase(BaseTestGenerator):
|
|||
'https://github.com/login/oauth/authorize',
|
||||
'OAUTH2_API_BASE_URL': 'https://api.github.com/',
|
||||
'OAUTH2_USERINFO_ENDPOINT': 'user',
|
||||
'OAUTH2_SCOPE': 'email profile',
|
||||
'OAUTH2_SCOPE': 'openid email profile',
|
||||
'OAUTH2_ICON': 'fa-github',
|
||||
'OAUTH2_BUTTON_COLOR': '#3253a8',
|
||||
},
|
||||
|
|
@ -82,9 +87,30 @@ class Oauth2LoginMockTestCase(BaseTestGenerator):
|
|||
'OAUTH2_ICON': 'briefcase',
|
||||
'OAUTH2_BUTTON_COLOR': '#0000ff',
|
||||
'OAUTH2_ADDITIONAL_CLAIMS': {
|
||||
'groups': ['123','456'],
|
||||
'groups': ['123', '456'],
|
||||
'wids': ['789']
|
||||
}
|
||||
},
|
||||
{
|
||||
'OAUTH2_NAME': 'keycloak-pkce',
|
||||
'OAUTH2_DISPLAY_NAME': 'Keycloak with PKCE',
|
||||
'OAUTH2_CLIENT_ID': 'testclientid',
|
||||
'OAUTH2_CLIENT_SECRET': 'testclientsec',
|
||||
'OAUTH2_TOKEN_URL':
|
||||
'https://keycloak.org/auth/realms/TEST-REALM/protocol/'
|
||||
'openid-connect/token',
|
||||
'OAUTH2_AUTHORIZATION_URL':
|
||||
'https://keycloak.org/auth/realms/TEST-REALM/protocol/'
|
||||
'openid-connect/auth',
|
||||
'OAUTH2_API_BASE_URL':
|
||||
'https://keycloak.org/auth/realms/TEST-REALM',
|
||||
'OAUTH2_USERINFO_ENDPOINT': 'user',
|
||||
'OAUTH2_SCOPE': 'openid email profile',
|
||||
'OAUTH2_SSL_CERT_VERIFICATION': True,
|
||||
'OAUTH2_ICON': 'fa-black-tie',
|
||||
'OAUTH2_BUTTON_COLOR': '#3253a8',
|
||||
'OAUTH2_CHALLENGE_METHOD': 'S256',
|
||||
'OAUTH2_RESPONSE_TYPE': 'code',
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -101,6 +127,8 @@ class Oauth2LoginMockTestCase(BaseTestGenerator):
|
|||
self.test_oauth2_authentication()
|
||||
elif self.flag == 3:
|
||||
self.test_oauth2_authentication_with_additional_claims_success()
|
||||
elif self.flag == 4:
|
||||
self.test_oauth2_authentication_with_pkce()
|
||||
|
||||
def test_external_authentication(self):
|
||||
"""
|
||||
|
|
@ -184,6 +212,32 @@ class Oauth2LoginMockTestCase(BaseTestGenerator):
|
|||
respdata = 'Gravatar image for %s' % profile['email']
|
||||
self.assertTrue(respdata in res.data.decode('utf8'))
|
||||
|
||||
def test_oauth2_authentication_with_pkce(self):
|
||||
"""
|
||||
Ensure that when PKCE parameters are configured, they are passed
|
||||
to the OAuth client registration as part of client_kwargs, and that
|
||||
the default client_kwargs is correctly included.
|
||||
"""
|
||||
|
||||
with patch('pgadmin.authenticate.oauth2.OAuth.register') as \
|
||||
mock_register:
|
||||
from pgadmin.authenticate.oauth2 import OAuth2Authentication
|
||||
|
||||
OAuth2Authentication()
|
||||
|
||||
args, kwargs = mock_register.call_args
|
||||
client_kwargs = kwargs.get('client_kwargs', {})
|
||||
|
||||
# Check that PKCE and default client_kwargs are included
|
||||
self.assertEqual(
|
||||
client_kwargs.get('code_challenge_method'), 'S256')
|
||||
self.assertEqual(
|
||||
client_kwargs.get('response_type'), 'code')
|
||||
self.assertEqual(
|
||||
client_kwargs.get('scope'), 'openid email profile')
|
||||
self.assertEqual(
|
||||
client_kwargs.get('verify'), 'true')
|
||||
|
||||
def mock_user_profile_with_additional_claims(self):
|
||||
profile = {'email': 'oauth2@gmail.com', 'wids': ['789']}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue