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