Skip to content

Commit

Permalink
Merge branch '3.27.0_staged' into Status_Info_Model
Browse files Browse the repository at this point in the history
  • Loading branch information
TheBurchLog authored Jul 25, 2024
2 parents 243ff22 + 7b5e0f7 commit 65ebb6a
Show file tree
Hide file tree
Showing 15 changed files with 768 additions and 360 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ Brewtils Changelog
TBD

- Formalized Status Info model and added helper features to track the history of the status changes.

- Added support models for tracking primary replication
- New Models for User, UserToken, Role, and AliasUserMap
- Must upgrade to a minimum version of Beer Garden 3.27.0 to support new authentication models. If authentication is not enabled, upgrade
is not required.
- Removed 2.0 Legacy support for Principle and LegacyRole models
- Fixed bug in SystemClient to properly assign requester field from parent request

3.26.4
------
Expand Down
1 change: 0 additions & 1 deletion brewtils/auto_decorator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from inspect import Parameter as InspectParameter # noqa
from inspect import signature


from brewtils.models import Command, Parameter


Expand Down
260 changes: 194 additions & 66 deletions brewtils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
"Event",
"Events",
"Queue",
"Principal",
"LegacyRole",
"RefreshToken",
"UserToken",
"Job",
"RequestFile",
"File",
Expand All @@ -39,8 +37,11 @@
"Garden",
"Operation",
"Resolvable",
"Role",
"User",
"Subscriber",
"Topic",
"Replication",
]


Expand Down Expand Up @@ -94,15 +95,24 @@ class Events(Enum):
USER_UPDATED = 44
USERS_IMPORTED = 45
ROLE_UPDATED = 46
ROLES_IMPORTED = 47
ROLE_DELETED = 47
COMMAND_PUBLISHING_BLOCKLIST_SYNC = 48
COMMAND_PUBLISHING_BLOCKLIST_REMOVE = 49
COMMAND_PUBLISHING_BLOCKLIST_UPDATE = 50
TOPIC_CREATED = 54
TOPIC_UPDATED = 55
TOPIC_REMOVED = 56
REPLICATION_CREATED = 57
REPLICATION_UPDATED = 58

# Next: 57
# Next: 59


class Permissions(Enum):
READ_ONLY = 1
OPERATOR = 2
PLUGIN_ADMIN = 3
GARDEN_ADMIN = 4


class BaseModel(object):
Expand Down Expand Up @@ -1159,81 +1169,32 @@ def __repr__(self):
return "<Queue: name=%s, size=%s>" % (self.name, self.size)


class Principal(BaseModel):
schema = "PrincipalSchema"
class UserToken(BaseModel):
schema = "UserTokenSchema"

def __init__(
self,
id=None, # noqa # shadows built-in
uuid=None,
issued_at=None,
expires_at=None,
username=None,
roles=None,
permissions=None,
preferences=None,
metadata=None,
):
self.id = id
self.uuid = uuid
self.issued_at = issued_at
self.expires_at = expires_at
self.username = username
self.roles = roles
self.permissions = permissions
self.preferences = preferences
self.metadata = metadata

def __str__(self):
return "%s" % self.username

def __repr__(self):
return "<Principal: username=%s, roles=%s, permissions=%s>" % (
return "<UserToken: uuid=%s, issued_at=%s, expires_at=%s, username=%s>" % (
self.uuid,
self.issued_at,
self.expires_at,
self.username,
self.roles,
self.permissions,
)


class LegacyRole(BaseModel):
schema = "LegacyRoleSchema"

def __init__(
self,
id=None, # noqa # shadows built-in
name=None,
description=None,
permissions=None,
):
self.id = id
self.name = name
self.description = description
self.permissions = permissions

def __str__(self):
return "%s" % self.name

def __repr__(self):
return "<LegacyRole: name=%s, permissions=%s>" % (self.name, self.permissions)


class RefreshToken(BaseModel):
schema = "RefreshTokenSchema"

def __init__(
self,
id=None, # noqa # shadows built-in
issued=None,
expires=None,
payload=None,
):
self.id = id
self.issued = issued
self.expires = expires
self.payload = payload or {}

def __str__(self):
return "%s" % self.payload

def __repr__(self):
return "<RefreshToken: issued=%s, expires=%s, payload=%s>" % (
self.issued,
self.expires,
self.payload,
)


Expand Down Expand Up @@ -1520,6 +1481,8 @@ def __init__(
parent=None,
children=None,
metadata=None,
default_user=None,
shared_users=None,
):
self.id = id
self.name = name
Expand All @@ -1537,6 +1500,9 @@ def __init__(
self.children = children
self.metadata = metadata or {}

self.default_user = default_user
self.shared_users = shared_users

def __str__(self):
return "%s" % self.name

Expand Down Expand Up @@ -1707,6 +1673,150 @@ def __repr__(self):
)


class User(BaseModel):
schema = "UserSchema"

def __init__(
self,
username=None,
id=None,
password=None,
roles=None,
local_roles=None,
upstream_roles=None,
user_alias_mapping=None,
metadata=None,
is_remote=False,
protected=False,
file_generated=False,
):
self.username = username
self.id = id
self.password = password
self.roles = roles or []
self.local_roles = local_roles or []
self.upstream_roles = upstream_roles or []
self.is_remote = is_remote
self.user_alias_mapping = user_alias_mapping or []
self.metadata = metadata or {}
self.protected = protected
self.file_generated = file_generated

def __str__(self):
return "%s: %s" % (self.username, self.roles)

def __repr__(self):
return "<User: username=%s, roles=%s>" % (
self.username,
self.roles,
)

def __eq__(self, other):
if not isinstance(other, User):
# don't attempt to compare against unrelated types
return NotImplemented

return (
self.username == other.username
and self.roles == other.roles
and self.upstream_roles == other.upstream_roles
and self.is_remote == other.is_remote
and self.user_alias_mapping == other.user_alias_mapping
and self.protected == other.protected
and self.file_generated == other.file_generated
)


class Role(BaseModel):
schema = "RoleSchema"

# TODO: REMOVE after DB model Updated with Permissions enum
PERMISSION_TYPES = {
"GARDEN_ADMIN",
"PLUGIN_ADMIN",
"OPERATOR",
"READ_ONLY", # Default value if not role is provided
}

def __init__(
self,
name,
permission=None,
description=None,
id=None,
scope_gardens=None,
scope_namespaces=None,
scope_systems=None,
scope_instances=None,
scope_versions=None,
scope_commands=None,
protected=False,
file_generated=False,
):
self.name = name
self.permission = permission or Permissions.READ_ONLY.name
self.description = description
self.id = id
self.scope_gardens = scope_gardens or []
self.scope_namespaces = scope_namespaces or []
self.scope_systems = scope_systems or []
self.scope_instances = scope_instances or []
self.scope_versions = scope_versions or []
self.scope_commands = scope_commands or []
self.protected = protected
self.file_generated = file_generated

def __str__(self):
return "%s" % (self.name)

def __repr__(self):
return (
"<Role: id=%s, name=%s, description=%s, permission=%s, scope_garden=%s, "
"scope_namespaces=%s, scope_systems=%s, scope_instances=%s, "
"scope_versions=%s, scope_commands=%s>"
) % (
self.id,
self.name,
self.description,
self.permission,
self.scope_gardens,
self.scope_namespaces,
self.scope_systems,
self.scope_instances,
self.scope_versions,
self.scope_commands,
)

def __eq__(self, other):
if not isinstance(other, Role):
# don't attempt to compare against unrelated types
return NotImplemented

return (
self.name == other.name
and self.description == other.description
and self.permission == other.permission
and self.scope_gardens == other.scope_gardens
and self.scope_namespaces == other.scope_namespaces
and self.scope_systems == other.scope_systems
and self.scope_instances == other.scope_instances
and self.scope_versions == other.scope_versions
and self.scope_commands == other.scope_commands
)


class UpstreamRole(Role):
schema = "UpstreamRoleSchema"


class AliasUserMap(BaseModel):
schema = "AliasUserMapSchema"

def __init__(self, target_garden, username):
self.target_garden = target_garden
self.username = username


class Subscriber(BaseModel):
schema = "SubscriberSchema"

Expand Down Expand Up @@ -1778,3 +1888,21 @@ def __repr__(self):
self.name,
self.subscribers,
)


class Replication(BaseModel):
schema = "ReplicationSchema"

def __init__(self, id=None, replication_id=None, expires_at=None):
self.id = id
self.replication_id = replication_id
self.expires_at = expires_at

def __str__(self):
return "%s:%s" % (self.replication_id, self.expires_at)

def __repr__(self):
return "<Replication: replication_id=%s, expires_at=%s>" % (
self.replication_id,
self.expires_at,
)
3 changes: 2 additions & 1 deletion brewtils/request_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import six
from requests import ConnectionError as RequestsConnectionError

from brewtils.decorators import _parse_method
import brewtils.plugin
from brewtils.decorators import _parse_method
from brewtils.errors import (
BGGivesUpError,
DiscardMessageException,
Expand Down Expand Up @@ -61,6 +61,7 @@ def process_command(self, request):

if parent_request:
request.parent = Request(id=str(parent_request.id))
request.requester = parent_request.requester
request.has_parent = True

# check for kwargs on the target command
Expand Down
9 changes: 8 additions & 1 deletion brewtils/rest/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@ def enable_auth(method):

@functools.wraps(method)
def wrapper(self, *args, **kwargs):

# Load Token initially if authentication settings are provided
if not self.session.headers.get("Authorization") and (
(self.username and self.password) or self.client_cert
):
self.get_tokens()

original_response = method(self, *args, **kwargs)

if original_response.status_code != 401:
return original_response

# Try to use credentials
# Refresh Token if expired and caused 401
if (self.username and self.password) or self.client_cert:
credential_response = self.get_tokens()

Expand Down
Loading

0 comments on commit 65ebb6a

Please sign in to comment.