mirror of
https://github.com/ansible/awx.git
synced 2024-10-27 17:55:10 +03:00
cli: add support for granting and revoking roles from users/teams
This commit is contained in:
parent
276b577103
commit
6762702868
@ -20,7 +20,7 @@ from rest_framework.fields import JSONField as DRFJSONField
|
||||
from rest_framework.request import clone_request
|
||||
|
||||
# AWX
|
||||
from awx.main.fields import JSONField
|
||||
from awx.main.fields import JSONField, ImplicitRoleField
|
||||
from awx.main.models import InventorySource, NotificationTemplate
|
||||
|
||||
|
||||
@ -252,6 +252,16 @@ class Metadata(metadata.SimpleMetadata):
|
||||
if getattr(view, 'related_search_fields', None):
|
||||
metadata['related_search_fields'] = view.related_search_fields
|
||||
|
||||
# include role names in metadata
|
||||
roles = []
|
||||
model = getattr(view, 'model', None)
|
||||
if model:
|
||||
for field in model._meta.get_fields():
|
||||
if type(field) is ImplicitRoleField:
|
||||
roles.append(field.name)
|
||||
if len(roles) > 0:
|
||||
metadata['object_roles'] = roles
|
||||
|
||||
from rest_framework import generics
|
||||
if isinstance(view, generics.ListAPIView) and hasattr(view, 'paginator'):
|
||||
metadata['max_page_size'] = view.paginator.max_page_size
|
||||
|
@ -350,6 +350,141 @@ class SettingsList(CustomAction):
|
||||
return self.page.get()
|
||||
|
||||
|
||||
class RoleMixin(object):
|
||||
|
||||
has_roles = [
|
||||
['organizations', 'organization'],
|
||||
['projects', 'project'],
|
||||
['inventories', 'inventory'],
|
||||
['inventory_scripts', 'inventory_script'],
|
||||
['teams', 'team'],
|
||||
['credentials', 'credential'],
|
||||
['job_templates', 'job_template'],
|
||||
['workflow_job_templates', 'workflow_job_template'],
|
||||
]
|
||||
roles = {} # this is calculated once
|
||||
|
||||
def add_arguments(self, parser):
|
||||
from .options import pk_or_name
|
||||
|
||||
if not RoleMixin.roles:
|
||||
for resource, flag in self.has_roles:
|
||||
options = self.page.__class__(
|
||||
self.page.endpoint.replace(self.resource, resource),
|
||||
self.page.connection
|
||||
).options()
|
||||
RoleMixin.roles[flag] = [
|
||||
role.replace('_role', '')
|
||||
for role in options.json.get('object_roles', [])
|
||||
]
|
||||
|
||||
possible_roles = set()
|
||||
for v in RoleMixin.roles.values():
|
||||
possible_roles.update(v)
|
||||
|
||||
resource_group = parser.choices[self.action].add_mutually_exclusive_group(
|
||||
required=True
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help='The ID (or name) of the {} to {} access to/from'.format(
|
||||
self.resource, self.action
|
||||
)
|
||||
)
|
||||
for _type in RoleMixin.roles.keys():
|
||||
if _type == 'team' and self.resource == 'team':
|
||||
# don't add a team to a team
|
||||
continue
|
||||
|
||||
class related_page(object):
|
||||
|
||||
def __init__(self, connection, resource):
|
||||
self.conn = connection
|
||||
if resource == 'inventories':
|
||||
resource = 'inventory' # d'oh, this is special
|
||||
self.resource = resource
|
||||
|
||||
def get(self, **kwargs):
|
||||
v2 = api.Api(connection=self.conn).get().current_version.get()
|
||||
return getattr(v2, self.resource).get(**kwargs)
|
||||
|
||||
resource_group.add_argument(
|
||||
'--{}'.format(_type),
|
||||
type=functools.partial(
|
||||
pk_or_name, None, _type,
|
||||
page=related_page(
|
||||
self.page.connection,
|
||||
dict((v, k) for k, v in self.has_roles)[_type]
|
||||
)
|
||||
),
|
||||
metavar='ID',
|
||||
help='The ID (or name) of the target {}'.format(_type),
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--role', type=str, choices=possible_roles, required=True,
|
||||
help='The name of the role to {}'.format(self.action)
|
||||
)
|
||||
|
||||
def perform(self, **kwargs):
|
||||
for resource, flag in self.has_roles:
|
||||
if flag in kwargs:
|
||||
role = kwargs['role']
|
||||
if role not in RoleMixin.roles[flag]:
|
||||
options = ', '.join(RoleMixin.roles[flag])
|
||||
raise ValueError(
|
||||
"invalid choice: '{}' must be one of {}".format(
|
||||
role, options
|
||||
)
|
||||
)
|
||||
value = kwargs[flag]
|
||||
target = '/api/v2/{}/{}'.format(resource, value)
|
||||
detail = self.page.__class__(
|
||||
target,
|
||||
self.page.connection
|
||||
).get()
|
||||
object_roles = detail['summary_fields']['object_roles']
|
||||
actual_role = object_roles[role + '_role']
|
||||
params = {'id': actual_role['id']}
|
||||
if self.action == 'grant':
|
||||
params['associate'] = True
|
||||
if self.action == 'revoke':
|
||||
params['disassociate'] = True
|
||||
|
||||
try:
|
||||
self.page.get().related.roles.post(params)
|
||||
except NoContent:
|
||||
# we expect to enter this block because these endpoints return
|
||||
# HTTP 204 on success
|
||||
pass
|
||||
|
||||
|
||||
class UserGrant(RoleMixin, CustomAction):
|
||||
|
||||
resource = 'users'
|
||||
action = 'grant'
|
||||
|
||||
|
||||
class UserRevoke(RoleMixin, CustomAction):
|
||||
|
||||
resource = 'users'
|
||||
action = 'revoke'
|
||||
|
||||
|
||||
class TeamGrant(RoleMixin, CustomAction):
|
||||
|
||||
resource = 'teams'
|
||||
action = 'grant'
|
||||
|
||||
|
||||
class TeamRevoke(RoleMixin, CustomAction):
|
||||
|
||||
resource = 'teams'
|
||||
action = 'revoke'
|
||||
|
||||
|
||||
class SettingsModify(CustomAction):
|
||||
action = 'modify'
|
||||
resource = 'settings'
|
||||
|
@ -41,6 +41,10 @@ def pk_or_name(v2, model_name, value, page=None):
|
||||
page = getattr(v2, model_name)
|
||||
|
||||
if page:
|
||||
if model_name == 'users':
|
||||
identity = 'username'
|
||||
elif model_name == 'instances':
|
||||
model_name = 'hostname'
|
||||
results = page.get(**{identity: value})
|
||||
if results.count == 1:
|
||||
return int(results.results[0].id)
|
||||
|
@ -181,7 +181,7 @@ class TestOptions(unittest.TestCase):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {'GET': {}, 'POST': {}}
|
||||
})
|
||||
ResourceOptionsParser(None, page, 'users', self.parser)
|
||||
ResourceOptionsParser(None, page, 'jobs', self.parser)
|
||||
assert method in self.parser.choices
|
||||
|
||||
out = StringIO()
|
||||
|
Loading…
Reference in New Issue
Block a user