-
Notifications
You must be signed in to change notification settings - Fork 21
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
feat: Add minimal client implementation for auth services #76
base: master
Are you sure you want to change the base?
Changes from 3 commits
fda4554
d5deaaf
88410de
a8a1ac7
dfb0fab
ad7e3f0
bfd9308
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
.. _api_tag_page: | ||
|
||
nisystemlink.clients.auth | ||
====================== | ||
|
||
.. autoclass:: nisystemlink.clients.auth.AuthClient | ||
:exclude-members: __init__ | ||
|
||
.. automethod:: __init__ | ||
.. automethod:: authenticate | ||
|
||
|
||
.. automodule:: nisystemlink.clients.auth.models | ||
:members: | ||
:imported-members: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
"""Example of getting workspace ID.""" | ||
|
||
from nisystemlink.clients.auth import AuthClient | ||
from nisystemlink.clients.auth.utilities import get_workspace_id | ||
from nisystemlink.clients.core import ApiException, HttpConfiguration | ||
|
||
|
||
server_url = "" # SystemLink API URL | ||
server_api_key = "" # SystemLink API key | ||
workspace_name = "" # Systemlink workspace name | ||
|
||
auth_client = AuthClient( | ||
HttpConfiguration(server_uri=server_url, api_key=server_api_key) | ||
) | ||
|
||
try: | ||
caller_info = auth_client.authenticate() | ||
workspaces_info = caller_info.workspaces | ||
workspace_id = None | ||
|
||
if workspaces_info: | ||
workspace_id = get_workspace_id( | ||
workspaces_info=workspaces_info, | ||
workspace_name=workspace_name, | ||
) | ||
|
||
if workspace_id: | ||
print(f"Workspace ID: {workspace_id}") | ||
|
||
except ApiException as exp: | ||
print(exp) | ||
|
||
except Exception as exp: | ||
print(exp) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from ._auth_client import AuthClient | ||
|
||
# flake8: noqa |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
"""Implementation of AuthClient.""" | ||
|
||
from typing import Optional | ||
|
||
from nisystemlink.clients import core | ||
from nisystemlink.clients.core._uplink._base_client import BaseClient | ||
from nisystemlink.clients.core._uplink._methods import get | ||
|
||
from . import models | ||
|
||
|
||
class AuthClient(BaseClient): | ||
"""A set of methods to access the APIs of SystemLink Auth Client.""" | ||
|
||
def __init__(self, configuration: Optional[core.HttpConfiguration] = None): | ||
"""Initialize an instance. | ||
|
||
Args: | ||
configuration: Defines the web server to connect to and information about | ||
how to connect. If not provided, an instance of | ||
:class:`JupyterHttpConfiguration <nisystemlink.clients.core.JupyterHttpConfiguration>` # noqa: W505 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
is used. | ||
|
||
Raises: | ||
ApiException: if unable to communicate with the Auth Service. | ||
""" | ||
if configuration is None: | ||
configuration = core.JupyterHttpConfiguration() | ||
|
||
super().__init__(configuration, base_path="/niauth/v1/") | ||
|
||
@get("auth") | ||
def authenticate(self) -> models.AuthInfo: | ||
Giriharan219 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Authenticates the given x-ni-api-key and returns information about the caller.""" | ||
Giriharan219 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
... |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from ._auth_models import AuthInfo, Workspace | ||
|
||
# flake8: noqa |
Giriharan219 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
"""Models utilized for Auth in SystemLink.""" | ||
|
||
from __future__ import annotations | ||
|
||
from datetime import datetime | ||
from enum import Enum | ||
from typing import Any, Dict, List, Optional | ||
|
||
from nisystemlink.clients.core._uplink._json_model import JsonModel | ||
from pydantic import Field | ||
|
||
|
||
class AuthStatement(JsonModel): | ||
"""Auth Statement information.""" | ||
|
||
actions: Optional[List[str]] = None | ||
"""A list of actions the user is allowed to perform.""" | ||
resource: Optional[List[str]] = None | ||
"""A list of resources the user is allowed to access.""" | ||
workspace: Optional[str] = Field(None, example="workspace-id") | ||
Giriharan219 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""The workspace the user is allowed to access.""" | ||
|
||
|
||
class AuthPolicy(JsonModel): | ||
"""Auth Policy information.""" | ||
|
||
statements: Optional[List[AuthStatement]] = None | ||
"""A list of statements defining the actions the user can perform on a resource in a workspace. | ||
""" | ||
|
||
|
||
class Statement(JsonModel): | ||
"""Statement information.""" | ||
|
||
actions: Optional[List[str]] = None | ||
"""A list of actions the user is allowed to perform.""" | ||
resource: Optional[List[str]] = None | ||
"""A list of resources the user is allowed to access.""" | ||
workspace: Optional[str] = Field(None, example="workspace-id") | ||
"""The workspace the user is allowed to access.""" | ||
description: Optional[str] = None | ||
"""A description for this statement.""" | ||
Giriharan219 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class Policy(JsonModel): | ||
Giriharan219 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Policy information.""" | ||
|
||
id: Optional[str] = Field(None, example="policy-id") | ||
"""The unique id.""" | ||
name: Optional[str] = Field(None, example="policy-name") | ||
"""The policies's name.""" | ||
type: Optional[str] = Field(None, example="role") | ||
"""The type of the policy.""" | ||
built_in: Optional[bool] = Field(None, alias="builtIn", example=True) | ||
"""Whether the policy is built-in.""" | ||
user_id: Optional[str] = Field(None, alias="userId", example="user-id") | ||
"""The user id.""" | ||
created: Optional[datetime] = Field(None, example="2019-12-02T15:31:45.379Z") | ||
"""The created timestamp.""" | ||
updated: Optional[datetime] = Field(None, example="2019-12-02T15:31:45.379Z") | ||
"""The last updated timestamp.""" | ||
deleted: Optional[bool] = Field(None, example=True) | ||
"""Whether the policy is deleted or not.""" | ||
properties: Optional[Dict[str, str]] = Field(None, example={"key1": "value1"}) | ||
"""A map of key value properties.""" | ||
statements: Optional[List[Statement]] = None | ||
"""A list of statements defining the actions the user can perform on a resource in a workspace. | ||
""" | ||
template_id: Optional[str] = Field( | ||
None, alias="templateId", example="policy-template-id" | ||
) | ||
""" | ||
The id of the policy template. Only set if the policy has been created based on a template and | ||
does not contain inline statements. | ||
""" | ||
workspace: Optional[str] = Field(None, example="workspace-id") | ||
""" | ||
The workspace the policy template applies to. Only set if the policy has been created based on a | ||
template and does not contain inline statements. | ||
""" | ||
|
||
|
||
class Status(Enum): | ||
"""Enumeration to represent different status of user's registration.""" | ||
|
||
PENDING = "pending" | ||
ACTIVE = "active" | ||
|
||
|
||
class User(JsonModel): | ||
"""User information.""" | ||
|
||
id: Optional[str] = Field(None, example="user-id") | ||
"""The unique id.""" | ||
first_name: Optional[str] = Field( | ||
None, alias="firstName", example="user-first-name" | ||
) | ||
"""The user's first name.""" | ||
last_name: Optional[str] = Field(None, alias="lastName", example="user-last-name") | ||
"""The user's last name.""" | ||
email: Optional[str] = Field(None, example="[email protected]") | ||
"""The user's email.""" | ||
phone: Optional[str] = Field(None, example="555-555-5555") | ||
"""The user's contact phone number.""" | ||
niua_id: Optional[str] = Field(None, alias="niuaId", example="[email protected]") | ||
"""The external id (niuaId, SID, login name).""" | ||
login: Optional[str] = None | ||
""" | ||
The login name of the user. This the "username" or equivalent entered when | ||
the user authenticates with the identity provider. | ||
""" | ||
accepted_to_s: Optional[bool] = Field(None, alias="acceptedToS", example=True) | ||
"""(deprecated) Whether the user accepted the terms of service.""" | ||
properties: Optional[Dict[str, str]] = Field(None, example={"key1": "value1"}) | ||
"""A map of key value properties.""" | ||
keywords: Optional[List[str]] = None | ||
"""A list of keywords associated with the user.""" | ||
created: Optional[datetime] = Field(None, example="2019-12-02T15:31:45.379Z") | ||
"""The created timestamp.""" | ||
updated: Optional[datetime] = Field(None, example="2019-12-02T15:31:45.379Z") | ||
"""The last updated timestamp.""" | ||
org_id: Optional[str] = Field(None, alias="orgId", example="org-id") | ||
"""The id of the organization.""" | ||
policies: Optional[List[str]] = None | ||
"""A list of policy ids to reference existing policies.""" | ||
status: Optional[Status] = Field(None, example="active") | ||
"""The status of the users' registration.""" | ||
entitlements: Optional[Any] = None | ||
"""(deprecated) Features to which the user is entitled within the application.""" | ||
|
||
|
||
class Org(JsonModel): | ||
"""User's Organization information.""" | ||
|
||
id: Optional[str] = Field(None, example="org-id") | ||
"""The unique id.""" | ||
name: Optional[str] = Field(None, example="org-name") | ||
"""The name of the organization.""" | ||
owner_id: Optional[str] = Field(None, alias="ownerId", example="user-id") | ||
"""The userId of the organization owner.""" | ||
|
||
|
||
class Workspace(JsonModel): | ||
"""Workspace information.""" | ||
|
||
id: Optional[str] = Field(None, example="workspace-id") | ||
"""The unique id.""" | ||
name: Optional[str] = Field(None, example="workspace-name") | ||
"""The workspace name.""" | ||
enabled: Optional[bool] = Field(None, example=True) | ||
"""Whether the workspace is enabled or not.""" | ||
default: Optional[bool] = Field(None, example=True) | ||
""" | ||
Whether the workspace is the default. The default workspace is used when callers omit a \ | ||
workspace id. | ||
""" | ||
|
||
|
||
class AuthInfo(JsonModel): | ||
"""Information about the authenticated caller.""" | ||
|
||
user: Optional[User] | ||
"""Details of authenticated caller.""" | ||
org: Optional[Org] | ||
"""Organization of authenticated caller.""" | ||
workspaces: Optional[List[Workspace]] | ||
"""List of workspaces the authenticated caller has access.""" | ||
policies: Optional[List[AuthPolicy]] | ||
"""List of policies for the authenticated caller.""" | ||
properties: Optional[Dict[str, str]] = Field(None, example={"key1": "value1"}) | ||
"""A map of key value properties.""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
"""Utilities for Auth Client.""" | ||
|
||
from typing import List, Union | ||
|
||
from nisystemlink.clients.auth.models import Workspace | ||
|
||
|
||
def get_workspace_id( | ||
Giriharan219 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
workspaces_info: List[Workspace], | ||
workspace_name: str, | ||
Giriharan219 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) -> Union[str, None]: | ||
Giriharan219 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Get workspace id from the list of workspace info using `workspace_name`. | ||
|
||
Args: | ||
workspaces_info (List[workspace_info]): List of workspace info. | ||
workspace_name (str): Workspace name. | ||
|
||
Returns: | ||
Union[str, None]: Workspace ID of the `workspace_name`. | ||
""" | ||
for workspace_info in workspaces_info: | ||
if workspace_info.name == workspace_name and workspace_info.id: | ||
return workspace_info.id | ||
return None |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
"""Integration tests for AuthClient.""" | ||
|
||
import pytest | ||
from nisystemlink.clients.auth import AuthClient | ||
|
||
|
||
@pytest.fixture(scope="class") | ||
def client(enterprise_config) -> AuthClient: | ||
"""Fixture to create a AuthClient instance.""" | ||
return AuthClient(enterprise_config) | ||
|
||
|
||
@pytest.mark.enterprise | ||
@pytest.mark.integration | ||
class TestAuthClient: | ||
"""A set of test methods to test SystemLink Auth API.""" | ||
|
||
def test__authenticate__succeeds(self, client: AuthClient): | ||
"""Test the case of getting caller information with SystemLink Credentials.""" | ||
response = client.authenticate() | ||
assert response is not None |
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.
Naming this
AccessControlClient
makes it intuitive and clubs the related feature across multiple services under the same client.@mure, @spanglerco, @cameronwaterman - please share your thoughts
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.
@santhoshramaraj I think that it's worth leaving this as-is (auth), then creating a secondary API specifically to expose access control functionality (creating/changing roles and/or workspace permissions management), as they are closely related, but really two different concepts.
As I mentioned in my comments, I think the auth functionality is pretty fundamental and might be worth somehow incorporating into the base class itself, but access control definitely doesn't belong there.