def test_dictionary_setnone_deletekey(self): '''This test case ensures that None values set on non immutable objects trigger a delete of the specified key.''' desc = {"attr1": "test", "attr2": "value"} obj = DictionaryObject(desc, immutable=False) obj.attr1 = None self.assertFalse("attr1" in desc)
def test_hash_nocontext(self): '''This test case ensures an exception is raised if no hash context is given or if the context does not contain salt entry.''' for hash_ctx in [None, {}, {"unknown_attr": "sample value"}]: passwd = self._hasher.hash_password("123", DictionaryObject(hash_ctx)) self.assertEqual( passwd, self._hasher.hash_password("123", DictionaryObject({"salt": 9999})))
def test_format_resource_none_ok(self): '''This test case ensures validation works correctly even when resource does not contain keys.''' desc = {} resource = DictionaryObject(desc, immutable=False) self._validator.format_resource(resource, None) self.assertEqual(desc, {})
def test_hash_none(self): '''This test case ensures that a hash is generated for empty or null passwords.''' hash_ctx = DictionaryObject({"salt": 123}) expected_result = "M2M5OTA5YWZlYzI1MzU0ZDU1MWRhZTIxNTkwYmIyNmUzOGQ1M2YyMTczYjhkM2RjM2VlZTRjMDQ3ZTdhYjFjMWViOGI4NTEwM2UzYmU3YmE2MTNiMzFiYjVjOWMzNjIxNGRjOWYxNGE0MmZkN2EyZmRiODQ4NTZiY2E1YzQ0YzI=" for plain_passwd in [None, "", " "]: result = self._hasher.hash_password(plain_passwd, hash_ctx) self.assertEqual(expected_result, result)
def test_hash_ok(self): '''This test case ensures a base64 encoded hash is return correctly by password hasher.''' plain_passwd = "abc123test" hash_ctx = DictionaryObject({"salt": 123}) expected_result = "MzYxZTA5NTk2ZmVjNzZlMzc0MGZiY2I1YTc3NGM2YjM4OTBkMDFlNDEwYzQ5ZDdkNzAwM2IyNWJkZTUxOWE4MTAyMDM5Mzg1YTI4NDA0MmUxODNlN2JhMmNlZmM3NzU0NDQ2MTk5ZjhkYjQzZWEwMDE3MzhmODJmNDBmZWExM2M=" result = self._hasher.hash_password(plain_passwd, hash_ctx) self.assertEqual(expected_result, result)
def test_dictionary_notimmutable(self): '''This test case ensures a dictionary object can be built so that his attributes and underlining dictionaries can be changed at runtime.''' new_attr2 = "abc_altered" desc = {"attr1": "abcd", "attr2": "abc"} obj = DictionaryObject(desc, immutable=False) self.assertEqual(desc["attr1"], obj.attr1) self.assertEqual(desc["attr2"], obj.attr2) obj.attr2 = new_attr2 obj.new_attr2 = new_attr2 self.assertEqual(new_attr2, desc["attr2"]) self.assertEqual(new_attr2, obj.attr2) self.assertEqual(new_attr2, obj.new_attr2)
def test_format_resource_ok(self): '''This test case ensures format_resource works correctly for an expected resource.''' desc = {"username": "******", "password": "******"} resource = DictionaryObject(desc, immutable=False) self._validator.format_resource(resource, None) self.assertEqual(desc["username"], resource.username) self.assertTrue("password" not in desc)
def format_collection(self, resources, request): # pylint: disable=W0613 '''This method must be overriden by each subclass in order to provide custom logic which must be executed after a collection is fetched from database. By default, this method simply iterates over the list of available resources and invoke format_resource. Usually you will want to override this method in order to suppress sensitive data to be sent to clients.''' resources = resources or [] for resource in resources: self.format_resource(DictionaryObject(resource, immutable=False), request)
def hash_password(self, plain_passwd, hash_ctx=None): '''This method provides the sha512 with salt algorithm for a given plain password. In addition, the hash is base64 encoded.''' if not hash_ctx or not hash_ctx.dictionary.get("salt"): hash_ctx = DictionaryObject({"salt": self._DEFAULT_SALT}) plain_passwd = (plain_passwd or "").strip() salt = hash_ctx.salt text = (plain_passwd + str(salt)).encode() hashed_text = hashlib.sha512(text).hexdigest() return base64.b64encode(hashed_text.encode()).decode()
def _test_authenticate_ok(self, return_url, expected_url): '''This method provides a template test case for ensuring authenticate succeeds for various return_url values.''' user = User(username="******", password="******", person_id=1) user.user_id = 123 creation_time, expiration_time = self._mock_creationexpiration_time() token = Token({ "client_id": self._IDP_CLIENTID, "type": "login", "user_id": user.user_id, "creation_time": creation_time, "expiration_time": expiration_time }) request, user_repo_cls, user_repo, tokens_service_cls, \ tokens_service, clienturl_facade = self._mock_authenticate_dependencies(token, user, return_url) response = self._idp_controller.authenticate( request, tokens_service_cls=tokens_service_cls, user_repo_cls=user_repo_cls) self.assertIsNotNone(response) self.assertEqual(302, response.status_code) location = response.headers.get("Location") self.assertEqual(expected_url, location) user_repo.load_by_username.assert_called_once_with(user.username) self._hasher.hash_password.assert_called_once_with( user.password, DictionaryObject({"salt": user.user_id})) tokens_service_cls.assert_called_once_with(clienturl_facade.session) tokens_service.generate.assert_called_once_with( { "client_id": self._IDP_CLIENTID, "user_id": user.user_id, "expires_in": self._EXPIRES_IN }, TokenGeneratorFactory.LOGIN_TOKEN) tokens_service.encrypt.assert_called_once_with(token, token.client_id)
def _validate_user(self, username, password, user_repo): '''This method validates the given username and password against the record found in database. If no user is found or password does not match an exception is raised.''' user = user_repo.load_by_username(username) if not user: raise OAuth2AuthenticationError("Username or password do not match.") password_hash = self._passwords_hasher.hash_password(password, DictionaryObject({"salt": user.user_id})) if password_hash != user.password: # when the account is created for the first time there is no user_id available so a default salt is used. password_hash_default = self._passwords_hasher.hash_password(password) if password_hash_default != user.password: raise OAuth2AuthenticationError("Username or password do not match.") return user
def _test_validate_user_template(self, resource, request): '''This method provides a template for validate user test cases.''' request.request_id = 9876 default_person = Person(first_name="-", last_name="-", email_address=resource.username) person_id = 1 plain_passwd = resource.password user_id = resource.user_id hashed_passwd = "123" self._hasher.hash_password = Mock(return_value=hashed_passwd) if request.method.lower() == "post": self._person_facade.new_model = Mock(return_value=default_person) self._person_facade.create = Mock(return_value=[person_id]) self._validator.validate(resource, request) self.assertEqual(hashed_passwd, resource.password) self._hasher.hash_password.assert_called_once_with( plain_passwd, DictionaryObject({"salt": user_id})) if request.method.lower() == "post": self.assertEqual(person_id, resource.person_id) self._model_facade_cls.assert_called_once_with( Person, self._db_conn) self._conn_manager.get_connection.assert_called_once_with( request.request_id) self._person_facade.new_model.assert_called_once_with( first_name="-", last_name="-", email_address=resource.username) self._person_facade.create.assert_called_once_with(default_person)
def _hash_password(self, resource): '''This method hashes the password from the given resource.''' hash_ctx = {"salt": resource.user_id} resource.password = self._passwd_hasher.hash_password(resource.password, DictionaryObject(hash_ctx))
def get_item(self, request, version, resource_url, resource_id): '''This method provides the API for retrieving a single item from a collection. The item is uniquely identified by resource_id. Below you can find a success response example: .. code-block:: html GET - /api/1.0/simple-resources/1 HTTP/1.1 200 OK Content-Type: application/json Content-Length: ... { "id": 1, "name": "Test resource", "description": "Simple description" } Of course there are cases when exceptions might occur. Below, you can find a list of error response retrieved from get_item API: * **10000** - Whenever we try to retrieve a resource with unknown type. (Not registered to ROA). * **10030** - Whenever we try to retrieve a resource and an unexpected database exception occurs. * **10040** - Whenever we try to retrieve a resource which does not exist. ''' if version != "latest": version = float(version) fields = request.params.get("fields") resource = self._resources_registry.find_by_url(resource_url, version) if not resource: return self._handle_resource_notfound(version, resource_url) self._inject_security_context(request, resource.model) access_token = self.validate_security_context(request, "read") model_facade = self._model_facade_cls( resource.model, self._get_current_connection(request)) try: model = model_facade.find_by_pk( {model_facade.model_pk_cols[0]: resource_id}) if not self._is_model_owned_by(model, access_token, resource): model = None except FantasticoDbError as dbex: return self._handle_resource_dberror(version, resource_url, dbex) if not model: return self._handle_resource_item_notfound(version, resource_url, resource_id) json_serializer = self._json_serializer_cls(resource) resource_body = json_serializer.serialize(model, fields) if resource.validator and model: resource.validator().format_resource( DictionaryObject(resource_body, immutable=False), request) resource_body = json.dumps(resource_body) response = Response(body=resource_body.encode(), content_type="application/json", status_code=200) self._add_cors_headers(response) return response