Skip to content

Commit 538b323

Browse files
committed
Adds workspaces support
1 parent 71c979f commit 538b323

File tree

4 files changed

+225
-3
lines changed

4 files changed

+225
-3
lines changed

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
requests~=2.24.0
2-
PGPy~=0.5.3
2+
PGPy~=0.5.3
3+
cryptography~=37.0.4
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import json
2+
3+
from Workspace import Workspace
4+
from sendsafely import SendSafely, Package
5+
6+
# Edit these variables
7+
api_key = ""
8+
api_secret = ""
9+
packageCode = ""
10+
keyCode = ""
11+
base_url = "https://companyabc.sendsafely.com"
12+
# Make sure all directories exist on your file system
13+
upload_file = "fileToUpload.txt"
14+
download_filename = "fileDownloaded.txt"
15+
download_dir = "."
16+
workspace_label = 'New Workspace'
17+
18+
def main():
19+
sendsafely = SendSafely(base_url, api_key, api_secret)
20+
workspace = Workspace(sendsafely, packageCode=packageCode,
21+
keyCode=keyCode)
22+
23+
with open(upload_file, 'wb') as file:
24+
content = bytes("SendSafely lets you easily exchange encrypted files and information with anyone on any device.", "utf-8")
25+
file.write(content)
26+
27+
28+
response=workspace.create_directory('new_folder')
29+
print(json.dumps(response, indent=4, sort_keys=True))
30+
31+
f = workspace.encrypt_and_upload_file(upload_file, directory_name='new_folder')
32+
file_id = f["fileId"]
33+
print("Successfully encrypted and uploaded file id " + str(file_id))
34+
35+
files = workspace.list_workspace_files()
36+
print(json.dumps(files, indent=4, sort_keys=True))
37+
38+
workspace.download_workspace_file(workspace_directory='new_folder', file_name=upload_file,
39+
download_filename=download_filename)
40+
print("Successfully downloaded and decrypted file " + download_filename)
41+
42+
response = workspace.delete_workspace_file(workspace_directory='new_folder', file_name=upload_file)
43+
print("Successfully removed file")
44+
45+
if __name__ == '__main__':
46+
main()

sendsafely/Package.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,14 @@ def delete_file_from_package(self, file_id):
283283
raise DeleteFileException(details=response["message"])
284284
return response
285285

286-
def get_file_information(self, file_id):
286+
def get_file_information(self, file_id, directory_id=None):
287287
"""
288288
Return the file information for a specified fileId
289289
"""
290290
endpoint = "/package/" + self.package_id + "/file/" + file_id
291+
if directory_id:
292+
endpoint = "/package/" + self.package_id + "/directory/" + directory_id + "/file/" + file_id
293+
291294
url = self.sendsafely.BASE_URL + endpoint
292295
headers = make_headers(self.sendsafely.API_SECRET, self.sendsafely.API_KEY, endpoint)
293296
response = requests.get(url=url, headers=headers).json()
@@ -300,7 +303,7 @@ def download_and_decrypt_file(self, file_id, directory_id=None, download_directo
300303
Downloads & decrypts the specified file to the path specified
301304
"""
302305
self._block_operation_without_keycode()
303-
file_info = self.get_file_information(file_id)
306+
file_info = self.get_file_information(file_id, directory_id)
304307
if not file_name:
305308
file_name = file_info["fileName"]
306309
total = file_info["fileParts"]

sendsafely/Workspace.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import json
2+
3+
import requests
4+
5+
from exceptions import DeleteFileException
6+
from sendsafely import Package
7+
from utilities import _generate_keycode, make_headers
8+
9+
10+
class Workspace(Package):
11+
def __init__(self, sendsafely_instance, packageCode, keyCode):
12+
"""
13+
The packageCode and keyCode are obtained from the Workspace page, inside https://corp.sendsafely.com/secure/workspace/
14+
:param sendsafely_instance: The authenticated SendSafely object.
15+
"""
16+
self.sendsafely = sendsafely_instance
17+
package_variables = {}
18+
package_variables["packageId"] = packageCode
19+
package_variables["packageCode"] = packageCode
20+
package_variables["clientSecret"] = keyCode
21+
package_variables["serverSecret"] = ''
22+
super().__init__(sendsafely_instance, package_variables)
23+
self.initialized_via_keycode = True
24+
self.info = self.get_info()
25+
self.package_id = self.info["packageId"]
26+
self.server_secret = self.info["serverSecret"]
27+
self.root_directory = self.info["rootDirectoryId"]
28+
29+
def get_workspaces(self, workspace_label: str = None, row_index: int = 0, page_size: int = 100):
30+
"""
31+
:returns: The a list of workspaces in the account
32+
"""
33+
endpoint = f'/package/workspaces/'
34+
url = self.sendsafely.BASE_URL + endpoint
35+
headers = make_headers(self.sendsafely.API_SECRET, self.sendsafely.API_KEY, endpoint)
36+
response = requests.get(url, headers=headers, params={'rowIndex': row_index, 'pageSize': page_size}).json()
37+
workspace_packages = response['packages']
38+
if workspace_label is not None:
39+
workspace_packages = [x for x in workspace_packages if x['packageLabel'] == workspace_label]
40+
41+
return workspace_packages
42+
43+
def list_workspace_files(self):
44+
"""
45+
List all the files stored in the workspace
46+
:return: The JSON response
47+
"""
48+
info = self.get_info()
49+
files_list = self._extract_files(info)
50+
return files_list
51+
52+
def _extract_files(self, files_info: dict, parent_folder='/', directory_id=None):
53+
out_files = []
54+
files_list = files_info.get('files')
55+
if files_list is not None:
56+
for file in files_list:
57+
file['directory'] = parent_folder
58+
file['directoryId'] = directory_id
59+
out_files.extend(files_list)
60+
61+
directory = files_info.get('directories') \
62+
if files_info.get('subDirectories') is None else files_info.get('subDirectories')
63+
if directory is None:
64+
return []
65+
else:
66+
for subfolder in directory:
67+
name = subfolder['name']
68+
subfolder_files = self._extract_files(subfolder, parent_folder=parent_folder + name + '/',
69+
directory_id=subfolder['directoryId'])
70+
out_files.extend(subfolder_files)
71+
return out_files
72+
73+
def _get_file_directory_id(self, file_name, workspace_directory):
74+
package_info = self.get_info()
75+
files = []
76+
if workspace_directory is None or workspace_directory in ('.', '/'):
77+
files = package_info.get('files')
78+
directory_id = None
79+
else:
80+
directories = package_info.get('directories')
81+
folders_list = [x for x in workspace_directory.split('/') if len(x) > 0]
82+
for sub_folder in folders_list:
83+
for dir in directories:
84+
if dir['name'] == sub_folder:
85+
if sub_folder == folders_list[-1]:
86+
files = dir['files']
87+
directory_id = dir['directoryId']
88+
else:
89+
directories = dir['subDirectories']
90+
91+
assert len(files) > 0, f"No files found. File {file_name} not found"
92+
file_id = None
93+
for file in files:
94+
if file_name == file['fileName']:
95+
file_id = file['fileId']
96+
return file_id, directory_id
97+
98+
def download_workspace_file(self, file_name, workspace_directory: str = None,
99+
download_directory: str = ".", download_filename: str = None):
100+
"""
101+
Downloads & decrypts the specified file to the path specified.
102+
103+
:param file_name: Name of file to download
104+
:param workspace_directory: Subdirectory where the file is stored. Write folders like this /directory/sublevel1/sublevel2
105+
:param download_directory: Destination folder where the file will be downloaded
106+
107+
"""
108+
file_id, directory_id = self._get_file_directory_id(file_name, workspace_directory)
109+
if download_filename is None:
110+
download_filename = file_name
111+
self.download_and_decrypt_file(file_id,
112+
directory_id=directory_id,
113+
download_directory=download_directory,
114+
file_name=download_filename)
115+
116+
def create_directory(self, directory_name):
117+
"""
118+
Creates a new subdirectory in the workspace.
119+
:param directory_name: Nama of the new subdirectory
120+
:return: The JSON response
121+
"""
122+
body = {"directoryName": directory_name}
123+
endpoint = '/package/' + self.package_id + '/directory/' + self.root_directory + '/subdirectory/'
124+
url = self.sendsafely.BASE_URL + endpoint
125+
headers = make_headers(self.sendsafely.API_SECRET, self.sendsafely.API_KEY, endpoint,
126+
request_body=json.dumps(body))
127+
response = requests.put(url=url, headers=headers, json=body).json()
128+
return response
129+
130+
def encrypt_and_upload_file(self, filepath, directory_name=None, directory_id=None, progress_instance=None):
131+
"""
132+
Adds the passed file to the package with the specified workspace
133+
If bigger than 2621440 Bytes, split the file by 2621440 Bytes and set parts according to the amount of splits
134+
:param filepath: The path of the file we're uploading
135+
:param directory_name: The subdirectory where the file will be uploaded
136+
:return: The JSON response
137+
"""
138+
if directory_name is not None and directory_id is None:
139+
directory_id = None
140+
package_info = self.get_info()
141+
directories = package_info.get('directories')
142+
folders_list = [x for x in directory_name.split('/') if len(x) > 0]
143+
for sub_folder in folders_list:
144+
for dir in directories:
145+
if dir['name'] == sub_folder:
146+
if sub_folder == folders_list[-1]:
147+
directory_id = dir['directoryId']
148+
assert directory_id is not None, f"Error directory {directory_name} not found"
149+
150+
response = super().encrypt_and_upload_file(filepath=filepath,
151+
directory_id=directory_id,
152+
progress_instance=progress_instance)
153+
return response
154+
155+
def delete_workspace_file(self, file_name, workspace_directory):
156+
"""
157+
Deletes the file with the specified name from the specified directory
158+
"""
159+
file_id, directory_id = self._get_file_directory_id(file_name, workspace_directory)
160+
if directory_id is None:
161+
directory_id = self.root_directory
162+
163+
endpoint = '/package/' + self.package_id + '/directory/' + directory_id + '/file/' + file_id
164+
url = self.sendsafely.BASE_URL + endpoint
165+
headers = make_headers(self.sendsafely.API_SECRET, self.sendsafely.API_KEY, endpoint)
166+
try:
167+
response = requests.delete(url=url, headers=headers).json()
168+
except Exception as e:
169+
raise DeleteFileException(details=e)
170+
if response["response"] != "SUCCESS":
171+
raise DeleteFileException(details=response["message"])
172+
return response

0 commit comments

Comments
 (0)