Skip to content

feat: add authentication_type method to authenticators #127

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

Merged
merged 1 commit into from
Oct 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
30 changes: 20 additions & 10 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "package-lock.json|^.secrets.baseline$",
"lines": null
},
"generated_at": "2021-10-13T21:20:56Z",
"generated_at": "2021-10-15T20:17:29Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -91,6 +91,16 @@
"verified_result": null
}
],
"ibm_cloud_sdk_core/authenticators/authenticator.py": [
{
"hashed_secret": "fdee05598fdd57ff8e9ae29e92c25a04f2c52fa6",
"is_secret": false,
"is_verified": false,
"line_number": 29,
"type": "Secret Keyword",
"verified_result": null
}
],
"resources/ibm-credentials-basic.env": [
{
"hashed_secret": "5eb942810a75ebc850972a89285d570d484c89c4",
Expand Down Expand Up @@ -202,7 +212,7 @@
"hashed_secret": "37e94c31b6a756ba2afd2fe9a9765172cd79ac47",
"is_secret": false,
"is_verified": false,
"line_number": 102,
"line_number": 103,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down Expand Up @@ -246,15 +256,15 @@
"hashed_secret": "5eb942810a75ebc850972a89285d570d484c89c4",
"is_secret": false,
"is_verified": false,
"line_number": 96,
"line_number": 97,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "da2f27d2c57a0e1ed2dc3a34b4ef02faf2f7a4c2",
"is_secret": false,
"is_verified": false,
"line_number": 135,
"line_number": 136,
"type": "Hex High Entropy String",
"verified_result": null
}
Expand All @@ -274,23 +284,23 @@
"hashed_secret": "4080eeeaf54faf879b9e8d99c49a8503f7e855bb",
"is_secret": false,
"is_verified": false,
"line_number": 70,
"line_number": 73,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "37e94c31b6a756ba2afd2fe9a9765172cd79ac47",
"is_secret": false,
"is_verified": false,
"line_number": 93,
"line_number": 96,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "da2f27d2c57a0e1ed2dc3a34b4ef02faf2f7a4c2",
"is_secret": false,
"is_verified": false,
"line_number": 118,
"line_number": 121,
"type": "Hex High Entropy String",
"verified_result": null
}
Expand Down Expand Up @@ -336,23 +346,23 @@
"hashed_secret": "34a0a47a51d5bf739df0214450385e29ee7e9847",
"is_secret": false,
"is_verified": false,
"line_number": 393,
"line_number": 417,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "2863fa4b5510c46afc2bd2998dfbc0cf3d6df032",
"is_secret": false,
"is_verified": false,
"line_number": 469,
"line_number": 497,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "b9cad336062c0dc3bb30145b1a6697fccfe755a6",
"is_secret": false,
"is_verified": false,
"line_number": 530,
"line_number": 558,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
15 changes: 15 additions & 0 deletions ibm_cloud_sdk_core/authenticators/authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@

class Authenticator(ABC):
"""This interface defines the common methods and constants associated with an Authenticator implementation."""

# Constants representing the various authenticator types.
AUTHTYPE_BASIC = 'basic'
AUTHTYPE_BEARERTOKEN = 'bearerToken'
AUTHTYPE_IAM = 'iam'
AUTHTYPE_CONTAINER = 'container'
AUTHTYPE_CP4D = 'cp4d'
AUTHTYPE_NOAUTH = 'noAuth'
AUTHTYPE_UNKNOWN = 'unknown'

@abstractmethod
def authenticate(self, req: dict) -> None:
"""Perform the necessary authentication steps for the specified request.
Expand All @@ -40,3 +50,8 @@ def validate(self) -> None:
To be implemented by subclasses.
"""
pass

# pylint: disable=R0201
def authentication_type(self) -> str:
"""Returns the authenticator's type. This method should be overridden by each authenticator implementation."""
return Authenticator.AUTHTYPE_UNKNOWN
9 changes: 5 additions & 4 deletions ibm_cloud_sdk_core/authenticators/basic_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ class BasicAuthenticator(Authenticator):
Raises:
ValueError: The username or password is not specified or contains invalid characters.
"""
authentication_type = 'basic'

def __init__(self, username: str, password: str) -> None:
self.username = username
self.password = password
self.validate()
self.authorization_header = self.__construct_basic_auth_header()

def authentication_type(self) -> str:
"""Returns this authenticator's type ('basic')."""
return Authenticator.AUTHTYPE_BASIC

def validate(self) -> None:
"""Validate username and password.
Expand All @@ -61,13 +63,12 @@ def validate(self) -> None:
'The username and password shouldn\'t start or end with curly brackets or quotes. '
'Please remove any surrounding {, }, or \" characters.')


def __construct_basic_auth_header(self) -> str:
authstring = "{0}:{1}".format(self.username, self.password)
base64_authorization = base64.b64encode(authstring.encode('utf-8')).decode('utf-8')
base64_authorization = base64.b64encode(
authstring.encode('utf-8')).decode('utf-8')
return 'Basic {0}'.format(base64_authorization)


def authenticate(self, req: Request) -> None:
"""Add basic authentication information to a request.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@ class BearerTokenAuthenticator(Authenticator):
Raises:
ValueError: Bearer token is none.
"""
authentication_type = 'bearerToken'

def __init__(self, bearer_token: str) -> None:
self.bearer_token = bearer_token
self.validate()

def authentication_type(self) -> str:
"""Returns this authenticator's type ('bearertoken')."""
return Authenticator.AUTHTYPE_BEARERTOKEN

def validate(self) -> None:
"""Validate the bearer token.

Expand Down
6 changes: 5 additions & 1 deletion ibm_cloud_sdk_core/authenticators/container_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from .iam_request_based_authenticator import IAMRequestBasedAuthenticator
from ..token_managers.container_token_manager import ContainerTokenManager
from .authenticator import Authenticator


class ContainerAuthenticator(IAMRequestBasedAuthenticator):
Expand Down Expand Up @@ -62,7 +63,6 @@ class ContainerAuthenticator(IAMRequestBasedAuthenticator):
ValueError: Neither of iam_profile_name or iam_profile_idk are set,
or client_id, and/or client_secret are not valid for IAM token requests.
"""
authentication_type = 'container'

def __init__(self,
cr_token_filename: Optional[str] = None,
Expand All @@ -86,6 +86,10 @@ def __init__(self,

self.validate()

def authentication_type(self) -> str:
"""Returns this authenticator's type ('container')."""
return Authenticator.AUTHTYPE_CONTAINER

def validate(self) -> None:
"""Validates the iam_profile_name, iam_profile_id, client_id, and client_secret for IAM token requests.

Expand Down
8 changes: 6 additions & 2 deletions ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ class CloudPakForDataAuthenticator(Authenticator):
TypeError: The `disable_ssl_verification` is not a bool.
ValueError: The username, password/apikey, and/or url are not valid for CP4D token requests.
"""
authenticationdict = 'cp4d'

def __init__(self,
username: str = None,
Expand All @@ -71,6 +70,10 @@ def __init__(self,

self.validate()

def authentication_type(self) -> str:
"""Returns this authenticator's type ('cp4d')."""
return Authenticator.AUTHTYPE_CP4D

def validate(self) -> None:
"""Validate username, password, and url for token requests.

Expand All @@ -85,7 +88,8 @@ def validate(self) -> None:

if ((self.token_manager.password is None and self.token_manager.apikey is None)
or (self.token_manager.password is not None and self.token_manager.apikey is not None)):
raise ValueError('Exactly one of `apikey` or `password` must be specified.')
raise ValueError(
'Exactly one of `apikey` or `password` must be specified.')

if self.token_manager.url is None:
raise ValueError('The url shouldn\'t be None.')
Expand Down
6 changes: 5 additions & 1 deletion ibm_cloud_sdk_core/authenticators/iam_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from typing import Dict, Optional

from .authenticator import Authenticator
from .iam_request_based_authenticator import IAMRequestBasedAuthenticator
from ..token_managers.iam_token_manager import IAMTokenManager
from ..utils import has_bad_first_or_last_char
Expand Down Expand Up @@ -54,7 +55,6 @@ class IAMAuthenticator(IAMRequestBasedAuthenticator):
TypeError: The `disable_ssl_verification` is not a bool.
ValueError: The apikey, client_id, and/or client_secret are not valid for IAM token requests.
"""
authentication_type = 'iam'

def __init__(self,
apikey: str,
Expand All @@ -77,6 +77,10 @@ def __init__(self,

self.validate()

def authentication_type(self) -> str:
"""Returns this authenticator's type ('iam')."""
return Authenticator.AUTHTYPE_IAM

def validate(self) -> None:
"""Validates the apikey, client_id, and client_secret for IAM token requests.

Expand Down
5 changes: 4 additions & 1 deletion ibm_cloud_sdk_core/authenticators/no_auth_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@

class NoAuthAuthenticator(Authenticator):
"""Performs no authentication."""
authentication_type = 'noAuth'

def authentication_type(self) -> str:
"""Returns this authenticator's type ('noauth')."""
return Authenticator.AUTHTYPE_NOAUTH

def validate(self) -> None:
pass
Expand Down
23 changes: 14 additions & 9 deletions ibm_cloud_sdk_core/get_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,28 @@ def get_authenticator_from_environment(service_name: str) -> Authenticator:
def __construct_authenticator(config: dict) -> Authenticator:
# Determine the authentication type if not specified explicitly.
if config.get('AUTH_TYPE'):
auth_type = config.get('AUTH_TYPE').lower()
auth_type = config.get('AUTH_TYPE')
elif config.get('AUTHTYPE'):
auth_type = config.get('AUTHTYPE').lower()
auth_type = config.get('AUTHTYPE')
else:
# If authtype wasn't specified explicitly, then determine the default.
# If the APIKEY property is specified, then it should be IAM, otherwise Container Auth.
auth_type = 'iam' if config.get('APIKEY') else 'container'
if config.get('APIKEY'):
auth_type = Authenticator.AUTHTYPE_IAM
else:
auth_type = Authenticator.AUTHTYPE_CONTAINER

auth_type = auth_type.lower()
authenticator = None

if auth_type == 'basic':
if auth_type == Authenticator.AUTHTYPE_BASIC.lower():
authenticator = BasicAuthenticator(
username=config.get('USERNAME'),
password=config.get('PASSWORD'))
elif auth_type == 'bearertoken':
elif auth_type == Authenticator.AUTHTYPE_BEARERTOKEN.lower():
authenticator = BearerTokenAuthenticator(
bearer_token=config.get('BEARER_TOKEN'))
elif auth_type == 'container':
elif auth_type == Authenticator.AUTHTYPE_CONTAINER.lower():
authenticator = ContainerAuthenticator(
cr_token_filename=config.get('CR_TOKEN_FILENAME'),
iam_profile_name=config.get('IAM_PROFILE_NAME'),
Expand All @@ -70,14 +75,14 @@ def __construct_authenticator(config: dict) -> Authenticator:
disable_ssl_verification=config.get(
'AUTH_DISABLE_SSL', 'false').lower() == 'true',
scope=config.get('SCOPE'))
elif auth_type == 'cp4d':
elif auth_type == Authenticator.AUTHTYPE_CP4D.lower():
authenticator = CloudPakForDataAuthenticator(
username=config.get('USERNAME'),
password=config.get('PASSWORD'),
url=config.get('AUTH_URL'),
apikey=config.get('APIKEY'),
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true')
elif auth_type == 'iam' and config.get('APIKEY'):
elif auth_type == Authenticator.AUTHTYPE_IAM.lower() and config.get('APIKEY'):
authenticator = IAMAuthenticator(
apikey=config.get('APIKEY'),
url=config.get('AUTH_URL'),
Expand All @@ -86,7 +91,7 @@ def __construct_authenticator(config: dict) -> Authenticator:
disable_ssl_verification=config.get(
'AUTH_DISABLE_SSL', 'false').lower() == 'true',
scope=config.get('SCOPE'))
elif auth_type == 'noauth':
elif auth_type == Authenticator.AUTHTYPE_NOAUTH.lower():
authenticator = NoAuthAuthenticator()

return authenticator
21 changes: 21 additions & 0 deletions test/test_authenticator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# coding=utf-8
# pylint: disable=missing-docstring

from requests import Request
from ibm_cloud_sdk_core.authenticators import Authenticator


class TestAuthenticator(Authenticator):
"""A test of the Authenticator base class"""

def validate(self) -> None:
"""Simulated validate() method."""

def authenticate(self, req: Request) -> None:
"""Simulated authenticate() method."""


def test_authenticator():
authenticator = TestAuthenticator()
assert authenticator is not None
assert authenticator.authentication_type() == Authenticator.AUTHTYPE_UNKNOWN
3 changes: 2 additions & 1 deletion test/test_basic_authenticator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# pylint: disable=missing-docstring
import pytest

from ibm_cloud_sdk_core.authenticators import BasicAuthenticator
from ibm_cloud_sdk_core.authenticators import BasicAuthenticator, Authenticator


def test_basic_authenticator():
authenticator = BasicAuthenticator('my_username', 'my_password')
assert authenticator is not None
assert authenticator.authentication_type() == Authenticator.AUTHTYPE_BASIC
assert authenticator.username == 'my_username'
assert authenticator.password == 'my_password'

Expand Down
3 changes: 2 additions & 1 deletion test/test_bearer_authenticator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# pylint: disable=missing-docstring
import pytest

from ibm_cloud_sdk_core.authenticators import BearerTokenAuthenticator
from ibm_cloud_sdk_core.authenticators import BearerTokenAuthenticator, Authenticator


def test_bearer_authenticator():
authenticator = BearerTokenAuthenticator('my_bearer_token')
assert authenticator is not None
assert authenticator.authentication_type() == Authenticator.AUTHTYPE_BEARERTOKEN
assert authenticator.bearer_token == 'my_bearer_token'

authenticator.set_bearer_token('james bond')
Expand Down
Loading