-
Notifications
You must be signed in to change notification settings - Fork 190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable OAuth2 authentication. #646
base: master
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for opal-docs canceled.
|
Hi @ojecborec, Thanks again for the help & contribution.
|
Following PR is enabling OAuth2 third party authentication for both server and client. Client would send access token generated by Client Credentials grant and server would validate this token by either calling introspect endpoint or reading JWT signature. I'll have a look at pre-commit checks and failing tests. |
If I understand correctly some pre-commit checks are failing on code like this
Why is |
Can you help me understand how is this test failure related to code I'm submitting (I'm not a Python developer) ?
|
@ojecborec I'll help with Python you'll help with OAuth2 :) (As I'm not an expert, although willing to make myself more familiar of course). I still don't understand what's the missing use case for OPAL (from user's high level perspective) - Do you want opal-client to authenticate to opal-server using a specific Google account? (OAuth2 is usually used for authenticating web applications on behalf of actual people, isn't it?). Regarding pre-commit - it enforces the "Black" formatting. You can install Regarding the tests - Yeah seems like the stack trace is misleading. I believe the test raised an exception before
|
@ojecborec Apologies - I see now similar failures on other PRs, so those probably don't have to do with you changes. |
@ojecborec - You can rebase onto master as tests should be fixed there now |
Company software usually depends on some kind of authentication. You cannot utilize resources without providing username/password or some kind of token. OPAL server generates JWTs which do the job but often you already have dedicated OAuth2/OpenID service that your existing applications integrate with. This patch allows both OPAL server and client leverage this dedicated OAuth2/OpenID service for either token generation or validation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @ojecborec - Here's a few comments on a first look mainly regarding the Authenticator interface.
Should still dive further into reviewing the core implementation
return self._delegate().enabled | ||
|
||
async def authenticate(self, headers): | ||
if hasattr(self._delegate(), "authenticate") and callable( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not a great way to achieve polymorphism.
What you probably want is to have an Authenticator interface/abstract class.
Have authenticate
& enabled
simply raise NotImplementedError()
(or use @abc.abstractmethod
, although we don't really use it in OPAL's code base).
Than have different classes inherit Authenticator
and have those directly implement those methods. (No need to access them through delegation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've refactored Authenticator
as an interface.
|
||
class _ClientAuthenticator(Authenticator): | ||
def __init__(self): | ||
if opal_common_config.AUTH_TYPE == "oauth2": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having this class "proxy" the actual implementation according to the configured AUTH_TYPE
- you probably want to use the factory
design pattern.
Have both CachedOAuth2Authenticator
& _JWTAuthenticator
inherit from Authenticator
and implement its declared methods.
Then have another class / function (e.g AuthenticatorFactory.create
) with return type Authenticator
instantiate the right subclass according to configured auth type (or other relevant arguments).
Basically what you're doing here is having a class "deciding what it is" within its constructor, which again doesn't make use of Python's native polymorphism / OOP support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modified code to use AuthenticatorFactory
.
|
||
class Authenticator: | ||
@property | ||
def enabled(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Methods in this class should have a specified return type, also their arguments should be typed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
self._delegate = delegate | ||
|
||
@property | ||
def enabled(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to have it again if it's the same as in the _OAuth2Authenticator
super class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
@@ -159,6 +159,64 @@ class OpalCommonConfig(Confi): | |||
[".rego"], | |||
description="List of extensions to serve as policy modules", | |||
) | |||
AUTH_TYPE = confi.str("AUTH_TYPE", None, description="Authentication type.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List available types in description
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amended description.
|
||
class _ServerAuthenticator(Authenticator): | ||
def __init__(self): | ||
if opal_common_config.AUTH_TYPE == "oauth2": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comments as in _ClientAuthenticator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored.
Seems like 2 security/snyk (permit) tests have failed but I don't have access to read details. |
I've updated The
The
Branch |
@roekatz |
@roekatz Is there anything I can do to get this PR merged? |
@roekatz have your comments been resolved? Can you let me know why security tests are failing. Thank you. |
@Abiji-2020 Would you mind checking this PR? |
Hey @ojecborec, I see this PR has been open for quite some time, and I want to try helping you to close it ASAP. There are a lot of commits and changes in this PR - some of them are probably unrelated. |
Hi @danyi1212. Sorry for not replying earlier. This has been now |
Hi @danyi1212. Have you made any progress in reviewing this PR? |
Hi @danyi1212. Have you had any spare time to review this PR? |
# Conflicts: # packages/opal-client/opal_client/config.py # packages/opal-server/opal_server/config.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-client/opal_client/data/updater.py # packages/opal-client/opal_client/policy/fetcher.py # packages/opal-client/opal_client/policy/updater.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-common/opal_common/fetcher/providers/http_fetch_provider.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-client/opal_client/data/updater.py # packages/opal-client/opal_client/policy/fetcher.py # packages/opal-client/opal_client/policy/updater.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-common/opal_common/fetcher/providers/http_fetch_provider.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/jwk.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/data/api.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-client/opal_client/data/updater.py # packages/opal-client/opal_client/policy/fetcher.py # packages/opal-client/opal_client/policy/updater.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-common/opal_common/fetcher/providers/http_fetch_provider.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/jwk.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/data/api.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-client/opal_client/data/updater.py # packages/opal-client/opal_client/policy/fetcher.py # packages/opal-client/opal_client/policy/updater.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-common/opal_common/fetcher/providers/http_fetch_provider.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/jwk.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/data/api.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-client/opal_client/data/updater.py # packages/opal-client/opal_client/policy/fetcher.py # packages/opal-client/opal_client/policy/updater.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-common/opal_common/fetcher/providers/http_fetch_provider.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/jwk.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/data/api.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-client/opal_client/data/updater.py # packages/opal-client/opal_client/policy/fetcher.py # packages/opal-client/opal_client/policy/updater.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-common/opal_common/fetcher/providers/http_fetch_provider.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/jwk.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/data/api.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-client/opal_client/data/updater.py # packages/opal-client/opal_client/policy/fetcher.py # packages/opal-client/opal_client/policy/updater.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-common/opal_common/fetcher/providers/http_fetch_provider.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/jwk.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/data/api.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-client/opal_client/data/updater.py # packages/opal-client/opal_client/policy/fetcher.py # packages/opal-client/opal_client/policy/updater.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-common/opal_common/fetcher/providers/http_fetch_provider.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/server.py # packages/requires.txt
# Conflicts: # packages/opal-client/opal_client/client.py # packages/opal-common/opal_common/authentication/authenticator.py # packages/opal-common/opal_common/authentication/jwk.py # packages/opal-common/opal_common/authentication/oauth2.py # packages/opal-common/opal_common/config.py # packages/opal-server/opal_server/authentication/authenticator.py # packages/opal-server/opal_server/data/api.py # packages/opal-server/opal_server/server.py # packages/requires.txt
I've created the new branch to enable reviewing changes for #602.