from random import choice as random_choice
from time import time
from os import path
+from base_topic import BaseTopic # To allow project names in project_id
from authconn import AuthException
from authconn_keystone import AuthconnKeystone
Authorization. Initially it should support Openstack Keystone as a
backend through a plugin model where more backends can be added and a
RBAC model to manage permissions on operations.
+ This class must be threading safe
"""
periodin_db_pruning = 60 * 30 # for the internal backend only. every 30 minutes expired tokens will be pruned
# Note: it is faster to rewrite the value than to check if it is already there or not
if self.config["authentication"]["backend"] == "internal":
return
-
+
operations = []
with open(self.resources_to_operations_file, "r") as stream:
resources_to_operations_yaml = yaml.load(stream)
for resource, operation in resources_to_operations_yaml["resources_to_operations"].items():
- operation_key = operation.replace(".", ":")
- if operation_key not in operations:
- operations.append(operation_key)
- self.resources_to_operations_mapping[resource] = operation_key
+ if operation not in operations:
+ operations.append(operation)
+ self.resources_to_operations_mapping[resource] = operation
records = self.db.get_list("roles_operations")
if not isinstance(is_allowed, bool):
continue
- if operation == ".":
+ if operation == ":":
root = is_allowed
continue
- if len(operation) != 1 and operation[-1] == ".":
- self.logger.warning("Invalid operation {0} terminated in '.'. "
+ if len(operation) != 1 and operation[-1] == ":":
+ self.logger.warning("Invalid operation {0} terminated in ':'. "
"Operation will be discarded"
.format(operation))
continue
- operation_key = operation.replace(".", ":")
- if operation_key not in role_ops.keys():
- role_ops[operation_key] = is_allowed
+ if operation not in role_ops.keys():
+ role_ops[operation] = is_allowed
else:
self.logger.info("In role {0}, the operation {1} with the value {2} was discarded due to "
"repetition.".format(role_with_operations["role"], operation, is_allowed))
if self.config["authentication"]["backend"] != "internal" and \
role_with_operations["role"] != "anonymous":
- keystone_id = self.backend.create_role(role_with_operations["role"])
+ keystone_id = [role for role in self.backend.get_role_list()
+ if role["name"] == role_with_operations["role"]]
+ if keystone_id:
+ keystone_id = keystone_id[0]
+ else:
+ keystone_id = self.backend.create_role(role_with_operations["role"])
operation_to_roles_item["_id"] = keystone_id["_id"]
self.db.create("roles_operations", operation_to_roles_item)
now = time()
session = self.tokens_cache.get(token_id)
if session and session["expires"] < now:
- del self.tokens_cache[token_id]
+ # delete token. MUST be done with care, as another thread maybe already delete it. Do not use del
+ self.tokens_cache.pop(token_id, None)
session = None
if session:
return session
if self.config["global"].get("test.user_not_authorized"):
return {"id": "fake-token-id-for-test",
"project_id": self.config["global"].get("test.project_not_authorized", "admin"),
- "username": self.config["global"]["test.user_not_authorized"]}
+ "username": self.config["global"]["test.user_not_authorized"], "admin": True}
else:
raise
token_id = ''.join(random_choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
for _ in range(0, 32))
- if indata.get("project_id"):
- project_id = indata.get("project_id")
- if project_id not in user_content["projects"]:
- raise AuthException("project {} not allowed for this user"
- .format(project_id), http_code=HTTPStatus.UNAUTHORIZED)
+ project_id = indata.get("project_id")
+ if project_id:
+ if project_id != "admin":
+ # To allow project names in project_id
+ proj = self.db.get_one("projects", {BaseTopic.id_field("projects", project_id): project_id})
+ if proj["_id"] not in user_content["projects"] and proj["name"] not in user_content["projects"]:
+ raise AuthException("project {} not allowed for this user"
+ .format(project_id), http_code=HTTPStatus.UNAUTHORIZED)
else:
project_id = user_content["projects"][0]
if project_id == "admin":
session_admin = True
else:
- project = self.db.get_one("projects", {"_id": project_id})
+ # To allow project names in project_id
+ project = self.db.get_one("projects", {BaseTopic.id_field("projects", project_id): project_id})
session_admin = project.get("admin", False)
new_session = {"issued_at": now, "expires": now + 3600,
"_id": token_id, "id": token_id, "project_id": project_id, "username": user_content["username"],