Skip to content

Commit

Permalink
[vdk-plugins] vdk-control-api-auth: Add api-token flow
Browse files Browse the repository at this point in the history
As part of the ongoing work to extract the authentication login of
Versatile Data Kit into a stand-alone library and make it available
to all components (core and plugins) to use, we need to ensure that
the interface surface is as generic as possible.

This change introduces an`Authentication` class, which will act as
the entry point for using the library and authenticating.

Additionally, the api token authentication flow from vdk-control-cli
is also adapted and added to vdk-control-api-auth.

Testing Done: Unit tests.

Signed-off-by: Andon Andonov <[email protected]>
  • Loading branch information
doks5 committed May 3, 2022
1 parent 6c5b658 commit feade87
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 1 deletion.
24 changes: 23 additions & 1 deletion projects/vdk-plugins/vdk-control-api-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,29 @@ utilities used by vdk-control-cli and other plugins.
## Usage

This is a library plugin, not a runnable plugin, and it is intended to be
used as a dependency for other plugins which need to authenticate users.
used as a dependency for other plugins, which need to authenticate users.

To use the library within a plugin or another Versatile Data Kit component,
just import the `Authentication` class, and create an instance of it. The
different authentication flows require different parameters to be specified.

Once everything is done, in order to authenticate, call `.authenticate()` on
the `Authentication` instance.

Example Usage:
```python
from vdk.plugin.control_api_auth.authentication import Authentication

auth = Authentication(
token="<oauth-api-token>",
authorization_url="https://some-authorization-endpoint",
auth_type="api-token",
)

auth.authenticate() # authenticate

auth.read_access_token() # fetch the cached access token
```

## Build and testing

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright 2021 VMware, Inc.
# SPDX-License-Identifier: Apache-2.0
from vdk.plugin.control_api_auth.auth_config import InMemAuthConfiguration
from vdk.plugin.control_api_auth.auth_exception import VDKAuthException
from vdk.plugin.control_api_auth.base_auth import BaseAuth
from vdk.plugin.control_api_auth.login_types import LoginTypes


class Authentication:
"""Main class used for authentication."""

def __init__(
self,
username: str = None,
password: str = None,
client_id: str = None,
client_secret: str = None,
token: str = None,
authorization_url: str = None,
auth_type: str = None,
cache_locally: bool = False,
):
"""
:param username: A user's username in case basic authentication is used.
:param password: A user's password in case basic authentication is used.
:param client_id:
The client identifier;
See https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
:param client_secret:
The client identifier;
See https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
:param token:
OAuth api token or a refresh token as specified in
https://datatracker.ietf.org/doc/html/rfc6749#section-1.5 and used
to obtain access token from the authorization server.
:param authorization_url:
The URL which exchanges token for access token.
:param auth_type:
What type of authentication should be used (e.g., refresh_token,
basic_auth, etc.).
:param cache_locally:
A flag, indicating if credentials should be cached locally (in a
file).
"""
self._username = username
self._password = password
self._client_id = client_id
self._client_secret = client_secret
self._token = token
self._auth_url = authorization_url
self._auth_type = auth_type
# Check if credentials should be cached on the local filesystem
if cache_locally:
self._auth = BaseAuth()
else:
self._auth = BaseAuth(conf=InMemAuthConfiguration())

def authenticate(self) -> None:
if not self._auth_type:
raise VDKAuthException(
what="Unable to log in.",
why="auth_type was not specified.",
consequence="Subsequent requests to Control Service will not "
" be authenticated.",
countermeasure="Specify what type of authentication is to be " "used.",
)
if not self._auth_url:
raise VDKAuthException(
what="Unable to log in.",
why="auth_url was not specified.",
consequence="Authentication is not possible. All subsequent "
"requests to Control Service will not be authenticated.",
countermeasure="Provide a valid authorization url.",
)

if self._auth_type == LoginTypes.API_TOKEN.value:
self.__authenticate_with_api_token()
else:
raise VDKAuthException(
what="Unexpected authentication type.",
why=f"Unknown auth_type {self._auth_type} was used.",
consequence="Authentication is not possible.",
countermeasure="Provide a valid auth_type.",
)

def read_access_token(self):
"""Read access token from cache."""
return self._auth.read_access_token()

def __authenticate_with_api_token(self):
"""Authenticate by providing only a OAuth2 API token."""
self._auth.update_api_token_authorization_url(
api_token_authorization_url=self._auth_url
)
self._auth.update_api_token(api_token=self._token)
self._auth.update_auth_type(auth_type=LoginTypes.API_TOKEN.value)
self._auth.acquire_and_cache_access_token()

# NOTE: Implementation to be added with subsequent PR
def __authenticate_with_authorization_code(self):
"""
Authenticate with authorization code as described in
https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1
:return:
"""
raise NotImplementedError("This method is yet to be implemented.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2021 VMware, Inc.
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_httpserver.pytest_plugin import PluginHTTPServer
from test_core_auth import allow_oauthlib_insecure_transport
from test_core_auth import get_json_response_mock
from vdk.plugin.control_api_auth.auth_exception import VDKAuthException
from vdk.plugin.control_api_auth.authentication import Authentication


def test_api_token_success_authentication(httpserver: PluginHTTPServer):
allow_oauthlib_insecure_transport()
httpserver.expect_request("/foo").respond_with_json(get_json_response_mock())

auth = Authentication(
token="apitoken",
authorization_url=httpserver.url_for("/foo"),
auth_type="api-token",
)
auth.authenticate()

assert auth.read_access_token() == "axczfe12casASDCz"


def test_api_token_no_auth_url():
auth = Authentication(token="apitoken", auth_type="api-token")

with pytest.raises(VDKAuthException) as exc_info:
auth.authenticate()

raised_exception = exc_info.value
assert "auth_url was not specified" in raised_exception.message


def test_api_token_no_auth_type_specified(httpserver: PluginHTTPServer):
httpserver.expect_request("/foo").respond_with_json(get_json_response_mock())
auth = Authentication(
token="apitoken", authorization_url=httpserver.url_for("/foo")
)

with pytest.raises(VDKAuthException) as exc_info:
auth.authenticate()
raised_exception = exc_info.value

assert "auth_type was not specified" in raised_exception.message

0 comments on commit feade87

Please sign in to comment.