Skip to content

Commit

Permalink
HUE-864 [oozie] Allow a read only user on the dashboard
Browse files Browse the repository at this point in the history
Add a 'dashboard_jobs_access' that enable users to see all the jobs in the Dashboard.
Add tests.
Don't run tests as superuser anymore.
Add 'add_permission' test util.
i18n all PERMISSION_ACTIONS.

Permissions:

A Workflow/Coordinator can:
  * be accessed only by its owner or a superuser or by a user with 'dashboard_jobs_access' permissions
  * be submitted/modified only by its owner or a superuser

Permissions checking happens by calling:
  * check_job_access_permission()
  * check_job_edition_permission()
  • Loading branch information
romainr committed Oct 11, 2012
1 parent fb7e1c6 commit 8b4a015
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 123 deletions.
4 changes: 4 additions & 0 deletions apps/oozie/src/oozie/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@

REQUIRES_HADOOP = True
IS_URL_NAMESPACED = True

PERMISSION_ACTIONS = (
("dashboard_jobs_access", "Oozie Dashboard read-only user for all jobs"),
)
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ ${ layout.menubar(section='dashboard') }
</div>
% endfor
</div>
% endif
% endif

% if has_job_edition_permission(oozie_coordinator, user):
<div class="row-fluid">
<div class="span3">${ _('Manage') }</div>
<div class="span6">
Expand All @@ -113,6 +114,7 @@ ${ layout.menubar(section='dashboard') }
</form>
</div>
</div>
% endif
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,17 @@ ${layout.menubar(section='dashboard')}
<td>${ job.user }</td>
<td><a href="${ job.get_absolute_url() }" data-row-selector="true"></a>${ job.id }</td>
<td>
<a title="${_('Kill %(coordinator)s') % dict(coordinator=job.id)}"
class="btn small confirmationModal"
alt="${ _('Are you sure you want to kill coordinator %s?') % job.id }"
href="javascript:void(0)"
data-message="${ _('The coordinator was killed!') }"
data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }"
data-url="${ url('oozie:manage_oozie_jobs', job_id=job.id, action='kill') }">
${ _('Kill') }
</a>
% if has_job_edition_permission(job, user):
<a title="${_('Kill %(coordinator)s') % dict(coordinator=job.id)}"
class="btn small confirmationModal"
alt="${ _('Are you sure you want to kill coordinator %s?') % job.id }"
href="javascript:void(0)"
data-message="${ _('The coordinator was killed!') }"
data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }"
data-url="${ url('oozie:manage_oozie_jobs', job_id=job.id, action='kill') }">
${ _('Kill') }
</a>
% endif
</td>
</tr>
%endfor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ ${ layout.menubar(section='dashboard') }
% endfor
% endif

% if has_job_edition_permission(oozie_workflow, user):
<div class="row-fluid">
<div class="span3">
${ _('Manage') }
Expand All @@ -132,6 +133,7 @@ ${ layout.menubar(section='dashboard') }
</form>
</div>
</div>
% endif

<br/><br/>

Expand Down
20 changes: 11 additions & 9 deletions apps/oozie/src/oozie/templates/dashboard/list_oozie_workflows.mako
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,17 @@ ${ layout.menubar(section='dashboard') }
<td>${ job.user }</td>
<td><a href="${ job.get_absolute_url() }" data-row-selector="true"></a>${ job.id }</td>
<td>
<a title="${_('Kill %(workflow)s') % dict(workflow=job.id)}"
class="btn small confirmationModal"
alt="${ _('Are you sure you want to kill workflow %s?') % job.id }"
href="javascript:void(0)"
data-url="${ url('oozie:manage_oozie_jobs', job_id=job.id, action='kill') }"
data-message="${ _('The workflow was killed!') }"
data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }">
${ _('Kill') }
</a>
% if has_job_edition_permission(job, user):
<a title="${_('Kill %(workflow)s') % dict(workflow=job.id)}"
class="btn small confirmationModal"
alt="${ _('Are you sure you want to kill workflow %s?') % job.id }"
href="javascript:void(0)"
data-url="${ url('oozie:manage_oozie_jobs', job_id=job.id, action='kill') }"
data-message="${ _('The workflow was killed!') }"
data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }">
${ _('Kill') }
</a>
% endif
</td>
</tr>
% endfor
Expand Down
190 changes: 117 additions & 73 deletions apps/oozie/src/oozie/tests.py

Large diffs are not rendered by default.

54 changes: 42 additions & 12 deletions apps/oozie/src/oozie/views/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

from oozie.conf import OOZIE_JOBS_COUNT
from oozie.models import History, Job
from oozie.settings import DJANGO_APPS


LOG = logging.getLogger(__name__)
Expand All @@ -41,15 +42,22 @@
"""
Permissions:
A Workflow/Coordinator can be accessed/submitted/modified only by its owner or a superuser.
A Workflow/Coordinator can:
* be accessed only by its owner or a superuser or by a user with 'dashboard_jobs_access' permissions
* be submitted/modified only by its owner or a superuser
Permissions checking happens by calling check_access_and_get_oozie_job().
Permissions checking happens by calling:
* check_job_access_permission()
* check_job_edition_permission()
"""


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)
job = check_job_access_permission(request, job_id)
check_job_edition_permission(job, request.user)

response = {'status': -1, 'data': ''}

Expand All @@ -76,37 +84,39 @@ def decorate(request, *args, **kwargs):
@show_oozie_error
def list_oozie_workflows(request):
kwargs = {'cnt': OOZIE_JOBS_COUNT.get(),}
if not request.user.is_superuser:
if not has_dashboard_jobs_access(request.user):
kwargs['user'] = request.user.username

workflows = get_oozie().get_workflows(**kwargs)

return render('dashboard/list_oozie_workflows.mako', request, {
'user': request.user,
'jobs': split_oozie_jobs(workflows.jobs),
'has_job_edition_permission': has_job_edition_permission,
})


@show_oozie_error
def list_oozie_coordinators(request):
kwargs = {'cnt': OOZIE_JOBS_COUNT.get(),}
if not request.user.is_superuser:
if not has_dashboard_jobs_access(request.user):
kwargs['user'] = request.user.username

coordinators = get_oozie().get_coordinators(**kwargs)

return render('dashboard/list_oozie_coordinators.mako', request, {
'jobs': split_oozie_jobs(coordinators.jobs),
'has_job_edition_permission': has_job_edition_permission,
})


@show_oozie_error
def list_oozie_workflow(request, job_id, coordinator_job_id=None):
oozie_workflow = check_access_and_get_oozie_job(request, job_id)
oozie_workflow = check_job_access_permission(request, job_id)

oozie_coordinator = None
if coordinator_job_id is not None:
oozie_coordinator = check_access_and_get_oozie_job(request, coordinator_job_id)
oozie_coordinator = check_job_access_permission(request, coordinator_job_id)

history = History.cross_reference_submission_history(request.user, job_id, coordinator_job_id)

Expand All @@ -125,20 +135,20 @@ def list_oozie_workflow(request, job_id, coordinator_job_id=None):
if param in oozie_workflow.conf_dict:
parameters[param] = oozie_workflow.conf_dict[param]


return render('dashboard/list_oozie_workflow.mako', request, {
'history': history,
'oozie_workflow': oozie_workflow,
'oozie_coordinator': oozie_coordinator,
'hue_workflow': hue_workflow,
'hue_coord': hue_coord,
'parameters': parameters,
'has_job_edition_permission': has_job_edition_permission,
})


@show_oozie_error
def list_oozie_coordinator(request, job_id):
oozie_coordinator = check_access_and_get_oozie_job(request, job_id)
oozie_coordinator = check_job_access_permission(request, job_id)

# Cross reference the submission history (if any)
coordinator = None
Expand All @@ -150,14 +160,15 @@ def list_oozie_coordinator(request, job_id):
return render('dashboard/list_oozie_coordinator.mako', request, {
'oozie_coordinator': oozie_coordinator,
'coordinator': coordinator,
'has_job_edition_permission': has_job_edition_permission,
})


@show_oozie_error
def list_oozie_workflow_action(request, action):
try:
action = get_oozie().get_action(action)
workflow = check_access_and_get_oozie_job(request, action.id.split('@')[0])
workflow = check_job_access_permission(request, action.id.split('@')[0])
except RestException, ex:
raise PopupException(_("Error accessing Oozie action %s") % (action,),
detail=ex.message)
Expand Down Expand Up @@ -189,7 +200,7 @@ def split_oozie_jobs(oozie_jobs):
return jobs


def check_access_and_get_oozie_job(request, job_id):
def check_job_access_permission(request, job_id):
"""
Decorator ensuring that the user has access to the workflow or coordinator.
Expand All @@ -210,10 +221,29 @@ def check_access_and_get_oozie_job(request, job_id):
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:
if request.user.is_superuser \
or oozie_job.user == request.user.username \
or has_dashboard_jobs_access(request.user):
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)


def check_job_edition_permission(oozie_job, user):
if has_job_edition_permission(oozie_job, user):
return oozie_job
else:
message = _("Permission denied. %(username)s don't have the permissions to modify job %(id)s") % \
{'username': user.username, 'id': oozie_job.id}
raise PopupException(message)


def has_job_edition_permission(oozie_job, user):
return user.is_superuser or oozie_job.user == user.username


def has_dashboard_jobs_access(user):
return user.is_superuser or user.has_hue_permission(action="dashboard_jobs_access", app=DJANGO_APPS[0])
7 changes: 4 additions & 3 deletions apps/useradmin/src/useradmin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,18 @@
in Django 1.2) to manipulate this row by row. This does not map nicely
onto actions which may not relate to database models.
"""
from enum import Enum
import logging

from django.db import models
from django.contrib.auth import models as auth_models
from django.utils.translation import ugettext_lazy as _t

from desktop import appmanager
from desktop.lib.exceptions import PopupException
from enum import Enum

import useradmin.conf

from django.utils.translation import ugettext_lazy as _

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -121,7 +122,7 @@ def check_hue_permission(self, perm=None, app=None, action=None):
if self.has_hue_permission(perm):
return
else:
raise PopupException(_("You do not have permissions to %(description)s.") % dict(description=perm.description))
raise PopupException(_t("You do not have permissions to %(description)s.") % dict(description=perm.description))

def get_profile(user):
"""
Expand Down
11 changes: 9 additions & 2 deletions desktop/core/src/desktop/lib/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@
from django.contrib.auth.models import Group, User
from useradmin.models import HuePermission, GroupPermission


def grant_access(username, groupname, appname):
add_permission(username, groupname, 'access', appname)


def add_permission(username, groupname, permname, appname):
user = User.objects.get(username=username)

group, created = Group.objects.get_or_create(name=groupname)
perm = HuePermission.objects.get(app=appname, action='access')
perm, created = HuePermission.objects.get_or_create(app=appname, action=permname)
GroupPermission.objects.get_or_create(group=group, hue_permission=perm)
user = User.objects.get(username=username)

if not user.groups.filter(name=group.name).exists():
user.groups.add(group)
user.save()
13 changes: 4 additions & 9 deletions desktop/libs/hadoop/src/hadoop/pseudo_hdfs4.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import atexit
import getpass
import logging
import pwd
import os
import shutil
import signal
Expand Down Expand Up @@ -60,7 +59,7 @@ class PseudoHdfs4(object):

def __init__(self):
self._tmpdir = tempfile.mkdtemp(prefix='tmp_hue_')
self._superuser = pwd.getpwuid(os.getuid()).pw_name
self._superuser = getpass.getuser()
self._fs = None
self._jt = None

Expand Down Expand Up @@ -420,14 +419,10 @@ def _write_core_site(self):
'fs.default.name': self._fs_default_name,
'hadoop.security.authorization': 'true',
'hadoop.security.authentication': 'simple',
'hadoop.proxyuser.%s.groups' % (self.superuser,): 'users,supergroup',
'hadoop.proxyuser.%s.hosts' % (self.superuser,): 'localhost',
'hadoop.proxyuser.hue.hosts': '*',
'hadoop.proxyuser.hue.hosts': '*',
'hadoop.proxyuser.hue.groups': '*',
'hadoop.proxyuser.oozie.hosts': '*',
'hadoop.proxyuser.oozie.groups': '*',
'hadoop.proxyuser.%s.hosts' % getpass.getuser(): '*',
'hadoop.proxyuser.%s.groups' % getpass.getuser(): '*',
'hadoop.proxyuser.%s.hosts' % (getpass.getuser(),): '*',
'hadoop.proxyuser.%s.groups' % (getpass.getuser(),): '*',
'hadoop.tmp.dir': self._tmppath('hadoop_tmp_dir'),
}
write_config(core_configs, self._tmppath('conf/core-site.xml'))
Expand Down
8 changes: 3 additions & 5 deletions desktop/libs/liboozie/src/liboozie/oozie_api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,21 @@ def wait_until_completion(cls, oozie_jobid, timeout=300.0, step=5):
job = cls.oozie.get_job(oozie_jobid)
start = time.time()

LOG.info('[%d] cluster status: %s' % (time.time(), cls.cluster.jt.cluster_status()))

while job.is_running() and time.time() - start < timeout:
time.sleep(step)
LOG.info('Checking status of %s...' % oozie_jobid)
job = cls.oozie.get_job(oozie_jobid)
LOG.info('[%d] Status after %d: %s' % (time.time(), time.time() - start, job))

logs = cls.oozie.get_job_log(oozie_jobid)

if job.is_running():
logs = cls.oozie.get_job_log(oozie_jobid)
msg = "[%d] %s took more than %d to complete: %s" % (time.time(), oozie_jobid, timeout, logs)
LOG.info(msg)
raise Exception(msg)
else:
LOG.info('[%d] Job duration %s: %d' % (time.time(), job.id, time.time() - start))
LOG.info('[%d] Job %s tooke %d: %s' % (time.time(), job.id, time.time() - start, logs))

LOG.info('[%d] cluster status: %s' % (time.time(), cls.cluster.jt.cluster_status()))
return job

@classmethod
Expand Down

0 comments on commit 8b4a015

Please sign in to comment.