Fix bug 731
[osm/NBI.git] / osm_nbi / auth.py
index 751dd90..d916189 100644 (file)
@@ -40,6 +40,7 @@ from http import HTTPStatus
 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
@@ -54,6 +55,7 @@ class Authenticator:
     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
@@ -154,16 +156,15 @@ class Authenticator:
         # 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")
 
@@ -192,19 +193,18 @@ class Authenticator:
                     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))
@@ -229,7 +229,12 @@ class Authenticator:
 
                 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)
@@ -479,7 +484,8 @@ class Authenticator:
             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
@@ -500,7 +506,7 @@ class Authenticator:
             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
 
@@ -531,17 +537,21 @@ class Authenticator:
 
         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"],