summary |
shortlog |
log |
commit | commitdiff |
tree
raw |
patch |
inline | side by side (from parent 1:
bee508e)
Change-Id: Idb64610544e67af4cf56110358dcb1bb2d85f0b9
Signed-off-by: tierno <alfonso.tiernosepulveda@telefonica.com>
+# 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.
+
+
"""
Authenticator is responsible for authenticating the users,
create the tokens unscoped and scoped, retrieve the role
list inside the projects that they are inserted
"""
"""
Authenticator is responsible for authenticating the users,
create the tokens unscoped and scoped, retrieve the role
list inside the projects that they are inserted
"""
-__author__ = "Eduardo Sousa <esousa@whitestack.com>"
+__author__ = "Eduardo Sousa <esousa@whitestack.com>; Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
__date__ = "$27-jul-2018 23:59:59$"
import cherrypy
__date__ = "$27-jul-2018 23:59:59$"
import cherrypy
RBAC model to manage permissions on operations.
"""
RBAC model to manage permissions on operations.
"""
+ periodin_db_pruning = 60*30 # for the internal backend only. every 30 minutes expired tokens will be pruned
+
def __init__(self):
"""
Authenticator initializer. Setup the initial state of the object,
def __init__(self):
"""
Authenticator initializer. Setup the initial state of the object,
self.backend = None
self.config = None
self.db = None
self.backend = None
self.config = None
self.db = None
+ self.tokens_cache = dict()
+ self.next_db_prune_time = 0 # time when next cleaning of expired tokens must be done
+
self.logger = logging.getLogger("nbi.authenticator")
def start(self, config):
self.logger = logging.getLogger("nbi.authenticator")
def start(self, config):
self.config = config
try:
self.config = config
try:
- if not self.backend:
- if config["authentication"]["backend"] == "keystone":
- self.backend = AuthconnKeystone(self.config["authentication"])
- elif config["authentication"]["backend"] == "internal":
- pass
- else:
- raise AuthException("Unknown authentication backend: {}"
- .format(config["authentication"]["backend"]))
if not self.db:
if config["database"]["driver"] == "mongo":
self.db = dbmongo.DbMongo()
if not self.db:
if config["database"]["driver"] == "mongo":
self.db = dbmongo.DbMongo()
else:
raise AuthException("Invalid configuration param '{}' at '[database]':'driver'"
.format(config["database"]["driver"]))
else:
raise AuthException("Invalid configuration param '{}' at '[database]':'driver'"
.format(config["database"]["driver"]))
+ if not self.backend:
+ if config["authentication"]["backend"] == "keystone":
+ self.backend = AuthconnKeystone(self.config["authentication"])
+ elif config["authentication"]["backend"] == "internal":
+ self._internal_tokens_prune()
+ else:
+ raise AuthException("Unknown authentication backend: {}"
+ .format(config["authentication"]["backend"]))
except Exception as e:
raise AuthException(str(e))
except Exception as e:
raise AuthException(str(e))
# 2. Try using session before request a new token. If not, basic authentication will generate
token = cherrypy.session.get("Authorization")
if token == "logout":
# 2. Try using session before request a new token. If not, basic authentication will generate
token = cherrypy.session.get("Authorization")
if token == "logout":
- token = None # force Unauthorized response to insert user pasword again
+ token = None # force Unauthorized response to insert user password again
elif user_passwd64 and cherrypy.request.config.get("auth.allow_basic_authentication"):
# 3. Get new token from user password
user = None
elif user_passwd64 and cherrypy.request.config.get("auth.allow_basic_authentication"):
# 3. Get new token from user password
user = None
else:
try:
self.backend.validate_token(token)
else:
try:
self.backend.validate_token(token)
- return self.tokens[token]
+ # TODO: check if this can be avoided. Backend may provide enough information
+ return self.tokens_cache[token]
except AuthException:
self.del_token(token)
raise
except AuthException:
self.del_token(token)
raise
elif remote.ip:
new_session["remote_host"] = remote.ip
elif remote.ip:
new_session["remote_host"] = remote.ip
- self.tokens[token] = new_session
+ # TODO: check if this can be avoided. Backend may provide enough information
+ self.tokens_cache[token] = new_session
return deepcopy(new_session)
return deepcopy(new_session)
if self.config["authentication"]["backend"] == "internal":
return self._internal_get_token_list(session)
else:
if self.config["authentication"]["backend"] == "internal":
return self._internal_get_token_list(session)
else:
- return [deepcopy(token) for token in self.tokens.values()
+ # TODO: check if this can be avoided. Backend may provide enough information
+ return [deepcopy(token) for token in self.tokens_cache.values()
if token["username"] == session["username"]]
def get_token(self, session, token):
if self.config["authentication"]["backend"] == "internal":
return self._internal_get_token(session, token)
else:
if token["username"] == session["username"]]
def get_token(self, session, token):
if self.config["authentication"]["backend"] == "internal":
return self._internal_get_token(session, token)
else:
- token_value = self.tokens.get(token)
+ # TODO: check if this can be avoided. Backend may provide enough information
+ token_value = self.tokens_cache.get(token)
if not token_value:
raise AuthException("token not found", http_code=HTTPStatus.NOT_FOUND)
if token_value["username"] != session["username"] and not session["admin"]:
if not token_value:
raise AuthException("token not found", http_code=HTTPStatus.NOT_FOUND)
if token_value["username"] != session["username"] and not session["admin"]:
else:
try:
self.backend.revoke_token(token)
else:
try:
self.backend.revoke_token(token)
+ del self.tokens_cache[token]
return "token '{}' deleted".format(token)
except KeyError:
raise AuthException("Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND)
return "token '{}' deleted".format(token)
except KeyError:
raise AuthException("Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND)
- def _internal_authorize(self, token):
+ def _internal_authorize(self, token_id):
raise AuthException("Needed a token or Authorization http header", http_code=HTTPStatus.UNAUTHORIZED)
raise AuthException("Needed a token or Authorization http header", http_code=HTTPStatus.UNAUTHORIZED)
- if token not in self.tokens:
- raise AuthException("Invalid token or Authorization http header", http_code=HTTPStatus.UNAUTHORIZED)
- session = self.tokens[token]
+ # try to get from cache first
+ session = self.tokens_cache.get(token_id)
+ if session and session["expires"] < now:
+ del self.tokens_cache[token_id]
+ session = None
+ if session:
+ return session
+
+ # get from database if not in cache
+ session = self.db.get_one("tokens", {"_id": token_id})
if session["expires"] < now:
if session["expires"] < now:
raise AuthException("Expired Token or Authorization http header", http_code=HTTPStatus.UNAUTHORIZED)
raise AuthException("Expired Token or Authorization http header", http_code=HTTPStatus.UNAUTHORIZED)
+ self.tokens_cache[token_id] = session
+ except DbException as e:
+ if e.http_code == HTTPStatus.NOT_FOUND:
+ raise AuthException("Invalid Token or Authorization http header", http_code=HTTPStatus.UNAUTHORIZED)
+ else:
+ raise
+
except AuthException:
if self.config["global"].get("test.user_not_authorized"):
return {"id": "fake-token-id-for-test",
except AuthException:
if self.config["global"].get("test.user_not_authorized"):
return {"id": "fake-token-id-for-test",
# Try using username/password
if indata.get("username"):
user_rows = self.db.get_list("users", {"username": indata.get("username")})
# Try using username/password
if indata.get("username"):
user_rows = self.db.get_list("users", {"username": indata.get("username")})
if user_rows:
user_content = user_rows[0]
salt = user_content["_admin"]["salt"]
if user_rows:
user_content = user_rows[0]
salt = user_content["_admin"]["salt"]
elif remote.ip:
new_session["remote_host"] = remote.ip
elif remote.ip:
new_session["remote_host"] = remote.ip
- self.tokens[token_id] = new_session
+ self.tokens_cache[token_id] = new_session
+ self.db.create("tokens", new_session)
+ # check if database must be prune
+ self._internal_tokens_prune(now)
return deepcopy(new_session)
def _internal_get_token_list(self, session):
return deepcopy(new_session)
def _internal_get_token_list(self, session):
- token_list = []
- for token_id, token_value in self.tokens.items():
- if token_value["username"] == session["username"]:
- token_list.append(deepcopy(token_value))
+ now = time()
+ token_list = self.db.get_list("tokens", {"username": session["username"], "expires.gt": now})
return token_list
def _internal_get_token(self, session, token_id):
return token_list
def _internal_get_token(self, session, token_id):
- token_value = self.tokens.get(token_id)
+ token_value = self.db.get_one("tokens", {"_id": token_id}, fail_on_empty=False)
if not token_value:
raise AuthException("token not found", http_code=HTTPStatus.NOT_FOUND)
if token_value["username"] != session["username"] and not session["admin"]:
if not token_value:
raise AuthException("token not found", http_code=HTTPStatus.NOT_FOUND)
if token_value["username"] != session["username"] and not session["admin"]:
def _internal_del_token(self, token_id):
try:
def _internal_del_token(self, token_id):
try:
- del self.tokens[token_id]
+ self.tokens_cache.pop(token_id, None)
+ self.db.del_one("tokens", {"_id": token_id})
return "token '{}' deleted".format(token_id)
return "token '{}' deleted".format(token_id)
- except KeyError:
- raise AuthException("Token '{}' not found".format(token_id), http_code=HTTPStatus.NOT_FOUND)
+ except DbException as e:
+ if e.http_code == HTTPStatus.NOT_FOUND:
+ raise AuthException("Token '{}' not found".format(token_id), http_code=HTTPStatus.NOT_FOUND)
+ else:
+ raise
+
+ def _internal_tokens_prune(self, now=None):
+ now = now or time()
+ if not self.next_db_prune_time or self.next_db_prune_time >= now:
+ self.db.del_list("tokens", {"expires.lt": now})
+ self.next_db_prune_time = self.periodin_db_pruning + now
+ self.tokens_cache.clear() # force to reload tokens from database