瀏覽代碼

[oozie] Workflows and Coordinators security access checks

Also take care of sharing permissions
Adding many tests

Editor Permissions:

A Workflow/Coordinator can be accessed/submitted by its owner, a superuser or by anyone if its 'is_shared'
property and SHARE_JOBS are set to True.

A Workflow/Coordinator can be modified only by its owner or a superuser.
Permissions checking happens by adding the decorators.

Dashboard Permissions:
A Workflow/Coordinator can be accessed/submitted/modified only by its owner or a superuser.
Permissions checking happens by calling check_access_and_get_oozie_job().
Romain Rigaux 13 年之前
父節點
當前提交
3f884f6c59

+ 168 - 7
apps/oozie/src/oozie/tests.py

@@ -29,7 +29,7 @@ from desktop.lib.django_test_util import make_logged_in_client
 from desktop.lib.test_utils import grant_access
 from liboozie import oozie_api
 from liboozie.types import WorkflowList, Workflow as OozieWorkflow, Coordinator as OozieCoordinator,\
-  CoordinatorList
+  CoordinatorList, WorkflowAction
 
 from oozie.models import Workflow, Node, Job, Coordinator, Fork
 from oozie.conf import SHARE_JOBS
@@ -241,19 +241,32 @@ class TestEditor:
 
   def test_workflow_permissions(self):
     response = self.c.get(reverse('oozie:edit_workflow', args=[self.wf.id]))
-    assert_equal(200, response.status_code)
+    assert_true('Editor' in response.content, response.content)
+    assert_false(self.wf.is_shared)
 
     # Login as someone else
     client_not_me = make_logged_in_client(username='not_me', is_superuser=False, groupname='test')
     grant_access("not_me", "test", "oozie")
 
+    # List
+    finish = SHARE_JOBS.set_for_testing(True)
+    try:
+      response = client_not_me.get(reverse('oozie:list_workflows'))
+      assert_false('wf-name-1' in response.content, response.content)
+    finally:
+      finish()
+    finish = SHARE_JOBS.set_for_testing(False)
+    try:
+      response = client_not_me.get(reverse('oozie:list_workflows'))
+      assert_false('wf-name-1' in response.content, response.content)
+    finally:
+      finish()
 
-    # Edit
+    # View
     finish = SHARE_JOBS.set_for_testing(True)
     try:
       response = client_not_me.get(reverse('oozie:edit_workflow', args=[self.wf.id]))
-      assert_equal(200, response.status_code)
-      assert_true('wf-name-1' in response.content, response.content)
+      assert_true('Permission denied' in response.content, response.content)
     finally:
       finish()
     finish = SHARE_JOBS.set_for_testing(False)
@@ -264,9 +277,20 @@ class TestEditor:
     finally:
       finish()
 
-    # Share
+    # Share it !
     self.wf.is_shared = True
     self.wf.save()
+
+    # List
+    finish = SHARE_JOBS.set_for_testing(True)
+    try:
+      response = client_not_me.get(reverse('oozie:list_workflows'))
+      assert_equal(200, response.status_code)
+      assert_true('wf-name-1' in response.content, response.content)
+    finally:
+      finish()
+
+    # Edit
     finish = SHARE_JOBS.set_for_testing(True)
     try:
       response = client_not_me.get(reverse('oozie:edit_workflow', args=[self.wf.id]))
@@ -275,11 +299,24 @@ class TestEditor:
     finally:
       finish()
 
+    # Submit
+    finish = SHARE_JOBS.set_for_testing(False)
+    try:
+      response = client_not_me.post(reverse('oozie:submit_workflow', args=[self.wf.id]))
+      assert_true('Permission denied' in response.content, response.content)
+    finally:
+      finish()
+    finish = SHARE_JOBS.set_for_testing(True)
+    try:
+      response = client_not_me.post(reverse('oozie:submit_workflow', args=[self.wf.id]))
+      assert_false('Permission denied' in response.content, response.content)
+    finally:
+      finish()
+
     # Delete
     finish = SHARE_JOBS.set_for_testing(False)
     try:
       response = client_not_me.post(reverse('oozie:delete_workflow', args=[self.wf.id]))
-      assert_equal(200, response.status_code)
       assert_true('Permission denied' in response.content, response.content)
     finally:
       finish()
@@ -288,6 +325,93 @@ class TestEditor:
     assert_equal(200, response.status_code)
 
 
+  def test_coordinator_permissions(self):
+    coord = create_coordinator(self.wf)
+
+    response = self.c.get(reverse('oozie:edit_coordinator', args=[coord.id]))
+    assert_true('Editor' in response.content, response.content)
+
+    # Login as someone else
+    client_not_me = make_logged_in_client(username='not_me', is_superuser=False, groupname='test')
+    grant_access("not_me", "test", "oozie")
+
+    # List
+    finish = SHARE_JOBS.set_for_testing(True)
+    try:
+      response = client_not_me.get(reverse('oozie:list_coordinator'))
+      assert_false('MyCoord' in response.content, response.content)
+    finally:
+      finish()
+    finish = SHARE_JOBS.set_for_testing(False)
+    try:
+      response = client_not_me.get(reverse('oozie:list_coordinator'))
+      assert_false('MyCoord' in response.content, response.content)
+    finally:
+      finish()
+
+    # View
+    finish = SHARE_JOBS.set_for_testing(True)
+    try:
+      response = client_not_me.get(reverse('oozie:edit_coordinator', args=[coord.id]))
+      assert_true('Permission denied' in response.content, response.content)
+    finally:
+      finish()
+    finish = SHARE_JOBS.set_for_testing(False)
+    try:
+      response = client_not_me.get(reverse('oozie:edit_coordinator', args=[coord.id]))
+      assert_false('MyCoord' in response.content, response.content)
+    finally:
+      finish()
+
+    # Share it !
+    coord.is_shared = True
+    coord.save()
+
+    # List
+    finish = SHARE_JOBS.set_for_testing(True)
+    try:
+      response = client_not_me.get(reverse('oozie:list_coordinator'))
+      assert_equal(200, response.status_code)
+      assert_true('MyCoord' in response.content, response.content)
+    finally:
+      finish()
+
+    # Edit
+    finish = SHARE_JOBS.set_for_testing(True)
+    try:
+      response = client_not_me.get(reverse('oozie:edit_coordinator', args=[coord.id]))
+      assert_equal(200, response.status_code)
+      assert_true('MyCoord' in response.content, response.content)
+    finally:
+      finish()
+
+    # Submit
+    finish = SHARE_JOBS.set_for_testing(False)
+    try:
+      response = client_not_me.post(reverse('oozie:submit_coordinator', args=[coord.id]))
+      assert_true('Permission denied' in response.content, response.content)
+    finally:
+      finish()
+    finish = SHARE_JOBS.set_for_testing(True)
+    try:
+      response = client_not_me.post(reverse('oozie:submit_coordinator', args=[coord.id]))
+      assert_false('Permission denied' in response.content, response.content)
+    finally:
+      finish()
+
+    # Delete
+    # TODO view!
+#    finish = SHARE_JOBS.set_for_testing(False)
+#    try:
+#      response = client_not_me.post(reverse('oozie:delete_coordinator', args=[coord.id]))
+#      assert_true('Permission denied' in response.content, response.content)
+#    finally:
+#      finish()
+#
+#    response = self.c.post(reverse('oozie:delete_coordinator', args=[coord.id]), follow=True)
+#    assert_equal(200, response.status_code)
+
+
   def test_coordinator_gen_xml(self):
     coord = create_coordinator(self.wf)
 
@@ -456,6 +580,39 @@ class TestDashboard:
     assert_equal(0, data['status'])
 
 
+  def test_workflow_permissions(self):
+    response = self.c.get(reverse('oozie:list_oozie_workflow', args=[MockOozieApi.WORKFLOW_IDS[0]]))
+    assert_true('WordCount1' in response.content, response.content)
+    assert_false('Permission denied' in response.content, response.content)
+
+    response = self.c.get(reverse('oozie:list_oozie_workflow_action', args=['XXX']))
+    assert_false('Permission denied' in response.content, response.content)
+
+    # Login as someone else
+    client_not_me = make_logged_in_client(username='not_me', is_superuser=False, groupname='test')
+    grant_access("not_me", "test", "oozie")
+
+    response = client_not_me.get(reverse('oozie:list_oozie_workflow', args=[MockOozieApi.WORKFLOW_IDS[0]]))
+    assert_true('Permission denied' in response.content, response.content)
+
+    response = client_not_me.get(reverse('oozie:list_oozie_workflow_action', args=['XXX']))
+    assert_true('Permission denied' in response.content, response.content)
+
+
+  def test_coordinator_permissions(self):
+    response = self.c.get(reverse('oozie:list_oozie_coordinator', args=[MockOozieApi.COORDINATOR_IDS[0]]))
+    assert_true('DailyWordCount1' in response.content, response.content)
+    assert_false('Permission denied' in response.content, response.content)
+
+    # Login as someone else
+    client_not_me = make_logged_in_client(username='not_me', is_superuser=False, groupname='test')
+    grant_access("not_me", "test", "oozie")
+
+    response = client_not_me.get(reverse('oozie:list_oozie_coordinator', args=[MockOozieApi.COORDINATOR_IDS[0]]))
+    assert_true('Permission denied' in response.content, response.content)
+
+
+
 class MockOozieApi:
   JSON_WORKFLOW_LIST = [{u'status': u'RUNNING', u'run': 0, u'startTime': u'Mon, 30 Jul 2012 22:35:48 GMT', u'appName': u'WordCount1', u'lastModTime': u'Mon, 30 Jul 2012 22:37:00 GMT', u'actions': [], u'acl': None, u'appPath': None, u'externalId': None, u'consoleUrl': u'http://runreal:11000/oozie?job=0000012-120725142744176-oozie-oozi-W', u'conf': None, u'parentId': None, u'createdTime': u'Mon, 30 Jul 2012 22:35:48 GMT', u'toString': u'Workflow id[0000012-120725142744176-oozie-oozi-W] status[SUCCEEDED]', u'endTime': u'Mon, 30 Jul 2012 22:37:00 GMT', u'id': u'0000012-120725142744176-oozie-oozi-W', u'group': None, u'user': u'romain'},
                         {u'status': u'KILLED', u'run': 0, u'startTime': u'Mon, 30 Jul 2012 22:31:08 GMT', u'appName': u'WordCount2', u'lastModTime': u'Mon, 30 Jul 2012 22:32:20 GMT', u'actions': [], u'acl': None, u'appPath': None, u'externalId': None, u'consoleUrl': u'http://runreal:11000/oozie?job=0000011-120725142744176-oozie-oozi-W', u'conf': None, u'parentId': None, u'createdTime': u'Mon, 30 Jul 2012 22:31:08 GMT', u'toString': u'Workflow id[0000011-120725142744176-oozie-oozi-W] status[SUCCEEDED]', u'endTime': u'Mon, 30 Jul 2012 22:32:20 GMT', u'id': u'0000011-120725142744176-oozie-oozi-W', u'group': None, u'user': u'romain'},
@@ -469,6 +626,7 @@ class MockOozieApi:
                            {u'startTime': u'Sun, 01 Jul 2012 00:00:00 GMT', u'actions': [], u'frequency': 1, u'concurrency': 1, u'pauseTime': None, u'group': None, u'toString': u'Coornidator application id[0000009-120706144403213-oozie-oozi-C] status[DONEWITHERROR]', u'consoleUrl': None, u'mat_throttling': 0, u'status': u'DONEWITHERROR', u'conf': None, u'user': u'romain', u'timeOut': 120, u'coordJobPath': u'hdfs://localhost:8020/user/hue/jobsub/_romain_-design-2', u'timeUnit': u'DAY', u'coordJobId': u'0000009-120706144403213-oozie-oozi-C', u'coordJobName': u'DailyWordCount4', u'nextMaterializedTime': u'Thu, 05 Jul 2012 00:00:00 GMT', u'coordExternalId': None, u'acl': None, u'lastAction': u'Thu, 05 Jul 2012 00:00:00 GMT', u'executionPolicy': u'FIFO', u'timeZone': u'America/Los_Angeles', u'endTime': u'Wed, 04 Jul 2012 18:54:00 GMT'}]
   COORDINATOR_IDS = [coord['coordJobId'] for coord in JSON_COORDINATOR_LIST]
 
+  WORKFLOW_ACTION = {u'status': u'OK', u'retries': 0, u'transition': u'end', u'stats': None, u'startTime': u'Fri, 10 Aug 2012 05:24:21 GMT', u'toString': u'Action name[WordCount] status[OK]', u'cred': u'null', u'errorMessage': None, u'errorCode': None, u'consoleUrl': u'http://localhost:50030/jobdetails.jsp?jobid=job_201208072118_0044', u'externalId': u'job_201208072118_0044', u'externalStatus': u'SUCCEEDED', u'conf': u'<map-reduce xmlns="uri:oozie:workflow:0.2">\r\n  <job-tracker>localhost:8021</job-tracker>\r\n  <name-node>hdfs://localhost:8020</name-node>\r\n  <configuration>\r\n    <property>\r\n      <name>mapred.mapper.regex</name>\r\n      <value>dream</value>\r\n    </property>\r\n    <property>\r\n      <name>mapred.input.dir</name>\r\n      <value>/user/romain/words/20120702</value>\r\n    </property>\r\n    <property>\r\n      <name>mapred.output.dir</name>\r\n      <value>/user/romain/out/rrwords/20120702</value>\r\n    </property>\r\n    <property>\r\n      <name>mapred.mapper.class</name>\r\n      <value>org.apache.hadoop.mapred.lib.RegexMapper</value>\r\n    </property>\r\n    <property>\r\n      <name>mapred.combiner.class</name>\r\n      <value>org.apache.hadoop.mapred.lib.LongSumReducer</value>\r\n    </property>\r\n    <property>\r\n      <name>mapred.reducer.class</name>\r\n      <value>org.apache.hadoop.mapred.lib.LongSumReducer</value>\r\n    </property>\r\n    <property>\r\n      <name>mapred.output.key.class</name>\r\n      <value>org.apache.hadoop.io.Text</value>\r\n    </property>\r\n    <property>\r\n      <name>mapred.output.value.class</name>\r\n      <value>org.apache.hadoop.io.LongWritable</value>\r\n    </property>\r\n  </configuration>\r\n</map-reduce>', u'type': u'map-reduce', u'trackerUri': u'localhost:8021', u'externalChildIDs': None, u'endTime': u'Fri, 10 Aug 2012 05:24:38 GMT', u'data': None, u'id': u'0000021-120807211836060-oozie-oozi-W@WordCount', u'name': u'WordCount'}
 
   def get_workflows(self, **kwargs):
     return WorkflowList(self, {'offset': 0, 'total': 4, 'workflows': MockOozieApi.JSON_WORKFLOW_LIST})
@@ -482,6 +640,9 @@ class MockOozieApi:
   def get_coordinator(self, job_id):
     return OozieCoordinator(self, MockOozieApi.JSON_COORDINATOR_LIST[0])
 
+  def get_action(self, action_id):
+    return WorkflowAction(MockOozieApi.WORKFLOW_ACTION)
+
   def job_control(self, job_id, action):
     return 'Done'
 

+ 0 - 1
apps/oozie/src/oozie/urls.py

@@ -61,7 +61,6 @@ urlpatterns += patterns(
   url(r'^list_oozie_coordinators/$', 'list_oozie_coordinators', name='list_oozie_coordinators'),
   url(r'^list_oozie_workflow/(?P<job_id>[-\w]+)/(?P<coordinator_job_id>[-\w]+)?$', 'list_oozie_workflow', name='list_oozie_workflow'),
   url(r'^list_oozie_coordinator/(?P<job_id>[-\w]+)$', 'list_oozie_coordinator', name='list_oozie_coordinator'),
-  url(r'^list_oozie_coordinator_from_job/(?P<job_id>[-\w]+)$', 'list_oozie_coordinator_from_job', name='list_oozie_coordinator_from_job'),
   url(r'^list_oozie_workflow_action/(?P<action>[-\w@]+)$', 'list_oozie_workflow_action', name='list_oozie_workflow_action'),
   url(r'^manage_oozie_jobs/(?P<job_id>[-\w]+)/(?P<action>(start|suspend|resume|kill|rerun))$', 'manage_oozie_jobs', name='manage_oozie_jobs'),
 )

+ 82 - 57
apps/oozie/src/oozie/views/dashboard.py

@@ -14,7 +14,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-from oozie.views.editor import can_access_job
+from desktop.log.access import access_warn
 
 try:
   import json
@@ -23,6 +23,7 @@ except ImportError:
 import logging
 
 from django.http import HttpResponse
+from django.utils.functional import wraps
 from django.utils.translation import ugettext as _
 
 from desktop.lib.django_util import render, PopupException
@@ -30,45 +31,39 @@ from desktop.lib.rest.http_client import RestException
 from liboozie.oozie_api import get_oozie
 
 from oozie.models import History
+from oozie.views.editor import can_access_job
 
 
 LOG = logging.getLogger(__name__)
 
 
+"""
+Permissions:
+
+A Workflow/Coordinator can be accessed/submitted/modified only by its owner or a superuser.
+
+Permissions checking happens by calling check_access_and_get_oozie_job().
+"""
+
+
 def manage_oozie_jobs(request, job_id, action):
   if request.method != 'POST':
     raise PopupException(_('Please use a POST request to manage an Oozie job.'))
 
+  check_access_and_get_oozie_job(request, job_id)
+
   response = {'status': -1, 'data': ''}
 
   try:
     response['data'] = get_oozie().job_control(job_id, action)
     response['status'] = 0
-    request.info(_('Action %(action)s was performed on ob %(job_id)s') % {'action': action, 'job_id': job_id})
+    request.info(_('Action %(action)s was performed on job %(job_id)s') % {'action': action, 'job_id': job_id})
   except RestException, ex:
-    raise PopupException("Error %s Oozie job %s" % (action, job_id,),
-                         detail=ex.message)
+    response['data'] = _("Error performing %s on Oozie job %s: %s") % (action, job_id, ex.message)
 
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 
-def split_oozie_jobs(oozie_jobs):
-  jobs = {}
-  jobs_running = []
-  jobs_completed = []
-
-  for job in oozie_jobs:
-    if job.status == 'RUNNING':
-      jobs_running.append(job)
-    else:
-      jobs_completed.append(job)
-
-  jobs['running_jobs'] = sorted(jobs_running, key=lambda w: w.status)
-  jobs['completed_jobs'] = sorted(jobs_completed, key=lambda w: w.status)
-
-  return jobs
-
-
 def list_oozie_workflows(request):
   kwargs = {'cnt': 50,}
   if not request.user.is_superuser:
@@ -94,51 +89,18 @@ def list_oozie_coordinators(request):
   })
 
 
-def list_oozie_coordinator_from_job(request, job_id):
-  return list_oozie_coordinator(request, History.objects.get(job__id=job_id).oozie_job_id)
-
-
-def list_oozie_coordinator(request, job_id):
-  try:
-    oozie_coordinator = get_oozie().get_coordinator(job_id)
-  except RestException, ex:
-    raise PopupException(_("Error accessing Oozie job %s") % (job_id,),
-                         detail=ex.message)
-
-  # Cross reference the submission history (if any)
-  coordinator = None
-  try:
-    coordinator = History.objects.get(oozie_job_id=job_id).job.get_full_node()
-  except History.DoesNotExist, ex:
-    pass
-
-  return render('dashboard/list_oozie_coordinator.mako', request, {
-    'oozie_coordinator': oozie_coordinator,
-    'coordinator': coordinator,
-  })
-
-
 def list_oozie_workflow(request, job_id, coordinator_job_id=None):
-  try:
-    oozie_workflow = get_oozie().get_job(job_id)
-  except RestException, ex:
-    raise PopupException(_("Error accessing Oozie job %s") % (job_id,),
-                         detail=ex._headers['oozie-error-message'])
+  oozie_workflow = check_access_and_get_oozie_job(request, job_id)
 
   oozie_coordinator = None
   if coordinator_job_id is not None:
-    try:
-      oozie_coordinator = get_oozie().get_coordinator(coordinator_job_id)
-    except RestException, ex:
-      raise PopupException(_("Error accessing Oozie job: %s") % (coordinator_job_id,),
-                           detail=ex._headers['oozie-error-message'])
+    oozie_coordinator = check_access_and_get_oozie_job(request, coordinator_job_id)
 
   history = History.cross_reference_submission_history(request.user, job_id, coordinator_job_id)
 
   hue_coord = history and history.get_coordinator() or History.get_coordinator_from_config(oozie_workflow.conf_dict)
   hue_workflow = (hue_coord and hue_coord.workflow) or (history and history.get_workflow()) or History.get_workflow_from_config(oozie_workflow.conf_dict)
 
-
   if hue_coord: can_access_job(request, hue_coord.workflow.id)
   if hue_workflow: can_access_job(request, hue_workflow.id)
 
@@ -162,10 +124,26 @@ def list_oozie_workflow(request, job_id, coordinator_job_id=None):
   })
 
 
+def list_oozie_coordinator(request, job_id):
+  oozie_coordinator = check_access_and_get_oozie_job(request, job_id)
+
+  # Cross reference the submission history (if any)
+  coordinator = None
+  try:
+    coordinator = History.objects.get(oozie_job_id=job_id).job.get_full_node()
+  except History.DoesNotExist:
+    pass
+
+  return render('dashboard/list_oozie_coordinator.mako', request, {
+    'oozie_coordinator': oozie_coordinator,
+    'coordinator': coordinator,
+  })
+
+
 def list_oozie_workflow_action(request, action):
   try:
     action = get_oozie().get_action(action)
-    workflow = get_oozie().get_job(action.id.split('@')[0])
+    workflow = check_access_and_get_oozie_job(request, action.id.split('@')[0])
   except RestException, ex:
     raise PopupException(_("Error accessing Oozie action %s") % (action,),
                          detail=ex.message)
@@ -174,3 +152,50 @@ def list_oozie_workflow_action(request, action):
     'action': action,
     'workflow': workflow,
   })
+
+
+def split_oozie_jobs(oozie_jobs):
+  jobs = {}
+  jobs_running = []
+  jobs_completed = []
+
+  for job in oozie_jobs:
+    if job.status == 'RUNNING':
+      jobs_running.append(job)
+    else:
+      jobs_completed.append(job)
+
+  jobs['running_jobs'] = sorted(jobs_running, key=lambda w: w.status)
+  jobs['completed_jobs'] = sorted(jobs_completed, key=lambda w: w.status)
+
+  return jobs
+
+
+def check_access_and_get_oozie_job(request, job_id):
+  """
+  Decorator ensuring that the user has access to the workflow or coordinator.
+
+  Arg: 'workflow' or 'coordinator' oozie id.
+  Return: the Oozie workflow of coordinator or raise an exception
+
+  Notice: its gets an id in input and returns the full object in output (not an id).
+  """
+  if job_id is not None:
+    if job_id.endswith('W'):
+      get_job = get_oozie().get_job
+    else:
+      get_job = get_oozie().get_coordinator
+
+    try:
+      oozie_job = get_job(job_id)
+    except RestException, ex:
+      raise PopupException(_("Error accessing Oozie job %s") % (job_id,),
+                           detail=ex._headers['oozie-error-message'])
+
+  if request.user.is_superuser or oozie_job.user == request.user.username:
+    return oozie_job
+  else:
+    message = _("Permission denied. %(username)s don't have the permissions to access job %(id)s") % \
+        {'username': request.user.username, 'id': oozie_job.id}
+    access_warn(request, message)
+    raise PopupException(message)

+ 32 - 20
apps/oozie/src/oozie/views/editor.py

@@ -46,6 +46,17 @@ from oozie.conf import SHARE_JOBS
 LOG = logging.getLogger(__name__)
 
 
+"""
+Permissions:
+
+A Workflow/Coordinator can be accessed/submitted by its owner, a superuser or by anyone if its 'is_shared'
+property and SHARE_JOBS are set to True.
+
+A Workflow/Coordinator can be modified only by its owner or a superuser.
+
+Permissions checking happens by adding the decorators.
+"""
+
 def can_access_job(request, job_id):
   """
   Logic for testing if a user can access a certain Workflow / Coordinator.
@@ -54,15 +65,14 @@ def can_access_job(request, job_id):
     return
   try:
     job = Job.objects.select_related().get(pk=job_id).get_full_node()
-    if not SHARE_JOBS.get() and not request.user.is_superuser \
-      and job.owner != request.user.username:
-      # TODO is shared perms
+    if request.user.is_superuser or job.owner == request.user.username or (SHARE_JOBS.get() and job.is_shared):
+      return job
+    else:
       message = _("Permission denied. %(username)s don't have the permissions to access job %(id)s") % \
           {'username': request.user.username, 'id': job.id}
       access_warn(request, message)
       raise PopupException(message)
-    else:
-      return job
+
   except Job.DoesNotExist:
     raise PopupException(_('job %(id)s not found') % {'id': job_id})
 
@@ -77,24 +87,26 @@ def check_job_modification(request, job):
     raise PopupException(_('Not allowed to modified this job'))
 
 
-def check_job_modification_permission(view_func):
+def check_job_modification_permission(authorize_get=False):
   """
   Decorator ensuring that the user has the permissions to modify a workflow or coordinator.
 
   Need to appear below @check_job_access_permission
   """
-  def decorate(request, *args, **kwargs):
-    if 'workflow' in kwargs:
-      job_type = 'workflow'
-    else:
-      job_type = 'coordinator'
+  def inner(view_func):
+    def decorate(request, *args, **kwargs):
+      if 'workflow' in kwargs:
+        job_type = 'workflow'
+      else:
+        job_type = 'coordinator'
 
-    job = kwargs.get(job_type)
-    if job is not None:
-      check_job_modification(request, job)
+      job = kwargs.get(job_type)
+      if job is not None and not (authorize_get and request.method == 'GET'):
+        check_job_modification(request, job)
 
-    return view_func(request, *args, **kwargs)
-  return wraps(view_func)(decorate)
+      return view_func(request, *args, **kwargs)
+    return wraps(view_func)(decorate)
+  return inner
 
 
 def check_job_access_permission(view_func):
@@ -232,7 +244,7 @@ def edit_workflow(request, workflow):
 
 
 @check_job_access_permission
-@check_job_modification_permission
+@check_job_modification_permission()
 def delete_workflow(request, workflow):
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
@@ -452,7 +464,7 @@ def create_coordinator(request, workflow=None):
 
 
 @check_job_access_permission
-@check_job_modification_permission
+@check_job_modification_permission(True)
 def edit_coordinator(request, coordinator):
   history = History.objects.filter(submitter=request.user, job=coordinator)
 
@@ -500,7 +512,7 @@ def edit_coordinator(request, coordinator):
 
 
 @check_job_access_permission
-@check_job_modification_permission
+@check_job_modification_permission()
 def create_coordinator_dataset(request, coordinator):
   """Returns {'status' 0/1, data:html or url}"""
 
@@ -531,7 +543,7 @@ def create_coordinator_dataset(request, coordinator):
 
 
 @check_job_access_permission
-@check_job_modification_permission
+@check_job_modification_permission()
 def create_coordinator_data(request, coordinator, data_type):
   """Returns {'status' 0/1, data:html or url}"""
 

+ 0 - 1
desktop/libs/liboozie/src/liboozie/oozie_api.py

@@ -122,7 +122,6 @@ class OozieApi(object):
     if jobtype == 'wf':
       wf_list = WorkflowList(self, resp, filters=kwargs)
     else:
-      print resp
       wf_list = CoordinatorList(self, resp, filters=kwargs)
     return wf_list