#!?usr/local/bin/python import gtk import ConfigParser import os.path import re import Creddy from policy import policy from principal_tree import principal_issued_tree, \ principal_assigned_tree, principal_attribute_tree, \ principal_rules_tree, principal_action_tree, action_principal_tree from new_credential import add_credential_dialog, add_principal_dialog, \ mark_action_dialog class window(gtk.Window): ''' The main GUI class. It presents the various TreeViews and menus to save/load/add, to add credentials, identities and actions and to change the policy translation variable. It keeps its current size and location in the .abac_policy_tool.cfg file in the user's home. ''' # Definition of the menus ui_def = ''' ''' # Path to the configuration cfg_path = os.path.join(os.path.expanduser('~'), '.abac_policy_tool.cfg') @staticmethod def wrapit(widget): ''' Put widget into a ScrolledWindow with automatic scrollbars on both directions, and return the ScrolledWindow. ''' sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(widget) return sw def report_error(self, message): ''' Put a MessageDialog up with the given message. This is a member method so that it can be centered on the window. ''' md = gtk.MessageDialog(self, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, message) md.run() md.destroy() def __init__(self, policy): ''' Initialize all the GTK hooks for menus, put the various TreeViews up (connected to the policy) and read teh configuration for current position. ''' gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) self.policy = policy self.set_title('ABAC Policy Tool') self.connect('destroy', self.quit) self.connect('show', self.shown) self.connect('configure-event', self.changed) self.pos = (0,0) self.size = (500, 500) # These are the TreeViews that will change when policy changes self.pages = [ ] # These are the TreeViews that change when policy translation changes self.translated = [ ] self.read_config() # Hook up thr TreeViews nb = gtk.Notebook() p = action_principal_tree(policy) nb.append_page(self.wrapit(p), gtk.Label("Principals by action permitted")) self.pages.append(p) self.translated.append(p) p = principal_action_tree(policy) nb.append_page(self.wrapit(p), gtk.Label("Actions perimtted to principals")) self.pages.append(p) self.translated.append(p) p = principal_issued_tree(policy) nb.append_page(self.wrapit(p), gtk.Label("Attributes Directly Issued")) self.pages.append(p) p = principal_rules_tree(policy) nb.append_page(self.wrapit(p), gtk.Label("Rules declared")) self.pages.append(p) self.translated.append(p) p = principal_assigned_tree(policy) nb.append_page(self.wrapit(p), gtk.Label("Attributes Directly Held")) self.pages.append(p) p = principal_attribute_tree(policy) nb.append_page(self.wrapit(p), gtk.Label("All Attributes")) self.pages.append(p) self.translated.append(p) # Make the Menus real ui = gtk.UIManager() ag = gtk.ActionGroup('action') ag.add_actions(( ('FileMenu', None, 'File'), ('FileNew', gtk.STOCK_NEW, None, None, None, self.new), ('FileSave', gtk.STOCK_SAVE, None, None, None, self.save), ('FileQuit', gtk.STOCK_QUIT, None, None, None, self.quit), ('EditMenu', None, 'Edit'), ('EditAddCred', None, "Add Credential", None, None, self.add_cred), ('EditAddPrincipal', None, "Add Principal", None, None, self.add_principal), ('EditMarkAction', None, "Mark Action", None, None, self.mark_action), ('ViewMenu', None, 'View'), ('Translation', None, 'Translate Credentials'), )) # load and append call the same method with different user data - # whether to clear current policy or not. ag.add_actions(( ('FileLoad', gtk.STOCK_OPEN, None, None, None, self.load), ), True) ag.add_actions(( ('FileAppend', gtk.STOCK_ADD, None, None, None, self.load), ), False) ag.add_radio_actions([ ('ViewSet', None, 'Set based descriptions', None, None, 0), ('ViewRole', None, 'Role based descriptions', None, None, 1), ('ViewName', None, 'ABAC with symbolic names', None, None, 2), ('ViewRaw', None, 'Raw ABAC', None, None, 3),], 2, self.translation_change) ui.insert_action_group(ag, -1) ui.add_ui_from_string(window.ui_def) # Put it all together and show it. mb = ui.get_widget('ui/menubar') vb = gtk.VBox() vb.pack_start(mb, False, False, 0) vb.pack_start(nb, True, True, 0) self.add(vb) self.show_all() def quit(self, widget=None, data=None): ''' Called from File->Quit in the menu. Save location/size and exit ''' self.save_config() gtk.main_quit() def save(self, widget=None, data=None): ''' Save the current state to a file. Wrangle the FileChooserDialog to pick the file and write to it. ''' d = gtk.FileChooserDialog('Save file as', self, gtk.FILE_CHOOSER_ACTION_SAVE, ( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) fil = gtk.FileFilter() fil.set_name('Zip files') fil.add_pattern('*.zip') d.set_select_multiple(False) d.set_current_folder('.') d.set_do_overwrite_confirmation(True) d.add_filter(fil) # If the policy has been saved, start at that file. if self.policy.filename is not None: d.set_filename(self.policy.filename) # Display the chooser rv = d.run() d.hide() # Write if the user wants to do it. if rv == gtk.RESPONSE_OK: self.policy.write_zip(d.get_filename()) d.destroy() def new(self, widget=None, data=None): ''' Called when the user asks for a blank policy. Put up a confirmation and clear if confirmed. ''' d = gtk.MessageDialog(self, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK_CANCEL, "Clear this policy?") rv = d.run() if rv == gtk.RESPONSE_OK: self.policy.clear() # After clearing, tell the TreeViews for p in self.pages: p.recalc() d.destroy() def load(self, widget=None, data=None): ''' Called to either load or append to the loaded policy. data is the clearit parameter to the load call. Other than that, just put up a requester and do the thing. ''' d = gtk.FileChooserDialog('Load file', self, gtk.FILE_CHOOSER_ACTION_OPEN, ( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) fil = gtk.FileFilter() fil.set_name('Zip files') fil.add_pattern('*.zip') d.set_select_multiple(False) d.set_current_folder('.') d.set_do_overwrite_confirmation(True) d.add_filter(fil) if self.policy.filename is not None: d.set_filename(self.policy.filename) rv = d.run() d.hide() if rv == gtk.RESPONSE_OK: self.policy.read_zip(d.get_filename(), data) # Tell the TreeViews to update. for p in self.pages: p.recalc() d.destroy() def add_principal(self, widget=None, data=None): ''' Called to add a principal with a user-supplied mnemonic name to the current policy. Do not allow duplicate mnemonic names. ''' while True: # Ask for a name d = add_principal_dialog(self) rv = d.run() d.hide() if rv == gtk.RESPONSE_OK: # Build the certificate name = d.pname.get_text() if name not in self.policy.principal_names(): try: cid = Creddy.ID(name, 5 * 3600 * 24 * 365) except RuntimeError, e: # Note this retries on failure self.report_error('Cannot create principal: %s' % e) continue # Identity to policy self.policy.add_identity(cid) # Update the trees for p in self.pages: p.recalc() # clean up the dialog d.destroy() return else: # Duplicate, retry d.destroy() self.report_error('Cannot overwrite name: %s' % name) else: # Cancel leave this function d.destroy() return def add_cred(self, widget=None, data=None): ''' Create a new credential and add it to the policy. Most of the work is parsing the data out of teh add credential dialog which may be reporting a request for one of three kinds of credential. ''' # Ask the user what they want d = add_credential_dialog(self, self.policy) rv = d.run() d.hide() if rv == gtk.RESPONSE_OK: # The name and issuer are always teh same iname = d.issuer.get_active_text() issuer = self.policy.issuers[self.policy.name_to_keyid(iname)] role = re.sub('\.', '_', d.role.get_text()) cred = None try: # Base credential cred = Creddy.Attribute(issuer, role, 5 * 365 * 3600 * 24) except RuntimeError, e: print e pass if cred is None: self.report_error("Missing or invalid role") d.destroy() return mode = d.mechanism.get_active_text() # Principal is always in the results p = d.subject_principal.get_text() p = self.policy.name_to_keyid(p) # Collect the dependent components if p is None: self.report_error("Missing or invalid subject principal") d.destroy() return if mode == 'Direct delegation' or mode == 'Delegation to role': r = d.subject_role.get_text() if r is None or r == '': self.report_error("Missing or invalid subject role") d.destroy() return if mode == 'Delegation to role': l = d.subject_link.get_text() if l is None or l == '': self.report_error("Missing or invalid subject linking role") d.destroy() return # Build that sucker if mode == 'Direct assignment': cred.principal(p) elif mode == 'Direct delegation': cred.role(p, r) else: cred.linking_role(p, l, r) try: # Actual creation cred.bake() except: self.report_error("Could not create credential?!") d.destroy() return # Add to policy self.policy.add_credential(cred) # Update the TreeViews for p in self.pages: p.recalc() d.destroy() def mark_action(self, widget=None, data=None): ''' Mark an action. Collect the user's request and propagate it. ''' d = mark_action_dialog(self, self.policy) rv = d.run() d.hide() if rv == gtk.RESPONSE_OK: action = d.get_value() if action is not None: self.policy.add_action(action) # Tell the TreeViews for p in self.pages: p.recalc() d.destroy() def shown(self, w): ''' Handles an event where the window appears. Move to the saved position and size. ''' self.move(*self.pos) self.resize(*self.size) def changed(self, w, e): ''' Handles an event where the window changes (resizes or moves). Remember the size and position. ''' self.pos = self.get_position() self.size = self.get_size() def translation_change(self, ra, c, user=None): ''' Called from any of the menu items that request a translation change. They're radio buttons, so each one unsets the last. Translate the radio value into the right policy string, set the translation value and update the TreeViews that depend on translation. ''' cv = c.get_current_value() if cv == 0: self.policy.translate = 'sets' elif cv == 1: self.policy.translate = 'roles' elif cv == 2: self.policy.translate = 'keyids' elif cv == 3: self.policy.translate = 'none' else: print >>sys.stderr, 'Unknown translation type!?' for p in self.translated: p.recalc() def get_intpair(self, sect, opt): ''' Utility to pull a pair of integers from a configuration file. The size and position are thsi kind of data, so this is used a couple places. ''' if not self.cfg.has_section(sect): self.cfg.add_section(sect) if self.cfg.has_option(sect, opt): try: return [int(x) for x in self.cfg.get(sect, opt).split(',', 1)] except ValueError: return None else: return None def read_config(self): ''' Get the saved size and position from the config file, if any ''' self.cfg = ConfigParser.SafeConfigParser() self.cfg.read(window.cfg_path) self.pos = self.get_intpair('geom', 'pos') or ( 0, 0) self.size = self.get_intpair('geom', 'size') or ( 500, 500) def save_config(self): ''' Save the current postion to the default config file. ''' self.cfg.set('geom', 'pos', '%d,%d' % self.pos) self.cfg.set('geom', 'size', '%d,%d' % self.size) try: f = open(window.cfg_path, 'w') self.cfg.write(f) f.close() except EnvironmentError, e: pass