[PATCH V3 0/5] Switch to a traditional login flow

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> V2 -> V3: improve when to show timeout message V1 -> V2: when username or password is wrong, back to login page with an error message. when session time out, back to login page with an error message. ShaoHe Feng (5): create a new login page redirect the URL to login page when session timeout or first login when login successfully, redirect to the last page. login page prompts error when username or password is wrong login page prompts error when session timeout src/kimchi/auth.py | 51 +++++++++-- src/kimchi/config.py.in | 7 ++ src/kimchi/root.py | 38 ++++++-- src/kimchi/server.py | 2 + ui/images/progressing.gif | Bin 0 -> 1152 bytes ui/js/src/kimchi.main.js | 6 +- ui/pages/login.html.tmpl | 228 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 310 insertions(+), 22 deletions(-) create mode 100644 ui/images/progressing.gif create mode 100644 ui/pages/login.html.tmpl -- 1.9.3

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> this page is used for the session timeout or first login. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- ui/images/progressing.gif | Bin 0 -> 1152 bytes ui/pages/login.html.tmpl | 220 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 ui/images/progressing.gif create mode 100644 ui/pages/login.html.tmpl diff --git a/ui/images/progressing.gif b/ui/images/progressing.gif new file mode 100644 index 0000000000000000000000000000000000000000..6552d41d9d4c874091bb51931c6adf64a95e8bd0 GIT binary patch literal 1152 zcmZ?wbhEHb6k!ly*v!E2|NsBHckiA#b7s?~O&uK_IXO9@p`oUxrgCy}4A_9;e{Mh5 zkYH!W09PYD17=2`8pWS1oFWVy3_2k7AY&O=+5}E^`WY^hIhD@I;2`qkhUamCHs#2? zD{Sl5ddqAu;o;E8n07H&VTOd$9w+z3Od8dz)1OOlg@hfQ?6P6gg)Nhut~h8s_iHy| zTx?f<DfNuF4qs+in1T70o7P)P87f1<g5(0~t-`bz%`;<c7;R*Hby&0+Cd*D$nyx)7 zxH>hoEs9-2GEYbl<TO?^r*$AZO&~F#<dR{_!xK!0-EtLPZ*p|yWoF`%>SmZA?aQ<9 zUT3kA$h=8X2{%vLaEeZ2FfFvxHCy+@)2xTXXx&nW19L8~I4mhFCLSR*N5YUN_g8RC zxvG$d3rC2Yoni>Hr8Z+mk+UtMt&Fyriq3S|nYyz*lh|s_)v7fmBnyPWjzbSOeSwpn ztM^*GK3gr@WX{W=;H4@s$Eix`o#&Z+UC|vq;?Mm$*qz>q{MpOLKD$V>lW^GM@gRp< zTfqAxmTMW#NQtz1Ow(GUENaLTnG@~OSSl%)a-~@^*=_L^Wu6LazRZG$dI^iu%U-z% zg<00g*_8=1ySk){v9Pk(*~<9qvQD2qQDdsojG1{NY_a__TQemj7eK-u-Fqm>f>utm zWMS=L&jJU059@Suul#8<RCK0juuU^pO9X~|9`=wYHdzoCYCehQmh?GGSt;IO&EY61 z^|cU^WKC}pQa3GC)^bS)J5HFrADAqr$@=Tg_KIeUMR6Q@nr+9QW_y9zNsLj!T)@YO zmFW>EN`0IXPrZ>S5M3ptD%hrTMQhc8T>+_g4tF#}E)IMpc<k8J3C>bEce!tTI4V-^ zEEL|{;B8wW%<SUK!k*>CYhksVX{CWv466RGb|aXRcN%49rjY*aHrnWY;d4a(eT< sn9_<U58lHODbH9KGQC;#6@s@G?AG7t-SQycLr1Fr#FC=U9tH+$0KoWQ9smFU literal 0 HcmV?d00001 diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl new file mode 100644 index 0000000..0e4bb0d --- /dev/null +++ b/ui/pages/login.html.tmpl @@ -0,0 +1,220 @@ +#* + * Project Kimchi + * + * Copyright IBM, Corp. 2014 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *# +#unicode UTF-8 +#import gettext +#from kimchi.cachebust import href +#silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang) +#silent _ = t.gettext +#silent _t = t.gettext +#from kimchi.config import get_version +<!DOCTYPE html> +<html lang="$lang.lang[0]"> +<head> +<meta charset="UTF-8"> +<title>Kimchi</title> +<meta http-equiv="X-UA-Compatible" content="IE=edge"/> +<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> +<style type="text/css"> +.login { + font-family: Arial; + margin: 0px; +} +.login input.invalid-field { + border-color: #C85305 !important; +} +.login .header { + background: none repeat scroll 0 0 #353D40; + border-bottom: 3px solid #008ABF; + border-top: 0 none; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); + height: 50px; + color: #F8F8F8; + font-size: 13px; + font-weight: 700; + font-family: Helvetica; + overflow: hidden; +} +.login .header .lang { + float: right; + margin-right: 20px; + margin-top: 14px; +} +.login .header .lang select { + vertical-align: middle; +} +.login .content-pane { + margin-left: auto; + margin-right: auto; + margin-top: 200px; + width: 260px; +} +.login .content-pane .trademark { + margin-bottom: 30px; +} +.login .content-pane .trademark .logo { + background: url("../images/logo.ico") no-repeat scroll center center transparent; + display: inline-block; + height: 25px; + width: 25px; + margin-left: -4px; + margin-right: 5px; + vertical-align: middle; +} +.login .content-pane .trademark .name { + vertical-align: middle; + padding-top: 2px; + font-weight: bold; + font-size: 18px; +} +.login .content-pane .trademark .version { + vertical-align: middle; + padding-top: 6px; + font-size: 13px; + padding-left: 6px; +} +.login .content-pane .message-area .item { + font-size: 12px; + margin-bottom: 30px; + margin-top: 30px; + height: 20px; +} +.login .content-pane .message-area .text { + color: #C85305; +} +.login .progress-indicator .progress-icon { + margin-right: 3px; + vertical-align: middle; + width: 15px; + height: 15px; +} +.login .progress-indicator .progress-text { + vertical-align: middle; + padding-top: 2px; + padding-left: 2px; +} +.login .content-pane .hint { + margin-bottom: 10px; + font-size: 14px; +} +.login .content-pane .field-area { + overflow: hidden; +} +.login .content-pane .field-area .text-field { + width: 240px; + padding: 5px 7px; + margin-bottom: 5px; + border: 1px solid #BABABA; + border-radius: 1px; +} +.login .button { + margin-top: 20px; + background: -moz-linear-gradient(center top , #1783BF 0%, #0C4C88 100%) repeat scroll 0 0 transparent; + border-color: #666666 #666666 #555555; + border-image: none; + border-radius: 5px 5px 5px 5px; + border-style: solid; + border-width: 1px; + color: #DDDDDD; + cursor: pointer; + font-weight: bolder; + padding: 5px 10px; + float: right; +} +</style> +<script> +function getCookie(cname) { + var name = cname + "="; + var ca = document.cookie.split(';'); + for(var i=0; i<ca.length; i++) { + var c = ca[i].trim(); + if (c.indexOf(name)==0) return c.substring(name.length,c.length); + } + return null; +} +function setCookie(cname,cvalue,exdays) { + var d = new Date(); + d.setTime(d.getTime()+(exdays*24*60*60*1000)); + var expires = "expires="+d.toGMTString(); + document.cookie = cname + "=" + cvalue + "; " + expires; +} +function validateForm() { + var user = document.forms["login"]["username"]; + var pass = document.forms["login"]["password"]; + var validateField = function(field){ + field.className = "text-field"; + var isValid = field.value && field.value.trim()!="" ? true : false; + if(!isValid){ + field.className += " invalid-field"; + field.focus(); + } + return isValid; + }; + var isValid = validateField(pass); + isValid = validateField(user) && isValid; + if(isValid) + document.getElementById("messWait").style.display = ""; + return isValid; +} +function changeLang() { + var lang = document.getElementById('userLang').value; + setCookie('kimchiLang', lang, 365); + window.location.reload(); +} +function setLang() { + var defaultLang = 'en_US'; + var clientLang = document.getElementsByTagName("html")[0].getAttribute("lang"); + var persistLang = getCookie('kimchiLang'); + document.getElementById("userLang").value = persistLang || clientLang || defaultLang; +} +function init() { + setLang(); +} +</script> +</head> +<body class="login" onload="init()"> +<div class="header"> + <div class="lang"> + <label>$_("Language"):</label> + <select id="userLang" onchange="changeLang()"> + <option value="en_US">English (US)</option> + <option value="zh_CN">中文(简体)</option> + <option value="pt_BR">Português (Brasil)</option> + </select> + </div> +</div> +<div class="content-pane"> + <div class="trademark"><span class="logo"></span><span class="name">Kimchi</span><span class="version">1.2</span></div> + <div class="message-area"> + <div id="messUserPass" class="item text" style="display: none;">$_("User name or password is incorrect.")</div> + <div id="messSession" class="item text" style="display: none;">$_("Session timeout, please re-login.")</div> + <div id="messWait" class="item progress-indicator" style="display: none;"> + <img src="images/progressing.gif" class="progress-icon"><span class="progress-text">$_("Processing")...</span> + </div> + </div> + <div class="hint">$_("Sign in with your linux account")</div> + <div class="field-area"> + <form name="login" action="/login?next=$getVar('data.next', '/')" method="POST" onsubmit="return validateForm()"> + <input type="hidden" name="next" value="$getVar('data.next', '/')"> + <input type="text" name="username" placeholder="$_("User Name")" class="text-field" autofocus> + <input type="password" name="password" placeholder="$_("Password")" class="text-field"> + <input type="submit" value="$_("Sign In")" class="button"> + </form> + </div> +</div> +</body> +</html> -- 1.9.3

We should use the default kimchi style in the login page. The same header. And instead of the user information in the right corner, the language selection would be displayed. Note the background is silver and the header is centered. And vertical and horizontal aligned would be the login form, like it is today. We should keep the same input styles. And I also prefer the "Log in" button to change to "Logging..." instead the message in the form. On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
this page is used for the session timeout or first login.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- ui/images/progressing.gif | Bin 0 -> 1152 bytes ui/pages/login.html.tmpl | 220 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 ui/images/progressing.gif create mode 100644 ui/pages/login.html.tmpl
diff --git a/ui/images/progressing.gif b/ui/images/progressing.gif new file mode 100644 index 0000000000000000000000000000000000000000..6552d41d9d4c874091bb51931c6adf64a95e8bd0 GIT binary patch literal 1152 zcmZ?wbhEHb6k!ly*v!E2|NsBHckiA#b7s?~O&uK_IXO9@p`oUxrgCy}4A_9;e{Mh5 zkYH!W09PYD17=2`8pWS1oFWVy3_2k7AY&O=+5}E^`WY^hIhD@I;2`qkhUamCHs#2? zD{Sl5ddqAu;o;E8n07H&VTOd$9w+z3Od8dz)1OOlg@hfQ?6P6gg)Nhut~h8s_iHy| zTx?f<DfNuF4qs+in1T70o7P)P87f1<g5(0~t-`bz%`;<c7;R*Hby&0+Cd*D$nyx)7 zxH>hoEs9-2GEYbl<TO?^r*$AZO&~F#<dR{_!xK!0-EtLPZ*p|yWoF`%>SmZA?aQ<9 zUT3kA$h=8X2{%vLaEeZ2FfFvxHCy+@)2xTXXx&nW19L8~I4mhFCLSR*N5YUN_g8RC zxvG$d3rC2Yoni>Hr8Z+mk+UtMt&Fyriq3S|nYyz*lh|s_)v7fmBnyPWjzbSOeSwpn ztM^*GK3gr@WX{W=;H4@s$Eix`o#&Z+UC|vq;?Mm$*qz>q{MpOLKD$V>lW^GM@gRp< zTfqAxmTMW#NQtz1Ow(GUENaLTnG@~OSSl%)a-~@^*=_L^Wu6LazRZG$dI^iu%U-z% zg<00g*_8=1ySk){v9Pk(*~<9qvQD2qQDdsojG1{NY_a__TQemj7eK-u-Fqm>f>utm zWMS=L&jJU059@Suul#8<RCK0juuU^pO9X~|9`=wYHdzoCYCehQmh?GGSt;IO&EY61 z^|cU^WKC}pQa3GC)^bS)J5HFrADAqr$@=Tg_KIeUMR6Q@nr+9QW_y9zNsLj!T)@YO zmFW>EN`0IXPrZ>S5M3ptD%hrTMQhc8T>+_g4tF#}E)IMpc<k8J3C>bEce!tTI4V-^ zEEL|{;B8wW%<SUK!k*>CYhksVX{CWv466RGb|aXRcN%49rjY*aHrnWY;d4a(eT< sn9_<U58lHODbH9KGQC;#6@s@G?AG7t-SQycLr1Fr#FC=U9tH+$0KoWQ9smFU
literal 0 HcmV?d00001
diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl new file mode 100644 index 0000000..0e4bb0d --- /dev/null +++ b/ui/pages/login.html.tmpl @@ -0,0 +1,220 @@ +#* + * Project Kimchi + * + * Copyright IBM, Corp. 2014 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *# +#unicode UTF-8 +#import gettext +#from kimchi.cachebust import href +#silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang) +#silent _ = t.gettext +#silent _t = t.gettext +#from kimchi.config import get_version +<!DOCTYPE html> +<html lang="$lang.lang[0]"> +<head> +<meta charset="UTF-8"> +<title>Kimchi</title> +<meta http-equiv="X-UA-Compatible" content="IE=edge"/> +<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> +<style type="text/css"> +.login { + font-family: Arial; + margin: 0px; +} +.login input.invalid-field { + border-color: #C85305 !important; +} +.login .header { + background: none repeat scroll 0 0 #353D40; + border-bottom: 3px solid #008ABF; + border-top: 0 none; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); + height: 50px; + color: #F8F8F8; + font-size: 13px; + font-weight: 700; + font-family: Helvetica; + overflow: hidden; +} +.login .header .lang { + float: right; + margin-right: 20px; + margin-top: 14px; +} +.login .header .lang select { + vertical-align: middle; +} +.login .content-pane { + margin-left: auto; + margin-right: auto; + margin-top: 200px; + width: 260px; +} +.login .content-pane .trademark { + margin-bottom: 30px; +} +.login .content-pane .trademark .logo { + background: url("../images/logo.ico") no-repeat scroll center center transparent; + display: inline-block; + height: 25px; + width: 25px; + margin-left: -4px; + margin-right: 5px; + vertical-align: middle; +} +.login .content-pane .trademark .name { + vertical-align: middle; + padding-top: 2px; + font-weight: bold; + font-size: 18px; +} +.login .content-pane .trademark .version { + vertical-align: middle; + padding-top: 6px; + font-size: 13px; + padding-left: 6px; +} +.login .content-pane .message-area .item { + font-size: 12px; + margin-bottom: 30px; + margin-top: 30px; + height: 20px; +} +.login .content-pane .message-area .text { + color: #C85305; +} +.login .progress-indicator .progress-icon { + margin-right: 3px; + vertical-align: middle; + width: 15px; + height: 15px; +} +.login .progress-indicator .progress-text { + vertical-align: middle; + padding-top: 2px; + padding-left: 2px; +} +.login .content-pane .hint { + margin-bottom: 10px; + font-size: 14px; +} +.login .content-pane .field-area { + overflow: hidden; +} +.login .content-pane .field-area .text-field { + width: 240px; + padding: 5px 7px; + margin-bottom: 5px; + border: 1px solid #BABABA; + border-radius: 1px; +} +.login .button { + margin-top: 20px; + background: -moz-linear-gradient(center top , #1783BF 0%, #0C4C88 100%) repeat scroll 0 0 transparent; + border-color: #666666 #666666 #555555; + border-image: none; + border-radius: 5px 5px 5px 5px; + border-style: solid; + border-width: 1px; + color: #DDDDDD; + cursor: pointer; + font-weight: bolder; + padding: 5px 10px; + float: right; +} +</style> +<script> +function getCookie(cname) { + var name = cname + "="; + var ca = document.cookie.split(';'); + for(var i=0; i<ca.length; i++) { + var c = ca[i].trim(); + if (c.indexOf(name)==0) return c.substring(name.length,c.length); + } + return null; +} +function setCookie(cname,cvalue,exdays) { + var d = new Date(); + d.setTime(d.getTime()+(exdays*24*60*60*1000)); + var expires = "expires="+d.toGMTString(); + document.cookie = cname + "=" + cvalue + "; " + expires; +} +function validateForm() { + var user = document.forms["login"]["username"]; + var pass = document.forms["login"]["password"]; + var validateField = function(field){ + field.className = "text-field"; + var isValid = field.value && field.value.trim()!="" ? true : false; + if(!isValid){ + field.className += " invalid-field"; + field.focus(); + } + return isValid; + }; + var isValid = validateField(pass); + isValid = validateField(user) && isValid; + if(isValid) + document.getElementById("messWait").style.display = ""; + return isValid; +} +function changeLang() { + var lang = document.getElementById('userLang').value; + setCookie('kimchiLang', lang, 365); + window.location.reload(); +} +function setLang() { + var defaultLang = 'en_US'; + var clientLang = document.getElementsByTagName("html")[0].getAttribute("lang"); + var persistLang = getCookie('kimchiLang'); + document.getElementById("userLang").value = persistLang || clientLang || defaultLang; +} +function init() { + setLang(); +} +</script> +</head> +<body class="login" onload="init()"> +<div class="header"> + <div class="lang"> + <label>$_("Language"):</label> + <select id="userLang" onchange="changeLang()"> + <option value="en_US">English (US)</option> + <option value="zh_CN">中文(简体)</option> + <option value="pt_BR">Português (Brasil)</option> + </select> + </div> +</div> +<div class="content-pane"> + <div class="trademark"><span class="logo"></span><span class="name">Kimchi</span><span class="version">1.2</span></div> + <div class="message-area"> + <div id="messUserPass" class="item text" style="display: none;">$_("User name or password is incorrect.")</div> + <div id="messSession" class="item text" style="display: none;">$_("Session timeout, please re-login.")</div> + <div id="messWait" class="item progress-indicator" style="display: none;"> + <img src="images/progressing.gif" class="progress-icon"><span class="progress-text">$_("Processing")...</span> + </div> + </div> + <div class="hint">$_("Sign in with your linux account")</div> + <div class="field-area"> + <form name="login" action="/login?next=$getVar('data.next', '/')" method="POST" onsubmit="return validateForm()"> + <input type="hidden" name="next" value="$getVar('data.next', '/')"> + <input type="text" name="username" placeholder="$_("User Name")" class="text-field" autofocus> + <input type="password" name="password" placeholder="$_("Password")" class="text-field"> + <input type="submit" value="$_("Sign In")" class="button"> + </form> + </div> +</div> +</body> +</html>
����

On 06/10/2014 07:09 AM, Yu Xin Huo wrote:
New Login UI
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel
Looks better! Thanks for the patch! What about if we increase the height header in the login page? And also add a "Log in" title to the form?

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> If the content type is application/json still raise 401 status code. And let UI redirect to login page. or the backe redirects to login page directly. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- src/kimchi/auth.py | 11 +++++++++++ src/kimchi/config.py.in | 3 +++ src/kimchi/root.py | 28 +++++++++++++++++++--------- ui/js/src/kimchi.main.js | 5 +---- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index dc78ded..a38dbd3 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -28,6 +28,7 @@ import re import termios import time +import urllib2 from kimchi import template @@ -41,6 +42,12 @@ REFRESH = 'robot-refresh' +def redirect_login(): + next_url = urllib2.quote( + cherrypy.request.path_info.encode('utf-8'), safe="") + raise cherrypy.HTTPRedirect("/login.html?next=%s" % next_url, 303) + + def debug(msg): pass # cherrypy.log.error(msg) @@ -234,6 +241,10 @@ def kimchiauth(admin_methods=None): raise cherrypy.HTTPError(403) return + # not a REST full request, redirect login page directly + if not template.can_accept('application/json'): + redirect_login() + if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi' diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index 0206570..d4cbda0 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -187,6 +187,9 @@ class KimchiConfig(dict): '/spice.html': { 'tools.kimchiauth.on': True }, + '/kimchi-ui.html': { + 'tools.kimchiauth.on': True + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 8b1d09b..181ab13 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -81,7 +81,7 @@ def get(self): @cherrypy.expose def default(self, page, **kwargs): if page.endswith('.html'): - return template.render(page, None) + return template.render(page, kwargs) raise cherrypy.HTTPError(404) @cherrypy.expose @@ -110,14 +110,24 @@ def __init__(self, model, dev_env): self.messages = messages @cherrypy.expose - def login(self, *args): - params = parse_request() - try: - username = params['username'] - password = params['password'] - except KeyError, item: - e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) - raise cherrypy.HTTPError(400, e.message) + def login(self, *args, **kwargs): + username = kwargs.get('username') + password = kwargs.get('password') + # forms base authentication + if username is not None: + # UI can pass the redirect url by "next" query parameter + next_url = kwargs.get('next', "/") + next_url = type(next_url) is list and next_url[0] + auth.login(username, password) + raise cherrypy.HTTPRedirect(next_url, 303) + else: + try: + params = parse_request() + username = params['username'] + password = params['password'] + except KeyError, item: + e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) + raise cherrypy.HTTPError(400, e.message) try: user_info = auth.login(username, password) diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 184029d..2a8f461 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -227,10 +227,7 @@ kimchi.main = function() { kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - kimchi.window.open({ - url: 'login-window.html', - id: 'login-window-wrapper' - }); + document.location.href='login.html'; return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) { -- 1.9.3

On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
If the content type is application/json still raise 401 status code. And let UI redirect to login page.
or the backe redirects to login page directly.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- src/kimchi/auth.py | 11 +++++++++++ src/kimchi/config.py.in | 3 +++ src/kimchi/root.py | 28 +++++++++++++++++++--------- ui/js/src/kimchi.main.js | 5 +---- 4 files changed, 34 insertions(+), 13 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index dc78ded..a38dbd3 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -28,6 +28,7 @@ import re import termios import time +import urllib2
from kimchi import template @@ -41,6 +42,12 @@ REFRESH = 'robot-refresh'
+def redirect_login(): + next_url = urllib2.quote( + cherrypy.request.path_info.encode('utf-8'), safe="") + raise cherrypy.HTTPRedirect("/login.html?next=%s" % next_url, 303) + + def debug(msg): pass # cherrypy.log.error(msg) @@ -234,6 +241,10 @@ def kimchiauth(admin_methods=None): raise cherrypy.HTTPError(403) return
+ # not a REST full request, redirect login page directly + if not template.can_accept('application/json'): + redirect_login() + if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi'
diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index 0206570..d4cbda0 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -187,6 +187,9 @@ class KimchiConfig(dict): '/spice.html': { 'tools.kimchiauth.on': True }, + '/kimchi-ui.html': { + 'tools.kimchiauth.on': True + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 8b1d09b..181ab13 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -81,7 +81,7 @@ def get(self): @cherrypy.expose def default(self, page, **kwargs): if page.endswith('.html'): - return template.render(page, None) + return template.render(page, kwargs) raise cherrypy.HTTPError(404)
@cherrypy.expose @@ -110,14 +110,24 @@ def __init__(self, model, dev_env): self.messages = messages
@cherrypy.expose - def login(self, *args): - params = parse_request() - try: - username = params['username'] - password = params['password'] - except KeyError, item: - e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) - raise cherrypy.HTTPError(400, e.message)
+ def login(self, *args, **kwargs): + username = kwargs.get('username') + password = kwargs.get('password') + # forms base authentication + if username is not None: + # UI can pass the redirect url by "next" query parameter + next_url = kwargs.get('next', "/") + next_url = type(next_url) is list and next_url[0] + auth.login(username, password) + raise cherrypy.HTTPRedirect(next_url, 303) + else: + try: + params = parse_request() + username = params['username'] + password = params['password'] + except KeyError, item: + e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) + raise cherrypy.HTTPError(400, e.message)
I didn't understand this code. Why did you get username and password from kwargs and in "else" from parse_request()? Should that info be in a single location? And if you raise/return, you don't need a "else" it eliminates some indentation levels.
try: user_info = auth.login(username, password) diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 184029d..2a8f461 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -227,10 +227,7 @@ kimchi.main = function() { kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - kimchi.window.open({ - url: 'login-window.html', - id: 'login-window-wrapper' - }); + document.location.href='login.html'; return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) {

On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
If the content type is application/json still raise 401 status code. And let UI redirect to login page.
or the backe redirects to login page directly.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- src/kimchi/auth.py | 11 +++++++++++ src/kimchi/config.py.in | 3 +++ src/kimchi/root.py | 28 +++++++++++++++++++--------- ui/js/src/kimchi.main.js | 5 +---- 4 files changed, 34 insertions(+), 13 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index dc78ded..a38dbd3 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -28,6 +28,7 @@ import re import termios import time +import urllib2
from kimchi import template @@ -41,6 +42,12 @@ REFRESH = 'robot-refresh'
+def redirect_login(): + next_url = urllib2.quote( + cherrypy.request.path_info.encode('utf-8'), safe="") + raise cherrypy.HTTPRedirect("/login.html?next=%s" % next_url, 303) + + def debug(msg): pass # cherrypy.log.error(msg) @@ -234,6 +241,10 @@ def kimchiauth(admin_methods=None): raise cherrypy.HTTPError(403) return
+ # not a REST full request, redirect login page directly + if not template.can_accept('application/json'): + redirect_login() + if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi'
diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index 0206570..d4cbda0 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -187,6 +187,9 @@ class KimchiConfig(dict): '/spice.html': { 'tools.kimchiauth.on': True }, + '/kimchi-ui.html': { + 'tools.kimchiauth.on': True + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 8b1d09b..181ab13 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -81,7 +81,7 @@ def get(self): @cherrypy.expose def default(self, page, **kwargs): if page.endswith('.html'): - return template.render(page, None) + return template.render(page, kwargs) raise cherrypy.HTTPError(404)
@cherrypy.expose @@ -110,14 +110,24 @@ def __init__(self, model, dev_env): self.messages = messages
@cherrypy.expose - def login(self, *args): - params = parse_request() - try: - username = params['username'] - password = params['password'] - except KeyError, item: - e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) - raise cherrypy.HTTPError(400, e.message)
+ def login(self, *args, **kwargs): + username = kwargs.get('username') + password = kwargs.get('password') + # forms base authentication + if username is not None: + # UI can pass the redirect url by "next" query parameter + next_url = kwargs.get('next', "/") + next_url = type(next_url) is list and next_url[0] + auth.login(username, password) + raise cherrypy.HTTPRedirect(next_url, 303) + else: + try: + params = parse_request() + username = params['username'] + password = params['password'] + except KeyError, item: + e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) + raise cherrypy.HTTPError(400, e.message)
I didn't understand this code. Why did you get username and password from kwargs and in "else" from parse_request()? Should that info be in a single location?
On 06/10/2014 02:11 AM, Aline Manera wrote: parse_request() get the password and username from body for REST API request. for form authentication. The cherrypy will do it by itself, and pass it to login
And if you raise/return, you don't need a "else" it eliminates some indentation levels.
I can remove the “else”
try: user_info = auth.login(username, password) diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 184029d..2a8f461 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -227,10 +227,7 @@ kimchi.main = function() { kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - kimchi.window.open({ - url: 'login-window.html', - id: 'login-window-wrapper' - }); + document.location.href='login.html'; return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) {
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

On 06/10/2014 09:42 AM, Sheldon wrote:
On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
If the content type is application/json still raise 401 status code. And let UI redirect to login page.
or the backe redirects to login page directly.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- src/kimchi/auth.py | 11 +++++++++++ src/kimchi/config.py.in | 3 +++ src/kimchi/root.py | 28 +++++++++++++++++++--------- ui/js/src/kimchi.main.js | 5 +---- 4 files changed, 34 insertions(+), 13 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index dc78ded..a38dbd3 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -28,6 +28,7 @@ import re import termios import time +import urllib2
from kimchi import template @@ -41,6 +42,12 @@ REFRESH = 'robot-refresh'
+def redirect_login(): + next_url = urllib2.quote( + cherrypy.request.path_info.encode('utf-8'), safe="") + raise cherrypy.HTTPRedirect("/login.html?next=%s" % next_url, 303) + + def debug(msg): pass # cherrypy.log.error(msg) @@ -234,6 +241,10 @@ def kimchiauth(admin_methods=None): raise cherrypy.HTTPError(403) return
+ # not a REST full request, redirect login page directly + if not template.can_accept('application/json'): + redirect_login() + if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi'
diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index 0206570..d4cbda0 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -187,6 +187,9 @@ class KimchiConfig(dict): '/spice.html': { 'tools.kimchiauth.on': True }, + '/kimchi-ui.html': { + 'tools.kimchiauth.on': True + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 8b1d09b..181ab13 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -81,7 +81,7 @@ def get(self): @cherrypy.expose def default(self, page, **kwargs): if page.endswith('.html'): - return template.render(page, None) + return template.render(page, kwargs) raise cherrypy.HTTPError(404)
@cherrypy.expose @@ -110,14 +110,24 @@ def __init__(self, model, dev_env): self.messages = messages
@cherrypy.expose - def login(self, *args): - params = parse_request() - try: - username = params['username'] - password = params['password'] - except KeyError, item: - e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) - raise cherrypy.HTTPError(400, e.message)
+ def login(self, *args, **kwargs): + username = kwargs.get('username') + password = kwargs.get('password') + # forms base authentication + if username is not None: + # UI can pass the redirect url by "next" query parameter + next_url = kwargs.get('next', "/") + next_url = type(next_url) is list and next_url[0] + auth.login(username, password) + raise cherrypy.HTTPRedirect(next_url, 303) + else: + try: + params = parse_request() + username = params['username'] + password = params['password'] + except KeyError, item: + e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) + raise cherrypy.HTTPError(400, e.message)
I didn't understand this code. Why did you get username and password from kwargs and in "else" from parse_request()? Should that info be in a single location?
On 06/10/2014 02:11 AM, Aline Manera wrote: parse_request() get the password and username from body for REST API request. for form authentication. The cherrypy will do it by itself, and pass it to login
And when in which situation we get the username and password from kwargs? PS. Sorry about too many questions but I am trying to understand the code =)
And if you raise/return, you don't need a "else" it eliminates some indentation levels.
I can remove the “else”
try: user_info = auth.login(username, password) diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 184029d..2a8f461 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -227,10 +227,7 @@ kimchi.main = function() { kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - kimchi.window.open({ - url: 'login-window.html', - id: 'login-window-wrapper' - }); + document.location.href='login.html'; return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) {

On 06/10/2014 09:27 PM, Aline Manera wrote:
On 06/10/2014 09:42 AM, Sheldon wrote:
On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
If the content type is application/json still raise 401 status code. And let UI redirect to login page.
or the backe redirects to login page directly.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- src/kimchi/auth.py | 11 +++++++++++ src/kimchi/config.py.in | 3 +++ src/kimchi/root.py | 28 +++++++++++++++++++--------- ui/js/src/kimchi.main.js | 5 +---- 4 files changed, 34 insertions(+), 13 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index dc78ded..a38dbd3 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -28,6 +28,7 @@ import re import termios import time +import urllib2
from kimchi import template @@ -41,6 +42,12 @@ REFRESH = 'robot-refresh'
+def redirect_login(): + next_url = urllib2.quote( + cherrypy.request.path_info.encode('utf-8'), safe="") + raise cherrypy.HTTPRedirect("/login.html?next=%s" % next_url, 303) + + def debug(msg): pass # cherrypy.log.error(msg) @@ -234,6 +241,10 @@ def kimchiauth(admin_methods=None): raise cherrypy.HTTPError(403) return
+ # not a REST full request, redirect login page directly + if not template.can_accept('application/json'): + redirect_login() + if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi'
diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index 0206570..d4cbda0 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -187,6 +187,9 @@ class KimchiConfig(dict): '/spice.html': { 'tools.kimchiauth.on': True }, + '/kimchi-ui.html': { + 'tools.kimchiauth.on': True + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 8b1d09b..181ab13 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -81,7 +81,7 @@ def get(self): @cherrypy.expose def default(self, page, **kwargs): if page.endswith('.html'): - return template.render(page, None) + return template.render(page, kwargs) raise cherrypy.HTTPError(404)
@cherrypy.expose @@ -110,14 +110,24 @@ def __init__(self, model, dev_env): self.messages = messages
@cherrypy.expose - def login(self, *args): - params = parse_request() - try: - username = params['username'] - password = params['password'] - except KeyError, item: - e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) - raise cherrypy.HTTPError(400, e.message)
+ def login(self, *args, **kwargs): + username = kwargs.get('username') + password = kwargs.get('password') + # forms base authentication + if username is not None: + # UI can pass the redirect url by "next" query parameter + next_url = kwargs.get('next', "/") + next_url = type(next_url) is list and next_url[0] + auth.login(username, password) + raise cherrypy.HTTPRedirect(next_url, 303) + else: + try: + params = parse_request() + username = params['username'] + password = params['password'] + except KeyError, item: + e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) + raise cherrypy.HTTPError(400, e.message)
I didn't understand this code. Why did you get username and password from kwargs and in "else" from parse_request()? Should that info be in a single location?
On 06/10/2014 02:11 AM, Aline Manera wrote: parse_request() get the password and username from body for REST API request. for form authentication. The cherrypy will do it by itself, and pass it to login
And when in which situation we get the username and password from kwargs?
PS. Sorry about too many questions but I am trying to understand the code =)
Request Headersview source Accept |text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8| Accept-Encoding |gzip, deflate| Accept-Language |zh-cn,en-us;q=0.7,en;q=0.3| Connection |keep-alive| Cookie |userid=root; kimchiLang=zh_CN; username=shhfeng; ticketVM=VqO6AWlH; lastPage="/#tabs/templates"; kimchi||=fc2fa059ee694c3d959fa1a1902557d21526e78e| DNT |1| Host |localhost:8001| Referer |https://localhost:8001/login.html| User-Agent |Mozilla/5.0 (X11; Linux x86_64; rv:29.0) Gecko/20100101 Firefox/29.0| Request Headers From Upload Stream Content-Length |41| Content-Type |application/x-www-form-urlencoded| post body. I think the cherrypy know x-www-form-urlencoded, it can parser the body by itself. Parametersapplication/x-www-form-urlencoded next |/| password |123456| username |shhfeng| Source |next=%2F&username=shhfeng&password=123456| By our rest quest, the Content-Type is application/json we paser it by ourself. I have check the cherrypy code: some explanation for application/x-www-form-urlencoded def _get_body_params(self): warnings.warn( "body_params is deprecated in CherryPy 3.2, will be removed in " "CherryPy 3.3.", DeprecationWarning ) return self.body.params body_params = property(_get_body_params, doc= """ If the request Content-Type is 'application/x-www-form-urlencoded' or multipart, this will be a dict of the params pulled from the entity body; that is, it will be the portion of request.params that come from the message body (sometimes called "POST params", although they can be sent with various HTTP method verbs). This value is set between the 'before_request_body' and 'before_handler' hooks (assuming that process_request_body is True). Deprecated in 3.2, will be removed for 3.3 in favor of :attr:`request.body.params<cherrypy._cprequest.RequestBody.params>`.""")
And if you raise/return, you don't need a "else" it eliminates some indentation levels.
I can remove the “else”
try: user_info = auth.login(username, password) diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 184029d..2a8f461 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -227,10 +227,7 @@ kimchi.main = function() { kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - kimchi.window.open({ - url: 'login-window.html', - id: 'login-window-wrapper' - }); + document.location.href='login.html'; return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) {
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> let cookie remember the last page. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- src/kimchi/root.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 181ab13..651c847 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -93,7 +93,11 @@ def tabs(self, page, **kwargs): data['ui_dir'] = paths.ui_dir if page.endswith('.html'): - return template.render('tabs/' + page, data) + context = template.render('tabs/' + page, data) + cherrypy.response.cookie[ + "lastPage"] = "/#tabs/" + page.rstrip(".html") + cherrypy.response.cookie['lastPage']['path'] = '/' + return context raise cherrypy.HTTPError(404) @@ -115,9 +119,13 @@ def login(self, *args, **kwargs): password = kwargs.get('password') # forms base authentication if username is not None: - # UI can pass the redirect url by "next" query parameter - next_url = kwargs.get('next', "/") - next_url = type(next_url) is list and next_url[0] + next_url = cherrypy.request.cookie.get("lastPage") + if next_url is None: + # UI can pass the redirect url by "next" query parameter + next_url = kwargs.get('next', "/") + next_url = type(next_url) is list and next_url[0] + else: + next_url = next_url.value auth.login(username, password) raise cherrypy.HTTPRedirect(next_url, 303) else: -- 1.9.3

On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
let cookie remember the last page.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- src/kimchi/root.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 181ab13..651c847 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -93,7 +93,11 @@ def tabs(self, page, **kwargs): data['ui_dir'] = paths.ui_dir
if page.endswith('.html'): - return template.render('tabs/' + page, data) + context = template.render('tabs/' + page, data) + cherrypy.response.cookie[ + "lastPage"] = "/#tabs/" + page.rstrip(".html") + cherrypy.response.cookie['lastPage']['path'] = '/'
Isn't only "lastPage" enough for that? Why [lastPage][path] is needed?
+ return context raise cherrypy.HTTPError(404)
@@ -115,9 +119,13 @@ def login(self, *args, **kwargs): password = kwargs.get('password') # forms base authentication if username is not None: - # UI can pass the redirect url by "next" query parameter - next_url = kwargs.get('next', "/") - next_url = type(next_url) is list and next_url[0] + next_url = cherrypy.request.cookie.get("lastPage") + if next_url is None: + # UI can pass the redirect url by "next" query parameter + next_url = kwargs.get('next', "/") + next_url = type(next_url) is list and next_url[0] + else: + next_url = next_url.value auth.login(username, password) raise cherrypy.HTTPRedirect(next_url, 303) else:

On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
let cookie remember the last page.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- src/kimchi/root.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 181ab13..651c847 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -93,7 +93,11 @@ def tabs(self, page, **kwargs): data['ui_dir'] = paths.ui_dir
if page.endswith('.html'): - return template.render('tabs/' + page, data) + context = template.render('tabs/' + page, data) + cherrypy.response.cookie[ + "lastPage"] = "/#tabs/" + page.rstrip(".html") + cherrypy.response.cookie['lastPage']['path'] = '/'
Isn't only "lastPage" enough for that? Why [lastPage][path] is needed?
On 06/10/2014 02:13 AM, Aline Manera wrote: path is used to set the domain of the this cookie. without this path, the lastPage cookie will just take effect for on page.
+ return context raise cherrypy.HTTPError(404)
@@ -115,9 +119,13 @@ def login(self, *args, **kwargs): password = kwargs.get('password') # forms base authentication if username is not None: - # UI can pass the redirect url by "next" query parameter - next_url = kwargs.get('next', "/") - next_url = type(next_url) is list and next_url[0] + next_url = cherrypy.request.cookie.get("lastPage") + if next_url is None: + # UI can pass the redirect url by "next" query parameter + next_url = kwargs.get('next', "/") + next_url = type(next_url) is list and next_url[0] + else: + next_url = next_url.value auth.login(username, password) raise cherrypy.HTTPRedirect(next_url, 303) else:
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> when username or password is wrong, come back to login page with an error message. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/auth.py | 19 +++++++++++++------ ui/pages/login.html.tmpl | 6 ++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index a38dbd3..9cb40d3 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -131,9 +131,8 @@ def _pam_conv(auth, query_list, userData=None): try: auth.authenticate() - except PAM.error, (resp, code): - msg_args = {'username': username, 'code': code} - raise OperationFailed("KCHAUTH0001E", msg_args) + except PAM.error: + raise return True @@ -196,9 +195,17 @@ def check_auth_httpba(): def login(username, password): - if not authenticate(username, password): - debug("User cannot be verified with the supplied password") - return None + try: + if not authenticate(username, password): + debug("User cannot be verified with the supplied password") + return None + except PAM.error, (resp, code): + if (cherrypy.request.path_info == "/login" and + not template.can_accept('application/json')): + raise cherrypy.HTTPRedirect("/login.html?error=userPassWrong", 303) + msg_args = {'username': username, 'code': code} + raise OperationFailed("KCHAUTH0001E", msg_args) + user = User(username) debug("User verified, establishing session") cherrypy.session.acquire_lock() diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl index 0e4bb0d..fc6cee6 100644 --- a/ui/pages/login.html.tmpl +++ b/ui/pages/login.html.tmpl @@ -181,8 +181,14 @@ function setLang() { var persistLang = getCookie('kimchiLang'); document.getElementById("userLang").value = persistLang || clientLang || defaultLang; } +function setMessage() { + var err = "$getVar('data.error', '')"; + if(err=="userPassWrong") + document.getElementById("messUserPass").style.display = ""; +} function init() { setLang(); + setMessage(); } </script> </head> -- 1.9.3

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> When session timeout, come back to login page with an error message. When session logout, close session directly. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/auth.py | 21 +++++++++++++++++++-- src/kimchi/config.py.in | 4 ++++ src/kimchi/server.py | 2 ++ ui/js/src/kimchi.main.js | 3 ++- ui/pages/login.html.tmpl | 2 ++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 9cb40d3..dcdd74a 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -161,7 +161,7 @@ def check_auth_session(): cherrypy.session.timeout * 60): cherrypy.session[USER_NAME] = None cherrypy.lib.sessions.expire() - raise cherrypy.HTTPError(401) + raise cherrypy.HTTPError(401, "sessionTimeout") else: cherrypy.session[REFRESH] = time.time() return True @@ -223,7 +223,7 @@ def logout(): cherrypy.session[USER_NAME] = None cherrypy.session[REFRESH] = 0 cherrypy.session.release_lock() - cherrypy.lib.sessions.expire() + cherrypy.lib.sessions.close() def has_permission(admin_methods): @@ -238,6 +238,7 @@ def has_permission(admin_methods): def kimchiauth(admin_methods=None): debug("Entering kimchiauth...") + session_missing = cherrypy.session.missing if check_auth_session(): if not has_permission(admin_methods): raise cherrypy.HTTPError(403) @@ -252,8 +253,24 @@ def kimchiauth(admin_methods=None): if not template.can_accept('application/json'): redirect_login() + # from browser, and it stays on one page. + if session_missing and cherrypy.request.cookie.get("lastPage") is not None: + raise cherrypy.HTTPError(401, "sessionTimeout") + if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi' e = InvalidOperation('KCHAUTH0002E') raise cherrypy.HTTPError(401, e.message.encode('utf-8')) + + +def kimchisession(admin_methods=None): + session = cherrypy.request.cookie.get("kimchi") + last_page = cherrypy.request.cookie.get("lastPage") + # when client browser first login in, both the session and lastPage cookie + # are None. + # when session timeout, only session cookie is None. + if (session is None and last_page is None and + template.can_accept('text/html')): + if last_page is None: + redirect_login() diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index d4cbda0..f557516 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -179,6 +179,7 @@ class KimchiConfig(dict): 'tools.sessions.locking': 'explicit', 'tools.sessions.storage_type': 'ram', 'tools.sessions.timeout': SESSIONSTIMEOUT, + 'tools.kimchisession.on': True, 'tools.kimchiauth.on': False }, '/vnc_auto.html': { @@ -190,6 +191,9 @@ class KimchiConfig(dict): '/kimchi-ui.html': { 'tools.kimchiauth.on': True }, + '/login.html': { + 'tools.kimchisession.on': False, + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/server.py b/src/kimchi/server.py index 7344349..30140ce 100644 --- a/src/kimchi/server.py +++ b/src/kimchi/server.py @@ -77,6 +77,8 @@ def __init__(self, options): cherrypy.tools.nocache = cherrypy.Tool('on_end_resource', set_no_cache) cherrypy.tools.kimchiauth = cherrypy.Tool('before_handler', auth.kimchiauth) + cherrypy.tools.kimchisession = cherrypy.Tool('before_request_body', + auth.kimchisession) # Setting host to 127.0.0.1. This makes kimchi runs # as a localhost app, inaccessible to the outside # directly. You must go through the proxy. diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 2a8f461..78971f8 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -223,11 +223,12 @@ kimchi.main = function() { } if (jqXHR['status'] === 401) { + var isSessionTimeout = jqXHR['responseText'].indexOf("sessionTimeout")!=-1; kimchi.user.showUser(false); kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - document.location.href='login.html'; + document.location.href= isSessionTimeout ? 'login.html/?error=sessionTimeout' : 'login.html'; return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) { diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl index fc6cee6..4a7c87e 100644 --- a/ui/pages/login.html.tmpl +++ b/ui/pages/login.html.tmpl @@ -185,6 +185,8 @@ function setMessage() { var err = "$getVar('data.error', '')"; if(err=="userPassWrong") document.getElementById("messUserPass").style.display = ""; + if(err=="sessionTimeout") + document.getElementById("messSession").style.display = ""; } function init() { setLang(); -- 1.9.3

On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
When session timeout, come back to login page with an error message.
When session logout, close session directly.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/auth.py | 21 +++++++++++++++++++-- src/kimchi/config.py.in | 4 ++++ src/kimchi/server.py | 2 ++ ui/js/src/kimchi.main.js | 3 ++- ui/pages/login.html.tmpl | 2 ++ 5 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 9cb40d3..dcdd74a 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -161,7 +161,7 @@ def check_auth_session(): cherrypy.session.timeout * 60): cherrypy.session[USER_NAME] = None cherrypy.lib.sessions.expire() - raise cherrypy.HTTPError(401) + raise cherrypy.HTTPError(401, "sessionTimeout") else: cherrypy.session[REFRESH] = time.time() return True @@ -223,7 +223,7 @@ def logout(): cherrypy.session[USER_NAME] = None cherrypy.session[REFRESH] = 0 cherrypy.session.release_lock() - cherrypy.lib.sessions.expire() + cherrypy.lib.sessions.close()
def has_permission(admin_methods): @@ -238,6 +238,7 @@ def has_permission(admin_methods):
def kimchiauth(admin_methods=None): debug("Entering kimchiauth...") + session_missing = cherrypy.session.missing if check_auth_session(): if not has_permission(admin_methods): raise cherrypy.HTTPError(403) @@ -252,8 +253,24 @@ def kimchiauth(admin_methods=None): if not template.can_accept('application/json'): redirect_login()
+ # from browser, and it stays on one page. + if session_missing and cherrypy.request.cookie.get("lastPage") is not None: + raise cherrypy.HTTPError(401, "sessionTimeout") +
Why is it needed? The session timeout is handled in check_auth_session() which already return cherrypy.HTTPError(401, "sessionTimeout")
if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi'
e = InvalidOperation('KCHAUTH0002E') raise cherrypy.HTTPError(401, e.message.encode('utf-8')) + + +def kimchisession(admin_methods=None): + session = cherrypy.request.cookie.get("kimchi") + last_page = cherrypy.request.cookie.get("lastPage") + # when client browser first login in, both the session and lastPage cookie + # are None. + # when session timeout, only session cookie is None. + if (session is None and last_page is None and + template.can_accept('text/html')): + if last_page is None: + redirect_login() diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index d4cbda0..f557516 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -179,6 +179,7 @@ class KimchiConfig(dict): 'tools.sessions.locking': 'explicit', 'tools.sessions.storage_type': 'ram', 'tools.sessions.timeout': SESSIONSTIMEOUT, + 'tools.kimchisession.on': True, 'tools.kimchiauth.on': False }, '/vnc_auto.html': { @@ -190,6 +191,9 @@ class KimchiConfig(dict): '/kimchi-ui.html': { 'tools.kimchiauth.on': True }, + '/login.html': { + 'tools.kimchisession.on': False, + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/server.py b/src/kimchi/server.py index 7344349..30140ce 100644 --- a/src/kimchi/server.py +++ b/src/kimchi/server.py @@ -77,6 +77,8 @@ def __init__(self, options): cherrypy.tools.nocache = cherrypy.Tool('on_end_resource', set_no_cache) cherrypy.tools.kimchiauth = cherrypy.Tool('before_handler', auth.kimchiauth) + cherrypy.tools.kimchisession = cherrypy.Tool('before_request_body', + auth.kimchisession) # Setting host to 127.0.0.1. This makes kimchi runs # as a localhost app, inaccessible to the outside # directly. You must go through the proxy. diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 2a8f461..78971f8 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -223,11 +223,12 @@ kimchi.main = function() { }
if (jqXHR['status'] === 401) { + var isSessionTimeout = jqXHR['responseText'].indexOf("sessionTimeout")!=-1; kimchi.user.showUser(false); kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - document.location.href='login.html'; + document.location.href= isSessionTimeout ? 'login.html/?error=sessionTimeout' : 'login.html'; return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) { diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl index fc6cee6..4a7c87e 100644 --- a/ui/pages/login.html.tmpl +++ b/ui/pages/login.html.tmpl @@ -185,6 +185,8 @@ function setMessage() { var err = "$getVar('data.error', '')"; if(err=="userPassWrong") document.getElementById("messUserPass").style.display = ""; + if(err=="sessionTimeout") + document.getElementById("messSession").style.display = ""; } function init() { setLang();

On 06/10/2014 02:22 AM, Aline Manera wrote:
On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
When session timeout, come back to login page with an error message.
When session logout, close session directly.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/auth.py | 21 +++++++++++++++++++-- src/kimchi/config.py.in | 4 ++++ src/kimchi/server.py | 2 ++ ui/js/src/kimchi.main.js | 3 ++- ui/pages/login.html.tmpl | 2 ++ 5 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 9cb40d3..dcdd74a 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -161,7 +161,7 @@ def check_auth_session(): cherrypy.session.timeout * 60): cherrypy.session[USER_NAME] = None cherrypy.lib.sessions.expire() - raise cherrypy.HTTPError(401) + raise cherrypy.HTTPError(401, "sessionTimeout") else: cherrypy.session[REFRESH] = time.time() return True @@ -223,7 +223,7 @@ def logout(): cherrypy.session[USER_NAME] = None cherrypy.session[REFRESH] = 0 cherrypy.session.release_lock() - cherrypy.lib.sessions.expire() + cherrypy.lib.sessions.close()
def has_permission(admin_methods): @@ -238,6 +238,7 @@ def has_permission(admin_methods):
def kimchiauth(admin_methods=None): debug("Entering kimchiauth...") + session_missing = cherrypy.session.missing if check_auth_session(): if not has_permission(admin_methods): raise cherrypy.HTTPError(403) @@ -252,8 +253,24 @@ def kimchiauth(admin_methods=None): if not template.can_accept('application/json'): redirect_login()
+ # from browser, and it stays on one page. + if session_missing and cherrypy.request.cookie.get("lastPage") is not None: + raise cherrypy.HTTPError(401, "sessionTimeout") +
Why is it needed? The session timeout is handled in check_auth_session() which already return cherrypy.HTTPError(401, "sessionTimeout") check_auth_session() just check the robot request sessionTimeout. we do not set "lastPage" cookie expire.
Here it means some kimchi page still stay on browser, the session expire but the lastPage can get.
if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi'
e = InvalidOperation('KCHAUTH0002E') raise cherrypy.HTTPError(401, e.message.encode('utf-8')) + + +def kimchisession(admin_methods=None): + session = cherrypy.request.cookie.get("kimchi") + last_page = cherrypy.request.cookie.get("lastPage") + # when client browser first login in, both the session and lastPage cookie + # are None. + # when session timeout, only session cookie is None. + if (session is None and last_page is None and + template.can_accept('text/html')): + if last_page is None: + redirect_login() diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index d4cbda0..f557516 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -179,6 +179,7 @@ class KimchiConfig(dict): 'tools.sessions.locking': 'explicit', 'tools.sessions.storage_type': 'ram', 'tools.sessions.timeout': SESSIONSTIMEOUT, + 'tools.kimchisession.on': True, 'tools.kimchiauth.on': False }, '/vnc_auto.html': { @@ -190,6 +191,9 @@ class KimchiConfig(dict): '/kimchi-ui.html': { 'tools.kimchiauth.on': True }, + '/login.html': { + 'tools.kimchisession.on': False, + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/server.py b/src/kimchi/server.py index 7344349..30140ce 100644 --- a/src/kimchi/server.py +++ b/src/kimchi/server.py @@ -77,6 +77,8 @@ def __init__(self, options): cherrypy.tools.nocache = cherrypy.Tool('on_end_resource', set_no_cache) cherrypy.tools.kimchiauth = cherrypy.Tool('before_handler', auth.kimchiauth) + cherrypy.tools.kimchisession = cherrypy.Tool('before_request_body', + auth.kimchisession) # Setting host to 127.0.0.1. This makes kimchi runs # as a localhost app, inaccessible to the outside # directly. You must go through the proxy. diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 2a8f461..78971f8 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -223,11 +223,12 @@ kimchi.main = function() { }
if (jqXHR['status'] === 401) { + var isSessionTimeout = jqXHR['responseText'].indexOf("sessionTimeout")!=-1; kimchi.user.showUser(false); kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - document.location.href='login.html'; + document.location.href= isSessionTimeout ? 'login.html/?error=sessionTimeout' : 'login.html'; return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) { diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl index fc6cee6..4a7c87e 100644 --- a/ui/pages/login.html.tmpl +++ b/ui/pages/login.html.tmpl @@ -185,6 +185,8 @@ function setMessage() { var err = "$getVar('data.error', '')"; if(err=="userPassWrong") document.getElementById("messUserPass").style.display = ""; + if(err=="sessionTimeout") + document.getElementById("messSession").style.display = ""; } function init() { setLang();
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

On 06/10/2014 02:22 AM, Aline Manera wrote:
On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
When session timeout, come back to login page with an error message.
When session logout, close session directly.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/auth.py | 21 +++++++++++++++++++-- src/kimchi/config.py.in | 4 ++++ src/kimchi/server.py | 2 ++ ui/js/src/kimchi.main.js | 3 ++- ui/pages/login.html.tmpl | 2 ++ 5 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 9cb40d3..dcdd74a 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -161,7 +161,7 @@ def check_auth_session(): cherrypy.session.timeout * 60): cherrypy.session[USER_NAME] = None cherrypy.lib.sessions.expire() - raise cherrypy.HTTPError(401) + raise cherrypy.HTTPError(401, "sessionTimeout") else: cherrypy.session[REFRESH] = time.time() return True @@ -223,7 +223,7 @@ def logout(): cherrypy.session[USER_NAME] = None cherrypy.session[REFRESH] = 0 cherrypy.session.release_lock() - cherrypy.lib.sessions.expire() + cherrypy.lib.sessions.close()
def has_permission(admin_methods): @@ -238,6 +238,7 @@ def has_permission(admin_methods):
def kimchiauth(admin_methods=None): debug("Entering kimchiauth...") + session_missing = cherrypy.session.missing if check_auth_session(): if not has_permission(admin_methods): raise cherrypy.HTTPError(403) @@ -252,8 +253,24 @@ def kimchiauth(admin_methods=None): if not template.can_accept('application/json'): redirect_login()
+ # from browser, and it stays on one page. + if session_missing and cherrypy.request.cookie.get("lastPage") is not None: + raise cherrypy.HTTPError(401, "sessionTimeout") +
Why is it needed? The session timeout is handled in check_auth_session() which already return cherrypy.HTTPError(401, "sessionTimeout")
here is the difference between kimchi and lastPage cookie. kimchi cookie set expires. but lastPage does set it. also we set lastPage only for browser. not for REST Full request. when session timeout, cherrypy will clean session data. check_auth_session will check session data. no session date may means session timeout or http base authentication. Here is the session cookie ID and lastPage cookie. Server |nginx/1.4.7| Set-Cookie |kimchi=02fff0323dbe0bfbffb6f377e48ac5d9802de890; expires=Wed, 11 Jun 2014 03:33:34 GMT; httponly; Path||=/; secure ||lastPage="/#tabs/templates"; Path=/| Strict-Transport-Security |max-age=31536000; includeSubdomains;|
if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi'
e = InvalidOperation('KCHAUTH0002E') raise cherrypy.HTTPError(401, e.message.encode('utf-8')) + + +def kimchisession(admin_methods=None): + session = cherrypy.request.cookie.get("kimchi") + last_page = cherrypy.request.cookie.get("lastPage") + # when client browser first login in, both the session and lastPage cookie + # are None. + # when session timeout, only session cookie is None. + if (session is None and last_page is None and + template.can_accept('text/html')): + if last_page is None: + redirect_login() diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index d4cbda0..f557516 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -179,6 +179,7 @@ class KimchiConfig(dict): 'tools.sessions.locking': 'explicit', 'tools.sessions.storage_type': 'ram', 'tools.sessions.timeout': SESSIONSTIMEOUT, + 'tools.kimchisession.on': True, 'tools.kimchiauth.on': False }, '/vnc_auto.html': { @@ -190,6 +191,9 @@ class KimchiConfig(dict): '/kimchi-ui.html': { 'tools.kimchiauth.on': True }, + '/login.html': { + 'tools.kimchisession.on': False, + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/server.py b/src/kimchi/server.py index 7344349..30140ce 100644 --- a/src/kimchi/server.py +++ b/src/kimchi/server.py @@ -77,6 +77,8 @@ def __init__(self, options): cherrypy.tools.nocache = cherrypy.Tool('on_end_resource', set_no_cache) cherrypy.tools.kimchiauth = cherrypy.Tool('before_handler', auth.kimchiauth) + cherrypy.tools.kimchisession = cherrypy.Tool('before_request_body', + auth.kimchisession) # Setting host to 127.0.0.1. This makes kimchi runs # as a localhost app, inaccessible to the outside # directly. You must go through the proxy. diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 2a8f461..78971f8 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -223,11 +223,12 @@ kimchi.main = function() { }
if (jqXHR['status'] === 401) { + var isSessionTimeout = jqXHR['responseText'].indexOf("sessionTimeout")!=-1; kimchi.user.showUser(false); kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - document.location.href='login.html'; + document.location.href= isSessionTimeout ? 'login.html/?error=sessionTimeout' : 'login.html'; return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) { diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl index fc6cee6..4a7c87e 100644 --- a/ui/pages/login.html.tmpl +++ b/ui/pages/login.html.tmpl @@ -185,6 +185,8 @@ function setMessage() { var err = "$getVar('data.error', '')"; if(err=="userPassWrong") document.getElementById("messUserPass").style.display = ""; + if(err=="sessionTimeout") + document.getElementById("messSession").style.display = ""; } function init() { setLang();
-- Thanks and best regards! Sheldon Feng(???)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

On 6/6/2014 12:10 AM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
When session timeout, come back to login page with an error message.
When session logout, close session directly.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/auth.py | 21 +++++++++++++++++++-- src/kimchi/config.py.in | 4 ++++ src/kimchi/server.py | 2 ++ ui/js/src/kimchi.main.js | 3 ++- ui/pages/login.html.tmpl | 2 ++ 5 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 9cb40d3..dcdd74a 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -161,7 +161,7 @@ def check_auth_session(): cherrypy.session.timeout * 60): cherrypy.session[USER_NAME] = None cherrypy.lib.sessions.expire() - raise cherrypy.HTTPError(401) + raise cherrypy.HTTPError(401, "sessionTimeout") else: cherrypy.session[REFRESH] = time.time() return True @@ -223,7 +223,7 @@ def logout(): cherrypy.session[USER_NAME] = None cherrypy.session[REFRESH] = 0 cherrypy.session.release_lock() - cherrypy.lib.sessions.expire() + cherrypy.lib.sessions.close()
def has_permission(admin_methods): @@ -238,6 +238,7 @@ def has_permission(admin_methods):
def kimchiauth(admin_methods=None): debug("Entering kimchiauth...") + session_missing = cherrypy.session.missing if check_auth_session(): if not has_permission(admin_methods): raise cherrypy.HTTPError(403) @@ -252,8 +253,24 @@ def kimchiauth(admin_methods=None): if not template.can_accept('application/json'): redirect_login()
+ # from browser, and it stays on one page. + if session_missing and cherrypy.request.cookie.get("lastPage") is not None: + raise cherrypy.HTTPError(401, "sessionTimeout") + if not from_browser(): cherrypy.response.headers['WWW-Authenticate'] = 'Basic realm=kimchi'
e = InvalidOperation('KCHAUTH0002E') raise cherrypy.HTTPError(401, e.message.encode('utf-8')) + + +def kimchisession(admin_methods=None): + session = cherrypy.request.cookie.get("kimchi") + last_page = cherrypy.request.cookie.get("lastPage") + # when client browser first login in, both the session and lastPage cookie + # are None. + # when session timeout, only session cookie is None. + if (session is None and last_page is None and + template.can_accept('text/html')): + if last_page is None: + redirect_login() diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index d4cbda0..f557516 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -179,6 +179,7 @@ class KimchiConfig(dict): 'tools.sessions.locking': 'explicit', 'tools.sessions.storage_type': 'ram', 'tools.sessions.timeout': SESSIONSTIMEOUT, + 'tools.kimchisession.on': True, 'tools.kimchiauth.on': False }, '/vnc_auto.html': { @@ -190,6 +191,9 @@ class KimchiConfig(dict): '/kimchi-ui.html': { 'tools.kimchiauth.on': True }, + '/login.html': { + 'tools.kimchisession.on': False, + }, '/data/screenshots': { 'tools.staticdir.on': True, 'tools.staticdir.dir': get_screenshot_path(), diff --git a/src/kimchi/server.py b/src/kimchi/server.py index 7344349..30140ce 100644 --- a/src/kimchi/server.py +++ b/src/kimchi/server.py @@ -77,6 +77,8 @@ def __init__(self, options): cherrypy.tools.nocache = cherrypy.Tool('on_end_resource', set_no_cache) cherrypy.tools.kimchiauth = cherrypy.Tool('before_handler', auth.kimchiauth) + cherrypy.tools.kimchisession = cherrypy.Tool('before_request_body', + auth.kimchisession) # Setting host to 127.0.0.1. This makes kimchi runs # as a localhost app, inaccessible to the outside # directly. You must go through the proxy. diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js index 2a8f461..78971f8 100644 --- a/ui/js/src/kimchi.main.js +++ b/ui/js/src/kimchi.main.js @@ -223,11 +223,12 @@ kimchi.main = function() { }
if (jqXHR['status'] === 401) { + var isSessionTimeout = jqXHR['responseText'].indexOf("sessionTimeout")!=-1; kimchi.user.showUser(false); kimchi.previousAjax = ajaxSettings; $(".empty-when-logged-off").empty(); $(".remove-when-logged-off").remove(); - document.location.href='login.html'; + document.location.href= isSessionTimeout ? 'login.html_/_?error=sessionTimeout' : 'login.html'; remove the '/' before '?'
document.location.href= isSessionTimeout ? 'login.html?error=sessionTimeout' : 'login.html';
return; } else if((jqXHR['status'] == 0) && ("error"==jqXHR.statusText)) { diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl index fc6cee6..4a7c87e 100644 --- a/ui/pages/login.html.tmpl +++ b/ui/pages/login.html.tmpl @@ -185,6 +185,8 @@ function setMessage() { var err = "$getVar('data.error', '')"; if(err=="userPassWrong") document.getElementById("messUserPass").style.display = ""; + if(err=="sessionTimeout") + document.getElementById("messSession").style.display = ""; } function init() { setLang();

On 06/05/2014 01:10 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
V2 -> V3: improve when to show timeout message
V1 -> V2: when username or password is wrong, back to login page with an error message. when session time out, back to login page with an error message.
ShaoHe Feng (5): create a new login page redirect the URL to login page when session timeout or first login when login successfully, redirect to the last page. login page prompts error when username or password is wrong login page prompts error when session timeout
src/kimchi/auth.py | 51 +++++++++-- src/kimchi/config.py.in | 7 ++ src/kimchi/root.py | 38 ++++++-- src/kimchi/server.py | 2 + ui/images/progressing.gif | Bin 0 -> 1152 bytes ui/js/src/kimchi.main.js | 6 +- ui/pages/login.html.tmpl | 228 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 310 insertions(+), 22 deletions(-) create mode 100644 ui/images/progressing.gif create mode 100644 ui/pages/login.html.tmpl
The tests are failing with this patch set. ====================================================================== FAIL: test_accepts (test_rest.HttpsRestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 134, in test_accepts self.assertTrue('<!doctype html>' in resp.read().lower()) AssertionError: False is not true ====================================================================== FAIL: test_auth_bad_creds (test_rest.HttpsRestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 1459, in test_auth_bad_creds self.assertEquals(401, resp.status) AssertionError: 401 != 303 ====================================================================== FAIL: test_auth_browser_no_httpba (test_rest.HttpsRestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 1474, in test_auth_browser_no_httpba self.assertEquals(401, resp.status) AssertionError: 401 != 303 ====================================================================== FAIL: test_auth_protected (test_rest.HttpsRestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 1453, in test_auth_protected self.assertEquals(401, resp.status) AssertionError: 401 != 303 ====================================================================== FAIL: test_auth_unprotected (test_rest.HttpsRestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 1439, in test_auth_unprotected self.assertEquals(200, resp.status) AssertionError: 200 != 303 ====================================================================== FAIL: test_accepts (test_rest.RestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 134, in test_accepts self.assertTrue('<!doctype html>' in resp.read().lower()) AssertionError: False is not true ====================================================================== FAIL: test_auth_bad_creds (test_rest.RestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 1459, in test_auth_bad_creds self.assertEquals(401, resp.status) AssertionError: 401 != 303 ====================================================================== FAIL: test_auth_browser_no_httpba (test_rest.RestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 1474, in test_auth_browser_no_httpba self.assertEquals(401, resp.status) AssertionError: 401 != 303 ====================================================================== FAIL: test_auth_protected (test_rest.RestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 1453, in test_auth_protected self.assertEquals(401, resp.status) AssertionError: 401 != 303 ====================================================================== FAIL: test_auth_unprotected (test_rest.RestTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_rest.py", line 1439, in test_auth_unprotected self.assertEquals(200, resp.status) AssertionError: 200 != 303 ====================================================================== FAIL: test_kimchi_config (test_config.ConfigTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_config.py", line 173, in test_kimchi_config self.assertEquals(kimchi_config, configObj) AssertionError: {'/libs': {'tools.expires.on': True, 'tools.nocache.on': False, 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/libs', 'tools.expires.secs': 31536000, 'tools.staticdir.on': True}, '/config/ui/tabs.xml': {'tools.nocache.on': True, 'tools.staticfile.on': True, 'tools.staticfile.filename': '/home/alinefm/kimchi/config/ui/tabs.xml'}, '/favicon.ico': {'tools.staticfile.on': True, 'tools.staticfile.filename': '/home/alinefm/kimchi/ui/images/logo.ico'}, '/js': {'tools.expires.on': True, 'tools.nocache.on': False, 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/js', 'tools.expires.secs': 31536000, 'tools.staticdir.on': True}, '/images': {'tools.nocache.on': False, 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/images', 'tools.staticdir.on': True}, '/spice.html': {'tools.kimchiauth.on': True}, '/login.html': {'tools.kimchisession.on': False}, '/': {'request.methods_with_bodies': ('POST', 'PUT'), 'tools.sessions.name': 'kimchi', 'tools.proxy.on': True, 'tools.sessions.locking': 'explicit', 'tools.sessions.httponly': True, 'tools.sessions.storage_type': 'ram', 'tools.sessions.timeout': 10, 'tools.sessions.on': True, 'tools.sessions.secure': True, 'tools.nocache.on': True, 'tools.kimchisession.on': True, 'tools.trailing_slash.on': False, 'tools.kimchiauth.on': False}, '/data/screenshots': {'tools.nocache.on': False, 'tools.staticdir.dir': '/home/alinefm/kimchi/data/screenshots', 'tools.staticdir.on': True}, '/help': {'tools.nocache.on': False, 'tools.staticdir.index': 'en_US/index.html', 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/pages/help', 'tools.staticdir.on': True}, '/vnc_auto.html': {'tools.kimchiauth.on': True}, '/kimchi-ui.html': {'tools.kimchiauth.on': True}, '/css': {'tools.expires.on': True, 'tools.nocache.on': False, 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/css', 'tools.expires.secs': 31536000, 'tools.staticdir.on': True}, '/data/debugreports': {'tools.kimchiauth.on': True, 'tools.nocache.on': False, 'tools.staticdir.dir': '/home/alinefm/kimchi/data/debugreports', 'tools.staticdir.content_types': {'xz': 'application/x-xz'}, 'tools.staticdir.on': True}} != {'/libs': {'tools.expires.on': True, 'tools.nocache.on': False, 'tools.expires.secs': 31536000, 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/libs', 'tools.staticdir.on': True}, '/favicon.ico': {'tools.staticfile.on': True, 'tools.staticfile.filename': '/home/alinefm/kimchi/ui/images/logo.ico'}, '/js': {'tools.expires.on': True, 'tools.nocache.on': False, 'tools.expires.secs': 31536000, 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/js', 'tools.staticdir.on': True}, '/images': {'tools.nocache.on': False, 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/images', 'tools.staticdir.on': True}, '/spice.html': {'tools.kimchiauth.on': True}, '/help': {'tools.nocache.on': False, 'tools.staticdir.index': 'en_US/index.html', 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/pages/help', 'tools.staticdir.on': True}, '/vnc_auto.html': {'tools.kimchiauth.on': True}, '/css': {'tools.expires.on': True, 'tools.nocache.on': False, 'tools.expires.secs': 31536000, 'tools.staticdir.dir': '/home/alinefm/kimchi/ui/css', 'tools.staticdir.on': True}, '/config/ui/tabs.xml': {'tools.nocache.on': True, 'tools.staticfile.on': True, 'tools.staticfile.filename': '/home/alinefm/kimchi/config/ui/tabs.xml'}, '/': {'request.methods_with_bodies': ('POST', 'PUT'), 'tools.sessions.name': 'kimchi', 'tools.proxy.on': True, 'tools.sessions.locking': 'explicit', 'tools.sessions.httponly': True, 'tools.sessions.storage_type': 'ram', 'tools.sessions.timeout': 10, 'tools.sessions.on': True, 'tools.sessions.secure': True, 'tools.nocache.on': True, 'tools.trailing_slash.on': False, 'tools.kimchiauth.on': False}, '/data/screenshots': {'tools.nocache.on': False, 'tools.staticdir.dir': '/home/alinefm/kimchi/data/screenshots', 'tools.staticdir.on': True}, '/data/debugreports': {'tools.kimchiauth.on': True, 'tools.nocache.on': False, 'tools.staticdir.dir': '/home/alinefm/kimchi/data/debugreports', 'tools.staticdir.content_types': {'xz': 'application/x-xz'}, 'tools.staticdir.on': True}} ---------------------------------------------------------------------- Ran 159 tests in 179.293s FAILED (failures=11) [09/Jun/2014:15:25:53] ENGINE Waiting for child threads to terminate... make[3]: *** [check-local] Error 1 make[3]: Leaving directory `/home/alinefm/kimchi/tests' make[2]: *** [check-am] Error 2 make[2]: Leaving directory `/home/alinefm/kimchi/tests' make[1]: *** [check] Error 2 make[1]: Leaving directory `/home/alinefm/kimchi/tests' make: *** [check-recursive] Error 1
participants (4)
-
Aline Manera
-
shaohef@linux.vnet.ibm.com
-
Sheldon
-
Yu Xin Huo