Skip to content

Commit

Permalink
v0.2.79: release authzero lib
Browse files Browse the repository at this point in the history
- added `authzero` standalone module
- update minor functionality in `Timer`
- added some additional utilities
  • Loading branch information
trisongz committed Mar 13, 2024
1 parent ad01438 commit 3395cad
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 120 deletions.
11 changes: 9 additions & 2 deletions lazyops/libs/authzero/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@
get_az_mtg_api,
)

from .configs import settings as az_settings
from .configs import AuthZeroSettings, settings as az_settings
from .types.auth import AuthZeroTokenAuth
from .types.user_roles import UserRole
from .types.security import (
Authorization,
APIKey,
)
from .types import errors
from .clients import (
AuthZeroOAuthClient,
AuthZeroAPIClient,
CurrentUser,
OptionalUser,
ValidUser,
get_current_user,
require_auth_role
require_auth_role,
require_roles,
require_api_key,
)
4 changes: 3 additions & 1 deletion lazyops/libs/authzero/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
OptionalUser,
ValidUser,
get_current_user,
require_auth_role
require_auth_role,
require_roles,
require_api_key,
)
44 changes: 44 additions & 0 deletions lazyops/libs/authzero/clients/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,50 @@ def validation_func(*args, **kwargs):
return
return create_function_wrapper(validation_func)

def require_roles(
roles: Union[str, List[str]],
require_all: Optional[bool] = False,
dry_run: Optional[bool] = False,
verbose: Optional[bool] = True,
):
"""
Creates a role validator wrapper
"""
if not isinstance(roles, list): roles = [roles]
def validation_func(*args, **kwargs):
"""
Validation Function
"""
current_user = extract_current_user(*args, **kwargs)
if not current_user.has_user_roles(roles, require_all = require_all):
if verbose: logger.info(f'User {current_user.user_id} does not have required roles: {roles} / {current_user.role}')
if dry_run: return
raise errors.InvalidRolesException(detail = f'User {current_user.user_id} does not have required roles: {roles}')

return create_function_wrapper(validation_func)


def require_api_key(
api_keys: Union[str, List[str]],
dry_run: Optional[bool] = False,
verbose: Optional[bool] = False,
):
"""
Creates an api key validator wrapper
"""
if not isinstance(api_keys, list): api_keys = [api_keys]
def has_api_key(*args, api_key: APIKey, **kwargs):
"""
Checks if the api key is valid
"""
if api_key not in api_keys:
if verbose: logger.info(f'`{api_key}` is not a valid api key')
if dry_run: return
raise errors.InvalidAPIKeyException(detail = f'`{api_key}` is not a valid api key')

return create_function_wrapper(has_api_key)


def get_current_user(
required: Optional[bool] = True,
roles_enabled: Optional[bool] = False,
Expand Down
21 changes: 19 additions & 2 deletions lazyops/libs/authzero/clients/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ def get_app_redirection(
Gets the app redirection
"""
if redirect.startswith('http'): return redirect

if 'docs=' in redirect:
base_url = str(self.app.url_path_for('docs').make_absolute_url(self.app_ingress)) + '#/operations'
redirect = redirect.replace('docs=', '')
Expand All @@ -240,6 +239,22 @@ def create_docs_index(self, schema: Dict[str, Any]):
self.docs_schema_index[doc_name] = schema['paths'][path][method]['operationId']


def create_openapi_source_spec(self, spec: Dict[str, Any], spec_map: Optional[Dict[str, Any]] = None):
"""
Creates the Source Spec
- Handles some custom logic for the OpenAPI Spec
Namely:
- AppResponse-Input -> AppResponse
- AppResponse-Output -> AppResponse
"""
if not spec_map: return
_spec = json.dumps(spec)
for key, value in spec_map.items():
_spec = _spec.replace(key, value)
self.source_openapi_schema = json.loads(_spec)


def mount_oauth_components(
self,
login_path: Optional[str] = '/login',
Expand All @@ -251,6 +266,8 @@ def mount_oauth_components(
enable_authorize: Optional[bool] = True,
authorize_path: Optional[str] = '/authorize',

enable_whoami: Optional[bool] = None,

include_in_schema: Optional[bool] = None,
user_class: Optional[Type['CurrentUser']] = None,
):
Expand Down Expand Up @@ -363,7 +380,7 @@ async def authorize_user(
response.set_cookie(**current_user.get_session_cookie_kwargs())
return response

if self.settings.is_local_env:
if enable_whoami or self.settings.is_local_env:
@router.get('/whoami')
async def get_whoami_for_user(
current_user: Optional[_CurrentUser] = Depends(get_current_user(required = False, user_class = user_class)),
Expand Down
5 changes: 5 additions & 0 deletions lazyops/libs/authzero/types/user_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,10 @@ def parse_role(cls, role: Union[str, int]) -> 'UserRole':
if role is None: return UserRole.ANON
return cls(UserPrivilageIntLevel[role]) if isinstance(role, int) else cls(role.upper())

def __hash__(self):
"""
Returns the hash of the user role
"""
return hash(self.value)


101 changes: 2 additions & 99 deletions lazyops/libs/fastapi_utils/types/user_roles.py
Original file line number Diff line number Diff line change
@@ -1,100 +1,3 @@

from lazyops.types.common import UpperStrEnum
from typing import Union

UserPrivilageLevel = {
'ANON': 0,

# Access Level (1-20)
'READ': 5,
'WRITE': 10,
'MODIFY': 15,
'PRIVILAGED': 20, # 'PRIVILAGED' is a user with 'READ', 'WRITE', and 'MODIFY' privilages

# User Level (30-50)
'USER': 30, # 'USER' has 'READ', 'WRITE', and 'MODIFY' privilages
'USER_API': 30, # 'USER_API' is a user with 'USER' and 'API' privilages
'USER_STAFF': 35, # 'USER_STAFF' is a user with 'USER' and 'STAFF' privilages
'USER_PRIVILAGED': 40, # 'USER_PRIVILAGED' is a user with 'USER' and 'PRIVILAGED' privilages
'USER_ADMIN': 45, # 'USER_ADMIN' is a user with 'USER' and 'ADMIN' privilages

# Internal Level (50-70)
'STAFF': 50, # 'STAFF' has 'READ', 'WRITE', and 'MODIFY' privilages
'API_CLIENT': 50, # 'API_CLIENT' is a user with 'API' privilages
'SERVICE': 50, # 'SERVICE' is a user with 'API' privilages
'SYSTEM': 60, # 'SYSTEM' has 'READ', 'WRITE', and 'MODIFY' privilages

# Admin Level (100+)
'ADMIN': 100, # 'ADMIN' has 'READ', 'WRITE', and 'MODIFY' privilages
'SYSTEM_ADMIN': 150,
'SUPER_ADMIN': 1000,
}

class UserRole(UpperStrEnum):
ANON = 'ANON'

# Access Level (1-20)
READ = 'READ'
WRITE = 'WRITE'
MODIFY = 'MODIFY'
PRIVILAGED = 'PRIVILAGED'

# User Level (30-50)
USER = 'USER'
USER_API = 'USER_API'
USER_STAFF = 'USER_STAFF'
USER_PRIVILAGED = 'USER_PRIVILAGED'
USER_ADMIN = 'USER_ADMIN'

# Internal Level (50-70)
STAFF = 'STAFF'
SYSTEM = 'SYSTEM'
SERVICE = 'SERVICE'
API_CLIENT = 'API_CLIENT'

# Admin Level (100+)
ADMIN = 'ADMIN'
SYSTEM_ADMIN = 'SYSTEM_ADMIN'
SUPER_ADMIN = 'SUPER_ADMIN'

@property
def privilage_level(self) -> int:
"""
Returns the privilage level of the user role
- Can be subclassed to return a different value
"""
return UserPrivilageLevel[self.value]

def __lt__(self, other: Union[int, 'UserRole']) -> bool:
"""
Returns True if the user role is less than the other
"""
if isinstance(other, int):
return self.privilage_level < other
return self.privilage_level < other.privilage_level

def __le__(self, other: Union[int, 'UserRole']) -> bool:
"""
Returns True if the user role is less than or equal to the other
"""
if isinstance(other, int):
return self.privilage_level <= other
return self.privilage_level <= other.privilage_level

def __gt__(self, other: Union[int, 'UserRole']) -> bool:
"""
Returns True if the user role is greater than the other
"""
if isinstance(other, int):
return self.privilage_level > other
return self.privilage_level > other.privilage_level

def __ge__(self, other: Union[int, 'UserRole']) -> bool:
"""
Returns True if the user role is greater than or equal to the other
"""
if isinstance(other, int):
return self.privilage_level >= other
return self.privilage_level >= other.privilage_level


# We are going to use the `UserRole` that's defined in the authzero library
from lazyops.libs.authzero.types.user_roles import UserRole
14 changes: 14 additions & 0 deletions lazyops/libs/slack/types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from pydantic import model_validator
from lazyops.types import BaseModel, Field
from typing import Optional, Dict, Any, Union, List

Expand Down Expand Up @@ -38,4 +39,17 @@ class SlackPayload(BaseModel):
response_url: Optional[str] = None
trigger_id: Optional[str] = None
api_app_id: Optional[str] = None

ccommand: Optional[str] = None # A mutable copy of the command
ctext: Optional[str] = None # A mutable copy of the text

@model_validator(mode = 'after')
def set_mutable_context(self):
"""
Sets the ctext
"""
if self.text is not None: self.ctext = self.text
if self.command is not None: self.ccommand = self.command
return self


72 changes: 57 additions & 15 deletions lazyops/utils/times.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,18 @@ def __init__(
# so that it won't be assumed to be empty
self['_start'] = self.start

def dformat(
self,
@classmethod
def dformat_duration(
cls,
duration: float,
pretty: bool = None,
short: int = None,
include_ms: bool = None,
pretty: bool = True,
short: int = 0,
include_ms: bool = False,
as_int: bool = False,
) -> Dict[str, Union[float, int]]: # sourcery skip: low-code-quality
"""
Formats a duration (secs) into a dict
"""
pretty = pretty if pretty is not None else self.format_pretty
short = short if short is not None else self.format_short
include_ms = include_ms if include_ms is not None else self.format_ms
if not pretty:
unit = 'secs' if short else 'seconds'
value = int(duration) if as_int else duration
Expand Down Expand Up @@ -128,6 +126,54 @@ def dformat(
milliseconds = int(duration * 1000)
data[unit] = milliseconds
return data

@classmethod
def pformat_duration(
cls,
duration: float,
pretty: bool = True,
short: int = 0,
include_ms: bool = False,
) -> str: # sourcery skip: low-code-quality
"""
Formats a duration (secs) into a string
535003.0 -> 5 days, 5 hours, 50 minutes, 3 seconds
3593.0 -> 59 minutes, 53 seconds
"""
data = cls.dformat_duration(
duration = duration,
pretty = pretty,
short = short,
include_ms = include_ms,
as_int = True
)
if not data: return '0 secs'
sep = '' if short > 1 else ' '
if short > 2: return ''.join([f'{v}{sep}{k}' for k, v in data.items()])
return ' '.join([f'{v}{sep}{k}' for k, v in data.items()]) if short else ', '.join([f'{v}{sep}{k}' for k, v in data.items()])

def dformat(
self,
duration: float,
pretty: bool = None,
short: int = None,
include_ms: bool = None,
as_int: bool = False,
) -> Dict[str, Union[float, int]]: # sourcery skip: low-code-quality
"""
Formats a duration (secs) into a dict
"""
pretty = pretty if pretty is not None else self.format_pretty
short = short if short is not None else self.format_short
include_ms = include_ms if include_ms is not None else self.format_ms
return self.dformat_duration(
duration,
pretty = pretty,
short = short,
include_ms = include_ms,
as_int = as_int,
)

def pformat(
self,
Expand All @@ -145,17 +191,13 @@ def pformat(
pretty = pretty if pretty is not None else self.format_pretty
short = short if short is not None else self.format_short
include_ms = include_ms if include_ms is not None else self.format_ms
data = self.dformat(
duration = duration,
return self.pformat_duration(
duration,
pretty = pretty,
short = short,
include_ms = include_ms,
as_int = True
)
if not data: return '0 secs'
sep = '' if short > 1 else ' '
if short > 2: return ''.join([f'{v}{sep}{k}' for k, v in data.items()])
return ' '.join([f'{v}{sep}{k}' for k, v in data.items()]) if short else ', '.join([f'{v}{sep}{k}' for k, v in data.items()])


def checkpoint(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion lazyops/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION = '0.2.78'
VERSION = '0.2.79'
5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
'builder': [
'pyyaml',
'typer',
],
'authzero': [
'niquests',
'tldextract',
'fastapi',
]
}
args = {
Expand Down

0 comments on commit 3395cad

Please sign in to comment.