source: n4dgtklogin/trunk/fuentes/python3-n4dgtklogin.install/edupals/ui/n4dgtklogin.py @ 7011

Last change on this file since 7011 was 7011, checked in by Juanma, 2 years ago

show server field when login fails with unknown host

File size: 11.6 KB
Line 
1#!/usr/bin/env python3
2###
3#This class returns a login_grid whith a standarized login form
4###
5import os,sys,socket
6import threading
7import gi
8gi.require_version('Gtk', '3.0')
9gi.require_version('PangoCairo', '1.0')
10import json
11import cairo
12#import commands
13from gi.repository import Gtk, Gdk, GdkPixbuf, GObject, GLib, PangoCairo, Pango
14try:
15        import xmlrpc.client as n4d
16except ImportError:
17        raise ImportError("xmlrpc not available. Disabling server queries")
18import ssl
19import time
20import gettext
21
22gettext.textdomain('edupals.ui.common')
23_ = gettext.gettext
24GObject.threads_init()
25
26class 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"))
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
Note: See TracBrowser for help on using the repository browser.