1 | import urllib |
---|
2 | import os |
---|
3 | import gtk |
---|
4 | import gtk.glade |
---|
5 | import re |
---|
6 | import nautilus |
---|
7 | import subprocess |
---|
8 | |
---|
9 | gladeFile = "/usr/share/debathena-nautilus-afs-integration/afs-property-page.glade" |
---|
10 | |
---|
11 | import sys |
---|
12 | try: |
---|
13 | import afs.acl |
---|
14 | except ImportError: |
---|
15 | pass |
---|
16 | |
---|
17 | class AFSPermissionsPane(): |
---|
18 | def __init__(self, fp): |
---|
19 | self.filepath = fp |
---|
20 | self.acl = None |
---|
21 | self.noPermissions = False |
---|
22 | self.inAFS = True |
---|
23 | if 'afs.acl' not in sys.modules.keys(): |
---|
24 | if (not self.filepath.startswith("/afs")) and (not self.filepath.startswith("/mit")): |
---|
25 | self.inAFS = False |
---|
26 | b = gtk.VBox() |
---|
27 | l = gtk.Label("Sorry, this feature is not available on this platform.\n(afs.acl module could not be imported.)\n") |
---|
28 | l.set_single_line_mode(False) |
---|
29 | l.show() |
---|
30 | b.pack_start(l) |
---|
31 | b.show() |
---|
32 | self.rootWidget = b |
---|
33 | return |
---|
34 | if not os.path.exists(gladeFile): |
---|
35 | b = gtk.VBox() |
---|
36 | l = gtk.Label("Cannot find Glade file:\n%s" % gladeFile) |
---|
37 | l.set_single_line_mode(False) |
---|
38 | l.show() |
---|
39 | b.pack_start(l) |
---|
40 | b.show() |
---|
41 | self.rootWidget = b |
---|
42 | return |
---|
43 | self.widgets = gtk.glade.XML(gladeFile, "vboxMain") |
---|
44 | self.rootWidget = self.widgets.get_widget("vboxMain") |
---|
45 | try: |
---|
46 | self.acl = afs.acl.ACL.retrieve(self.filepath) |
---|
47 | except OSError as (errno, errstr): |
---|
48 | if errno == 13: |
---|
49 | self.noPermissions = True |
---|
50 | self.widgets = gtk.glade.XML(gladeFile, "vboxNoPerms") |
---|
51 | self.rootWidget = self.widgets.get_widget("vboxNoPerms") |
---|
52 | elif errno == 22: |
---|
53 | self.inAFS = False |
---|
54 | |
---|
55 | |
---|
56 | if self.acl != None: |
---|
57 | self._initWidgets() |
---|
58 | self.refreshACL() |
---|
59 | |
---|
60 | def _initWidgets(self): |
---|
61 | self.listStore = gtk.ListStore(str, int, bool, str) |
---|
62 | self.buttonActions = {"btnAdd": self.addItem, |
---|
63 | "btnEdit": self.editItem, |
---|
64 | "btnRemove": self.removeItem} |
---|
65 | for btn in self.buttonActions: |
---|
66 | self.widgets.get_widget(btn).connect("clicked", self.buttonHandler) |
---|
67 | self.treeView = self.widgets.get_widget("tvACL") |
---|
68 | self.treeView.set_model(self.listStore) |
---|
69 | colUser = gtk.TreeViewColumn('User') |
---|
70 | colBits = gtk.TreeViewColumn('Permissions') |
---|
71 | self.treeView.append_column(colUser) |
---|
72 | self.treeView.append_column(colBits) |
---|
73 | cellRenderer = gtk.CellRendererText() |
---|
74 | colUser.pack_start(cellRenderer) |
---|
75 | colBits.pack_start(cellRenderer) |
---|
76 | colUser.set_cell_data_func(cellRenderer, self.renderUser) |
---|
77 | colBits.set_cell_data_func(cellRenderer, self.renderBitmask) |
---|
78 | self.treeView.set_tooltip_column(3) |
---|
79 | |
---|
80 | def getWidget(self): |
---|
81 | if self.noPermissions: |
---|
82 | return NoPermissionsPane() |
---|
83 | else: |
---|
84 | return self.widgets.get_widget("vboxMain") |
---|
85 | |
---|
86 | def renderBitmask(self, col, cell, model, iter, user_data=None): |
---|
87 | rights = afs.acl.showRights(model.get_value(iter, 1)) |
---|
88 | if afs.acl.rightsToEnglish(rights): |
---|
89 | rights += ' (' + afs.acl.rightsToEnglish(rights) + ')' |
---|
90 | cell.set_property('text', rights) |
---|
91 | |
---|
92 | def renderUser(self, col, cell, model, iter, user_data=None): |
---|
93 | cell.set_property('strikethrough-set', True) |
---|
94 | isNeg = model.get_value(iter, 2) |
---|
95 | if isNeg: |
---|
96 | cell.set_property('strikethrough',True) |
---|
97 | else: |
---|
98 | cell.set_property('strikethrough', False) |
---|
99 | cell.set_property('text', model.get_value(iter, 0)) |
---|
100 | |
---|
101 | def refreshACL(self): |
---|
102 | if self.acl != None: |
---|
103 | self.widgets.get_widget("lblPath").set_text(self.filepath) |
---|
104 | self.listStore.clear() |
---|
105 | try: |
---|
106 | self.acl = afs.acl.ACL.retrieve(self.filepath) |
---|
107 | except OSError as (errno, errstr): |
---|
108 | if errno == 13: |
---|
109 | msg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, |
---|
110 | gtk.MESSAGE_QUESTION, |
---|
111 | gtk.BUTTONS_OK, |
---|
112 | "Cannot reload ACL for directory. Perhaps your AFS tokens expired or someone else changed permissions?") |
---|
113 | msg.run() |
---|
114 | msg.destroy() |
---|
115 | for i in self.acl.pos.items(): |
---|
116 | self.listStore.append(i + (False,i[0])) |
---|
117 | for i in self.acl.neg.items(): |
---|
118 | self.listStore.append(i + (True,i[0] + ' (Negative Rights)')) |
---|
119 | |
---|
120 | def removeItem(self): |
---|
121 | selectedRowIter = self.treeView.get_selection().get_selected()[1] |
---|
122 | if selectedRowIter == None: |
---|
123 | return |
---|
124 | entity, isNeg = self.listStore.get(selectedRowIter, 0, 2) |
---|
125 | if isNeg: |
---|
126 | msg = gtk.MessageDialog(None,gtk.DIALOG_MODAL, |
---|
127 | gtk.MESSAGE_INFO, gtk.BUTTONS_OK, |
---|
128 | "Removing negative ACLS is not yet supported") |
---|
129 | msg.run() |
---|
130 | msg.destroy() |
---|
131 | return |
---|
132 | msg = 'Are you sure you want to remove %s from the AFS ACL for this directory?' |
---|
133 | if (entity == os.getenv('ATHENA_USER')) or (entity == os.getenv('USER')): |
---|
134 | msg += "\n\nWARNING: You are about to remove yourself from this ACL!" |
---|
135 | question = gtk.MessageDialog(None, gtk.DIALOG_MODAL, |
---|
136 | gtk.MESSAGE_QUESTION, |
---|
137 | gtk.BUTTONS_YES_NO, |
---|
138 | msg % entity) |
---|
139 | response = question.run() |
---|
140 | question.destroy() |
---|
141 | if response == gtk.RESPONSE_YES: |
---|
142 | self.acl.remove(entity) |
---|
143 | self.applyChanges() |
---|
144 | |
---|
145 | def applyChanges(self): |
---|
146 | try: |
---|
147 | self.acl.apply(self.filepath) |
---|
148 | except OSError as (errno, errstr): |
---|
149 | msg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, |
---|
150 | gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, |
---|
151 | "Error: %s" % errstr) |
---|
152 | msg.run() |
---|
153 | msg.destroy() |
---|
154 | self.refreshACL() |
---|
155 | |
---|
156 | def editItem(self): |
---|
157 | selectedRowIter = self.treeView.get_selection().get_selected()[1] |
---|
158 | if selectedRowIter == None: |
---|
159 | return |
---|
160 | entity, acl, isNeg = self.listStore.get(selectedRowIter, 0, 1, 2) |
---|
161 | if isNeg: |
---|
162 | msg = gtk.MessageDialog(None,gtk.DIALOG_MODAL, |
---|
163 | gtk.MESSAGE_INFO, gtk.BUTTONS_OK, |
---|
164 | "Editing negative ACLS is not yet supported") |
---|
165 | msg.run() |
---|
166 | msg.destroy() |
---|
167 | return |
---|
168 | add = AddEditACLDialog() |
---|
169 | add.editMode(entity, afs.acl.showRights(acl)) |
---|
170 | response = add.run() |
---|
171 | while (response == gtk.RESPONSE_OK) and not add.validate(): |
---|
172 | response=add.run() |
---|
173 | if (response == gtk.RESPONSE_OK): |
---|
174 | self.acl.set(add.getEntity(), afs.acl.readRights(add.getAcl())) |
---|
175 | self.applyChanges() |
---|
176 | add.destroy() |
---|
177 | |
---|
178 | def addItem(self): |
---|
179 | add = AddEditACLDialog() |
---|
180 | response = add.run() |
---|
181 | while (response == gtk.RESPONSE_OK) and not add.validate(): |
---|
182 | response=add.run() |
---|
183 | if (response == gtk.RESPONSE_OK): |
---|
184 | self.acl.set(add.getEntity(), afs.acl.readRights(add.getAcl())) |
---|
185 | self.applyChanges() |
---|
186 | add.destroy() |
---|
187 | |
---|
188 | def buttonHandler(self, btn): |
---|
189 | self.buttonActions[btn.name]() |
---|
190 | |
---|
191 | class AddEditACLDialog(): |
---|
192 | def __init__(self, editMode=False): |
---|
193 | self.widgets = gtk.glade.XML(gladeFile, "dlgAdd") |
---|
194 | self.window = self.widgets.get_widget("dlgAdd") |
---|
195 | self.entry = self.widgets.get_widget("entryName") |
---|
196 | for rb in self.widgets.get_widget_prefix("rb"): |
---|
197 | rb.connect("toggled", self.toggleHandler) |
---|
198 | |
---|
199 | def editMode(self, user, acl): |
---|
200 | self.window.set_title('Edit ACL Entry') |
---|
201 | self.entry.set_text(user) |
---|
202 | self.entry.set_editable(False) |
---|
203 | self.widgets.get_widget('lblType').set_property('visible', False) |
---|
204 | for rb in self.widgets.get_widget_prefix("rb"): |
---|
205 | rb.set_property('visible', False) |
---|
206 | self.widgets.get_widget("cbAcl").get_child().set_text(acl) |
---|
207 | |
---|
208 | def run(self): |
---|
209 | return self.window.run() |
---|
210 | |
---|
211 | def destroy(self): |
---|
212 | self.window.destroy() |
---|
213 | |
---|
214 | def toggleHandler(self, radioBtn, data=None): |
---|
215 | if not radioBtn.get_active(): |
---|
216 | return |
---|
217 | if radioBtn.name == "rbUser": |
---|
218 | self.entry.set_text("") |
---|
219 | self.entry.set_editable(True) |
---|
220 | if radioBtn.name == "rbGroup": |
---|
221 | # Do this in the GUI, so that people can override it if they |
---|
222 | # really know what they're doing, or are using user groups or |
---|
223 | # something. |
---|
224 | if re.search(":", self.entry.get_text()) == None: |
---|
225 | self.entry.set_text("system:") |
---|
226 | self.entry.set_editable(True) |
---|
227 | if radioBtn.name == "rbAuthuser": |
---|
228 | self.entry.set_text("system:authuser") |
---|
229 | self.entry.set_editable(False) |
---|
230 | if radioBtn.name == "rbAnyuser": |
---|
231 | self.entry.set_text("system:anyuser") |
---|
232 | self.entry.set_editable(False) |
---|
233 | |
---|
234 | def getEntity(self): |
---|
235 | return self.entry.get_text().strip() |
---|
236 | |
---|
237 | def getAcl(self): |
---|
238 | combobox = self.widgets.get_widget("cbAcl") |
---|
239 | model = combobox.get_model() |
---|
240 | active = combobox.get_active() |
---|
241 | if active < 0: |
---|
242 | # in case someone typos whitespace in the box, which would parse |
---|
243 | # to "none". |
---|
244 | acl = combobox.get_child().get_text().strip() |
---|
245 | else: |
---|
246 | acl = model[active][0] |
---|
247 | return acl |
---|
248 | |
---|
249 | def validate(self): |
---|
250 | if not self.getEntity(): |
---|
251 | msg = gtk.MessageDialog(self.window,gtk.DIALOG_MODAL, |
---|
252 | gtk.MESSAGE_INFO, gtk.BUTTONS_OK, |
---|
253 | "You did not specify the user or group.") |
---|
254 | msg.run() |
---|
255 | msg.destroy() |
---|
256 | return False |
---|
257 | |
---|
258 | acl = self.getAcl() |
---|
259 | if not acl: |
---|
260 | msg = gtk.MessageDialog(self.window,gtk.DIALOG_MODAL, |
---|
261 | gtk.MESSAGE_INFO, gtk.BUTTONS_OK, |
---|
262 | "You did not specify the permissions.") |
---|
263 | msg.run() |
---|
264 | msg.destroy() |
---|
265 | return False |
---|
266 | try: |
---|
267 | r = afs.acl.readRights(acl) |
---|
268 | except ValueError: |
---|
269 | msg = gtk.MessageDialog(self.window,gtk.DIALOG_MODAL, |
---|
270 | gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, |
---|
271 | "Invalid permissions '%s'. Select one from the drop down list or type a valid AFS permission string into the box provided." % acl) |
---|
272 | msg.run() |
---|
273 | msg.destroy() |
---|
274 | return False |
---|
275 | if re.search('[widka]', afs.acl.showRights(r)) and self.widgets.get_widget("rbAnyuser").get_active(): |
---|
276 | msg = gtk.MessageDialog(self.window,gtk.DIALOG_MODAL, |
---|
277 | gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO, |
---|
278 | "WARNING: You are attempting to assign '%s' permissions to system:anyuser (i.e. any user, anywhere on the Internet). This is EXTREMELY DANGEROUS. You may experience data loss and your directory may be used by spammers to create malicious websites. MIT IS&T reserves the right to disable access to any AFS directories with these permissions. Consider selecting 'All MIT Users' instead.\n\nAre you absolutely sure you want to continue?" % acl) |
---|
279 | |
---|
280 | response = msg.run() |
---|
281 | msg.destroy() |
---|
282 | if response != gtk.RESPONSE_YES: |
---|
283 | return False |
---|
284 | return True |
---|
285 | |
---|
286 | |
---|
287 | class AFSPropertyPage(nautilus.PropertyPageProvider): |
---|
288 | def __init__(self): |
---|
289 | self.property_label = gtk.Label('AFS') |
---|
290 | |
---|
291 | def get_property_pages(self, files): |
---|
292 | # Does not work for multiple selections |
---|
293 | if len(files) != 1: |
---|
294 | return |
---|
295 | |
---|
296 | file = files[0] |
---|
297 | # Does not work on non-file:// URIs |
---|
298 | if file.get_uri_scheme() != 'file': |
---|
299 | return |
---|
300 | |
---|
301 | # Only works on directories |
---|
302 | if not file.is_directory(): |
---|
303 | return |
---|
304 | |
---|
305 | # if 'afs.acl' not in sys.modules.keys(): |
---|
306 | # return |
---|
307 | |
---|
308 | filepath = urllib.unquote(file.get_uri()[7:]) |
---|
309 | |
---|
310 | pane = AFSPermissionsPane(filepath) |
---|
311 | |
---|
312 | # If the file does not appear to be in AFS, don't show the pane |
---|
313 | if not pane.inAFS: |
---|
314 | return |
---|
315 | |
---|
316 | self.property_label.show() |
---|
317 | |
---|
318 | return nautilus.PropertyPage("NautilusPython::afs", |
---|
319 | self.property_label, |
---|
320 | pane.rootWidget), |
---|
321 | |
---|