X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fauth.py;h=9171c9494beacaaf38408982a57e48bb80eaf3f2;hp=7d586ad13ad2460e0abb50814c5a9705b462218d;hb=e4a07d5db1e38e6a52788d5788c6cc3396e1052d;hpb=29933fc257389f16f9c798f52a43e43800475a4a;ds=sidebyside diff --git a/osm_nbi/auth.py b/osm_nbi/auth.py index 7d586ad..9171c94 100644 --- a/osm_nbi/auth.py +++ b/osm_nbi/auth.py @@ -1,17 +1,23 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright 2018 Whitestack, LLC +# Copyright 2018 Telefonica S.A. # -# http://www.apache.org/licenses/LICENSE-2.0 +# 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. +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: esousa@whitestack.com or alfonso.tiernosepulveda@telefonica.com +## """ @@ -19,7 +25,6 @@ Authenticator is responsible for authenticating the users, create the tokens unscoped and scoped, retrieve the role list inside the projects that they are inserted """ -from os import path __author__ = "Eduardo Sousa ; Alfonso Tierno " __date__ = "$27-jul-2018 23:59:59$" @@ -34,7 +39,8 @@ from hashlib import sha256 from http import HTTPStatus from random import choice as random_choice from time import time -from uuid import uuid4 +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 @@ -102,8 +108,11 @@ class Authenticator: if "resources_to_operations" in config["rbac"]: self.resources_to_operations_file = config["rbac"]["resources_to_operations"] else: - for config_file in (__file__[:__file__.rfind("auth.py")] + "resources_to_operations.yml", - "./resources_to_operations.yml"): + possible_paths = ( + __file__[:__file__.rfind("auth.py")] + "resources_to_operations.yml", + "./resources_to_operations.yml" + ) + for config_file in possible_paths: if path.isfile(config_file): self.resources_to_operations_file = config_file break @@ -113,8 +122,11 @@ class Authenticator: if "roles_to_operations" in config["rbac"]: self.roles_to_operations_file = config["rbac"]["roles_to_operations"] else: - for config_file in (__file__[:__file__.rfind("auth.py")] + "roles_to_operations.yml", - "./roles_to_operations.yml"): + possible_paths = ( + __file__[:__file__.rfind("auth.py")] + "roles_to_operations.yml", + "./roles_to_operations.yml" + ) + for config_file in possible_paths: if path.isfile(config_file): self.roles_to_operations_file = config_file break @@ -141,6 +153,9 @@ class Authenticator: # Always reads operation to resource mapping from file (this is static, no need to store it in MongoDB) # Operations encoding: " " # 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) @@ -168,7 +183,7 @@ class Authenticator: .format(role_with_operations["role"])) continue - operations = {} + role_ops = {} root = None if not role_with_operations["operations"]: @@ -189,8 +204,8 @@ class Authenticator: continue operation_key = operation.replace(".", ":") - if operation_key not in operations.keys(): - operations[operation_key] = is_allowed + if operation_key not in role_ops.keys(): + role_ops[operation_key] = 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)) @@ -202,28 +217,29 @@ class Authenticator: now = time() operation_to_roles_item = { - "_id": str(uuid4()), "_admin": { "created": now, "modified": now, }, - "role": role_with_operations["role"], + "name": role_with_operations["role"], "root": root } - for operation, value in operations.items(): + for operation, value in role_ops.items(): operation_to_roles_item[operation] = value + if self.config["authentication"]["backend"] != "internal" and \ + role_with_operations["role"] != "anonymous": + 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) permissions = {oper: [] for oper in operations} records = self.db.get_list("roles_operations") - ignore_fields = ["_id", "_admin", "role", "root"] - roles = [] + ignore_fields = ["_id", "_admin", "name", "root"] for record in records: - - roles.append(record["role"]) record_permissions = {oper: record["root"] for oper in operations} operations_joined = [(oper, value) for oper, value in record.items() if oper not in ignore_fields] operations_joined.sort(key=lambda x: x[0].count(":")) @@ -237,17 +253,12 @@ class Authenticator: allowed_operations = [k for k, v in record_permissions.items() if v is True] for allowed_op in allowed_operations: - permissions[allowed_op].append(record["role"]) + permissions[allowed_op].append(record["name"]) for oper, role_list in permissions.items(): self.operation_to_allowed_roles[oper] = role_list if self.config["authentication"]["backend"] != "internal": - for role in roles: - if role == "anonymous": - continue - self.backend.create_role(role) - self.backend.assign_role_to_user("admin", "admin", "system_admin") def authorize(self): @@ -396,7 +407,7 @@ class Authenticator: operation = self.resources_to_operations_mapping[key] roles_required = self.operation_to_allowed_roles[operation] - roles_allowed = self.backend.get_role_list(session["id"]) + roles_allowed = self.backend.get_user_role_list(session["id"]) if "anonymous" in roles_required: return @@ -407,6 +418,9 @@ class Authenticator: raise AuthException("Access denied: lack of permissions.") + def get_user_list(self): + return self.backend.get_user_list() + def _normalize_url(self, url, method): # Removing query strings normalized_url = url if '?' not in url else url[:url.find("?")] @@ -420,7 +434,9 @@ class Authenticator: tmp_keys = [] for tmp_key in filtered_keys: splitted = tmp_key.split()[1].split("/") - if "<" in splitted[idx] and ">" in splitted[idx]: + if idx >= len(splitted): + continue + elif "<" in splitted[idx] and ">" in splitted[idx]: if splitted[idx] == "": tmp_keys.append(tmp_key) continue @@ -516,17 +532,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"],