[Kimchi-devel] [RFC] Validating entries at login

Ramon Medeiros ramonn at linux.vnet.ibm.com
Fri Feb 3 13:13:35 UTC 2017


Just rebase it:



On 2/3/17 11:06 AM, Aline Manera wrote:
>
> I am not able to apply the attached patch:
>
> [alinefm at alinefm-TP440 kimchi]$ git am -3 
> /home/alinefm/mail-patches/0001-Bug-fix-147-Block-authentication-request-after-too-m.patch
> Applying: Bug fix #147: Block authentication request after too many 
> failures
> fatal: sha1 information is lacking or useless (src/wok/i18n.py).
> error: could not build fake ancestor
> Patch failed at 0001 Bug fix #147: Block authentication request after 
> too many failures
> The copy of the patch that failed is found in: 
> /home/alinefm/wok/.git/modules/src/wok/plugins/kimchi/rebase-apply/patch
> When you have resolved this problem, run "git am --continue".
> If you prefer to skip this patch, run "git am --skip" instead.
> To restore the original branch and stop patching, run "git am --abort".
>
>
> On 02/01/2017 10:40 AM, Ramon Medeiros wrote:
>> Here is the patch
>>
>>
>> On 1/31/17 10:36 AM, Aline Manera wrote:
>>> Hrm... Could you send the patch so I can do some debug?
>>>
>>> On 01/31/2017 10:32 AM, Ramon Medeiros wrote:
>>>>
>>>>
>>>> On 1/31/17 10:30 AM, Aline Manera wrote:
>>>>> Hi Ramon,
>>>>>
>>>>> Does the WokRoot class have set self.api_schema attribute to get 
>>>>> the right data?
>>>>>
>>>>> It should have something like:
>>>>>
>>>>> self.api_schema = json.load(<path>)
>>>>>
>>>>> Regards,
>>>>> Aline Manera
>>>>
>>>> yes, it is:
>>>>
>>>> class WokRoot(Root):
>>>>     def __init__(self, model, dev_env=False):
>>>>         super(WokRoot, self).__init__(model, dev_env)
>>>>         self.default_page = 'wok-ui.html'
>>>>         for ident, node in sub_nodes.items():
>>>>             setattr(self, ident, node(model))
>>>>         with open(os.path.join(wok_paths.src_dir, 'API.json')) as f:
>>>>             self.api_schema = json.load(f)
>>>>>
>>>>> On 01/30/2017 04:41 PM, Ramon Medeiros wrote:
>>>>>>
>>>>>>
>>>>>> On 1/30/17 3:30 PM, Aline Manera wrote:
>>>>>>> Hi Ramon,
>>>>>>>
>>>>>>> There is a function in src/wok/control/utils.py called 
>>>>>>> model_fn() which determines the model function name used by 
>>>>>>> controller.
>>>>>>> I think you need to use the same function in your code and then 
>>>>>>> update API.json accordingly.
>>>>>>>
>>>>>> model_fn returned wokroot_login, which i already tried. May i 
>>>>>> missing something on API.json?
>>>>>>> On 01/27/2017 12:26 PM, Ramon Medeiros wrote:
>>>>>>>> Just adding more information:
>>>>>>>>
>>>>>>>> at the pdb output, you can see "wokroot_login" as method. I 
>>>>>>>> have already tried this one at API.json
>>>>>>>>
>>>>>>>>
>>>>>>>> On 1/27/17 12:24 PM, Ramon Medeiros wrote:
>>>>>>>>> Propose: valid strings username and password with API.json
>>>>>>>>>
>>>>>>>>> Issue: validator is not recognizing method
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Just saw that src/wok/control/utils.py has a method 
>>>>>>>>> validate_params, that reads API and validate the output of 
>>>>>>>>> request_params. I've added this changes to code:
>>>>>>>>>
>>>>>>>>> diff --git a/src/wok/API.json b/src/wok/API.json
>>>>>>>>> index 8965db9..3faa31b 100644
>>>>>>>>> --- a/src/wok/API.json
>>>>>>>>> +++ b/src/wok/API.json
>>>>>>>>> @@ -2,5 +2,24 @@
>>>>>>>>>      "$schema": "http://json-schema.org/draft-03/schema#",
>>>>>>>>>      "title": "Wok API",
>>>>>>>>>      "description": "Json schema for Wok API",
>>>>>>>>> -    "type": "object"
>>>>>>>>> +    "type": "object",
>>>>>>>>> +    "properties": {
>>>>>>>>> +        "login": {
>>>>>>>>> +            "type": "object",
>>>>>>>>> +            "properties": {
>>>>>>>>> +                "username": {
>>>>>>>>> +                    "description": "Username",
>>>>>>>>> +                    "required": true,
>>>>>>>>> +                    "type": "string",
>>>>>>>>> +                    "error": "WOKAUTH0003E"
>>>>>>>>> +                },
>>>>>>>>> +                "password": {
>>>>>>>>> +                    "description": "Password",
>>>>>>>>> +                    "required": true,
>>>>>>>>> +                    "type": "string",
>>>>>>>>> +                    "error": "WOKAUTH0003E"
>>>>>>>>> +                }
>>>>>>>>> +            }
>>>>>>>>> +        }
>>>>>>>>> +    }
>>>>>>>>>  }
>>>>>>>>> diff --git a/src/wok/root.py b/src/wok/root.py
>>>>>>>>> index e4cecae..55e1886 100644
>>>>>>>>> --- a/src/wok/root.py
>>>>>>>>> +++ b/src/wok/root.py
>>>>>>>>> @@ -32,7 +32,7 @@ from wok.i18n import messages
>>>>>>>>>  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.control.utils import parse_request, validate_params
>>>>>>>>>  from wok.exception import MissingParameter, UnauthorizedError
>>>>>>>>>  from wok.reqlogger import log_request
>>>>>>>>>
>>>>>>>>> @@ -170,6 +170,8 @@ class WokRoot(Root):
>>>>>>>>>
>>>>>>>>>          try:
>>>>>>>>>              params = parse_request()
>>>>>>>>> +            validate_params(params, self, "login")
>>>>>>>>>              username = params['username']
>>>>>>>>>              password = params['password']
>>>>>>>>>          except KeyError, item:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Debugging the code, i just saw that the action_name passed to 
>>>>>>>>> validate_params (login) is not found by the validator:
>>>>>>>>>
>>>>>>>>>  /root/WOK/src/wok/control/utils.py(109)validate_params()
>>>>>>>>> -> validator.validate(request)
>>>>>>>>> (Pdb) s
>>>>>>>>> --Call--
>>>>>>>>> > 
>>>>>>>>> /usr/lib/python2.7/site-packages/jsonschema/validators.py(121)validate() 
>>>>>>>>>
>>>>>>>>> -> def validate(self, *args, **kwargs):
>>>>>>>>> (Pdb) n
>>>>>>>>> > 
>>>>>>>>> /usr/lib/python2.7/site-packages/jsonschema/validators.py(122)validate() 
>>>>>>>>>
>>>>>>>>> -> for error in self.iter_errors(*args, **kwargs):
>>>>>>>>> (Pdb) s
>>>>>>>>> --Call--
>>>>>>>>> > 
>>>>>>>>> /usr/lib/python2.7/site-packages/jsonschema/validators.py(78)iter_errors()
>>>>>>>>> -> def iter_errors(self, instance, _schema=None):
>>>>>>>>> (Pdb) n
>>>>>>>>> > 
>>>>>>>>> /usr/lib/python2.7/site-packages/jsonschema/validators.py(79)iter_errors()
>>>>>>>>> -> if _schema is None:
>>>>>>>>> (Pdb)
>>>>>>>>> > 
>>>>>>>>> /usr/lib/python2.7/site-packages/jsonschema/validators.py(80)iter_errors()
>>>>>>>>> -> _schema = self.schema
>>>>>>>>> (Pdb)
>>>>>>>>> > 
>>>>>>>>> /usr/lib/python2.7/site-packages/jsonschema/validators.py(82)iter_errors()
>>>>>>>>> -> scope = _schema.get(u"id")
>>>>>>>>> (Pdb)
>>>>>>>>> > 
>>>>>>>>> /usr/lib/python2.7/site-packages/jsonschema/validators.py(83)iter_errors()
>>>>>>>>> -> if scope:
>>>>>>>>> (Pdb) print _schema
>>>>>>>>> {u'$schema': u'http://json-schema.org/draft-03/schema#', 
>>>>>>>>> u'type': u'object', u'description': u'Json schema for Wok 
>>>>>>>>> API', u'properties': {u'wokroot_login': {u'type': u'object', 
>>>>>>>>> u'properties': {u'username': {u'required': True, u'type': 
>>>>>>>>> u'string', u'description': u'Username', u'error': 
>>>>>>>>> u'WOKAUTH0003E'}, u'password': {u'required': True, u'type': 
>>>>>>>>> u'string', u'description': u'Password', u'error': 
>>>>>>>>> u'WOKAUTH0003E'}}}}, u'title': u'Wok API'}
>>>>>>>>> (Pdb) n
>>>>>>>>> > 
>>>>>>>>> /usr/lib/python2.7/site-packages/jsonschema/validators.py(85)iter_errors()
>>>>>>>>> -> try:
>>>>>>>>> (Pdb)
>>>>>>>>> > 
>>>>>>>>> /usr/lib/python2.7/site-packages/jsonschema/validators.py(86)iter_errors()
>>>>>>>>> -> ref = _schema.get(u"$ref")
>>>>>>>>>
>>>>>>>>> How i can know the correct one?
>>>>>>>>>
>>>>>>>>> _______________________________________________
>>>>>>>>> Kimchi-devel mailing list
>>>>>>>>> Kimchi-devel at ovirt.org
>>>>>>>>> http://lists.ovirt.org/mailman/listinfo/kimchi-devel
>>>>>>>>>
>>>>>>>>
>>>>>>>> _______________________________________________
>>>>>>>> Kimchi-devel mailing list
>>>>>>>> Kimchi-devel at ovirt.org
>>>>>>>> http://lists.ovirt.org/mailman/listinfo/kimchi-devel
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>
>

-------------- next part --------------
From 9cc0106eda6706c89926b6c8083b945fff2f0417 Mon Sep 17 00:00:00 2001
From: Ramon Medeiros <ramonn at linux.vnet.ibm.com>
Date: Wed, 18 Jan 2017 09:55:52 -0200
Subject: [PATCH] [Wok] Bug fix #147: Block authentication request after too
 many failures

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 at linux.vnet.ibm.com>
---
 src/wok/API.json         | 21 +++++++++++++++-
 src/wok/i18n.py          |  2 ++
 src/wok/root.py          | 63 ++++++++++++++++++++++++++++++++++++++++++++----
 ui/js/src/wok.login.js   | 19 +++++++++------
 ui/pages/i18n.json.tmpl  |  5 +++-
 ui/pages/login.html.tmpl |  6 ++---
 6 files changed, 98 insertions(+), 18 deletions(-)

diff --git a/src/wok/API.json b/src/wok/API.json
index 8965db9..3faa31b 100644
--- a/src/wok/API.json
+++ b/src/wok/API.json
@@ -2,5 +2,24 @@
     "$schema": "http://json-schema.org/draft-03/schema#",
     "title": "Wok API",
     "description": "Json schema for Wok API",
-    "type": "object"
+    "type": "object",
+    "properties": {
+        "login": {
+            "type": "object",
+            "properties": {
+                "username": {
+                    "description": "Username",
+                    "required": true,
+                    "type": "string",
+                    "error": "WOKAUTH0003E"
+                },
+                "password": {
+                    "description": "Password",
+                    "required": true,
+                    "type": "string",
+                    "error": "WOKAUTH0003E"
+                }
+            }
+        }
+    }
 }
diff --git a/src/wok/i18n.py b/src/wok/i18n.py
index 935c9c1..67b43ca 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..55e1886 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
@@ -30,8 +32,8 @@ from wok.i18n import messages
 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.control.utils import parse_request, validate_params
+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,12 +157,21 @@ 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')
 
         try:
             params = parse_request()
+            import pdb;pdb.set_trace()
+            validate_params(params, self, "login")
             username = params['username']
             password = params['password']
         except KeyError, item:
@@ -166,11 +179,51 @@ class WokRoot(Root):
             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:
+                    self.failed_logins.pop(user_ip_sid)
+
         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.pop(user_ip_sid)
         except cherrypy.HTTPError, e:
-            status = e.status
+
+            # 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)
             raise
         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 666a339..9e2a392 100644
--- a/ui/js/src/wok.login.js
+++ b/ui/js/src/wok.login.js
@@ -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">
-- 
2.10.1 (Apple Git-78)


More information about the Kimchi-devel mailing list