mirror of
https://github.com/ansible/awx.git
synced 2026-04-04 17:55:06 -02:30
Some TODO updates, a model revision, and getting association/disassociation working on user/team creds.
This commit is contained in:
49
TODO.md
49
TODO.md
@@ -3,25 +3,52 @@ TODO items for ansible commander
|
|||||||
|
|
||||||
4/2 NOTES
|
4/2 NOTES
|
||||||
=========
|
=========
|
||||||
* supervisord to start celery, modify ansible playbook to set up supervisord <- Chris
|
* supervisord to start celery, modify ansible playbook to set up supervisord <- ChrisC
|
||||||
* host relationships in DB, last launch job status per host, etc (self.play.inventory) <- Chris
|
* host relationships in DB, last launch job status per host, etc (self.play.inventory) <- ChrisC
|
||||||
* stats attributes on launch job (??)
|
* stats attributes on launch job status (??) -- which hosts succeeded, failed, etc (through relationship) <-- Chris C
|
||||||
* make launch job rest triggerable & launch job statuses readable. launch_job.start() <-- MPD
|
* make launch job rest triggerable & launch job statuses readable. launch_job.start() <-- MPD
|
||||||
* do we need something other than default playbook (ProjectOptions) <-- BOTH, TBD
|
* do we need something other than default playbook (ProjectOptions) <-- me, later
|
||||||
* way to send cntrl-c to kill job (method on job?) <-- Chris, low priority
|
* way to send cntrl-c to kill job (method on job?) <-- ChrisC, low priority
|
||||||
* documentation on how to run with callbacks from NOT a launchjob <-- Chris
|
* documentation on how to run with callbacks from NOT a launchjob <-- ChrisC
|
||||||
* interactive SSH agent support for launch jobs/creds
|
* interactive SSH agent support for launch jobs/creds <-- ChrisC
|
||||||
* michael to modify ansible to accept ssh password and sudo password from env vars
|
* michael to modify ansible to accept ssh password and sudo password from env vars
|
||||||
|
|
||||||
|
CH notes
|
||||||
|
========
|
||||||
|
need to add api/v1/group/N/all_hosts
|
||||||
|
need to add api/v1/host/N/all_groups or make sure it's in the resource
|
||||||
|
figure out how to mark each object generically w/ whether it can be edited/etc
|
||||||
|
enable generic filtering
|
||||||
|
sorting?
|
||||||
|
if you delete a subgroup, hosts go up to parent
|
||||||
|
verify unique_together on inventory and host name
|
||||||
|
|
||||||
|
make a way to start a launch job via the API.
|
||||||
|
|
||||||
|
future feature of being able to create SavedLaunchJob templates.
|
||||||
|
|
||||||
REST TODO
|
REST TODO
|
||||||
---------
|
---------
|
||||||
* credentials objects & permissions
|
* credentials objects & permissions
|
||||||
* tags
|
* tags (later)
|
||||||
* audit trails
|
* audit trails (later)
|
||||||
* launch jobs triggering
|
* launch jobs triggering <-- important
|
||||||
* related resources on everything that makes sense
|
* related resources on everything that makes sense <--- important
|
||||||
* expose log data from callback (decide on structure)
|
* expose log data from callback (decide on structure)
|
||||||
|
|
||||||
|
|
||||||
|
api/v1/ <-- discoverable
|
||||||
|
api/v1/me
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 2
|
||||||
|
foo: 3
|
||||||
|
related: [
|
||||||
|
'bar' : 'http://api/v1/users/5/bar',
|
||||||
|
'baz' : 'http://api/v1/users/10/baz',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LATER
|
LATER
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Ansible Commander. If not, see <http://www.gnu.org/licenses/>.
|
# along with Ansible Commander. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse, Http404
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from lib.main.models import *
|
from lib.main.models import *
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
@@ -147,14 +147,27 @@ class BaseSubList(BaseList):
|
|||||||
relationship = getattr(main, self.__class__.relationship)
|
relationship = getattr(main, self.__class__.relationship)
|
||||||
|
|
||||||
if not 'disassociate' in request.DATA:
|
if not 'disassociate' in request.DATA:
|
||||||
if not request.user.is_superuser and not self.__class__.parent_model.can_user_attach(request.user, main, sub, self.__class__.relationship):
|
if not request.user.is_superuser:
|
||||||
raise PermissionDenied()
|
if type(main) != User:
|
||||||
|
if not self.__class__.parent_model.can_user_attach(request.user, main, sub, self.__class__.relationship):
|
||||||
|
raise PermissionDenied()
|
||||||
|
else:
|
||||||
|
if not UserHelper.can_user_attach(request.user, main, sub, self.__class__.relationship):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
if sub in relationship.all():
|
if sub in relationship.all():
|
||||||
return Response(status=status.HTTP_409_CONFLICT)
|
return Response(status=status.HTTP_409_CONFLICT)
|
||||||
relationship.add(sub)
|
relationship.add(sub)
|
||||||
else:
|
else:
|
||||||
if not request.user.is_superuser and not self.__class__.parent_model.can_user_unattach(request.user, main, sub, self.__class__.relationship):
|
if not request.user.is_superuser:
|
||||||
raise PermissionDenied()
|
if type(main) != User:
|
||||||
|
if not self.__class__.parent_model.can_user_unattach(request.user, main, sub, self.__class__.relationship):
|
||||||
|
raise PermissionDenied()
|
||||||
|
else:
|
||||||
|
if not UserHelper.can_user_unattach(request.user, main, sub, self.__class__.relationship):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
if severable:
|
if severable:
|
||||||
relationship.remove(sub)
|
relationship.remove(sub)
|
||||||
else:
|
else:
|
||||||
@@ -174,6 +187,10 @@ class BaseDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
# somewhat lame that delete has to call it's own permissions check
|
# somewhat lame that delete has to call it's own permissions check
|
||||||
obj = self.model.objects.get(pk=kwargs['pk'])
|
obj = self.model.objects.get(pk=kwargs['pk'])
|
||||||
|
if getattr(obj, 'active', True) == False:
|
||||||
|
raise Http404()
|
||||||
|
if getattr(obj, 'is_active', True) == False:
|
||||||
|
raise Http404()
|
||||||
if not request.user.is_superuser and not self.delete_permissions_check(request, obj):
|
if not request.user.is_superuser and not self.delete_permissions_check(request, obj):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
if isinstance(obj, PrimordialModel):
|
if isinstance(obj, PrimordialModel):
|
||||||
|
|||||||
@@ -131,11 +131,14 @@ class UserHelper(object):
|
|||||||
def can_user_attach(cls, user, obj, sub_obj, relationship_type):
|
def can_user_attach(cls, user, obj, sub_obj, relationship_type):
|
||||||
if type(sub_obj) != User:
|
if type(sub_obj) != User:
|
||||||
if not sub_obj.can_user_read(user, sub_obj):
|
if not sub_obj.can_user_read(user, sub_obj):
|
||||||
print "N1"
|
|
||||||
return False
|
return False
|
||||||
rc = cls.can_user_administrate(user, obj)
|
rc = cls.can_user_administrate(user, obj)
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_user_unattach(cls, user, obj, sub_obj, relationship_type):
|
||||||
|
return cls.can_user_administrate(user, obj)
|
||||||
|
|
||||||
class PrimordialModel(models.Model):
|
class PrimordialModel(models.Model):
|
||||||
'''
|
'''
|
||||||
common model for all object types that have these standard fields
|
common model for all object types that have these standard fields
|
||||||
@@ -548,6 +551,13 @@ class Credential(CommonModelNameNotUnique):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_user_delete(cls, user, obj):
|
||||||
|
if obj.user is None and obj.team is None:
|
||||||
|
# unassociated credentials may be marked deleted by anyone
|
||||||
|
return True
|
||||||
|
return cls.can_user_administrate(user,obj)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_read(cls, user, obj):
|
def can_user_read(cls, user, obj):
|
||||||
''' a user can be read if they are on the same team or can be administrated '''
|
''' a user can be read if they are on the same team or can be administrated '''
|
||||||
|
|||||||
@@ -342,19 +342,29 @@ class ProjectsTest(BaseTest):
|
|||||||
# editing a credential to edit the user record is not legal, this is a test of the .validate
|
# editing a credential to edit the user record is not legal, this is a test of the .validate
|
||||||
# method on the serializer to allow 'write once' fields
|
# method on the serializer to allow 'write once' fields
|
||||||
self.put(edit_creds1, data=d_cred_user2, expect=400, auth=self.get_normal_credentials())
|
self.put(edit_creds1, data=d_cred_user2, expect=400, auth=self.get_normal_credentials())
|
||||||
self.put(edit_creds1, data=d_cred_user, expect=200, auth=self.get_other_credentials())
|
cred_put_u = self.put(edit_creds1, data=d_cred_user, expect=200, auth=self.get_other_credentials())
|
||||||
|
|
||||||
self.put(edit_creds2, data=d_cred_team, expect=401)
|
self.put(edit_creds2, data=d_cred_team, expect=401)
|
||||||
self.put(edit_creds2, data=d_cred_team, expect=401, auth=self.get_invalid_credentials())
|
self.put(edit_creds2, data=d_cred_team, expect=401, auth=self.get_invalid_credentials())
|
||||||
cred_team = Credential.objects.get(pk=cred_team.pk)
|
cred_team = Credential.objects.get(pk=cred_team.pk)
|
||||||
self.put(edit_creds2, data=d_cred_team, expect=200, auth=self.get_super_credentials())
|
self.put(edit_creds2, data=d_cred_team, expect=200, auth=self.get_super_credentials())
|
||||||
cred_team = Credential.objects.get(pk=cred_team.pk)
|
cred_team = Credential.objects.get(pk=cred_team.pk)
|
||||||
self.put(edit_creds2, data=d_cred_team, expect=200, auth=self.get_normal_credentials())
|
cred_put_t = self.put(edit_creds2, data=d_cred_team, expect=200, auth=self.get_normal_credentials())
|
||||||
self.put(edit_creds2, data=d_cred_team, expect=403, auth=self.get_other_credentials())
|
self.put(edit_creds2, data=d_cred_team, expect=403, auth=self.get_other_credentials())
|
||||||
|
|
||||||
|
cred_put_t['disassociate'] = 1
|
||||||
|
team_url = "/api/v1/teams/%s/credentials/" % cred_put_t['team']
|
||||||
|
self.post(team_url, data=cred_put_t, expect=204, auth=self.get_normal_credentials())
|
||||||
|
|
||||||
# can remove credentials from a user (via disassociate)
|
# can remove credentials from a user (via disassociate)
|
||||||
# can remove credentials from a team (via disassociate)
|
cred_put_u['disassociate'] = 1
|
||||||
# can delete a credential directly
|
url = cred_put_u['url']
|
||||||
|
user_url = "/api/v1/users/%s/credentials/" % cred_put_u['user']
|
||||||
|
self.post(user_url, data=cred_put_u, expect=204, auth=self.get_normal_credentials())
|
||||||
|
|
||||||
|
# can delete a credential directly -- probably won't be used too often
|
||||||
|
data = self.delete(url, expect=204, auth=self.get_other_credentials())
|
||||||
|
data = self.delete(url, expect=404, auth=self.get_other_credentials())
|
||||||
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# PERMISSIONS
|
# PERMISSIONS
|
||||||
|
|||||||
Reference in New Issue
Block a user