Skip to content

Commit f2d759e

Browse files
Merge pull request #443 from OP-TED/feature/TED-1247
add private key auth method support for SFTP
2 parents 639d940 + d64ac9d commit f2d759e

5 files changed

Lines changed: 60 additions & 24 deletions

File tree

requirements.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ agraph-python==101.0.10
2020
decorator~=5.1.1
2121
urllib3[secure]
2222
semantic-version==2.10.0
23-
pysftp~=0.2.9
23+
paramiko~=3.0.0
2424
ordered-set~=4.0.2
2525
json2html~=1.3.0
2626
minio~=7.1.1
27-
certifi
28-
networkx~=2.8.8
27+
certifi~=2022.12.7
2928
shortuuid~=1.0.11

ted_sws/__init__.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import json
1313
import os
1414
import pathlib
15-
15+
import base64
1616
import dotenv
1717

1818
from ted_sws.core.adapters.config_resolver import EnvConfigResolver, AirflowAndEnvConfigResolver, env_property
@@ -201,6 +201,21 @@ def SFTP_PUBLISH_PASSWORD(self, config_value: str) -> str:
201201
def SFTP_PUBLISH_PATH(self, config_value: str) -> str:
202202
return config_value
203203

204+
@env_property(config_resolver_class=AirflowAndEnvConfigResolver)
205+
def SFTP_PRIVATE_KEY_BASE64(self, config_value: str) -> str:
206+
return config_value
207+
208+
@env_property(config_resolver_class=AirflowAndEnvConfigResolver)
209+
def SFTP_PRIVATE_KEY_PASSPHRASE(self, config_value: str) -> str:
210+
return config_value
211+
212+
@property
213+
def SFTP_PRIVATE_KEY(self) -> str:
214+
sftp_private_key_base64 = self.SFTP_PRIVATE_KEY_BASE64
215+
if sftp_private_key_base64:
216+
sftp_private_key_base64 = base64.b64decode(str(sftp_private_key_base64)).decode(encoding="utf-8")
217+
return sftp_private_key_base64
218+
204219

205220
class SPARQLConfig:
206221

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import pysftp
1+
import io
22

3+
import paramiko
34
from ted_sws import config
45
from ted_sws.notice_publisher.adapters.sftp_publisher_abc import SFTPPublisherABC
56

@@ -9,36 +10,43 @@ class SFTPPublisher(SFTPPublisherABC):
910
1011
"""
1112

12-
def __init__(self, hostname: str = None, username: str = None, password: str = None, port: int = None):
13+
def __init__(self, hostname: str = None, username: str = None, password: str = None, port: int = None,
14+
private_key: str = None, private_key_passphrase: str = None):
1315
"""Constructor Method"""
1416
self.hostname = hostname if hostname else config.SFTP_PUBLISH_HOST
1517
self.username = username if username else config.SFTP_PUBLISH_USER
1618
self.password = password if password else config.SFTP_PUBLISH_PASSWORD
1719
self.port = port if port else config.SFTP_PUBLISH_PORT
1820
self.connection = None
1921
self.is_connected = False
22+
self.private_key = None
23+
self.private_key_passphrase = private_key_passphrase if private_key_passphrase else config.SFTP_PRIVATE_KEY_PASSPHRASE
24+
private_key = private_key if private_key else config.SFTP_PRIVATE_KEY
25+
if private_key:
26+
self.private_key = paramiko.RSAKey.from_private_key(io.StringIO(private_key),
27+
password=self.private_key_passphrase)
28+
29+
self._sftp = None
30+
self._ssh = None
2031

2132
def connect(self):
2233
"""Connects to the sftp server and returns the sftp connection object"""
2334

24-
cnopts = pysftp.CnOpts()
25-
# TODO: to be checked/removed when SSL will be setup
26-
cnopts.hostkeys = None
27-
# Get the sftp connection object
28-
self.connection = pysftp.Connection(
29-
host=self.hostname,
30-
username=self.username,
31-
password=self.password,
32-
port=self.port,
33-
cnopts=cnopts
34-
)
35-
35+
ssh_client = paramiko.SSHClient()
36+
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
37+
ssh_client.connect(self.hostname, username=self.username,
38+
pkey=self.private_key, port=self.port)
39+
self._ssh = ssh_client
40+
self._sftp = ssh_client.open_sftp()
3641
self.is_connected = True
3742

3843
def disconnect(self):
3944
"""Closes the sftp connection"""
4045
if self.is_connected:
41-
self.connection.close()
46+
self._sftp.close()
47+
self._ssh.close()
48+
self._sftp = None
49+
self._ssh = None
4250
self.is_connected = False
4351

4452
def __del__(self):
@@ -48,12 +56,25 @@ def publish(self, source_path: str, remote_path: str) -> bool:
4856
"""
4957
Publish file_content to the sftp server remote path.
5058
"""
51-
self.connection.put(source_path, remote_path)
59+
self._sftp.put(source_path, remote_path)
5260
return True
5361

5462
def remove(self, remote_path: str) -> bool:
5563
"""
5664
5765
"""
58-
self.connection.unlink(remote_path)
66+
self._sftp.remove(remote_path)
5967
return True
68+
69+
def exists(self, remote_path: str) -> bool:
70+
"""
71+
Check if remote_path exists.
72+
:param remote_path:
73+
:return:
74+
"""
75+
if self.is_connected:
76+
try:
77+
self._sftp.stat(remote_path)
78+
except IOError:
79+
return False
80+
return True

tests/e2e/notice_publisher/adapters/test_notice_publisher.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ def test_sftp_notice_publisher():
3232
with pytest.raises(Exception):
3333
sftp_publisher.publish(source_file.name, None)
3434

35+
assert not sftp_publisher.exists(remote_path)
3536
published = sftp_publisher.publish(source_file.name, remote_path)
3637
assert published
37-
assert sftp_publisher.connection.exists(remote_path)
38+
assert sftp_publisher.exists(remote_path)
3839
sftp_publisher.remove(remote_path)
39-
assert not sftp_publisher.connection.exists(remote_path)
40+
assert not sftp_publisher.exists(remote_path)
4041

4142
sftp_publisher.disconnect()
4243

tests/features/notice_publisher/test_notice_publisher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def the_mets_package_available_in_a_shared_sftp_drive(published_notice: Notice,
8181
publisher: SFTPPublisher = SFTPPublisher()
8282
remote_notice_path = f"{sftp_remote_folder_path}/{published_notice.ted_id}.zip"
8383
publisher.connect()
84-
assert publisher.connection.exists(remotepath=remote_notice_path)
84+
assert publisher.exists(remote_path=remote_notice_path)
8585
publisher.disconnect()
8686

8787

0 commit comments

Comments
 (0)