source: tools/policy/window.py @ 1d9b9cb

gec13
Last change on this file since 1d9b9cb was 1d9b9cb, checked in by Ted Faber <faber@…>, 12 years ago

Whoops. Forgot save. More documentation.

  • Property mode set to 100644
File size: 12.7 KB
Line 
1#!?usr/local/bin/python
2
3import gtk
4import ConfigParser
5import os.path
6import re
7import Creddy
8
9from policy import policy
10
11from principal_tree import principal_issued_tree, \
12        principal_assigned_tree, principal_attribute_tree, \
13        principal_rules_tree, principal_action_tree, action_principal_tree
14
15from new_credential import add_credential_dialog, add_principal_dialog, \
16        mark_action_dialog
17
18class window(gtk.Window):
19    '''
20    The main GUI class.  It presents the various TreeViews and menus to
21    save/load/add, to add credentials, identities and actions and to change the
22    policy translation variable.  It keeps its current size and location in the
23    .abac_policy_tool.cfg file in the user's home.
24    '''
25
26    # Definition of the menus
27    ui_def = '''
28    <ui>
29        <menubar>
30            <menu action="FileMenu">
31                <menuitem name="New" action="FileNew"/>
32                <menuitem name="Load" action="FileLoad"/>
33                <menuitem name="Append" action="FileAppend"/>
34                <menuitem name="Save" action="FileSave"/>
35                <menuitem name="Quit" action="FileQuit"/>
36            </menu>
37            <menu action="EditMenu">
38                <menuitem action="EditAddCred"/>
39                <menuitem action="EditAddPrincipal"/>
40                <menuitem action="EditMarkAction"/>
41            </menu>
42            <menu action="ViewMenu">
43                <menu action="Translation">
44                    <menuitem name="ViewSet" action="ViewSet"/>
45                    <menuitem name="ViewRole description" action="ViewRole"/>
46                    <menuitem name="ViewNames" action="ViewName"/>
47                    <menuitem name="ViewRaw" action="ViewRaw"/>
48                </menu>
49            </menu>
50        </menubar>
51    </ui>
52    '''
53    # Path to the configuration
54    cfg_path = os.path.join(os.path.expanduser('~'), '.abac_policy_tool.cfg')
55
56    @staticmethod
57    def wrapit(widget):
58        '''
59        Put widget into a ScrolledWindow with automatic scrollbars on both
60        directions, and return the ScrolledWindow.
61        '''
62        sw = gtk.ScrolledWindow()
63        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
64        sw.add(widget)
65        return sw
66
67    def report_error(self, message):
68        '''
69        Put a MessageDialog up with the given message.  This is a member method
70        so that it can be centered on the window.
71        '''
72        md = gtk.MessageDialog(self, gtk.DIALOG_MODAL, 
73                gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, 
74                message)
75        md.run()
76        md.destroy()
77
78    def __init__(self, policy):
79        '''
80        Initialize all the GTK hooks for menus, put the various TreeViews up
81        (connected to the policy) and read teh configuration for current
82        position.
83        '''
84        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
85        self.policy = policy
86        self.set_title('ABAC Policy Tool')
87        self.connect('destroy', self.quit)
88        self.connect('show', self.shown)
89        self.connect('configure-event', self.changed)
90        self.pos = (0,0)
91        self.size = (500, 500)
92        # These are the TreeViews that will change when policy changes
93        self.pages = [ ]
94        # These are the TreeViews that change when policy translation changes
95        self.translated = [ ]
96
97        self.read_config()
98
99        # Hook up thr TreeViews
100        nb = gtk.Notebook()
101        p = action_principal_tree(policy)
102        nb.append_page(self.wrapit(p), 
103                gtk.Label("Principals by action permitted"))
104        self.pages.append(p)
105        self.translated.append(p)
106
107        p = principal_action_tree(policy)
108        nb.append_page(self.wrapit(p), 
109                gtk.Label("Actions perimtted to principals"))
110        self.pages.append(p)
111        self.translated.append(p)
112
113        p = principal_issued_tree(policy)
114        nb.append_page(self.wrapit(p), gtk.Label("Attributes Directly Issued"))
115        self.pages.append(p)
116
117        p = principal_rules_tree(policy)
118        nb.append_page(self.wrapit(p), gtk.Label("Rules declared"))
119        self.pages.append(p)
120        self.translated.append(p)
121
122        p = principal_assigned_tree(policy)
123        nb.append_page(self.wrapit(p), gtk.Label("Attributes Directly Held"))
124        self.pages.append(p)
125
126        p = principal_attribute_tree(policy)
127        nb.append_page(self.wrapit(p), gtk.Label("All Attributes"))
128        self.pages.append(p)
129        self.translated.append(p)
130
131        # Make the Menus real
132        ui = gtk.UIManager()
133        ag = gtk.ActionGroup('action')
134        ag.add_actions((
135            ('FileMenu', None, 'File'),
136            ('FileNew', gtk.STOCK_NEW, None, None, None, self.new),
137            ('FileSave', gtk.STOCK_SAVE, None, None, None, self.save),
138            ('FileQuit', gtk.STOCK_QUIT, None, None, None, self.quit),
139            ('EditMenu', None, 'Edit'),
140            ('EditAddCred', None, "Add Credential", None, None, self.add_cred),
141            ('EditAddPrincipal', None, "Add Principal", None, None, 
142                self.add_principal),
143            ('EditMarkAction', None, "Mark Action", None, None, 
144                self.mark_action),
145            ('ViewMenu', None, 'View'),
146            ('Translation', None, 'Translate Credentials'),
147            ))
148        # load and append call the same method with different user data -
149        # whether to clear current policy or not.
150        ag.add_actions((
151            ('FileLoad', gtk.STOCK_OPEN, None, None, None, self.load),
152            ), True)
153        ag.add_actions((
154            ('FileAppend', gtk.STOCK_ADD, None, None, None, self.load),
155            ), False)
156        ag.add_radio_actions([
157            ('ViewSet', None, 'Set based descriptions', None, None, 0), 
158            ('ViewRole', None, 'Role based descriptions', None, None, 1), 
159            ('ViewName', None, 'ABAC with symbolic names', None, None, 2), 
160            ('ViewRaw', None, 'Raw ABAC', None, None, 3),], 
161            2, self.translation_change)
162
163        ui.insert_action_group(ag, -1)
164        ui.add_ui_from_string(window.ui_def)
165
166        # Put it all together and show it.
167        mb = ui.get_widget('ui/menubar')
168        vb = gtk.VBox()
169        vb.pack_start(mb, False, False, 0)
170        vb.pack_start(nb, True, True, 0)
171
172        self.add(vb)
173        self.show_all()
174   
175    def quit(self, widget=None, data=None):
176        '''
177        Called from File->Quit in the menu.  Save location/size and exit
178        '''
179        self.save_config()
180        gtk.main_quit()
181
182    def save(self, widget=None, data=None):
183        '''
184        Save the current state to a file.  Wrangle the FileChooserDialog to
185        pick the file and write to it.
186        '''
187        d = gtk.FileChooserDialog('Save file as', self, 
188                gtk.FILE_CHOOSER_ACTION_SAVE, (
189            gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
190            gtk.STOCK_OK, gtk.RESPONSE_OK))
191        fil = gtk.FileFilter()
192        fil.set_name('Zip files')
193        fil.add_pattern('*.zip')
194        d.set_select_multiple(False)
195        d.set_current_folder('.')
196        d.set_do_overwrite_confirmation(True)
197        d.add_filter(fil)
198        # If the policy has been saved, start at that file.
199        if self.policy.filename is not None:
200            d.set_filename(self.policy.filename)
201        # Display the chooser
202        rv = d.run()
203        d.hide()
204        # Write if the user wants to do it.
205        if rv == gtk.RESPONSE_OK:
206            self.policy.write_zip(d.get_filename())
207        d.destroy()
208
209    def new(self, widget=None, data=None):
210        '''
211        Called when the user asks for a blank policy.  Put up a confirmation
212        and clear if confirmed.
213        '''
214        d = gtk.MessageDialog(self, gtk.DIALOG_MODAL, 
215                gtk.MESSAGE_ERROR, gtk.BUTTONS_OK_CANCEL, 
216                "Clear this policy?")
217        rv = d.run()
218        if rv == gtk.RESPONSE_OK:
219            self.policy.clear()
220            # After clearing, tell the TreeViews
221            for p in self.pages:
222                p.recalc()
223        d.destroy()
224
225    def load(self, widget=None, data=None):
226        '''
227        Called to either load or append to the loaded policy.  data is the
228        clearit parameter to the load call.  Other than that, just put up a
229        requester and do the thing.
230        '''
231        d = gtk.FileChooserDialog('Load file', self, 
232                gtk.FILE_CHOOSER_ACTION_OPEN, (
233            gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
234            gtk.STOCK_OK, gtk.RESPONSE_OK))
235        fil = gtk.FileFilter()
236        fil.set_name('Zip files')
237        fil.add_pattern('*.zip')
238        d.set_select_multiple(False)
239        d.set_current_folder('.')
240        d.set_do_overwrite_confirmation(True)
241        d.add_filter(fil)
242        if self.policy.filename is not None:
243            d.set_filename(self.policy.filename)
244        rv = d.run()
245        d.hide()
246        if rv == gtk.RESPONSE_OK:
247            self.policy.read_zip(d.get_filename(), data)
248            # Tell the TreeViews to update.
249            for p in self.pages:
250                p.recalc()
251        d.destroy()
252
253    def add_principal(self, widget=None, data=None):
254        '''
255        Called to add a principal with a user-supplied mnemonic name to the
256        current policy.  Do not allow duplicate mnemonic names.
257        '''
258        while True:
259            # Ask for a name
260            d = add_principal_dialog(self)
261            rv = d.run()
262            d.hide()
263            if rv == gtk.RESPONSE_OK:
264                # Build the certificate
265                name = d.pname.get_text() 
266                if name not in self.policy.principal_names():
267                    try:
268                        cid = Creddy.ID(name, 5 * 3600 * 24 * 365)
269                    except RuntimeError, e:
270                        # Note this retries on failure
271                        self.report_error('Cannot create principal: %s' % e)
272                        continue
273                    # Identity to policy
274                    self.policy.add_identity(cid)
275                    # Update the trees
276                    for p in self.pages:
277                        p.recalc()
278                    # clean up the dialog
279                    d.destroy()
280                    return
281                else:
282                    # Duplicate, retry
283                    d.destroy()
284                    self.report_error('Cannot overwrite name: %s' % name)
285            else:
286                # Cancel leave this function
287                d.destroy()
288                return
289
290    def add_cred(self, widget=None, data=None):
291        '''
292        Create a new credential and add it to the policy.  Most of the work is
293        parsing the data out of teh add credential dialog which may be
294        reporting a request for one of three kinds of credential.
295        '''
296        # Ask the user what they want
297        d = add_credential_dialog(self, self.policy)
298        rv = d.run()
299        d.hide()
300        if rv == gtk.RESPONSE_OK:
301            # The name and issuer are always teh same
302            iname = d.issuer.get_active_text()
303            issuer = self.policy.issuers[self.policy.name_to_keyid(iname)]
304            role = re.sub('\.', '_', d.role.get_text())
305            cred = None
306            try: 
307                # Base credential
308                cred = Creddy.Attribute(issuer, role, 5 * 365 * 3600 * 24)
309            except RuntimeError, e:
310                print e
311                pass
312
313            if cred is None:
314                self.report_error("Missing or invalid role")
315                d.destroy()
316                return
317
318            mode = d.mechanism.get_active_text()
319            # Principal is always in the results
320            p = d.subject_principal.get_text()
321            p = self.policy.name_to_keyid(p)
322            # Collect the dependent components
323            if p is None:
324                self.report_error("Missing or invalid subject principal")
325                d.destroy()
326                return
327            if mode == 'Direct delegation' or mode == 'Delegation to role':
328                r = d.subject_role.get_text()
329                if r is None or r == '':
330                    self.report_error("Missing or invalid subject role")
331                    d.destroy()
332                    return
333            if mode == 'Delegation to role':
334                l = d.subject_link.get_text()
335                if l is None or l == '':
336                    self.report_error("Missing or invalid subject linking role")
337                    d.destroy()
338                    return
339            # Build that sucker
340            if mode == 'Direct assignment': cred.principal(p)
341            elif mode == 'Direct delegation': cred.role(p, r)
342            else: cred.linking_role(p, l, r)
343
344            try:
345                # Actual creation
346                cred.bake()
347            except:
348                self.report_error("Could not create credential?!")
349                d.destroy()
350                return
351            # Add to policy
352            self.policy.add_credential(cred)
353            # Update the TreeViews
354            for p in self.pages:
355                p.recalc()
356            d.destroy()
357
358    def mark_action(self, widget=None, data=None):
359        '''
360        Mark an action. Collect the user's request and propagate it.
361        '''
362        d = mark_action_dialog(self, self.policy)
363        rv = d.run()
364        d.hide()
365        if rv == gtk.RESPONSE_OK:
366            action = d.get_value()
367            if action is not None:
368                self.policy.add_action(action)
369            # Tell the TreeViews
370            for p in self.pages:
371                p.recalc()
372        d.destroy()
373
374
375    def shown(self, w):
376        '''
377        Handles an event where the window appears.  Move to the saved position
378        and size.
379        '''
380        self.move(*self.pos)
381        self.resize(*self.size)
382
383    def changed(self, w, e):
384        '''
385        Handles an event where the window changes (resizes or moves).  Remember
386        the size and position.
387        '''
388        self.pos = self.get_position()
389        self.size = self.get_size()
390
391    def translation_change(self, ra, c, user=None):
392        '''
393        Called from any of the menu items that request a translation change.
394        They're radio buttons, so each one unsets the last.  Translate the
395        radio value into the right policy string, set the translation value and
396        update the TreeViews that depend on translation.
397        '''
398        cv = c.get_current_value()
399        if cv == 0: self.policy.translate = 'sets'
400        elif cv == 1: self.policy.translate = 'roles'
401        elif cv == 2: self.policy.translate = 'keyids'
402        elif cv == 3: self.policy.translate = 'none'
403        else: print >>sys.stderr, 'Unknown translation type!?'
404
405        for p in self.translated:
406            p.recalc()
407
408    def get_intpair(self, sect, opt):
409        '''
410        Utility to pull a pair of integers from a configuration file.  The size
411        and position are thsi kind of data, so this is used a couple places.
412        '''
413        if not self.cfg.has_section(sect):
414            self.cfg.add_section(sect)
415
416        if self.cfg.has_option(sect, opt):
417            try:
418                return [int(x) for x in self.cfg.get(sect, opt).split(',', 1)]
419            except ValueError:
420                return None
421        else:
422            return None
423
424    def read_config(self):
425        '''
426        Get the saved size and position from the config file, if any
427        '''
428        self.cfg = ConfigParser.SafeConfigParser()
429        self.cfg.read(window.cfg_path)
430
431        self.pos = self.get_intpair('geom', 'pos') or ( 0, 0)
432        self.size = self.get_intpair('geom', 'size') or ( 500, 500)
433
434
435    def save_config(self):
436        '''
437        Save the current postion to the default config file.
438        '''
439        self.cfg.set('geom', 'pos', '%d,%d' % self.pos)
440        self.cfg.set('geom', 'size', '%d,%d' % self.size)
441        try:
442            f = open(window.cfg_path, 'w')
443            self.cfg.write(f)
444            f.close()
445        except EnvironmentError, e:
446            pass
Note: See TracBrowser for help on using the repository browser.