1
0
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:
Ryan Petrello 2019-08-30 09:07:14 -04:00
parent 276b577103
commit 6762702868
No known key found for this signature in database
GPG Key ID: F2AA5F2122351777
4 changed files with 151 additions and 2 deletions

View File

@ -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

View File

@ -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'

View File

@ -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)

View File

@ -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()