瀏覽代碼

[security] Edition and listing of roles and groups

Romain Rigaux 11 年之前
父節點
當前提交
d3ba014

+ 96 - 0
apps/security/src/security/api.py

@@ -18,9 +18,105 @@
 import json
 
 from django.http import HttpResponse
+from libsentry.api import get_api
 
 
+# HDFS
+
 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")
+
+
+# Hive
+
+def list_sentry_roles_by_group(request):
+  result = {'status': -1, 'message': 'Error'}
+
+  try:
+    roles = get_api(request.user).list_sentry_roles_by_group()
+    result['roles'] = roles
+    result['message'] = ''
+    result['status'] = 0
+  except Exception, e:
+    result['message'] = unicode(str(e), "utf8")
+
+  return HttpResponse(json.dumps(result), mimetype="application/json")
+
+
+def list_sentry_privileges_by_role(request):
+  result = {'status': -1, 'message': 'Error'}
+
+  try:
+    roleName = request.POST['roleName']
+    sentry_privileges = get_api(request.user).list_sentry_privileges_by_role(roleName)
+    result['sentry_privileges'] = sentry_privileges
+    result['message'] = ''
+    result['status'] = 0
+  except Exception, e:
+    result['message'] = unicode(str(e), "utf8")
+
+  return HttpResponse(json.dumps(result), mimetype="application/json")
+
+
+def hive_edit_role(request):  
+  result = {'status': -1, 'message': 'Error'}
+
+  try:
+    role = json.loads(request.POST['role'])
+    
+    api = get_api(request.user)
+    
+    try:
+      api.create_sentry_role(role['name'])
+    except Exception, e:
+      print e
+    for priviledge in role['priviledges']:
+      api.alter_sentry_role_grant_privilege(role['name'], {
+          'privilegeScope': priviledge['privilegeScope'],
+          'serverName': priviledge['serverName'],
+          'dbName': priviledge['dbName'],
+          'tableName': priviledge['tableName'],
+          'URI': priviledge['URI'],
+          'action': priviledge['action']
+      })
+    api.alter_sentry_role_add_groups(role['name'], role['groups'])
+
+    result['message'] = ''
+    result['status'] = 0
+  except Exception, e:
+    result['message'] = unicode(str(e), "utf8")
+
+  return HttpResponse(json.dumps(result), mimetype="application/json")
+
+
+
+def create_sentry_role(request):
+  result = {'status': -1, 'message': 'Error'}
+
+  try:
+    roleName = request.POST['roleName']
+
+    get_api(request.user).create_sentry_role(roleName)
+    result['message'] = ''
+    result['status'] = 0
+  except Exception, e:
+    result['message'] = unicode(str(e), "utf8")
+
+  return HttpResponse(json.dumps(result), mimetype="application/json")
+
+
+def drop_sentry_role(request):
+  result = {'status': -1, 'message': 'Error'}
+
+  try:
+    roleName = request.POST['roleName']
+
+    get_api(request.user).drop_sentry_role(roleName)
+    result['message'] = ''
+    result['status'] = 0
+  except Exception, e:
+    result['message'] = unicode(str(e), "utf8")
+
+  return HttpResponse(json.dumps(result), mimetype="application/json")

+ 119 - 26
apps/security/src/security/templates/hive.mako

@@ -32,51 +32,144 @@ ${ layout.menubar(section='hive') }
       <div class="sidebar-nav">
         <ul class="nav nav-list">
           <li class="nav-header">${ _('Properties') }</li>
-          <li class="active"><a href="#properties"><i class="fa fa-eye"></i> ${ _('View') }</a></li>
-          <li><a href="#listDataset"><i class="fa fa-group"></i> ${ _('Groups') }</a></li>
-          <li><a href="#listDataset"><i class="fa fa-cubes"></i> ${ _('Roles') }</a></li>
+          <li class="active"><a href="#edit"><i class="fa fa-pencil"></i> ${ _('Edit') }</a></li>
+          <li><a href="#view"><i class="fa fa-eye"></i> ${ _('View') }</a></li>
+          <li><a href="#roles"><i class="fa fa-group"></i> ${ _('Roles') }</a></li>
+          <li><a href="#privileges"><i class="fa fa-cubes"></i> ${ _('Privileges') }</a></li>
         </ul>
       </div>
     </div>
+
     <div class="span10">
-      <div class="card card-small">
-        <h1 class="card-heading simple">${ _('View') }</h1>
+
+      <div id="edit" class="mainSection card card-small">
+        <h1 class="card-heading simple">${ _('Edit') }</h1>        
         <div class="card-body">
-          Assist: 
-          ${ assist }        
+          <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>                
+          </a>
+        </div>
         <div>
-        <div class="card-body">     
-          Hadoop Groups: 
-          ${ hadoop_groups }
-        <div>            
-        <div class="card-body">     
-          Roles: 
-          ${ roles }
-        <div>      
-        <div class="form-actions" id="bottom-nav">
-          <a id="backBtn" class="btn disabled">${ _('Back') }</a>
-          <a id="nextBtn" class="btn btn-primary disable-feedback">${ _('Next') }</a>
+          <div class="span8">
+            <div data-bind="foreach: $root.assist.files">
+              <div data-bind="text: $data"></div>
+            </div>
+          </div>
+        </div>        
+                      
+        <div class="card-body">               
+          <div data-bind="with: $root.role">
+            Privileges
+            <div>
+              <div data-bind="foreach: priviledges">
+                <select data-bind="options: availablePriviledges, 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.tableName" placeholder="tableName"></input>
+                <input type="text" data-bind="value: $data.URI" placeholder="URI"></input>
+                <select data-bind="options: availableActions, value: action"></select>
+                <i class="fa fa-minus"></i>
+              </div>
+              <a href="javascript: void(0)" data-bind='click: addPriviledge'>
+                <i class="fa fa-plus"></i>
+              </a>
+            </div>
+            Groups
+            <div>
+              <select data-bind="options: $root.availableHadoopGroups, selectedOptions: groups" size="5" multiple="true"></select>
+            </div>
+            Name
+            <div>
+              <input type="text" data-bind="value: $data.name"></input>
+            </div>
+          </div>
+          <div>
+            <button type="button" rel="tooltip" data-placement="bottom" data-original-title="${ _('Cancel') }" class="btn">
+              <i class="fa fa-undo"></i>
+            </button>
+            <button type="button" rel="tooltip" data-placement="bottom" data-loading-text="${ _('Saving...') }" data-original-title="${ _('Save') }" class="btn"
+                data-bind="click: $root.role.edit">
+              <i class="fa fa-save"></i>
+            </button>
+          </div>          
         </div>
+      </div>
 
-        </div>
+      <div id="view" class="mainSection hide card card-small">
+        <div class="card-heading simple"><h3>${ _('View') }</h3></div>
+      </div>
 
-        <div id="listDataset" class="section hide">
-          <div class="alert alert-info"><h3>${ _('Existing datasets') }</h3></div>
+      <div id="roles" class="mainSection hide card card-small">
+        <div class="card-heading simple"><h3>${ _('Roles') }</h3></div>
+           
+        <div data-bind="foreach: $root.roles">
+          <div data-bind="text: ko.mapping.toJSON($data), click: $root.list_sentry_privileges_by_role"></div><i class="fa fa-minus"></i>
         </div>
+      </div>
 
-        <div id="listHistory" class="section hide">
-          <div class="alert alert-info"><h3>${ _('History') }</h3></div>          
+      <div id="privileges" class="mainSection hide card card-small">
+        <div class="card-heading simple"><h3>${ _('Privileges') }</h3></div>      
+        
+        <div data-bind="foreach: $root.privileges">
+          <div data-bind="text: ko.mapping.toJSON($data)"></div><i class="fa fa-minus"></i>
         </div>
-
-      </form>
+      </div>
 
     </div>
-    </div>
+  </div>
 
   </div>
 
 </div>
 
 
+<script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/knockout.mapping-2.3.2.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/routie-0.3.0.min.js" type="text/javascript" charset="utf-8"></script>
+
+<script src="/security/static/js/hive.ko.js" type="text/javascript" charset="utf-8"></script>
+
+
+<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();
+      $("#" + mainSection).show();
+      highlightMainMenu(mainSection);
+    }
+
+    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");
+    },
+    "privileges": function () {
+      showMainSection("privileges");
+    },
+    "view": function () {
+      showMainSection("view");
+    }
+  });
+</script>
 
 ${ commonfooter(messages) | n,unicode }

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

@@ -26,5 +26,13 @@ urlpatterns = patterns('security.views',
 
 
 urlpatterns += patterns('security.api',
-  url(r'^api/get_acls$', 'get_acls', name='get_acls'),
-)
+  url(r'^api/hdfs/get_acls$', 'get_acls', name='get_acls'),
+)
+
+urlpatterns += patterns('security.api',
+  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/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/edit_role$', 'hive_edit_role', name='hive_edit_role'),
+)

+ 7 - 3
apps/security/src/security/views.py

@@ -15,6 +15,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import json
+
 from beeswax.api import autocomplete
 from desktop.lib.django_util import render
 
@@ -27,12 +29,14 @@ def get_hadoop_groups(): # Mock for now, maybe pull from LDAP
   
 def hive(request):
   assist = autocomplete(request, database=None, table=None)
-  roles = get_api(request.user).list_sentry_roles_by_group()
+  
+#    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, {      
       'assist': assist,
-      'hadoop_groups': get_hadoop_groups(),
-      'roles': roles
+      'initial': json.dumps({'hadoop_groups': get_hadoop_groups()}),
   })
 
 

+ 1 - 1
apps/security/static/js/hdfs.ko.js

@@ -62,7 +62,7 @@ var Assist = function (vm, assist) {
   };  
   
   self.getAcls = function () {
-    $.getJSON('/security/api/get_acls', {
+    $.getJSON('/security/api/hdfs/get_acls', {
     	'path': self.path()
       }, function (data) {
         self.acls.removeAll();

+ 100 - 11
apps/security/static/js/hive.ko.js

@@ -15,25 +15,114 @@
 // limitations under the License.
 
 
-var View = function (vm, query) {
+// groups, priviledges
+
+
+var Priviledge = function (vm, priviledge) {
+  var self = this;
+
+  self.privilegeScope = ko.observable(typeof priviledge.privilegeScope != "undefined" && priviledge.privilegeScope != null ? priviledge.privilegeScope : "");
+  self.serverName = ko.observable(typeof priviledge.serverName != "undefined" && priviledge.serverName != null ? priviledge.serverName : "");
+  self.dbName = ko.observable(typeof priviledge.dbName != "undefined" && priviledge.dbName != null ? priviledge.dbName : "");
+  self.tableName = ko.observable(typeof priviledge.tableName != "undefined" && priviledge.tableName != null ? priviledge.tableName : "");
+  self.URI = ko.observable(typeof priviledge.URI != "undefined" && priviledge.URI != null ? priviledge.URI : "");
+  self.action = ko.observable(typeof priviledge.action != "undefined" && priviledge.action != null ? priviledge.action : "");
+  
+  self.availablePriviledges = ko.observableArray(['SERVER', 'DATABASE', 'TABLE']);
+  self.availableActions = ko.observableArray(['SELECT', 'INSERT', 'ALL']);
+}
+
+var Role = function(vm, priviledge) {
+  var self = this;
+
+  self.priviledges = ko.observableArray();
+  self.groups = ko.observableArray();
+  self.name = ko.observable('');
+  
+  self.addGroup = function() {
+    self.groups.push('');
+  }
+  
+  self.addPriviledge = function() {
+	self.priviledges.push(new Priviledge(vm, {}));
+  }
+  
+  self.edit = function() {
+	$(".jHueNotify").hide();
+    $.post("/security/api/hive/edit_role", {
+        role: ko.mapping.toJSON(self)
+      }, function (data) {
+        if (data.status == 0) {
+          $(document).trigger("info", data.message);
+          self.roles.push(self);
+        }
+        else {
+          $(document).trigger("error", data.message);
+        }
+    }).fail(function (xhr, textStatus, errorThrown) {
+      $(document).trigger("error", xhr.responseText);
+    });
+  }
+}
+
+
+var Assist = function (vm) {
   var self = this;
 
-  self.qs = ko.mapping.fromJS(query.qs);
+  self.path = ko.observable('');
+  self.files = ko.observableArray();
 }
 
 
-var HiveViewModel = function (hive_json) {
+var HiveViewModel = function (initial) {
   var self = this;
 
   // Models
-  self.view = new View(self, hive_json.view);
-  self.groups = new View(self, hive_json.view);
-  self.roles = new View(self, hive_json.view);
+  self.roles = ko.observableArray();
+  self.privileges = ko.observableArray();
+  self.availableHadoopGroups = ko.mapping.fromJS(initial.hadoop_groups);
+  self.assist = new Assist(self);
+  
+  // Edition
+  self.role = new Role();
 
+  
+  self.init = function() {
+    self.list_sentry_roles_by_group();
+  };
 
-  function logGA(page) {
-    if (typeof trackOnGA == 'function') {
-      trackOnGA('security/' + page);
-    }
-  }
+  self.list_sentry_roles_by_group = function() {
+    $.getJSON('/security/api/hive/list_sentry_roles_by_group', {    	
+      }, function (data) {
+        $.each(data.roles, function(index, 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",
+      url: "/security/api/hive/list_sentry_privileges_by_role",
+      data: {    	
+        'roleName': role.name
+      },
+      success: function (data) {
+        $.each(data.sentry_privileges, function(index, item) {
+          self.privileges.push(item); 
+        });
+        window.location.hash = "#privileges";
+      }
+    }).fail(function (xhr, textStatus, errorThrown) {
+      $(document).trigger("error", xhr.responseText);
+    });
+  };  
 };
+
+function logGA(page) {
+  if (typeof trackOnGA == 'function') {
+    trackOnGA('security/' + page);
+  }
+}

+ 1 - 0
desktop/libs/libsentry/src/libsentry/api.py

@@ -101,6 +101,7 @@ class SentryApi(object):
       roles = {}
       for role in response.roles:
         roles[role.roleName] = {
+          'name': role.roleName,
           'grantorPrincipal': role.grantorPrincipal,
           'groups': [group.groupName for group in role.groups]
         }