1 | #!/usr/bin/env python3 |
---|
2 | ### |
---|
3 | #This class returns a login_grid whith a standarized login form |
---|
4 | ### |
---|
5 | import os,sys,socket |
---|
6 | import threading |
---|
7 | import gi |
---|
8 | gi.require_version('Gtk', '3.0') |
---|
9 | gi.require_version('PangoCairo', '1.0') |
---|
10 | import json |
---|
11 | import cairo |
---|
12 | #import commands |
---|
13 | from gi.repository import Gtk, Gdk, GdkPixbuf, GObject, GLib, PangoCairo, Pango |
---|
14 | try: |
---|
15 | import xmlrpc.client as n4d |
---|
16 | except ImportError: |
---|
17 | raise ImportError("xmlrpc not available. Disabling server queries") |
---|
18 | import ssl |
---|
19 | import time |
---|
20 | import gettext |
---|
21 | |
---|
22 | gettext.textdomain('edupals.ui.common') |
---|
23 | _ = gettext.gettext |
---|
24 | GObject.threads_init() |
---|
25 | |
---|
26 | class N4dGtkLogin(Gtk.Box): |
---|
27 | __gtype_name__='n4dgtklogin' |
---|
28 | |
---|
29 | def __init__(self,*args,**kwds): |
---|
30 | super().__init__(*args,**kwds) |
---|
31 | self.sw_n4d=True |
---|
32 | if hasattr(sys,'last_value'): |
---|
33 | #If there's any error at this point it only could be an ImportError caused by xmlrpc |
---|
34 | self.sw_n4d=False |
---|
35 | self.css_classes={} |
---|
36 | self.style_provider=Gtk.CssProvider() |
---|
37 | Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),self.style_provider,Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) |
---|
38 | self.default_spacing=12 |
---|
39 | self.username_placeholder=_("Username") |
---|
40 | self.server_placeholder=_("Server IP (Default value : server)") |
---|
41 | self.login_banner_default="llx-avatar" |
---|
42 | self.info_banner=None |
---|
43 | self.allowed_groups=[] |
---|
44 | #internal boxes |
---|
45 | self.form_box=Gtk.Box(spacing=0,orientation=Gtk.Orientation.VERTICAL) |
---|
46 | self.info_box=Gtk.Box(spacing=self.default_spacing,orientation=Gtk.Orientation.VERTICAL) |
---|
47 | self.main_grid=Gtk.Grid() |
---|
48 | self.render_form() |
---|
49 | #def __init__ |
---|
50 | |
---|
51 | def set_allowed_groups(self,groups): |
---|
52 | self.allowed_groups=groups |
---|
53 | #def set_allowed_groups |
---|
54 | |
---|
55 | def set_mw_proportion_ratio(self,left_panel,right_panel): |
---|
56 | for child in self.main_grid.get_children(): |
---|
57 | self.main_grid.remove(child) |
---|
58 | self.main_grid.attach(self.info_box,0,0,left_panel,1) |
---|
59 | self.main_grid.attach(self.form_box,0+left_panel,0,right_panel,1) |
---|
60 | #def set_mw_proportion_ratio |
---|
61 | |
---|
62 | def set_mw_background(self,image=None,cover=False,from_color='#ffffff',to_color='silver',gradient='linear'): |
---|
63 | mw_background=self._set_background(image,cover,from_color,to_color,gradient) |
---|
64 | self.css_classes['#mw']='{'+mw_background+';;}' |
---|
65 | self._set_css() |
---|
66 | #def set_mw_background |
---|
67 | |
---|
68 | def set_login_background(self,image=None,cover=False,from_color='#ffffff',to_color='@silver',gradient='linear'): |
---|
69 | form_background=self._set_background(image,cover,from_color,to_color,gradient) |
---|
70 | self.css_classes['#main']='{'+form_background+';;}' |
---|
71 | self._set_css() |
---|
72 | #def set_login_background |
---|
73 | |
---|
74 | def set_info_background(self,image=None,cover=False,from_color='#ffffff',to_color='@silver',gradient='linear'): |
---|
75 | info_background=self._set_background(image,cover,from_color,to_color,gradient) |
---|
76 | self.css_classes['#info']='{'+info_background+';;}' |
---|
77 | self._set_css() |
---|
78 | #def set_info_background |
---|
79 | |
---|
80 | def _set_css(self): |
---|
81 | css='' |
---|
82 | for css_class,style in self.css_classes.items(): |
---|
83 | css=css+css_class+' '+style |
---|
84 | css_style=eval('b"""'+css+'"""') |
---|
85 | self.style_provider.load_from_data(css_style) |
---|
86 | #def _set_css |
---|
87 | |
---|
88 | def _set_background(self,image=None,cover=False,from_color='#ffffff',to_color='silver',gradient='linear'): |
---|
89 | bg='' |
---|
90 | if image and os.path.isfile(image): |
---|
91 | if cover: |
---|
92 | bg='background-image:url("'+image+'"); background-repeat:no-repeat; background-size:100% 100%' |
---|
93 | else: |
---|
94 | bg='background-image:url("'+image+'"); background-repeat:no-repeat;' |
---|
95 | elif image: |
---|
96 | #try to locate the image in the default theme |
---|
97 | icon_theme=Gtk.IconTheme.get_default() |
---|
98 | icon_sizes=icon_theme.get_icon_sizes(image) |
---|
99 | if icon_sizes: |
---|
100 | max_size=max(icon_sizes) |
---|
101 | icon=icon_theme.lookup_icon(image,max_size,0) |
---|
102 | icon_path=icon.get_filename() |
---|
103 | bg='background-image:url("'+icon_path+'"); background-repeat:no-repeat; background-size:100% 100%' |
---|
104 | |
---|
105 | else: |
---|
106 | if gradient=='linear': |
---|
107 | bg='background-image:-gtk-gradient (linear, left top, left bottom, from ('+from_color+'), to ('+to_color+'))' |
---|
108 | elif gradient=='radial': |
---|
109 | bg='background-image:-gtk-gradient (radial, center center,0,center center,1, from ('+from_color+'), to ('+to_color+'))' |
---|
110 | return bg |
---|
111 | #def _set_background |
---|
112 | |
---|
113 | def set_default_username(self,username): |
---|
114 | self.username_placeholder=username |
---|
115 | self._set_text_for_entry(self.txt_username,username) |
---|
116 | #def set_default_username |
---|
117 | |
---|
118 | def set_default_server(self,server): |
---|
119 | self.server_placeholder=server |
---|
120 | if self.txt_server: |
---|
121 | self._set_text_for_entry(self.txt_server,server) |
---|
122 | #def set_default_server |
---|
123 | |
---|
124 | def _set_text_for_entry(self,widget,text): |
---|
125 | widget.set_placeholder_text(text) |
---|
126 | #def _set_text_for_entry |
---|
127 | |
---|
128 | def _lookup_user_face(self): |
---|
129 | sw_ok=False |
---|
130 | if os.path.isfile(os.path.expanduser('~/.face')): |
---|
131 | sw_ok=True |
---|
132 | self.set_login_banner(os.path.expanduser('~/.face')) |
---|
133 | return sw_ok |
---|
134 | #def _lookup_user_face |
---|
135 | |
---|
136 | def set_login_banner(self,banner): |
---|
137 | self.login_banner.set_from_pixbuf(self._get_image(banner)) |
---|
138 | #def set_banner |
---|
139 | |
---|
140 | def set_info_banner(self,banner,x=72,y=72): |
---|
141 | self.info_banner.set_from_pixbuf(self._get_image(banner)) |
---|
142 | #def set_info_banner |
---|
143 | |
---|
144 | def _get_image(self,image,x=72,y=72): |
---|
145 | icon_theme=Gtk.IconTheme.get_default() |
---|
146 | if icon_theme.has_icon(image): |
---|
147 | pixbuf=icon_theme.load_icon(image,x,0) |
---|
148 | else: |
---|
149 | if os.path.isfile(image): |
---|
150 | pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_scale(image,x,y,True) |
---|
151 | return pixbuf |
---|
152 | #def _get_image |
---|
153 | |
---|
154 | def set_info_text(self,title,subtitle,text): |
---|
155 | sw_ok=True |
---|
156 | info_msg='' |
---|
157 | self.lbl_info_msg.set_width_chars(25) |
---|
158 | self.lbl_info_msg.set_max_width_chars(25) |
---|
159 | try: |
---|
160 | msg="<b><big>"+title+"</big></b>" |
---|
161 | info_msg=msg+'\n'+subtitle+'\n\n'+text |
---|
162 | except Exception as e: |
---|
163 | sw_ok=False |
---|
164 | print(e) |
---|
165 | self.lbl_info_msg.set_markup(info_msg) |
---|
166 | self.lbl_info_msg.show() |
---|
167 | return sw_ok |
---|
168 | #def set_info_text |
---|
169 | |
---|
170 | def hide_server_entry(self): |
---|
171 | self.txt_server.props.no_show_all=True |
---|
172 | self.txt_server.hide() |
---|
173 | #def hide_server_entry |
---|
174 | |
---|
175 | def hide_info_box(self): |
---|
176 | self.info_box.props.no_show_all=True |
---|
177 | self.info_box.hide() |
---|
178 | #def hide_info_box |
---|
179 | |
---|
180 | def get_action_area(self): |
---|
181 | return self.info_box |
---|
182 | #def get_action_area |
---|
183 | |
---|
184 | def render_form(self): |
---|
185 | self.main_grid.set_hexpand(True) |
---|
186 | self.main_grid.set_vexpand(True) |
---|
187 | self.main_grid.set_column_homogeneous(True) |
---|
188 | self.main_grid.set_row_homogeneous(True) |
---|
189 | self._render_login_form() |
---|
190 | self._render_info_form() |
---|
191 | self.main_grid.attach(self.info_box,1,1,2,1) |
---|
192 | self.main_grid.attach(self.form_box,3,1,1,1) |
---|
193 | self.pack_start(self.main_grid,True,True,0) |
---|
194 | self.set_name("mw") |
---|
195 | self.form_box.set_name("main") |
---|
196 | #def render_form |
---|
197 | |
---|
198 | def _render_login_form(self): |
---|
199 | self.txt_username=Gtk.Entry() |
---|
200 | self.login_banner=Gtk.Image() |
---|
201 | if not self._lookup_user_face(): |
---|
202 | self.set_login_banner(self.login_banner_default) |
---|
203 | self.login_banner.set_margin_bottom(self.default_spacing) |
---|
204 | self.set_default_username(self.username_placeholder) |
---|
205 | self.txt_username.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,"emblem-personal") |
---|
206 | self.txt_password=Gtk.Entry() |
---|
207 | self.txt_password.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,"badge-small") |
---|
208 | self._set_text_for_entry(self.txt_password,_("Password")) |
---|
209 | self.txt_server=Gtk.Entry() |
---|
210 | self.set_default_server(self.server_placeholder) |
---|
211 | self.txt_server.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,"server") |
---|
212 | self.btn_sign=Gtk.Button(stock=Gtk.STOCK_OK) |
---|
213 | self.btn_sign.connect('clicked',self._validate) |
---|
214 | self.frame=Gtk.Frame() |
---|
215 | |
---|
216 | self.frame.set_shadow_type(Gtk.ShadowType.OUT) |
---|
217 | login_grid=Gtk.Grid() |
---|
218 | login_grid.set_margin_left(self.default_spacing) |
---|
219 | login_grid.set_margin_right(self.default_spacing) |
---|
220 | login_grid.set_margin_top(self.default_spacing) |
---|
221 | login_grid.set_margin_bottom(self.default_spacing) |
---|
222 | self.spinner=Gtk.Spinner() |
---|
223 | color=Gdk.Color(0,0,1) |
---|
224 | self.spinner.modify_bg(Gtk.StateType.NORMAL,color) |
---|
225 | login_grid.attach(self.spinner,0,1,1,5) |
---|
226 | login_grid.attach(self.login_banner,0,0,1,1) |
---|
227 | login_grid.attach(self.txt_username,0,1,1,1) |
---|
228 | self._set_widget_default_props(self.txt_username,_("Username")) |
---|
229 | self.txt_username.connect('activate',self._validate) |
---|
230 | login_grid.attach(self.txt_password,0,2,1,1) |
---|
231 | self._set_widget_default_props(self.txt_password,_("Password")) |
---|
232 | self.txt_password.set_visibility(False) |
---|
233 | self.txt_password.props.caps_lock_warning=True |
---|
234 | self.txt_password.connect('activate',self._validate) |
---|
235 | login_grid.attach(self.txt_server,0,3,1,1) |
---|
236 | self._set_widget_default_props(self.txt_server,_("Master Server IP")) |
---|
237 | self.txt_server.connect('activate',self._validate) |
---|
238 | self.btn_sign.set_margin_top(self.default_spacing) |
---|
239 | login_grid.attach(self.btn_sign,0,4,1,1) |
---|
240 | self.sta_info=Gtk.InfoBar() |
---|
241 | self.sta_info.set_show_close_button(True) |
---|
242 | self.sta_info.set_message_type(Gtk.MessageType.ERROR) |
---|
243 | self.lbl_error=Gtk.Label(_("Login failed")) |
---|
244 | self.sta_info.get_action_area().add(self.lbl_error) |
---|
245 | self.sta_info.set_visible(False) |
---|
246 | self.sta_info.set_no_show_all(True) |
---|
247 | self.sta_info.connect('response',self._status_info_hide) |
---|
248 | self.sta_info.set_valign(True) |
---|
249 | login_grid.props.valign=Gtk.Align.CENTER |
---|
250 | login_grid.props.halign=Gtk.Align.CENTER |
---|
251 | self.form_box.pack_start(self.sta_info,False,True,0) |
---|
252 | self.form_box.pack_start(login_grid,True,True,0) |
---|
253 | #def _render_login_form |
---|
254 | |
---|
255 | def _render_info_form(self): |
---|
256 | self.info_box.set_homogeneous(False) |
---|
257 | self.info_banner=Gtk.Image() |
---|
258 | info_detail_box=Gtk.Box(spacing=self.default_spacing,orientation=Gtk.Orientation.VERTICAL) |
---|
259 | info_detail_box.pack_start(self.info_banner,False,False,0) |
---|
260 | self.lbl_info_msg=Gtk.Label() |
---|
261 | self.lbl_info_msg.set_use_markup(True) |
---|
262 | self.lbl_info_msg.set_line_wrap(True) |
---|
263 | info_detail_box.pack_start(self.lbl_info_msg,True,True,0) |
---|
264 | self.css_classes['#label']='{background-color:rgba(200,200,200,0.8);;}' |
---|
265 | self.lbl_info_msg.set_name("label") |
---|
266 | self.lbl_info_msg.set_no_show_all(True) |
---|
267 | self.lbl_info_msg.hide() |
---|
268 | self.info_box.set_name("info") |
---|
269 | info_detail_box.props.valign=Gtk.Align.CENTER |
---|
270 | info_detail_box.props.halign=Gtk.Align.CENTER |
---|
271 | self.info_box.pack_start(info_detail_box,True,True,0) |
---|
272 | self._set_css() |
---|
273 | #def _render_info_form |
---|
274 | |
---|
275 | def _set_widget_default_props(self,widget,tooltip=None): |
---|
276 | widget.set_valign(True) |
---|
277 | widget.set_halign(True) |
---|
278 | widget.set_tooltip_text(tooltip) |
---|
279 | #def _set_widget_default_props |
---|
280 | |
---|
281 | def _status_info_hide(self,widget,data): |
---|
282 | self.sta_info.hide() |
---|
283 | self.frame.set_sensitive(True) |
---|
284 | #def _info_hide |
---|
285 | |
---|
286 | def _validate(self,widget=None): |
---|
287 | user=self.txt_username.get_text() |
---|
288 | pwd=self.txt_password.get_text() |
---|
289 | server=self.txt_server.get_text() |
---|
290 | if not server: |
---|
291 | server='server' |
---|
292 | try: |
---|
293 | socket.gethostbyname(server) |
---|
294 | except: |
---|
295 | server='localhost' |
---|
296 | self.spinner.start() |
---|
297 | self.frame.set_sensitive(False) |
---|
298 | th=threading.Thread(target=self._t_validate,args=[user,pwd,server]) |
---|
299 | th.start() |
---|
300 | #def _validate |
---|
301 | |
---|
302 | def _t_validate(self,user,pwd,server): |
---|
303 | ret=[False] |
---|
304 | self.lbl_error.set_text(_("Login failed")) |
---|
305 | if self.sw_n4d: |
---|
306 | try: |
---|
307 | self.n4dclient=self._n4d_connect(server) |
---|
308 | ret=self.n4dclient.validate_user(user,pwd) |
---|
309 | except socket.error as e: |
---|
310 | self.lbl_error.set_text(_("Unknown host")+' '+str(server)) |
---|
311 | ret=[False,str(e)] |
---|
312 | |
---|
313 | self.spinner.stop() |
---|
314 | if not ret[0]: |
---|
315 | self.sta_info.show() |
---|
316 | self.lbl_error.show() |
---|
317 | #show server entry if we can't connect to n4d in "server" |
---|
318 | self.txt_server.props.no_show_all=False |
---|
319 | self.txt_server.show() |
---|
320 | elif self.allowed_groups and not set(self.allowed_groups).intersection(ret[1]): |
---|
321 | #Check user groups |
---|
322 | self.lbl_error.set_text(_("User not allowed")) |
---|
323 | self.sta_info.show() |
---|
324 | self.lbl_error.show() |
---|
325 | else: |
---|
326 | GLib.idle_add(self.after_validate,user,pwd,server) |
---|
327 | #local validation |
---|
328 | #def _t_validate |
---|
329 | |
---|
330 | def after_validation_goto(self,func,data=None): |
---|
331 | self.after_validate=func |
---|
332 | #def after_validation_func |
---|
333 | |
---|
334 | def _n4d_connect(self,server): |
---|
335 | context=ssl._create_unverified_context() |
---|
336 | try: |
---|
337 | socket.gethostbyname(server) |
---|
338 | except: |
---|
339 | #It could be an ip |
---|
340 | try: |
---|
341 | socket.inet_aton(server) |
---|
342 | except Exception as e: |
---|
343 | print(e) |
---|
344 | raise |
---|
345 | c = n4d.ServerProxy("https://"+server+":9779",context=context,allow_none=True) |
---|
346 | return c |
---|
347 | #def _n4d_connect |
---|