[PATCH] [Kimchi 0/6] Multiple fixes on Kimchi test cases
by Aline Manera
Aline Manera (6):
Specify objectstore location when running on test mode
Update run_server() calls to do not pass model instance
Bug fix: Set default host value while generating the virt-viewer
config file
Fix snapshots test case
Fix memory hotplug test case
Improve logic to identify if a network is in use or not
i18n.py | 4 ++--
model/networks.py | 42 ++++++++++++++++++++++++---------------
model/virtviewerfile.py | 10 ++++------
root.py | 24 +++++++++++++---------
tests/test_authorization.py | 10 ++++------
tests/test_host.py | 15 +++-----------
tests/test_livemigration.py | 6 +-----
tests/test_mock_network.py | 10 ++++------
tests/test_mock_storagepool.py | 12 ++++-------
tests/test_mock_storagevolume.py | 11 ++++------
tests/test_mockmodel.py | 8 +++-----
tests/test_model.py | 15 ++++++++------
tests/test_model_network.py | 18 ++++++++++-------
tests/test_model_storagepool.py | 18 +++++++++--------
tests/test_model_storagevolume.py | 18 ++++++++++-------
tests/test_rest.py | 27 +++++++++++++------------
tests/test_template.py | 9 ++++-----
17 files changed, 129 insertions(+), 128 deletions(-)
--
2.9.3
7 years, 9 months
[PATCH V2] [Kimchi 0/6] Multiple fixes on Kimchi test cases
by Aline Manera
Only patch 1/2 has changed from V1.
Now it is checking if wok_options.test is not None before using its value.
Aline Manera (6):
Specify objectstore location when running on test mode
Update run_server() calls to do not pass model instance
Bug fix: Set default host value while generating the virt-viewer
config file
Fix snapshots test case
Fix memory hotplug test case
Improve logic to identify if a network is in use or not
i18n.py | 4 ++--
model/networks.py | 42 ++++++++++++++++++++++++---------------
model/virtviewerfile.py | 10 ++++------
root.py | 25 ++++++++++++++---------
tests/test_authorization.py | 10 ++++------
tests/test_host.py | 15 +++-----------
tests/test_livemigration.py | 6 +-----
tests/test_mock_network.py | 10 ++++------
tests/test_mock_storagepool.py | 12 ++++-------
tests/test_mock_storagevolume.py | 11 ++++------
tests/test_mockmodel.py | 8 +++-----
tests/test_model.py | 15 ++++++++------
tests/test_model_network.py | 18 ++++++++++-------
tests/test_model_storagepool.py | 18 +++++++++--------
tests/test_model_storagevolume.py | 18 ++++++++++-------
tests/test_rest.py | 27 +++++++++++++------------
tests/test_template.py | 9 ++++-----
17 files changed, 130 insertions(+), 128 deletions(-)
--
2.9.3
7 years, 9 months
[PATCH v6] [Wok] Bug fix #147: Block authentication request after too many failures
by ramonn@linux.vnet.ibm.com
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:
v6:
Change message
v5:
Friendly error message
Set maximum tries to 3
v4:
Use API.json for input validation
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/API.json | 25 +++++++++++++++++-
src/wok/i18n.py | 6 ++++-
src/wok/root.py | 69 +++++++++++++++++++++++++++++++++++++++++-------
ui/js/src/wok.login.js | 19 ++++++++-----
ui/pages/i18n.json.tmpl | 5 +++-
ui/pages/login.html.tmpl | 6 ++---
6 files changed, 107 insertions(+), 23 deletions(-)
diff --git a/src/wok/API.json b/src/wok/API.json
index 8965db9..3f7bfd7 100644
--- a/src/wok/API.json
+++ b/src/wok/API.json
@@ -2,5 +2,28 @@
"$schema": "http://json-schema.org/draft-03/schema#",
"title": "Wok API",
"description": "Json schema for Wok API",
- "type": "object"
+ "type": "object",
+ "properties": {
+ "wokroot_login": {
+ "type": "object",
+ "properties": {
+ "username": {
+ "description": "Username",
+ "required": true,
+ "type": "string",
+ "minLength": 1,
+ "error": "WOKAUTH0003E"
+ },
+ "password": {
+ "description": "Password",
+ "required": true,
+ "type": "string",
+ "minLength": 1,
+ "error": "WOKAUTH0006E"
+ }
+ },
+ "additionalProperties": false,
+ "error": "WOKAUTH0007E"
+ }
+ }
}
diff --git a/src/wok/i18n.py b/src/wok/i18n.py
index 935c9c1..4a21d43 100644
--- a/src/wok/i18n.py
+++ b/src/wok/i18n.py
@@ -40,8 +40,12 @@ 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."),
+ "WOKAUTH0003E": _("Specify username 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": _("Specify password to login into Wok."),
+ "WOKAUTH0007E": _("You need to specify username and password to login into Wok."),
+ "WOKAUTH0008E": _("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..f813ed2 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 OperationFailed, UnauthorizedError, WokException
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,24 +157,71 @@ class WokRoot(Root):
@cherrypy.expose
def login(self, *args):
+ def _raise_timeout(user_id):
+ length = self.failed_logins[user_ip_sid]["count"]
+ timeout = (length - 2) * 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()
+ validate_params(params, self, "login")
username = params['username']
password = params['password']
- except KeyError, item:
- details = e = MissingParameter('WOKAUTH0003E', {'item': str(item)})
- log_request(code, params, details, method, 400)
- raise cherrypy.HTTPError(400, e.message)
+ except WokException, e:
+ details = e = OperationFailed("WOKAUTH0008E")
+ status = e.getHttpStatusCode()
+ raise cherrypy.HTTPError(401, 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 - 2) * 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)
7 years, 9 months
[PATCH] [Wok 0/2] Fixes on server initialization
by Aline Manera
Aline Manera (2):
Bug fix: Set application options globally on Server() instead of wokd
command
Bug fix: Do not allow specifying model instance directly to Server()
src/wok/server.py | 16 +++++++++-------
src/wokd.in | 7 -------
tests/utils.py | 5 +----
3 files changed, 10 insertions(+), 18 deletions(-)
--
2.9.3
7 years, 9 months
[PATCH v7] [Wok] Bug fix #147: Block authentication request after too many failures
by ramonn@linux.vnet.ibm.com
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:
v7:
Change message
v6:
Change message
v5:
Friendly error message
Set maximum tries to 3
v4:
Use API.json for input validation
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/API.json | 25 +++++++++++++++-
src/wok/i18n.py | 6 +++-
src/wok/root.py | 76 +++++++++++++++++++++++++++++++++++++++++-------
ui/js/src/wok.login.js | 19 +++++++-----
ui/pages/i18n.json.tmpl | 5 +++-
ui/pages/login.html.tmpl | 6 ++--
6 files changed, 113 insertions(+), 24 deletions(-)
diff --git a/src/wok/API.json b/src/wok/API.json
index 8965db9..3f7bfd7 100644
--- a/src/wok/API.json
+++ b/src/wok/API.json
@@ -2,5 +2,28 @@
"$schema": "http://json-schema.org/draft-03/schema#",
"title": "Wok API",
"description": "Json schema for Wok API",
- "type": "object"
+ "type": "object",
+ "properties": {
+ "wokroot_login": {
+ "type": "object",
+ "properties": {
+ "username": {
+ "description": "Username",
+ "required": true,
+ "type": "string",
+ "minLength": 1,
+ "error": "WOKAUTH0003E"
+ },
+ "password": {
+ "description": "Password",
+ "required": true,
+ "type": "string",
+ "minLength": 1,
+ "error": "WOKAUTH0006E"
+ }
+ },
+ "additionalProperties": false,
+ "error": "WOKAUTH0007E"
+ }
+ }
}
diff --git a/src/wok/i18n.py b/src/wok/i18n.py
index 935c9c1..4a21d43 100644
--- a/src/wok/i18n.py
+++ b/src/wok/i18n.py
@@ -40,8 +40,12 @@ 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."),
+ "WOKAUTH0003E": _("Specify username 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": _("Specify password to login into Wok."),
+ "WOKAUTH0007E": _("You need to specify username and password to login into Wok."),
+ "WOKAUTH0008E": _("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..ca1d5b3 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 OperationFailed, UnauthorizedError, WokException
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,25 +157,77 @@ class WokRoot(Root):
@cherrypy.expose
def login(self, *args):
+ def _raise_timeout(user_id):
+ length = self.failed_logins[user_ip_sid]["count"]
+ timeout = (length - 2) * 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()
+ validate_params(params, self, "login")
username = params['username']
password = params['password']
- except KeyError, item:
- details = e = MissingParameter('WOKAUTH0003E', {'item': str(item)})
- log_request(code, params, details, method, 400)
- raise cherrypy.HTTPError(400, e.message)
+ except WokException, e:
+ details = e = OperationFailed("WOKAUTH0008E")
+ status = e.getHttpStatusCode()
+ raise cherrypy.HTTPError(401, 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 - 2) * 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
- 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)
+
+ # return same error message to frontend
+ if e.code == 401:
+ details = e = OperationFailed("WOKAUTH0008E")
+ status = e.getHttpStatusCode()
+ 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 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)
7 years, 9 months
[PATCH] [Kimchi] Bug fix #1089: osinfo.py tablet_bus='usb' for x86 modern
by ramonn@linux.vnet.ibm.com
From: Ramon Medeiros <ramonn(a)linux.vnet.ibm.com>
Signed-off-by: Ramon Medeiros <ramonn(a)linux.vnet.ibm.com>
---
osinfo.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/osinfo.py b/osinfo.py
index 6fd55ad..9933836 100644
--- a/osinfo.py
+++ b/osinfo.py
@@ -52,7 +52,8 @@ template_specs = {'x86': {'old': dict(disk_bus='ide',
nic_model='e1000', sound_model='ich6'),
'modern': dict(disk_bus='virtio',
nic_model='virtio',
- sound_model='ich6')},
+ sound_model='ich6',
+ tablet_bus='usb')},
'power': {'old': dict(disk_bus='scsi',
nic_model='spapr-vlan',
cdrom_bus='scsi',
@@ -86,8 +87,7 @@ template_specs = {'x86': {'old': dict(disk_bus='ide',
custom_specs = {'fedora': {'22': {'x86': dict(video_model='qxl')}},
- 'windows': {'xp': {'x86': dict(nic_model='pcnet',
- tablet_bus="usb")}}}
+ 'windows': {'xp': {'x86': dict(nic_model='pcnet')}}}
modern_version_bases = {'x86': {'debian': '6.0', 'ubuntu': '7.10',
--
2.10.1 (Apple Git-78)
7 years, 9 months
[PATCH] [Kimchi] Adding 'self.depends' option in root.py
by dhbarboza82@gmail.com
From: Daniel Henrique Barboza <danielhb(a)linux.vnet.ibm.com>
This option will be used by WoK to determine the dependencies
of the plug-ins. The change made is harmless - the patch does
not need the WoK changes to be upstream beforehand.
Signed-off-by: Daniel Henrique Barboza <danielhb(a)linux.vnet.ibm.com>
---
root.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/root.py b/root.py
index faa9eb9..5fa1690 100644
--- a/root.py
+++ b/root.py
@@ -73,6 +73,8 @@ class Kimchi(WokRoot):
}
}
+ self.depends = ['gingerbase']
+
# Some paths or URI's present in the objectstore have changed after
# Kimchi 2.0.0 release. Check here if an upgrade in the schema and data
# are necessary.
--
2.9.3
7 years, 9 months
[PATCH v5] [Wok] Bug fix #147: Block authentication request after too many failures
by ramonn@linux.vnet.ibm.com
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:
v5:
Friendly error message
Set maximum tries to 3
v4:
Use API.json for input validation
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/API.json | 25 +++++++++++++++++-
src/wok/i18n.py | 7 +++--
src/wok/root.py | 69 +++++++++++++++++++++++++++++++++++++++++-------
ui/js/src/wok.login.js | 19 ++++++++-----
ui/pages/i18n.json.tmpl | 5 +++-
ui/pages/login.html.tmpl | 6 ++---
6 files changed, 107 insertions(+), 24 deletions(-)
diff --git a/src/wok/API.json b/src/wok/API.json
index 8965db9..3f7bfd7 100644
--- a/src/wok/API.json
+++ b/src/wok/API.json
@@ -2,5 +2,28 @@
"$schema": "http://json-schema.org/draft-03/schema#",
"title": "Wok API",
"description": "Json schema for Wok API",
- "type": "object"
+ "type": "object",
+ "properties": {
+ "wokroot_login": {
+ "type": "object",
+ "properties": {
+ "username": {
+ "description": "Username",
+ "required": true,
+ "type": "string",
+ "minLength": 1,
+ "error": "WOKAUTH0003E"
+ },
+ "password": {
+ "description": "Password",
+ "required": true,
+ "type": "string",
+ "minLength": 1,
+ "error": "WOKAUTH0006E"
+ }
+ },
+ "additionalProperties": false,
+ "error": "WOKAUTH0007E"
+ }
+ }
}
diff --git a/src/wok/i18n.py b/src/wok/i18n.py
index 935c9c1..3cf669e 100644
--- a/src/wok/i18n.py
+++ b/src/wok/i18n.py
@@ -38,10 +38,13 @@ messages = {
"WOKASYNC0003E": _("Timeout of %(seconds)s seconds expired while running task '%(task)s."),
"WOKASYNC0004E": _("Unable to kill task due error: %(err)s"),
- "WOKAUTH0001E": _("Authentication failed for user '%(username)s'. [Error code: %(code)s]"),
+ "WOKAUTH0001E": _("The username or password you entered is incorrect. Please try again"),
"WOKAUTH0002E": _("You are not authorized to access Wok. Please, login first."),
- "WOKAUTH0003E": _("Specify %(item)s to login into Wok."),
+ "WOKAUTH0003E": _("Specify username 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": _("Specify password to login into Wok."),
+ "WOKAUTH0007E": _("You need to specify username and password to login into Wok."),
"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..c75a44a 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 UnauthorizedError, WokException
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,24 +157,71 @@ class WokRoot(Root):
@cherrypy.expose
def login(self, *args):
+ def _raise_timeout(user_id):
+ length = self.failed_logins[user_ip_sid]["count"]
+ timeout = (length - 2) * 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()
+ validate_params(params, self, "login")
username = params['username']
password = params['password']
- except KeyError, item:
- details = e = MissingParameter('WOKAUTH0003E', {'item': str(item)})
- log_request(code, params, details, method, 400)
- raise cherrypy.HTTPError(400, e.message)
+ except WokException, e:
+ details = e
+ status = e.getHttpStatusCode()
+ raise cherrypy.HTTPError(status, 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 - 2) * 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)
7 years, 9 months
[PATCH v2] [WoK 00/10] '/config/plugins' API implementation
by dhbarboza82@gmail.com
From: Daniel Henrique Barboza <danielhb(a)linux.vnet.ibm.com>
v2:
- added User Log capabilities on /config/plugins/enable|disable actions
- added 'enable=' as a valid entry in the parsing of the conf file
This patch set implements the '/config/plugins' API. The idea
of this API is to replace the current '/plugins' API while
adding a new attribute called 'enabled' in its return value,
representing the current state of the plug-in. It will also
return all the plug-ins, regardless of whether it is loaded
or not.
New actions /config/plugin/*name*/enable and
/config/plugin/*name*/disable were implemented to enable/disable
the plug-in *name* by editing the plug-in .conf file.
This is the API in action:
[danielhb@arthas wok_all_plugins]$ curl -k -u danielhb -H "Content-Type: application/json" -H "Accept: application/json" -X GET 'https://localhost:8001/config/plugins'Enter host password for user 'danielhb':
[
{
"enabled":true,
"name":"gingerbase"
},
{
"enabled":true,
"name":"kimchi"
},
{
"enabled":true,
"name":"ginger"
}
][danielhb@arthas wok_all_plugins]$
[danielhb@arthas wok_all_plugins]$ curl -k -u danielhb -H "Content-Type: application/json" -H "Accept: application/json" -X POST 'https://localhost:8001/config/plugins/ginger/disable' -d'{}'
Enter host password for user 'danielhb':
{
"enabled":false,
"name":"ginger"
}[danielhb@arthas wok_all_plugins]$
[danielhb@arthas wok_all_plugins]$ curl -k -u danielhb -H "Content-Type: applicationjson" -H "Accept: application/json" -X POST 'https://localhost:8001/config/plugins/ginger/enable' -d'{}'
Enter host password for user 'danielhb':
{
"enabled":true,
"name":"ginger"
}[danielhb@arthas wok_all_plugins]$
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Daniel Henrique Barboza (10):
/config/plugins API: docs changes
/config/plugins API: utils changes
/config/plugins: changes in config model
/config/plugins API: model changes
/config/plugins API: test changes
Removing old /plugins API
/config/plugins: changing existing UI calls
/config/plugins: exclude 'sample' plug-in when not in test_mode
Removing configobj, use file operations to edit conf files
/config/plugins API: user log and parser improvements
docs/API/config.md | 31 ++++++++++++++++++++
docs/API/plugins.md | 13 ---------
src/wok/control/config.py | 33 +++++++++++++++++++--
src/wok/control/plugins.py | 29 -------------------
src/wok/i18n.py | 4 +++
src/wok/model/plugins.py | 32 +++++++++++++++------
src/wok/server.py | 7 ++++-
src/wok/utils.py | 65 ++++++++++++++++++++++++++++++++++++-----
tests/test_api.py | 47 +++++++++++++++++++++++++++++-
tests/test_utils.py | 72 +++++++++++++++++++++++++++++++++++++++++++++-
ui/js/src/wok.api.js | 4 +--
ui/js/src/wok.logos.js | 11 ++++---
ui/js/src/wok.main.js | 12 +++++---
13 files changed, 288 insertions(+), 72 deletions(-)
delete mode 100644 docs/API/plugins.md
delete mode 100644 src/wok/control/plugins.py
--
2.7.4
7 years, 9 months
[PATCH v4] [WoK 0/2] /config/plugins API implementation
by dhbarboza82@gmail.com
From: Daniel Henrique Barboza <danielhb(a)linux.vnet.ibm.com>
v4:
- added more info in docs/API/config.md
- removed 'access_log' and 'error_log' from wok config
- changed the default value of "test" parameter from "true" to ""
- added 'self.admin_methods = ['POST'] ' in the Plugin Resource
of control/config.py
v3:
- atomic backend commit
- using regex on parser
- 'test_mode' is now being retrieved by wok config
- plug-in dependencies are now being fetched
- plug-ins are now being enabled/disabled the cherrypy tree
v2:
- added User Log capabilities on /config/plugins/enable|disable actions
- added 'enable=' as a valid entry in the parsing of the conf file
This patch set implements the '/config/plugins' API.
The idea of this API is to replace the current '/plugins' API while
adding new attributes in their return values:
- enabled: true if the plug-in is enabled, false otherwise
- depends: list of all the plug-ins that this plug-in depends on
- is_dependency_of: list of all plug-in that depends on this plug-in
This backend is capable of enabling/disabling the plugi-ns using the
API /config/plugins/*name*/enable|disable. Please check the commit
messages of each patch for further details.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Daniel Henrique Barboza (2):
/config/plugins API: backend changes
/config/plugins: changing existing UI calls
docs/API/config.md | 40 ++++++++
docs/API/plugins.md | 13 ---
src/wok/config.py.in | 3 +-
src/wok/control/config.py | 32 ++++++-
src/wok/control/plugins.py | 29 ------
src/wok/i18n.py | 4 +
src/wok/model/plugins.py | 40 ++++++--
src/wok/server.py | 56 ++---------
src/wok/utils.py | 225 +++++++++++++++++++++++++++++++++++++++++++--
tests/test_api.py | 59 ++++++++++++
tests/test_utils.py | 75 ++++++++++++++-
ui/js/src/wok.api.js | 4 +-
ui/js/src/wok.logos.js | 11 ++-
ui/js/src/wok.main.js | 10 +-
14 files changed, 481 insertions(+), 120 deletions(-)
delete mode 100644 docs/API/plugins.md
delete mode 100644 src/wok/control/plugins.py
--
2.9.3
7 years, 9 months