[Kimchi-devel] [RFC] Validating entries at login
Ramon Medeiros
ramonn at linux.vnet.ibm.com
Wed Feb 1 12:40:14 UTC 2017
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 038065d2a20980a6a66a4d325cb3df5df45da557 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] 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