From: selvi.j Date: Mon, 4 Apr 2022 06:54:30 +0000 (+0000) Subject: Feature 10914: Enforce Password change on First login X-Git-Tag: v12.0.0rc1~23 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=commitdiff_plain;h=a9a1fc8427db17f47ea7ff782e35d24be4094f95 Feature 10914: Enforce Password change on First login Added Implementation code for Enforce Password change on First login and expire password after preset number of days feature Change-Id: Id1ac670a8f4f27d701aef430f426e911b290b885 Signed-off-by: selvi.j --- diff --git a/osm_nbi/admin_topics.py b/osm_nbi/admin_topics.py index c2767c8..8eccd20 100644 --- a/osm_nbi/admin_topics.py +++ b/osm_nbi/admin_topics.py @@ -1144,6 +1144,7 @@ class UserTopicAuth(UserTopic): "_id": _id, "username": indata.get("username"), "password": indata.get("password"), + "old_password": indata.get("old_password"), "add_project_role_mappings": mappings_to_add, "remove_project_role_mappings": mappings_to_remove, } diff --git a/osm_nbi/auth.py b/osm_nbi/auth.py index eef2ae7..a99cea7 100644 --- a/osm_nbi/auth.py +++ b/osm_nbi/auth.py @@ -768,3 +768,27 @@ class Authenticator: else: self.tokens_cache.clear() self.msg.write("admin", "revoke_token", {"_id": token} if token else None) + + def check_password_expiry(self, outdata): + """ + This method will check for password expiry of the user + :param outdata: user token information + """ + user_content = None + detail = {} + present_time = time() + user = outdata["username"] + if self.config["authentication"].get("pwd_expiry_check"): + user_content = self.db.get_list("users", {"username": user})[0] + if not user_content.get("username") == "admin": + user_content["_admin"]["modified_time"] = present_time + if user_content.get("_admin").get("expire_time"): + expire_time = user_content["_admin"]["expire_time"] + else: + expire_time = present_time + uid = user_content["_id"] + self.db.set_one("users", {"_id": uid}, user_content) + if not present_time < expire_time: + return True + else: + pass diff --git a/osm_nbi/authconn_internal.py b/osm_nbi/authconn_internal.py index e342150..99d18e4 100644 --- a/osm_nbi/authconn_internal.py +++ b/osm_nbi/authconn_internal.py @@ -33,7 +33,7 @@ __date__ = "$06-jun-2019 11:16:08$" import logging import re -from osm_nbi.authconn import Authconn, AuthException # , AuthconnOperationException +from osm_nbi.authconn import Authconn, AuthException, AuthconnConflictException # , AuthconnOperationException from osm_common.dbbase import DbException from osm_nbi.base_topic import BaseTopic from osm_nbi.validation import is_valid_uuid @@ -352,6 +352,11 @@ class AuthconnInternal(Authconn): BaseTopic.format_on_new(user_info, make_public=False) salt = uuid4().hex user_info["_admin"]["salt"] = salt + present = time() + if not user_info["username"] == "admin": + if self.config.get("pwd_expiry_check"): + user_info["_admin"]["modified_time"] = present + user_info["_admin"]["expire_time"] = present if "password" in user_info: user_info["password"] = sha256( user_info["password"].encode("utf-8") + salt.encode("utf-8") @@ -369,9 +374,18 @@ class AuthconnInternal(Authconn): :param user_info: user info modifications """ uid = user_info["_id"] + old_pwd = user_info.get("old_password") user_data = self.db.get_one( self.users_collection, {BaseTopic.id_field("users", uid): uid} ) + if old_pwd: + salt = user_data["_admin"]["salt"] + shadow_password = sha256(old_pwd.encode('utf-8') + salt.encode('utf-8')).hexdigest() + if shadow_password != user_data["password"]: + raise AuthconnConflictException( + "Incorrect password", + http_code=HTTPStatus.CONFLICT + ) BaseTopic.format_on_edit(user_data, user_info) # User Name usnm = user_info.get("username") @@ -389,6 +403,13 @@ class AuthconnInternal(Authconn): user_data["password"] = sha256( pswd.encode("utf-8") + salt.encode("utf-8") ).hexdigest() + if not user_data["username"] == "admin": + if self.config.get("pwd_expiry_check"): + present = time() + if self.config.get("days"): + expire = present + 86400 * self.config.get("days") + user_data["_admin"]["modified_time"] = present + user_data["_admin"]["expire_time"] = expire # Project-Role Mappings # TODO: Check that user_info NEVER includes "project_role_mappings" if "project_role_mappings" not in user_data: diff --git a/osm_nbi/nbi.cfg b/osm_nbi/nbi.cfg index 60320ad..383b462 100644 --- a/osm_nbi/nbi.cfg +++ b/osm_nbi/nbi.cfg @@ -116,5 +116,9 @@ backend: "keystone" # internal or keystone or tacacs # tacacs_port: 49 # Default value # tacacs_timeout: 10 # Default value +# Password expiry configuration +# pwd_expiry_check: True # Uncomment to enable the password expiry check +# days: 30 # Default value + [rbac] # roles_to_operations: "roles_to_operations.yml" # initial role generation when database diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py index 67cf58b..502207f 100644 --- a/osm_nbi/nbi.py +++ b/osm_nbi/nbi.py @@ -953,7 +953,12 @@ class Server(object): self._set_location_header("admin", "v1", "tokens", outdata["_id"]) # for logging self._format_login(token_info) - + # password expiry check + if self.authenticator.check_password_expiry(outdata): + outdata = {"id": outdata["id"], + "message": "change_password", + "user_id": outdata["user_id"] + } # cherrypy.response.cookie["Authorization"] = outdata["id"] # cherrypy.response.cookie["Authorization"]['expires'] = 3600 elif method == "DELETE": diff --git a/osm_nbi/roles_to_operations.yml b/osm_nbi/roles_to_operations.yml index 13cbffd..a97e0c1 100644 --- a/osm_nbi/roles_to_operations.yml +++ b/osm_nbi/roles_to_operations.yml @@ -96,9 +96,11 @@ roles: permissions: default: true admin: false - users: false projects: false roles: false + # Users + users: false + users:id:patch: true - name: "project_user" permissions: @@ -113,9 +115,11 @@ roles: ns_instances: true vnf_instances: true slice_instances: true - users: false projects: false roles: false + # Users + users: false + users:id:patch: true # VIMs vims: false vims:get: true diff --git a/osm_nbi/tests/test_admin_topics.py b/osm_nbi/tests/test_admin_topics.py index 734a289..8124ce4 100755 --- a/osm_nbi/tests/test_admin_topics.py +++ b/osm_nbi/tests/test_admin_topics.py @@ -999,6 +999,22 @@ class Test_UserTopicAuth(TestCase): norm(str(e.exception)), "Wrong exception text", ) + with self.subTest(i=3): + self.auth.get_user_list.side_effect = [[user], []] + self.auth.get_user.return_value = user + old_password = self.test_name + new_pasw = "new-password" + self.topic.edit( + self.fake_session, + uid, + { + "old_password": old_password, + "password": new_pasw, + }, + ) + content = self.auth.update_user.call_args[0][0] + self.assertEqual(content["old_password"], old_password, "Wrong old password") + self.assertEqual(content["password"], new_pasw, "Wrong user password") def test_delete_user(self): with self.subTest(i=1): diff --git a/osm_nbi/validation.py b/osm_nbi/validation.py index 1d637c5..4db456a 100644 --- a/osm_nbi/validation.py +++ b/osm_nbi/validation.py @@ -937,6 +937,7 @@ user_edit_schema = { "type": "object", "properties": { "password": passwd_schema, + "old_password": passwd_schema, "username": string_schema, # To allow User Name modification "projects": {"oneOf": [nameshort_list_schema, array_edition_schema]}, "project_role_mappings": project_role_mappings,