Explorar o código

[security] Edit an HDFS acl

Romain Rigaux %!s(int64=11) %!d(string=hai) anos
pai
achega
c7ac355e2c

+ 25 - 11
apps/security/src/security/api/hdfs.py

@@ -21,22 +21,36 @@ from django.http import HttpResponse
 from django.utils.translation import ugettext as _
 
 
-def get_acls(request):  
+def _get_acl_name(acl):
+  return acl['type'] + ':' + acl['name'] + ':'
+
+def _get_acl(acl):
+  return _get_acl_name(acl) + ('r' if acl['r']  else '-') + ('w' if acl['w'] else '-') + ('x' if acl['x'] else '-')
+
+
+
+def get_acls(request):
   path = request.GET.get('path')
   acls = request.fs.get_acl_status(path)
   return HttpResponse(json.dumps(acls['AclStatus']), mimetype="application/json")
 
 
-def modify_acl_entries(request):  
-  path = request.GET.get('path')
-  aclspec = request.GET.get('aclspec')
-  info = request.fs.modify_acl_entries(path, aclspec)
-  return HttpResponse(json.dumps(info), mimetype="application/json")
+def update_acls(request):
+  path = request.POST.get('path')
+  acls = json.loads(request.POST.get('acls'))
 
+  _remove_acl_entries(request.fs, path, [acl for acl in acls if acl['status'] == 'deleted'])
+  _modify_acl_entries(request.fs, path, [acl for acl in acls if acl['status'] in ('new', 'modified')])
+
+  return HttpResponse(json.dumps({'status': 0}), mimetype="application/json")
+
+
+
+def _modify_acl_entries(fs, path, acls):
+  aclspec = ','.join([_get_acl(acl) for acl in acls])
+  return fs.modify_acl_entries(path, aclspec)
 
-def remove_acl_entries(request):  
-  path = request.GET.get('path')
-  aclspec = request.GET.get('aclspec')
-  info = request.fs.remove_acl_entries(path, aclspec)
-  return HttpResponse(json.dumps(info), mimetype="application/json")
 
+def _remove_acl_entries(fs, path, acls):
+  aclspec = ','.join([_get_acl_name(acl) for acl in acls])
+  return fs.remove_acl_entries(path, aclspec)

+ 8 - 8
apps/security/src/security/api/hive.py

@@ -70,11 +70,11 @@ def list_sentry_privileges_by_role(request):
   return HttpResponse(json.dumps(result), mimetype="application/json")
 
 
-def _hive_add_privileges(user, role, privileges):  
+def _hive_add_privileges(user, role, privileges):
     api = get_api(user)
-  
+
     _priviledges = {}
-  
+
     for priviledge in privileges:
       api.alter_sentry_role_grant_privilege(role['name'], {
           'privilegeScope': priviledge['privilegeScope'],
@@ -84,18 +84,18 @@ def _hive_add_privileges(user, role, privileges):
           'URI': priviledge['URI'],
           'action': priviledge['action']
       })
-  
+
     return _priviledges
 
 
-def hive_create_role(request):  
+def hive_create_role(request):
   result = {'status': -1, 'message': 'Error'}
 
   try:
     role = json.loads(request.POST['role'])
-    
+
     api = get_api(request.user)
-  
+
     api.create_sentry_role(role['name'])
     result['privileges'] = _hive_add_privileges(request.user, role, role['privileges'])
     api.alter_sentry_role_add_groups(role['name'], role['groups'])
@@ -110,7 +110,7 @@ def hive_create_role(request):
   return HttpResponse(json.dumps(result), mimetype="application/json")
 
 
-def hive_add_privileges(request):  
+def hive_add_privileges(request):
   result = {'status': -1, 'message': 'Error'}
 
   try:

+ 6 - 10
apps/security/src/security/templates/hdfs.mako

@@ -39,12 +39,12 @@ ${ layout.menubar(section='hdfs') }
     </div>
     <div class="span10">
       <div id="edit" class="section card card-small">
-        <h1 class="card-heading simple">${ _('Edit ACLs') }</h1>        
-        <div class="card-body">          
+        <h1 class="card-heading simple">${ _('Edit ACLs') }</h1>
+        <div class="card-body">
           <div>
             <input type="text" class="input-xxlarge" data-bind="value: $root.assist.path, valueUpdate:'afterkeydown'"/>
             <a class="btn btn-inverse" style="margin-left:10px", data-bind="attr: { href: '/filebrowser/view' + $root.assist.path() }" target="_blank" title="${ _('Open in File Browser') }">
-              <i class="fa fa-external-link"></i>                
+              <i class="fa fa-external-link"></i>
             </a>
           </div>
           <div>
@@ -57,7 +57,6 @@ ${ layout.menubar(section='hdfs') }
               <span data-bind="text: $root.assist.owner"></span>
               <span data-bind="text: $root.assist.group"></span>
               <div data-bind="foreach: $root.assist.acls">
-                ## Xeditable for edition?
                 <div data-bind="visible: status() != 'deleted'">
                   <input type="radio" value="group" data-bind="checked: type, attr: { name: 'aclType' + $index()} "/> ${ _('Group') }
                   <input type="radio" value="user" data-bind="checked: type, attr: { name: 'aclType' + $index()}"/> ${ _('User') }
@@ -74,13 +73,10 @@ ${ layout.menubar(section='hdfs') }
                 <i class="fa fa-plus"></i>
               </a>
               <div data-bind="visible: $root.assist.changed().length">
-                <button type="button" rel="tooltip" data-placement="bottom" data-original-title="${ _('Cancel') }" class="btn">
-                  <i class="fa fa-times"></i>
-                </button>
                 <button type="button" data-bind="click: $root.assist.updateAcls" rel="tooltip" data-placement="bottom" data-loading-text="${ _('Saving...') }" data-original-title="${ _('Save') }" class="btn">
                   <i class="fa fa-save"></i>
                 </button>
-              <div>              
+              <div>
             </div>
           </div>
         </div>
@@ -91,7 +87,7 @@ ${ layout.menubar(section='hdfs') }
       </div>
 
       <div id="listHistory" class="section  card card-small hide">
-        <div class="alert alert-info"><h3>${ _('History') }</h3></div>          
+        <div class="alert alert-info"><h3>${ _('History') }</h3></div>
       </div>
     </div>
 
@@ -107,7 +103,7 @@ ${ layout.menubar(section='hdfs') }
 
 <script type="text/javascript" charset="utf-8">
   var viewModel;
-  
+
   $(document).ready(function () {
     viewModel = new HdfsViewModel(${ assist | n,unicode });
     ko.applyBindings(viewModel);

+ 31 - 31
apps/security/src/security/templates/hive.mako

@@ -29,7 +29,7 @@ ${ layout.menubar(section='hive') }
 <script type="text/html" id="privilege-template">
   <select data-bind="options: availablePrivileges, value: privilegeScope"></select>
   <input type="text" data-bind="value: $data.serverName" placeholder="serverName"></input>
-  <input type="text" data-bind="value: $data.dbName" placeholder="dbName"></input>                
+  <input type="text" data-bind="value: $data.dbName" placeholder="dbName"></input>
   <input type="text" data-bind="value: $data.tableName" placeholder="tableName"></input>
   <input type="text" data-bind="value: $data.URI" placeholder="URI"></input>
   <i class="fa fa-minus"></i>
@@ -48,7 +48,7 @@ ${ layout.menubar(section='hive') }
             </br>
             <input type="checkbox" checked></input> All
             </br>
-            <select data-bind="options: $root.availableHadoopGroups" size="10" multiple="true"></select>            
+            <select data-bind="options: $root.availableHadoopGroups" size="10" multiple="true"></select>
           </li>
         </ul>
       </div>
@@ -57,11 +57,11 @@ ${ layout.menubar(section='hive') }
     <div class="span10">
 
       <div id="edit" class="mainSection card card-small">
-        <h1 class="card-heading simple">${ _('Edit') }</h1>        
+        <h1 class="card-heading simple">${ _('Edit') }</h1>
         <div class="card-body">
           <input type="text" class="input-xxlarge" data-bind="value: $root.assist.path, valueUpdate:'afterkeydown'"/>
           <a class="btn btn-inverse" style="margin-left:10px", data-bind="attr: { href: '/metastore/' + $root.assist.path() }" target="_blank" title="${ _('Open in Metastore Browser') }">
-            <i class="fa fa-external-link"></i>                
+            <i class="fa fa-external-link"></i>
           </a>
         </div>
         <div>
@@ -74,10 +74,10 @@ ${ layout.menubar(section='hive') }
               <div data-bind="text: $data, click: $root.list_sentry_privileges_for_provider"></div>
             </div>
           </div>
-        </div>        
+        </div>
       </div>
 
-      <div id="roles" class="mainSection hide card card-small">      
+      <div id="roles" class="mainSection hide card card-small">
         <div class="card-heading simple">
         <h3>${ _('Roles') }</h3>
 		  <%actionbar:render>
@@ -94,16 +94,16 @@ ${ layout.menubar(section='hive') }
 		    <%def name="creation()">
 		      <a href="javascript: void(0)" data-bind="click: function(){ $root.showCreateRole(true); }" class="btn"><i class="fa fa-plus-circle"></i> ${ _('Add') }</a>
 		    </%def>
-		  </%actionbar:render>        
+		  </%actionbar:render>
         </div>
-          
-          
-        <div class="card-body">                       
-          <div data-bind="with: $root.role, visible: showCreateRole">            
+
+
+        <div class="card-body">
+          <div data-bind="with: $root.role, visible: showCreateRole">
             <div class="span3">
               Name
               <input type="text" data-bind="value: $data.name"></input>
-            </div>                      
+            </div>
             <div class="span5">
               Privileges
               <div data-bind="template: { name: 'privilege-template', foreach: privileges}">
@@ -111,7 +111,7 @@ ${ layout.menubar(section='hive') }
               <a href="javascript: void(0)" data-bind="click: addPrivilege">
                 <i class="fa fa-plus"></i>
               </a>
-            </div>            
+            </div>
             <div class="span4">
               Groups
               <select data-bind="options: $root.availableHadoopGroups, selectedOptions: groups" size="5" multiple="true"></select>
@@ -122,14 +122,14 @@ ${ layout.menubar(section='hive') }
             <button type="button" rel="tooltip" data-placement="bottom" data-loading-text="${ _('Saving...') }" data-original-title="${ _('Save') }" class="btn"
                 data-bind="click: $root.role.create">
               <i class="fa fa-save"></i>
-            </button>            
-          </div>          
+            </button>
+          </div>
           <div>
-          </div>          
-        </div>           
-          
+          </div>
+        </div>
+
         </br></br>
-          
+
         <div>
         <table>
           <theader>
@@ -138,13 +138,13 @@ ${ layout.menubar(section='hive') }
             <th style="width:20%">${ _('Name') }</th>
             <th style="width:67%">${ _('Groups') }</th>
             <th style="width:10%">${ _('Grantor Principal') }</th>
-          </theader> 
-          <tbody data-bind="foreach: $root.roles">          
+          </theader>
+          <tbody data-bind="foreach: $root.roles">
             <tr>
               <td>
                 <input type="checkbox" data-bind="click: $root.role.remove"></input>
               </td>
-              <td>                                              
+              <td>
                 <a href="javascript:void(0);"><i class="fa fa-2x" data-bind="click: $root.list_sentry_privileges_by_role, css: {'fa-caret-right' : ! showPrivileges(), 'fa-caret-down': showPrivileges() }""></i></a>
               </td>
               <td data-bind="text: name"></td>
@@ -161,7 +161,7 @@ ${ layout.menubar(section='hive') }
               <td colspan="2"></td>
               <td colspan="3">
                 <span data-bind="text: name"></span>
-                <span data-bind="text: timestamp"></span>    
+                <span data-bind="text: timestamp"></span>
                 <a data-bind="attr: { href: '/metastore/' + database() }" target="_blank"><span data-bind="text: database"></span></a>
                 <span data-bind="text: action"></span>
                 <span data-bind="text: scope"></span>
@@ -191,10 +191,10 @@ ${ layout.menubar(section='hive') }
                 </a>
               </td>
             </tr>
-          </div>        
+          </div>
         </tbody>
         </div>
-        
+
       </div>
 
     </div>
@@ -214,14 +214,14 @@ ${ layout.menubar(section='hive') }
 
 <script type="text/javascript" charset="utf-8">
   var viewModel;
-  
+
   $(document).ready(function () {
     viewModel = new HiveViewModel(${ initial | n,unicode });
     ko.applyBindings(viewModel);
-    
+
     viewModel.init();
   });
-  
+
   function showMainSection(mainSection) {
     if ($("#" + mainSection).is(":hidden")) {
       $(".mainSection").hide();
@@ -231,16 +231,16 @@ ${ layout.menubar(section='hive') }
 
     logGA(mainSection);
   }
-  
+
   function highlightMainMenu(mainSection) {
     $(".nav.nav-list li").removeClass("active");
     $("a[href='#" + mainSection + "']").parent().addClass("active");
   }
-  
+
   routie({
     "edit": function () {
       showMainSection("edit");
-    },  
+    },
     "roles": function () {
       showMainSection("roles");
     },

+ 2 - 2
apps/security/src/security/templates/layout.mako

@@ -55,8 +55,8 @@ def is_selected(section, matcher):
 			    <button type="button" title="${ _('Edit') }" rel="tooltip" data-placement="bottom"><i class="fa fa-pencil"></i></button>
 			    <button type="button" title="${ _('Save') }" rel="tooltip" data-placement="bottom" data-loading-text="${ _("Saving...") }"><i class="fa fa-save"></i></button>
 			  % endif
-			</div>           
-            <ul class="nav">           
+			</div>
+            <ul class="nav">
               <li class="currentApp">
                 <a href="/${app_name}">
                   <i class="fa fa-lock"></i>

+ 2 - 4
apps/security/src/security/urls.py

@@ -27,16 +27,14 @@ urlpatterns = patterns('security.views',
 
 urlpatterns += patterns('security.api.hdfs',
   url(r'^api/hdfs/get_acls$', 'get_acls', name='get_acls'),
-  url(r'^api/hdfs/modify_acl_entries', 'modify_acl_entries', name='modify_acl_entries'),
-  url(r'^api/hdfs/remove_acl_entries', 'remove_acl_entries', name='remove_acl_entries'),
+  url(r'^api/hdfs/update_acls', 'update_acls', name='update_acls'),
 )
 
 
-
 urlpatterns += patterns('security.api.hive',
   url(r'^api/hive/list_sentry_roles_by_group', 'list_sentry_roles_by_group', name='list_sentry_roles_by_group'),
   url(r'^api/hive/list_sentry_privileges_by_role', 'list_sentry_privileges_by_role', name='list_sentry_privileges_by_role'),
-  url(r'^api/hive/list_sentry_privileges_for_provider$', 'list_sentry_privileges_for_provider', name='list_sentry_privileges_for_provider'),  
+  url(r'^api/hive/list_sentry_privileges_for_provider$', 'list_sentry_privileges_for_provider', name='list_sentry_privileges_for_provider'),
   url(r'^api/hive/create_sentry_role', 'create_sentry_role', name='create_sentry_role'),
   url(r'^api/hive/drop_sentry_role', 'drop_sentry_role', name='drop_sentry_role'),
   url(r'^api/hive/create_role$', 'hive_create_role', name='hive_create_role'),

+ 6 - 6
apps/security/src/security/views.py

@@ -26,23 +26,23 @@ from libsentry.api import get_api
 def get_hadoop_groups(): # Mock for now, maybe pull from LDAP
   return ['romain', 'sambashare', 'cdrom', 'lpadmin', 'admin', 'adm', 'lp', 'dialout', 'plugdev']
 
-  
+
 def hive(request):
   assist = autocomplete(request, database=None, table=None)
-  
+
 #    url(r'^api/autocomplete/$', 'autocomplete', name='api_autocomplete_databases'),
 #  url(r'^api/autocomplete/(?P<database>\w+)/$', 'autocomplete', name='api_autocomplete_tables'),
 #  url(r'^api/autocomplete/(?P<database>\w+)/(?P<table>\w+)$', 'autocomplete', name='api_autocomplete_columns'),
-  
-  return render("hive.mako", request, {      
+
+  return render("hive.mako", request, {
       'assist': assist,
       'initial': json.dumps({'hadoop_groups': get_hadoop_groups()}),
   })
 
 
 def hdfs(request):
-  
-  return render("hdfs.mako", request, {      
+
+  return render("hdfs.mako", request, {
       'assist': {},
       'hadoop_groups': get_hadoop_groups(),
   })

+ 53 - 36
apps/security/static/js/hdfs.ko.js

@@ -17,7 +17,7 @@
 function parseAcl(acl) {
   // ^(default:)?(user|group|mask|other):[[A-Za-z_][A-Za-z0-9._-]]*:([rwx-]{3})?(,(default:)?(user|group|mask|other):[[A-Za-z_][A-Za-z0-9._-]]*:([rwx-]{3})?)*$
   m = acl.match(/(.*?):(.*?):(.)(.)(.)/);
-  return ko.mapping.fromJS({
+  var acl = ko.mapping.fromJS({
     'type': m[1],
     'name': m[2],
      'r': m[3] != '-',
@@ -25,6 +25,23 @@ function parseAcl(acl) {
      'x': m[5] != '-',
      'status': '',
   });
+  acl.type.subscribe(function(){
+    acl.status('modified');
+  });
+  acl.name.subscribe(function(){
+	acl.status('modified');
+  });
+  acl.r.subscribe(function(){
+	acl.status('modified');
+  });
+  acl.w.subscribe(function(){
+	acl.status('modified');
+  });
+  acl.x.subscribe(function(){
+	acl.status('modified');
+  });
+
+  return acl;
 }
 
 function printAcl(acl) {
@@ -43,17 +60,17 @@ var Assist = function (vm, assist) {
   self.acls = ko.observableArray();
   self.owner = ko.observable('');
   self.group = ko.observable('');
-  
+
   self.changed = ko.computed(function() {
-	return [1]; //$.grep(self.acls(), function(acl){ return acl.changed(); });
+	return $.grep(self.acls(), function(acl){ return ['new', 'deleted', 'modified'].indexOf(acl.status()) != -1 });
   });
-  
+
   self.addAcl = function() {
 	var newAcl = parseAcl('group::---');
 	newAcl.status('new');
 	self.acls.push(newAcl);
   };
-  
+
   self.removeAcl = function(acl) {
 	if (acl.status() == 'new') {
 	  self.acls.remove(acl);
@@ -67,16 +84,19 @@ var Assist = function (vm, assist) {
       if (data['files'] && data['files'][0]['type'] == 'dir') { // Hack for now
         self.files.removeAll();
         $.each(data.files, function(index, item) {
-    	  self.files.push(item.path); 
+    	  self.files.push(item.path);
         });
       }
       self.getAcls();
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
-  };  
-  
+  };
+
   self.getAcls = function () {
+    $(".jHueNotify").hide();
+	logGA('get_acls');
+
     $.getJSON('/security/api/hdfs/get_acls', {
     	'path': self.path()
       }, function (data) {
@@ -92,33 +112,31 @@ var Assist = function (vm, assist) {
       }
     });
   };
-  
+
   self.updateAcls = function () {
-	var aclSpec = []
-	$.each(self.acls(), function (index, acl) {
-	  aclSpec.push(printAcl(acl));
-	});
-    
-    $.ajax({
-      type: "POST",
-      url: "/security/api/hdfs/remove_acl_entries",
-      data: {
-        'path': self.path(),
-        'aclspec': $.grep(self.acls(), function(acl){ return acl.status() == 'deleted'; }).join()
-      },
-      async: false
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-    });
+	$(".jHueNotify").hide();
+	logGA('updateAcls');
 
-    $.getJSON('/security/api/hdfs/modify_acl_entries', {
-    	'path': self.path(),
-    	'aclspec': aclSpec.join()
+    $.post("/security/api/hdfs/update_acls", {
+        'path': self.path(),
+        'acls': ko.mapping.toJSON(self.acls()),
       }, function (data) {
+        var toDelete = []
+    	$.each(self.acls(), function(index, item) {
+    	  if (item.status() == 'deleted') {
+    		toDelete.push(item);
+    	  } else {
+            item.status('');
+    	  }
+    	});
+        $.each(toDelete, function(index, item) {
+          self.acls.remove(item);
+        });
         $(document).trigger("info", 'Done!');
-    }).fail(function (xhr, textStatus, errorThrown) {
+      }
+    ).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
-    }); 
+    });
   }
 }
 
@@ -129,11 +147,10 @@ var HdfsViewModel = function (context_json) {
 
   self.assist = new Assist(self, context_json.assist);
   self.assist.path('/tmp/acl');
+};
 
-
-  function logGA(page) {
-    if (typeof trackOnGA == 'function') {
-      trackOnGA('security/hdfs' + page);
-    }
+function logGA(page) {
+  if (typeof trackOnGA == 'function') {
+    trackOnGA('security/hdfs' + page);
   }
-};
+}

+ 21 - 21
apps/security/static/js/hive.ko.js

@@ -27,7 +27,7 @@ var Privilege = function (vm, privilege) {
   self.tableName = ko.observable(typeof privilege.tableName != "undefined" && privilege.tableName != null ? privilege.tableName : "");
   self.URI = ko.observable(typeof privilege.URI != "undefined" && privilege.URI != null ? privilege.URI : "");
   self.action = ko.observable(typeof privilege.action != "undefined" && privilege.action != null ? privilege.action : "");
-  
+
   self.availablePrivileges = ko.observableArray(['SERVER', 'DATABASE', 'TABLE']);
   self.availableActions = ko.observableArray(['SELECT', 'INSERT', 'ALL', '']);
 }
@@ -45,15 +45,15 @@ var Role = function(vm, privilege) {
     self.groups.removeAll();
     self.privileges.removeAll();
   }
-  
+
   self.addGroup = function() {
     self.groups.push('');
   }
-  
+
   self.addPrivilege = function() {
 	self.privileges.push(new Privilege(vm, {}));
   }
-  
+
   self.create = function() {
 	$(".jHueNotify").hide();
     $.post("/security/api/hive/create_role", {
@@ -63,7 +63,7 @@ var Role = function(vm, privilege) {
           $(document).trigger("info", data.message);
           data.role['privileges'] = ko.observableArray();
           data.role['newPrivileges'] = ko.observableArray();
-          data.role['showPrivileges'] = ko.observable(false);          
+          data.role['showPrivileges'] = ko.observable(false);
           vm.roles.unshift(data.role);
           self.reset();
           vm.showCreateRole(false);
@@ -92,11 +92,11 @@ var Role = function(vm, privilege) {
       $(document).trigger("error", xhr.responseText);
     });
   }
-  
+
   self.addNewPrivilege = function(role) {
     role['newPrivileges'].push(new Privilege(self, {}));
   }
-  
+
   self.saveNewPrivileges = function(role) {
 	$(".jHueNotify").hide();
     $.post("/security/api/hive/add_privileges", {
@@ -105,7 +105,7 @@ var Role = function(vm, privilege) {
         if (data.status == 0) {
           $.each(data.privileges, function(index, privileges) { // TODO: get back a set<TSentryPrivilege>
             //role['privileges'] = ko.observableArray();
-            //vm.roles.unshift(data.role); privileges            
+            //vm.roles.unshift(data.role); privileges
           });
           // self.reset();
         }
@@ -115,7 +115,7 @@ var Role = function(vm, privilege) {
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
-  }  
+  }
 }
 
 
@@ -127,7 +127,7 @@ var Assist = function (vm) {
 	self.fetchDatabases();
   });
   self.files = ko.observableArray();
-  
+
   self.fetchDatabases = function() {
     var request = {
       url: '/beeswax/api/autocomplete', // impala too
@@ -153,12 +153,12 @@ var HiveViewModel = function (initial) {
   self.privileges = ko.observableArray();
   self.availableHadoopGroups = ko.mapping.fromJS(initial.hadoop_groups);
   self.assist = new Assist(self);
-  
+
   // Edition
   self.showCreateRole = ko.observable(false);
   self.role = new Role(self);
   self.privilege = new Privilege(self, {});
-  
+
 
   self.init = function() {
     self.list_sentry_roles_by_group();
@@ -169,10 +169,10 @@ var HiveViewModel = function (initial) {
     $.each(self.roles(), function (index, role) {
       if (role.name == roleName) {
         self.roles.remove(role);
-        return false;  
+        return false;
       }
     });
-  }; 
+  };
 
   self.list_sentry_roles_by_group = function() {
     $.getJSON('/security/api/hive/list_sentry_roles_by_group', {    	
@@ -181,13 +181,13 @@ var HiveViewModel = function (initial) {
           item['privileges'] = ko.observableArray();
           item['showPrivileges'] = ko.observable(false);
           item['newPrivileges'] = ko.observableArray();
-    	  self.roles.push(item); 
+    	  self.roles.push(item);
         });
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
   };
-  
+
   self.list_sentry_privileges_by_role = function(role) {
     $.ajax({
       type: "POST",
@@ -197,16 +197,16 @@ var HiveViewModel = function (initial) {
       },
       success: function (data) {
     	role.privileges.removeAll();
-        $.each(data.sentry_privileges, function(index, item) {          
-          role.privileges.push(ko.mapping.fromJS(item)); 
+        $.each(data.sentry_privileges, function(index, item) {
+          role.privileges.push(ko.mapping.fromJS(item));
         });
         role.showPrivileges(true);
       }
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
-  };  
-  
+  };
+
   self.list_sentry_privileges_for_provider = function(role) {
     $.ajax({
       type: "POST",
@@ -222,7 +222,7 @@ var HiveViewModel = function (initial) {
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
-  }; 
+  };
 };
 
 function logGA(page) {