Browse Source

HUE-3464 [oozie] Add granular permission to enable only the dashboard

If a user is in a group than has access to Oozie but also has a permission
'disable_editor_access', it will be able to access only the dashboard.
Romain Rigaux 9 years ago
parent
commit
ff33fdb83d

+ 8 - 0
apps/oozie/src/oozie/decorators.py

@@ -92,6 +92,14 @@ def check_document_modify_permission():
   return inner
   return inner
 
 
 
 
+def check_editor_access_permission(view_func):
+
+  def decorate(request, *args, **kwargs):
+    if not request.user.is_superuser and request.user.has_hue_permission(action="disable_editor_access", app="oozie"):
+      raise PopupException(_('Missing permission to access the Oozie Editor'), error_code=401)
+    return view_func(request, *args, **kwargs)
+  return wraps(view_func)(decorate)
+
 
 
 ## Oozie v1 below
 ## Oozie v1 below
 
 

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

@@ -24,4 +24,5 @@ IS_URL_NAMESPACED = True
 
 
 PERMISSION_ACTIONS = (
 PERMISSION_ACTIONS = (
   ("dashboard_jobs_access", "Oozie Dashboard read-only user for all jobs"),
   ("dashboard_jobs_access", "Oozie Dashboard read-only user for all jobs"),
+  ("disable_editor_access", "Disable Oozie Editor access"),
 )
 )

+ 30 - 0
apps/oozie/src/oozie/tests2.py

@@ -25,12 +25,14 @@ from django.db.models import Q
 
 
 from nose.tools import assert_true, assert_false, assert_equal, assert_not_equal
 from nose.tools import assert_true, assert_false, assert_equal, assert_not_equal
 
 
+from desktop.lib.test_utils import add_permission, remove_from_group
 from desktop.models import Document2
 from desktop.models import Document2
 
 
 from oozie.conf import ENABLE_V2
 from oozie.conf import ENABLE_V2
 from oozie.importlib.workflows import generate_v2_graph_nodes
 from oozie.importlib.workflows import generate_v2_graph_nodes
 from oozie.models2 import Workflow, find_dollar_variables, find_dollar_braced_variables, Node, _create_graph_adjaceny_list, _get_hierarchy_from_adj_list
 from oozie.models2 import Workflow, find_dollar_variables, find_dollar_braced_variables, Node, _create_graph_adjaceny_list, _get_hierarchy_from_adj_list
 from oozie.tests import OozieMockBase, save_temp_workflow, MockOozieApi
 from oozie.tests import OozieMockBase, save_temp_workflow, MockOozieApi
+from desktop.lib.django_test_util import make_logged_in_client
 
 
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
@@ -361,6 +363,34 @@ LIMIT $limit"""))
     wf_doc2.delete()
     wf_doc2.delete()
     wf_doc3.delete()
     wf_doc3.delete()
 
 
+  def test_editor_access_permissions(self):
+    group = 'no_editor'
+
+    try:
+      # Block editor section
+      response = self.c.get(reverse('oozie:list_editor_workflows'))
+      assert_equal(response.status_code, 200)
+      response = self.c.get(reverse('oozie:list_workflows'))
+      assert_equal(response.status_code, 200)
+
+      add_permission('test', 'no_editor', 'disable_editor_access', 'oozie')
+
+      response = self.c.get(reverse('oozie:list_editor_workflows'))
+      assert_equal(response.status_code, 401)
+      response = self.c.get(reverse('oozie:list_workflows'))
+      assert_equal(response.status_code, 200)
+
+      # Admin are not affected
+      admin = make_logged_in_client('admin', 'admin', is_superuser=True, recreate=True, groupname=group)
+
+      response = admin.get(reverse('oozie:list_editor_workflows'))
+      assert_equal(response.status_code, 200)
+      response = admin.get(reverse('oozie:list_workflows'))
+      assert_equal(response.status_code, 200)
+    finally:
+      remove_from_group("test", group)
+
+
 class TestExternalWorkflowGraph():
 class TestExternalWorkflowGraph():
 
 
   def setUp(self):
   def setUp(self):

+ 32 - 3
apps/oozie/src/oozie/views/editor2.py

@@ -36,7 +36,8 @@ from liboozie.credentials import Credentials
 from liboozie.oozie_api import get_oozie
 from liboozie.oozie_api import get_oozie
 from liboozie.submission2 import Submission
 from liboozie.submission2 import Submission
 
 
-from oozie.decorators import check_document_access_permission, check_document_modify_permission
+from oozie.decorators import check_document_access_permission, check_document_modify_permission,\
+  check_editor_access_permission
 from oozie.forms import ParameterForm
 from oozie.forms import ParameterForm
 from oozie.models import Workflow as OldWorklow, Coordinator as OldCoordinator, Bundle as OldBundle, Job
 from oozie.models import Workflow as OldWorklow, Coordinator as OldCoordinator, Bundle as OldBundle, Job
 from oozie.models2 import Node, Workflow, Coordinator, Bundle, NODES, WORKFLOW_NODE_PROPERTIES, import_workflow_from_hue_3_7,\
 from oozie.models2 import Node, Workflow, Coordinator, Bundle, NODES, WORKFLOW_NODE_PROPERTIES, import_workflow_from_hue_3_7,\
@@ -48,7 +49,7 @@ from oozie.views.editor import edit_workflow as old_edit_workflow, edit_coordina
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
 
 
-
+@check_editor_access_permission
 def list_editor_workflows(request):
 def list_editor_workflows(request):
   workflows = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='workflow2')]
   workflows = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='workflow2')]
 
 
@@ -61,6 +62,7 @@ def list_editor_workflows(request):
   })
   })
 
 
 
 
+@check_editor_access_permission
 def open_old_workflow(request):
 def open_old_workflow(request):
   doc_id = request.GET.get('workflow')
   doc_id = request.GET.get('workflow')
   workflow = Document.objects.get(id=doc_id).content_object.get_full_node()
   workflow = Document.objects.get(id=doc_id).content_object.get_full_node()
@@ -73,6 +75,7 @@ def open_old_workflow(request):
     return old_edit_workflow(request, workflow=workflow.id)
     return old_edit_workflow(request, workflow=workflow.id)
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def edit_workflow(request):
 def edit_workflow(request):
   workflow_id = request.GET.get('workflow')
   workflow_id = request.GET.get('workflow')
@@ -116,6 +119,7 @@ def _edit_workflow(request, doc, workflow):
   })
   })
 
 
 
 
+@check_editor_access_permission
 def new_workflow(request):
 def new_workflow(request):
   doc = None
   doc = None
   workflow = Workflow()
   workflow = Workflow()
@@ -125,6 +129,7 @@ def new_workflow(request):
   return _edit_workflow(request, doc, workflow)
   return _edit_workflow(request, doc, workflow)
 
 
 
 
+@check_editor_access_permission
 def delete_job(request):
 def delete_job(request):
   if request.method != 'POST':
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
     raise PopupException(_('A POST request is required.'))
@@ -154,6 +159,7 @@ def delete_job(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def copy_workflow(request):
 def copy_workflow(request):
   if request.method != 'POST':
   if request.method != 'POST':
@@ -192,6 +198,7 @@ def _import_workspace(fs, user, workflow):
   workflow.import_workspace(fs, source_workspace_dir, user)
   workflow.import_workspace(fs, source_workspace_dir, user)
 
 
 
 
+@check_editor_access_permission
 @check_document_modify_permission()
 @check_document_modify_permission()
 def save_workflow(request):
 def save_workflow(request):
   response = {'status': -1}
   response = {'status': -1}
@@ -236,6 +243,7 @@ def save_workflow(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 def new_node(request):
 def new_node(request):
   response = {'status': -1}
   response = {'status': -1}
 
 
@@ -264,6 +272,7 @@ def _get_workflows(user):
     ]
     ]
 
 
 
 
+@check_editor_access_permission
 def add_node(request):
 def add_node(request):
   response = {'status': -1}
   response = {'status': -1}
 
 
@@ -284,6 +293,7 @@ def add_node(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 def action_parameters(request):
 def action_parameters(request):
   response = {'status': -1}
   response = {'status': -1}
   parameters = set()
   parameters = set()
@@ -313,6 +323,7 @@ def action_parameters(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def workflow_parameters(request):
 def workflow_parameters(request):
   response = {'status': -1}
   response = {'status': -1}
@@ -328,6 +339,7 @@ def workflow_parameters(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 def gen_xml_workflow(request):
 def gen_xml_workflow(request):
   response = {'status': -1}
   response = {'status': -1}
 
 
@@ -344,6 +356,7 @@ def gen_xml_workflow(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def submit_workflow(request, doc_id):
 def submit_workflow(request, doc_id):
   workflow = Workflow(document=Document2.objects.get(id=doc_id))
   workflow = Workflow(document=Document2.objects.get(id=doc_id))
@@ -351,6 +364,7 @@ def submit_workflow(request, doc_id):
   return _submit_workflow_helper(request, workflow, submit_action=reverse('oozie:editor_submit_workflow', kwargs={'doc_id': workflow.id}))
   return _submit_workflow_helper(request, workflow, submit_action=reverse('oozie:editor_submit_workflow', kwargs={'doc_id': workflow.id}))
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def submit_single_action(request, doc_id, node_id):
 def submit_single_action(request, doc_id, node_id):
   parent_doc = Document2.objects.get(id=doc_id)
   parent_doc = Document2.objects.get(id=doc_id)
@@ -419,7 +433,7 @@ def _submit_workflow(user, fs, jt, workflow, mapping):
   return redirect(reverse('oozie:list_oozie_workflow', kwargs={'job_id': job_id}))
   return redirect(reverse('oozie:list_oozie_workflow', kwargs={'job_id': job_id}))
 
 
 
 
-
+@check_editor_access_permission
 def list_editor_coordinators(request):
 def list_editor_coordinators(request):
   coordinators = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='coordinator2')]
   coordinators = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='coordinator2')]
 
 
@@ -432,6 +446,7 @@ def list_editor_coordinators(request):
   })
   })
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def edit_coordinator(request):
 def edit_coordinator(request):
   coordinator_id = request.GET.get('coordinator', request.GET.get('uuid'))
   coordinator_id = request.GET.get('coordinator', request.GET.get('uuid'))
@@ -476,10 +491,12 @@ def edit_coordinator(request):
   })
   })
 
 
 
 
+@check_editor_access_permission
 def new_coordinator(request):
 def new_coordinator(request):
   return edit_coordinator(request)
   return edit_coordinator(request)
 
 
 
 
+@check_editor_access_permission
 def open_old_coordinator(request):
 def open_old_coordinator(request):
   doc_id = request.GET.get('coordinator')
   doc_id = request.GET.get('coordinator')
   coordinator_id = Document.objects.get(id=doc_id).object_id
   coordinator_id = Document.objects.get(id=doc_id).object_id
@@ -487,6 +504,7 @@ def open_old_coordinator(request):
   return old_edit_coordinator(request, coordinator=coordinator_id)
   return old_edit_coordinator(request, coordinator=coordinator_id)
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def copy_coordinator(request):
 def copy_coordinator(request):
   if request.method != 'POST':
   if request.method != 'POST':
@@ -514,6 +532,7 @@ def copy_coordinator(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 @check_document_modify_permission()
 @check_document_modify_permission()
 def save_coordinator(request):
 def save_coordinator(request):
   response = {'status': -1}
   response = {'status': -1}
@@ -546,6 +565,7 @@ def save_coordinator(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 def gen_xml_coordinator(request):
 def gen_xml_coordinator(request):
   response = {'status': -1}
   response = {'status': -1}
 
 
@@ -559,6 +579,7 @@ def gen_xml_coordinator(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def coordinator_parameters(request):
 def coordinator_parameters(request):
   response = {'status': -1}
   response = {'status': -1}
@@ -574,6 +595,7 @@ def coordinator_parameters(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def submit_coordinator(request, doc_id):
 def submit_coordinator(request, doc_id):
   coordinator = Coordinator(document=Document2.objects.get(id=doc_id))
   coordinator = Coordinator(document=Document2.objects.get(id=doc_id))
@@ -622,6 +644,7 @@ def _submit_coordinator(request, coordinator, mapping):
     raise PopupException(_("Error submitting coordinator %s") % (coordinator,), detail=ex._headers.get('oozie-error-message', ex))
     raise PopupException(_("Error submitting coordinator %s") % (coordinator,), detail=ex._headers.get('oozie-error-message', ex))
 
 
 
 
+@check_editor_access_permission
 def list_editor_bundles(request):
 def list_editor_bundles(request):
   bundles = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='bundle2')]
   bundles = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='bundle2')]
 
 
@@ -634,6 +657,7 @@ def list_editor_bundles(request):
   })
   })
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def edit_bundle(request):
 def edit_bundle(request):
   bundle_id = request.GET.get('bundle')
   bundle_id = request.GET.get('bundle')
@@ -657,10 +681,12 @@ def edit_bundle(request):
   })
   })
 
 
 
 
+@check_editor_access_permission
 def new_bundle(request):
 def new_bundle(request):
   return edit_bundle(request)
   return edit_bundle(request)
 
 
 
 
+@check_editor_access_permission
 def open_old_bundle(request):
 def open_old_bundle(request):
   doc_id = request.GET.get('bundle')
   doc_id = request.GET.get('bundle')
   bundle_id = Document.objects.get(id=doc_id).object_id
   bundle_id = Document.objects.get(id=doc_id).object_id
@@ -668,6 +694,7 @@ def open_old_bundle(request):
   return old_edit_bundle(request, bundle=bundle_id)
   return old_edit_bundle(request, bundle=bundle_id)
 
 
 
 
+@check_editor_access_permission
 @check_document_modify_permission()
 @check_document_modify_permission()
 def save_bundle(request):
 def save_bundle(request):
   response = {'status': -1}
   response = {'status': -1}
@@ -700,6 +727,7 @@ def save_bundle(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def copy_bundle(request):
 def copy_bundle(request):
   if request.method != 'POST':
   if request.method != 'POST':
@@ -727,6 +755,7 @@ def copy_bundle(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@check_editor_access_permission
 @check_document_access_permission()
 @check_document_access_permission()
 def submit_bundle(request, doc_id):
 def submit_bundle(request, doc_id):
   bundle = Bundle(document=Document2.objects.get(id=doc_id))
   bundle = Bundle(document=Document2.objects.get(id=doc_id))

+ 2 - 1
apps/useradmin/src/useradmin/models.py

@@ -279,7 +279,8 @@ def update_app_permissions(**kwargs):
         if not (new_dp.app == 'useradmin' and new_dp.action == 'access') and \
         if not (new_dp.app == 'useradmin' and new_dp.action == 'access') and \
            not (new_dp.app == 'metastore' and new_dp.action == 'write') and \
            not (new_dp.app == 'metastore' and new_dp.action == 'write') and \
            not (new_dp.app == 'hbase' and new_dp.action == 'write') and \
            not (new_dp.app == 'hbase' and new_dp.action == 'write') and \
-           not (new_dp.app == 'security' and new_dp.action == 'impersonate'):
+           not (new_dp.app == 'security' and new_dp.action == 'impersonate') and \
+           not (new_dp.app == 'oozie' and new_dp.action == 'disable_editor_access'):
           GroupPermission.objects.create(group=default_group, hue_permission=new_dp)
           GroupPermission.objects.create(group=default_group, hue_permission=new_dp)
 
 
     available = HuePermission.objects.count()
     available = HuePermission.objects.count()

+ 8 - 0
desktop/core/src/desktop/lib/test_utils.py

@@ -54,6 +54,14 @@ def add_to_group(username, groupname=None):
         user.groups.add(group)
         user.groups.add(group)
         user.save()
         user.save()
 
 
+def remove_from_group(username, groupname):
+    user = User.objects.get(username=username)
+    group, created = Group.objects.get_or_create(name=groupname)
+
+    if not user.groups.filter(name=group.name).exists():
+        user.groups.remove(group)
+        user.save()
+
 
 
 def reformat_json(json_obj):
 def reformat_json(json_obj):
     if isinstance(json_obj, basestring):
     if isinstance(json_obj, basestring):

+ 2 - 0
desktop/core/src/desktop/templates/common_header.mako

@@ -561,6 +561,7 @@ if USE_NEW_EDITOR.get():
                <li><a href="${url('oozie:list_oozie_bundles')}"><img src="${ static('oozie/art/icon_oozie_bundle_48.png') }" class="app-icon" /> ${_('Bundles')}</a></li>
                <li><a href="${url('oozie:list_oozie_bundles')}"><img src="${ static('oozie/art/icon_oozie_bundle_48.png') }" class="app-icon" /> ${_('Bundles')}</a></li>
              </ul>
              </ul>
            </li>
            </li>
+           % if not user.has_hue_permission(action="disable_editor_access", app="oozie") or user.is_superuser:
            <% from oozie.conf import ENABLE_V2 %>
            <% from oozie.conf import ENABLE_V2 %>
            % if not ENABLE_V2.get():
            % if not ENABLE_V2.get():
            <li class="dropdown-submenu">
            <li class="dropdown-submenu">
@@ -581,6 +582,7 @@ if USE_NEW_EDITOR.get():
              </ul>
              </ul>
            </li>
            </li>
            % endif
            % endif
+           % endif
          </ul>
          </ul>
        </li>
        </li>
        % endif
        % endif