Skip to content
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

DOM-40791 Integrate with Domino API Proxy #154

Merged
merged 2 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All notable changes to the `python-domino` library will be documented in this fi

### Changed
* Updated packaging in installation dependency
* Added support for Domino API Proxy.

## 1.1.0

Expand Down
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ You can set up the connection by creating a new instance of `Domino`:

- *project:* A project identifier (in the form of
ownerusername/projectname).

- *api_proxy:* (Optional) Location of the Domino API proxy as host:port.
If set, this proxy will be used to intercept any Domino API requests and insert an auth token.
This is the preferred method of authentication. Alternatively, the same behavior can be achieved
by setting the `DOMINO_API_PROXY` environment variable.
ddl-olsonJD marked this conversation as resolved.
Show resolved Hide resolved

- *api_key:* (Optional) An API key to authenticate with. If not
provided, the library expects to find one in the
Expand All @@ -69,12 +74,12 @@ You can set up the connection by creating a new instance of `Domino`:

- *auth_token:* (Optional) Authentication token.

- The authentication preference should always be given to the
authentication token. If it’s not passed, the path to domino token
file takes precedence, otherwise the API key is used. If none of
these three parameters are passed, then preference is given to the
Domino token file from the corresponding environment variable, then
to the API key from the corresponding environment variable.
- The authentication preference should always be given to the API proxy.
If it’s not passed, then the authentication token takes precedence, then the path to the domino
token file, otherwise the API key is used. If none of
these four parameters are passed, then preference is given to the Api Proxy from the
corresponding environment variable, then the Domino token file from the corresponding
environment variable, then to the API key from the corresponding environment variable.

- By default, the log level is set to `INFO`. To set the log level to
`DEBUG`, set the `DOMINO_LOG_LEVEL` environment variable to `DEBUG`.
Expand Down
45 changes: 35 additions & 10 deletions domino/authentication.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import os

from requests import models
from requests.auth import AuthBase, HTTPBasicAuth

from .constants import DOMINO_TOKEN_FILE_KEY_NAME, DOMINO_USER_API_KEY_KEY_NAME
from .constants import DOMINO_TOKEN_FILE_KEY_NAME, DOMINO_USER_API_KEY_KEY_NAME, \
DOMINO_API_PROXY
from .http_request_manager import _SessionInitializer


class ProxyAuth(AuthBase, _SessionInitializer):
"""
Class for authenticating requests using the Domino API Proxy.
"""

def __init__(self, api_proxy):
self.api_proxy = api_proxy

def __call__(self, r):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we update the header here as well, stating the auth method?

return r

def __initialize__(self, session):
session.proxies.update({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe here.

'http': f'http://{self.api_proxy}',
})


class BearerAuth(AuthBase):
Expand Down Expand Up @@ -32,21 +52,25 @@ def __call__(self, r):
return r


def get_auth_by_type(api_key=None, auth_token=None, domino_token_file=None):
def get_auth_by_type(api_key=None, auth_token=None, domino_token_file=None, api_proxy=None):
"""
Return appropriate authentication object for requests.

If no authentication credential is provided, the call fails with an AssertError

Precedence in the case of multiple credentials is:
1. auth_token string
2. domino_token_file
3. api_key
4. domino_token_file_from_env
5. api_key_from_env
1. api_proxy
2. auth_token string
3. domino_token_file
4. api_key
5. api_proxy_from_env
6. domino_token_file_from_env
7. api_key_from_env
"""

if auth_token is not None:
if api_proxy is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if api_proxy: is also another option.

return ProxyAuth(api_proxy)
elif auth_token is not None:
return BearerAuth(auth_token=auth_token)
elif domino_token_file is not None:
return BearerAuth(domino_token_file=domino_token_file)
Expand All @@ -55,11 +79,12 @@ def get_auth_by_type(api_key=None, auth_token=None, domino_token_file=None):
else:
# In the case that no authentication type was passed when this method
# called, fall back to deriving the auth info from the environment.
api_proxy_from_env = os.getenv(DOMINO_API_PROXY)
api_key_from_env = os.getenv(DOMINO_USER_API_KEY_KEY_NAME)
domino_token_file_from_env = os.getenv(DOMINO_TOKEN_FILE_KEY_NAME)
if api_key_from_env or domino_token_file_from_env:
if api_key_from_env or domino_token_file_from_env or api_proxy_from_env:
return get_auth_by_type(
api_key=api_key_from_env, domino_token_file=domino_token_file_from_env
api_key=api_key_from_env, domino_token_file=domino_token_file_from_env, api_proxy=api_proxy_from_env
)
else:
# All attempts failed -- nothing to do but raise an error.
Expand Down
1 change: 1 addition & 0 deletions domino/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@
DOMINO_LOG_LEVEL_KEY_NAME = "DOMINO_LOG_LEVEL"
DOMINO_USER_NAME_KEY_NAME = "DOMINO_USER_NAME"
DOMINO_VERIFY_CERTIFICATE = "DOMINO_VERIFY_CERTIFICATE"
DOMINO_API_PROXY = "DOMINO_API_PROXY"
7 changes: 4 additions & 3 deletions domino/domino.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

class Domino:
def __init__(
self, project, api_key=None, host=None, domino_token_file=None, auth_token=None
self, project, api_key=None, host=None, domino_token_file=None, auth_token=None, api_proxy=None,
):

self._configure_logging()
Expand All @@ -48,7 +48,7 @@ def __init__(
raise

# This call sets self.request_manager
self.authenticate(api_key, auth_token, domino_token_file)
self.authenticate(api_key, auth_token, domino_token_file, api_proxy)

# Get version
self._version = self.deployment_version().get("version")
Expand Down Expand Up @@ -82,7 +82,7 @@ def _configure_logging(self):
logging.basicConfig(level=logging_level)
self._logger = logging.getLogger(__name__)

def authenticate(self, api_key=None, auth_token=None, domino_token_file=None):
def authenticate(self, api_key=None, auth_token=None, domino_token_file=None, api_proxy=None):
"""
Method to authenticate the request manager. An existing domino client object can
use this with a new token if the existing credentials expire.
Expand All @@ -92,6 +92,7 @@ def authenticate(self, api_key=None, auth_token=None, domino_token_file=None):
api_key=api_key,
auth_token=auth_token,
domino_token_file=domino_token_file,
api_proxy=api_proxy,
)
)

Expand Down
6 changes: 6 additions & 0 deletions domino/http_request_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from .constants import DOMINO_VERIFY_CERTIFICATE
from .exceptions import ReloginRequiredException

class _SessionInitializer:
def __initialize__(self, session):
raise NotImplementedError('Session initializers must be callable.')
ddl-olsonJD marked this conversation as resolved.
Show resolved Hide resolved

class _HttpRequestManager:
"""
Expand All @@ -23,6 +26,9 @@ def __init__(self, auth: AuthBase):
self.request_session = requests.Session()
self.request_session.headers["User-Agent"] = f"python-domino/{__version__}"

if isinstance(self.auth, _SessionInitializer):
self.auth.__initialize__(self.request_session)

if os.environ.get(DOMINO_VERIFY_CERTIFICATE, None) in ["false", "f", "n", "no"]:
warning = "InsecureRequestWarning: Bypassing certificate verification is strongly ill-advised"
logging.warning(warning)
Expand Down