I'm trying to set up an SFTP server in python, using paramiko library. I'm using a PostgreSQL to store users credentials, by SQLAlchemy library.
When I try to login using FileZilla or WinSCP to the server, it gives me an error in this function:
def authenticate_user(username: str, password:str = None, public_key:str = None) -> bool:
engine = provide_engine()
if not engine:
return False
Session = sessionmaker(bind=engine)
session = Session()
try:
stmt = select(User).where(User.username == username)
user = session.execute(stmt).scalars().first()
if not user:
return False
if password:
print("Before checking.")
if user.check_password(password):
print("After checking.")
return True
elif public_key and user.ssh_keys and len(user.ssh_keys) > 0:
for key in user.ssh_keys:
if key.public_key == public_key:
return True
return False
finally:
session.close()
The error is:
ERROR:paramiko.transport:Unknown exception: string argument without an encoding
ERROR:paramiko.transport:Traceback (most recent call last):
ERROR:paramiko.transport: File "C:\Users\*******\AppData\Local\Programs\Python\Python312\Lib\site-packages\paramiko\transport.py", line 2262, in run
ERROR:paramiko.transport: handler(m)
ERROR:paramiko.transport: File "C:\Users\*******\AppData\Local\Programs\Python\Python312\Lib\site-packages\paramiko\auth_handler.py", line 609, in _parse_userauth_request
ERROR:paramiko.transport: result = self.transport.server_object.check_auth_password(
ERROR:paramiko.transport: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ERROR:paramiko.transport: File "d:\Files\*******\Python\cr-beta\database_sftp\SFTPServer.py", line 20, in check_auth_password
ERROR:paramiko.transport: if authenticate_user(username, password=password):
ERROR:paramiko.transport: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ERROR:paramiko.transport: File "d:\Files\*******\Python\cr-beta\database_sftp\SFTPServer.py", line 98, in authenticate_user
ERROR:paramiko.transport: user = session.execute(stmt).scalars().first()
ERROR:paramiko.transport: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ERROR:paramiko.transport: File "C:\Users\*******\AppData\Local\Programs\Python\Python312\Lib\site-packages\sqlalchemy\engine\result.py", line 1786, in first
ERROR:paramiko.transport: return self._only_one_row(
ERROR:paramiko.transport: ^^^^^^^^^^^^^^^^^^^
ERROR:paramiko.transport: File "C:\Users\*******\AppData\Local\Programs\Python\Python312\Lib\site-packages\sqlalchemy\engine\result.py", line 749, in _only_one_row
ERROR:paramiko.transport: row: Optional[_InterimRowType[Any]] = onerow(hard_close=True)
ERROR:paramiko.transport: ^^^^^^^^^^^^^^^^^^^^^^^
ERROR:paramiko.transport: File "C:\Users\*******\AppData\Local\Programs\Python\Python312\Lib\site-packages\sqlalchemy\engine\result.py", line 1673, in _fetchone_impl
ERROR:paramiko.transport: return self._real_result._fetchone_impl(hard_close=hard_close)
ERROR:paramiko.transport: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ERROR:paramiko.transport: File "C:\Users\*******\AppData\Local\Programs\Python\Python312\Lib\site-packages\sqlalchemy\engine\result.py", line 2259, in _fetchone_impl
ERROR:paramiko.transport: row = next(self.iterator, _NO_ROW)
ERROR:paramiko.transport: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ERROR:paramiko.transport: File "C:\Users\*******\AppData\Local\Programs\Python\Python312\Lib\site-packages\sqlalchemy\orm\loading.py", line 219, in chunks
ERROR:paramiko.transport: fetch = cursor._raw_all_rows()
ERROR:paramiko.transport: ^^^^^^^^^^^^^^^^^^^^^^
ERROR:paramiko.transport: File "C:\Users\*******\AppData\Local\Programs\Python\Python312\Lib\site-packages\sqlalchemy\engine\result.py", line 541, in _raw_all_rows
ERROR:paramiko.transport: return [make_row(row) for row in rows]
ERROR:paramiko.transport: ^^^^^^^^^^^^^
ERROR:paramiko.transport: File "lib\\sqlalchemy\\cyextension\\resultproxy.pyx", line 22, in sqlalchemy.cyextension.resultproxy.BaseRow.__init__
ERROR:paramiko.transport: File "lib\\sqlalchemy\\cyextension\\resultproxy.pyx", line 79, in sqlalchemy.cyextension.resultproxy._apply_processors
ERROR:paramiko.transport: File "C:\Users\*******\AppData\Local\Programs\Python\Python312\Lib\site-packages\sqlalchemy\sql\sqltypes.py", line 913, in process
ERROR:paramiko.transport: value = bytes(value)
ERROR:paramiko.transport: ^^^^^^^^^^^^
ERROR:paramiko.transport:TypeError: string argument without an encoding
I don't understand wher is the problem, also because, the username is stored as string in the Database, and the password i think that is handled well. Under the declaraton of the User table:
class User(Base):
__tablename__ = USERS_TABLE
id = Column(Integer, primary_key=True)
username = Column(String, unique=True, nullable=False)
password_hash = Column(LargeBinary, nullable=False)
created_at = Column(DateTime, default=datetime.now(timezone.utc))
ssh_keys = relationship("SSHKey", back_populates="user")
def set_hashed_password(self, hashed_password: bytes):
self.password_hash = hashed_password
def set_password(self, password: str):
self.password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
def check_password(self, password: str) -> bool:
return bcrypt.checkpw(password.encode(), self.password_hash)
I want to specify that i'm trying to login with a username and a password, and not with a ssh key.
UPDATE: I tried to check the real column type using:
from sqlalchemy import inspect
insp = inspect(engine)
print(list(x["type"] for x in insp.get_columns(USERS_TABLE) if x["name"] == "password_hash"))
and it print [VARCHAR()] in console.
I also check in pgAdmin4 and the type of the column is character varying.
Why if in User class I set column as LargeBinary it is a variable character? Is this the problem?
PostgreSQL tables has been created with a wrong type. The password_hashed column type was character varying, becouse the type was changed later.