Fix 1504 - NBI-HA do not manage correctly new roles (RBAC)
[osm/NBI.git] / osm_nbi / auth.py
index ec6a406..dcf12c3 100644 (file)
@@ -39,9 +39,10 @@ from http import HTTPStatus
 from time import time
 from os import path
 
-from osm_nbi.authconn import AuthException, AuthExceptionUnauthorized
+from osm_nbi.authconn import AuthException, AuthconnException, AuthExceptionUnauthorized
 from osm_nbi.authconn_keystone import AuthconnKeystone
 from osm_nbi.authconn_internal import AuthconnInternal
+from osm_nbi.authconn_tacacs import AuthconnTacacs
 from osm_common import dbmemory, dbmongo, msglocal, msgkafka
 from osm_common.dbbase import DbException
 from osm_nbi.validation import is_valid_uuid
@@ -80,6 +81,8 @@ class Authenticator:
         self.role_permissions = []
         self.valid_methods = valid_methods
         self.valid_query_string = valid_query_string
+        self.system_admin_role_id = None   # system_role id
+        self.test_project_id = None  # test_project_id
 
     def start(self, config):
         """
@@ -117,7 +120,10 @@ class Authenticator:
                     self.backend = AuthconnKeystone(self.config["authentication"], self.db, self.role_permissions)
                 elif config["authentication"]["backend"] == "internal":
                     self.backend = AuthconnInternal(self.config["authentication"], self.db, self.role_permissions)
-                    self._internal_tokens_prune()
+                    self._internal_tokens_prune("tokens")
+                elif config["authentication"]["backend"] == "tacacs":
+                    self.backend = AuthconnTacacs(self.config["authentication"], self.db, self.role_permissions)
+                    self._internal_tokens_prune("tokens_tacacs")
                 else:
                     raise AuthException("Unknown authentication backend: {}"
                                         .format(config["authentication"]["backend"]))
@@ -157,6 +163,15 @@ class Authenticator:
                     if permission not in self.role_permissions:
                         self.role_permissions.append(permission)
 
+            # get ids of role system_admin and test project
+            role_system_admin = self.db.get_one("roles", {"name": "system_admin"}, fail_on_empty=False)
+            if role_system_admin:
+                self.system_admin_role_id = role_system_admin["_id"]
+            test_project_name = self.config["authentication"].get("project_not_authorized", "admin")
+            test_project = self.db.get_one("projects", {"name": test_project_name}, fail_on_empty=False)
+            if test_project:
+                self.test_project_id = test_project["_id"]
+
         except Exception as e:
             raise AuthException(str(e))
 
@@ -222,8 +237,8 @@ class Authenticator:
 
         records = self.backend.get_role_list()
 
-        # Loading permissions to MongoDB if there is not any permission.
-        if not records or (len(records) == 1 and records[0]["name"] == "admin"):
+        # Loading permissions to AUTH. At lease system_admin must be present.
+        if not records or not next((r for r in records if r["name"] == "system_admin"), None):
             with open(self.roles_to_operations_file, "r") as stream:
                 roles_to_operations_yaml = yaml.load(stream, Loader=yaml.Loader)
 
@@ -245,7 +260,7 @@ class Authenticator:
                                             .format(permission, role_with_operations["name"],
                                                     self.roles_to_operations_file))
 
-                    # TODO chek permission is ok
+                    # TODO check permission is ok
                     if permission[-1] == ":":
                         raise AuthException("Invalid permission '{}' terminated in ':' for role '{}'; at file {}"
                                             .format(permission, role_with_operations["name"],
@@ -263,8 +278,13 @@ class Authenticator:
                 }
 
                 # self.db.create(self.roles_to_operations_table, role_with_operations)
-                self.backend.create_role(role_with_operations)
-                self.logger.info("Role '{}' created at database".format(role_with_operations["name"]))
+                try:
+                    self.backend.create_role(role_with_operations)
+                    self.logger.info("Role '{}' created".format(role_with_operations["name"]))
+                except (AuthException, AuthconnException) as e:
+                    if role_with_operations["name"] == "system_admin":
+                        raise
+                    self.logger.error("Role '{}' cannot be created: {}".format(role_with_operations["name"], e))
 
         # Create admin project&user if required
         pid = self.create_admin_project()
@@ -394,12 +414,12 @@ class Authenticator:
                 if cherrypy.session.get('Authorization'):
                     del cherrypy.session['Authorization']
                 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="{}"'.format(e)
-            elif self.config.get("user_not_authorized"):
-                # TODO provide user_id, roles id (not name), project_id
-                return {"id": "fake-token-id-for-test",
-                        "project_id": self.config.get("project_not_authorized", "admin"),
-                        "username": self.config["user_not_authorized"],
-                        "roles": ["system_admin"]}
+            if self.config["authentication"].get("user_not_authorized"):
+                return {"id": "testing-token", "_id": "testing-token",
+                        "project_id": self.test_project_id,
+                        "username": self.config["authentication"]["user_not_authorized"],
+                        "roles": [self.system_admin_role_id],
+                        "admin": True, "allow_show_user_project_role": True}
             raise
 
     def new_token(self, token_info, indata, remote):
@@ -466,6 +486,7 @@ class Authenticator:
         :return: True if access granted by permission rules, False if access granted by default rules (Bug 853)
         :raises: AuthExceptionUnauthorized if access denied
         """
+        self.load_operation_to_allowed_roles()
 
         roles_required = self.operation_to_allowed_roles[role_permission]
         roles_allowed = [role["name"] for role in token_info["roles"]]
@@ -575,10 +596,10 @@ class Authenticator:
             raise AuthException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
         return token_value
 
-    def _internal_tokens_prune(self, now=None):
+    def _internal_tokens_prune(self, token_collection, 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.db.del_list(token_collection, {"expires.lt": now})
             self.next_db_prune_time = self.periodin_db_pruning + now
             # self.tokens_cache.clear()  # not required any more