On 1/25/17 3:46 PM, Aline Manera wrote:
On 01/24/2017 06:12 PM, ramonn(a)linux.vnet.ibm.com wrote:
> From: Ramon Medeiros <ramonn(a)linux.vnet.ibm.com>
>
> To prevent brute force attack, creates a mechanism to allow 3 tries
> first. After that, a timeout will start and will be added 30 seconds for
> each failed try in a row.
>
> Signed-off-by: Ramon Medeiros <ramonn(a)linux.vnet.ibm.com>
> ---
> Changes:
>
> v3:
> Improve error handling on login page
>
> v2:
> Set timeout by user, ip and session id. This will avoid trouble with
> users using the same ip, like NAT.
>
> src/wok/i18n.py | 2 ++
> src/wok/root.py | 66
> ++++++++++++++++++++++++++++++++++++++++++++----
> ui/js/src/wok.login.js | 21 +++++++++------
> ui/pages/i18n.json.tmpl | 5 +++-
> ui/pages/login.html.tmpl | 6 ++---
> 5 files changed, 82 insertions(+), 18 deletions(-)
>
> diff --git a/src/wok/i18n.py b/src/wok/i18n.py
> index e454e31..7d595b8 100644
> --- a/src/wok/i18n.py
> +++ b/src/wok/i18n.py
> @@ -41,7 +41,9 @@ messages = {
> "WOKAUTH0001E": _("Authentication failed for user
> '%(username)s'. [Error code: %(code)s]"),
> "WOKAUTH0002E": _("You are not authorized to access Wok.
> Please, login first."),
> "WOKAUTH0003E": _("Specify %(item)s to login into Wok."),
> + "WOKAUTH0004E": _("You have failed to login in too much
> attempts. Please, wait for %(seconds)s seconds to try again."),
> "WOKAUTH0005E": _("Invalid LDAP configuration: %(item)s :
> %(value)s"),
> + "WOKAUTH0006E": _("The username or password you entered is
> incorrect. Please try again."),
>
> "WOKLOG0001E": _("Invalid filter parameter. Filter parameters
> allowed: %(filters)s"),
> "WOKLOG0002E": _("Creation of log file failed: %(err)s"),
> diff --git a/src/wok/root.py b/src/wok/root.py
> index 080b7f0..d314d25 100644
> --- a/src/wok/root.py
> +++ b/src/wok/root.py
> @@ -1,7 +1,7 @@
> #
> # Project Wok
> #
> -# Copyright IBM Corp, 2015-2016
> +# Copyright IBM Corp, 2015-2017
> #
> # Code derived from Project Kimchi
> #
> @@ -21,7 +21,9 @@
>
> import cherrypy
> import json
> +import re
> import os
> +import time
> from distutils.version import LooseVersion
>
> from wok import auth
> @@ -31,7 +33,7 @@ from wok.config import paths as wok_paths
> from wok.control import sub_nodes
> from wok.control.base import Resource
> from wok.control.utils import parse_request
> -from wok.exception import MissingParameter
> +from wok.exception import MissingParameter, UnauthorizedError
> from wok.reqlogger import log_request
>
>
> @@ -48,7 +50,8 @@ class Root(Resource):
> super(Root, self).__init__(model)
> self._handled_error = ['error_page.400', 'error_page.404',
> 'error_page.405', 'error_page.406',
> - 'error_page.415', 'error_page.500']
> + 'error_page.415', 'error_page.500',
> + 'error_page.403', 'error_page.401']
>
> if not dev_env:
> self._cp_config = dict([(key,
> self.error_production_handler)
> @@ -146,6 +149,7 @@ class WokRoot(Root):
> self.domain = 'wok'
> self.messages = messages
> self.extends = None
> + self.failed_logins = {}
>
> # set user log messages and make sure all parameters are
> present
> self.log_map = ROOT_REQUESTS
> @@ -153,6 +157,13 @@ class WokRoot(Root):
>
> @cherrypy.expose
> def login(self, *args):
> + def _raise_timeout(user_id):
> + length = self.failed_logins[user_ip_sid]["count"]
> + timeout = (length - 3) * 30
> + details = e = UnauthorizedError("WOKAUTH0004E",
> + {"seconds": timeout})
> + log_request(code, params, details, method, 403)
> + raise cherrypy.HTTPError(403, e.message)
> details = None
> method = 'POST'
> code = self.getRequestMessage(method, 'login')
> @@ -161,17 +172,62 @@ class WokRoot(Root):
> params = parse_request()
> username = params['username']
> password = params['password']
> +
It would be good to use strip() around username and password to avoid
multiple white spaces.
> + # no data passed: raise error
> + if len(username + password) == 0:
> + raise KeyError
> +
What is this for? You are summing 'username' and 'password' and get
the length of it. I am not sure is that what you want.
If it is to identify a missing parameter you should check them
individually. As username AND password should be a string with length
!= 0
Well,
when parse_request run, it never get a missing parameters, so i need to
check if the var is empty. I will check each one alone.
> except KeyError, item:
> details = e = MissingParameter('WOKAUTH0003E', {'item':
> str(item)})
> log_request(code, params, details, method, 400)
> raise cherrypy.HTTPError(400, e.message)
>
> + # get authentication info
> + remote_ip = cherrypy.request.remote.ip
> + session_id = str(cherrypy.session.originalid)
> + user_ip_sid = re.escape(username + remote_ip + session_id)
> +
> + # check for repetly
> + count = self.failed_logins.get(user_ip_sid, {"count":
> 0}).get("count")
> + if count >= 3:
> +
> + # verify if timeout is still valid
> + last_try = self.failed_logins[user_ip_sid]["time"]
> + if time.time() < (last_try + ((count - 3) * 30)):
> + _raise_timeout(user_ip_sid)
else:
# remove the entry from the dict
ok.
> +
> try:
> status = 200
> user_info = auth.login(username, password)
> +
> + # user logged sucessfuly: reset counters
> + if self.failed_logins.get(user_ip_sid) != None:
> + self.failed_logins.remove(user_ip_sid)
> except cherrypy.HTTPError, e:
> - status = e.status
> - raise
> +
> + # store time and prevent too much tries
> + if self.failed_logins.get(user_ip_sid) == None:
> + self.failed_logins[user_ip_sid] = {"time": time.time(),
> + "ip": remote_ip,
> + "session_id": session_id,
> + "username":
> username,
> + "count": 1}
> + else:
> +
> + # tries take more than 30 seconds between each one:
> do not
> + # increase count
> + if (time.time() -
> + self.failed_logins[user_ip_sid]["time"]) < 30:
> +
> + self.failed_logins[user_ip_sid]["time"] =
> time.time()
> + self.failed_logins[user_ip_sid]["count"] += 1
> +
> + # more than 3 fails: raise error
> + if self.failed_logins[user_ip_sid]["count"] > 3:
> + _raise_timeout(user_ip_sid)
> +
> + details = e = MissingParameter('WOKAUTH0006E')
> + raise cherrypy.HTTPError(401, e.message)
> finally:
> log_request(code, params, details, method, status)
>
> diff --git a/ui/js/src/wok.login.js b/ui/js/src/wok.login.js
> index fa2a98a..d9daacf 100644
> --- a/ui/js/src/wok.login.js
> +++ b/ui/js/src/wok.login.js
> @@ -1,7 +1,7 @@
> /*
> * Project Wok
> *
> - * Copyright IBM Corp, 2015-2016
> + * Copyright IBM Corp, 2015-2017
> *
> * Code derived from Project Kimchi
> *
> @@ -19,6 +19,10 @@
> */
> wok.login_main = function() {
> "use strict";
> + var i18n;
> + wok.getI18n(function(i18nObj){
> + i18n = i18nObj;
> + }, false, "i18n.json", true);
>
> // verify if language is available
> var selectedLanguage = wok.lang.get();
> @@ -50,7 +54,8 @@ wok.login_main = function() {
> var query = window.location.search;
> var error = /.*error=(.*?)(&|$)/g.exec(query);
> if (error && error[1] === "sessionTimeout") {
> - $("#messSession").show();
> + $("#errorArea").html(i18n["WOKAUT0001E"]);
> + $("#errorArea").show();
> }
>
> var userNameBox = $('#username');
> @@ -82,13 +87,13 @@ wok.login_main = function() {
> window.location.replace(window.location.pathname.replace(/\/+login.html/,
> '') + next_url);
> }, function(jqXHR, textStatus, errorThrown) {
> if (jqXHR.responseText == "") {
> - $("#messUserPass").hide();
> - $("#missServer").show();
> - } else {
> - $("#missServer").hide();
> - $("#messUserPass").show();
> + $("#errorArea").html(i18n["WOKAUT0002E"]);
> + $("#errorArea").show();
> + } else if ((jqXHR.responseJSON != undefined) &&
> + ! (jqXHR.responseJSON["reason"] == undefined)) {
> + $("#errorArea").html(jqXHR.responseJSON["reason"]);
> + $("#errorArea").show();
> }
> - $("#messSession").hide();
> $("#logging").hide();
> $("#login").show();
> });
> diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl
> index ba29532..4329ad0 100644
> --- a/ui/pages/i18n.json.tmpl
> +++ b/ui/pages/i18n.json.tmpl
> @@ -1,7 +1,7 @@
> #*
> * Project Wok
> *
> - * Copyright IBM Corp, 2014-2016
> + * Copyright IBM Corp, 2014-2017
> *
> * Code derived from Project Kimchi
> *
> @@ -39,6 +39,9 @@
>
> "WOKHOST6001M": "$_("Max:")",
>
> + "WOKAUT0001E": "$_("Session timeout, please
re-login.")",
> + "WOKAUT0002E": "$_("Server unreachable")",
> +
> "WOKSETT0001M": "$_("Application")",
> "WOKSETT0002M": "$_("User")",
> "WOKSETT0003M": "$_("Request")",
> diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl
> index f5a4b2d..6f967cf 100644
> --- a/ui/pages/login.html.tmpl
> +++ b/ui/pages/login.html.tmpl
> @@ -1,7 +1,7 @@
> #*
> * Project Wok
> *
> - * Copyright IBM Corp, 2014-2016
> + * Copyright IBM Corp, 2014-2017
> *
> * Code derived from Project Kimchi
> *
> @@ -104,9 +104,7 @@
> <div class="container">
> <div id="login-window" class="login-area
row">
> <div class="err-area">
> - <div id="messUserPass" class="alert
> alert-danger" style="display: none;">$_("The username or
password you
> entered is incorrect. Please try again.")</div>
> - <div id="messSession" class="alert
alert-danger"
> style="display: none;">$_("Session timeout, please
re-login.")</div>
> - <div id="missServer" class="alert
alert-danger"
> style="display: none;">$_("Server unreachable.")</div>
> + <div id="errorArea" class="alert
alert-danger"
> style="display: none;"></div>
> </div>
> <form id="form-login"
class="form-horizontal"
> method="post">
> <div class="form-group">