Init delivery2
Signed-off-by: Tiago Garcia <tiago.rgarcia@ua.pt>
This commit is contained in:
parent
e00c032df8
commit
511458f770
|
@ -1 +1,126 @@
|
|||
# SIO 2024 - Projeto 1
|
||||
|
||||
## Group
|
||||
|
||||
- João Pedro Fonseca Bastos - 113470 - joaop.bastos@ua.pt
|
||||
- Rúben da Loura Cristóvão Gomes - 113435 - rlcg@ua.pt
|
||||
- Tiago Rocha Garcia - 114184 - tiago.rgarcia@ua.pt
|
||||
|
||||
## API
|
||||
|
||||
### Usage
|
||||
|
||||
The API run as a RESTful service using the flask framework. A test API is hosted on the following URL: `https://sio.tiagorg.pt`.
|
||||
|
||||
To run, first create the virtual environment and install the dependencies:
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Then, run the API:
|
||||
|
||||
```bash
|
||||
flask run --port <port>
|
||||
```
|
||||
|
||||
*Note: The API is hosted on port 5000 by default.*
|
||||
*Note: You can also run the API in debug mode with the flag `--debug`.*
|
||||
|
||||
### Endpoints
|
||||
|
||||
The API has a list of endpoints that require different permission levels to access.
|
||||
|
||||
Mainly, it's divided into 3 categories:
|
||||
|
||||
- Anonymous: No authentication required.
|
||||
- Authenticated: Authentication required.
|
||||
- Authorized: Authentication and permissions required.
|
||||
|
||||
#### Anonymous Endpoints
|
||||
|
||||
- `GET /`: Returns a ping message.
|
||||
- `POST /reset`: Resets the database and deletes all data.
|
||||
- Required headers:
|
||||
- `Content-Type: application/json`
|
||||
- Required payload fields:
|
||||
- `password`: The reset password. *Note: The reset password is `123`.*
|
||||
- `GET /org/list`: Returns a list of organizations.
|
||||
- `POST /org/create`: Creates a new organization.
|
||||
- Required headers:
|
||||
- `Content-Type: application/json`
|
||||
- Required payload fields:
|
||||
- `name`: Organization name.
|
||||
- `username`: Manager username.
|
||||
- `full_name`: Manager full name.
|
||||
- `email`: Manager email.
|
||||
- `public_key`: Manager public key.
|
||||
- `GET /file/get/<file_handle>/content`: Downloads the file content.
|
||||
- `POST /user/login`: Logs in a user.
|
||||
- Required headers:
|
||||
- `Content-Type: application/json`
|
||||
- Required payload fields:
|
||||
- `org`: Organization name.
|
||||
- `username`: User username.
|
||||
- `password`: User password.
|
||||
- `credentials_file`: User credentials file.
|
||||
|
||||
#### Authenticated Endpoints
|
||||
|
||||
- `GET /user/list`: Returns a list of users.
|
||||
- Required headers:
|
||||
- `Authorization: token`
|
||||
- Optional payload parameters:
|
||||
- `username`: Filter by username.
|
||||
- `GET /file/list`: Returns a list of files.
|
||||
- Required headers:
|
||||
- `Authorization: token`
|
||||
- Optional payload parameters:
|
||||
- `username`: Filter by username.
|
||||
- `datetime`: Filter by datetime. The datetime filter has the following fields:
|
||||
- `value`: Epoch time in seconds.
|
||||
- `relation`: `ot` | `eq` | `nt`. (One of the following: older than, equal to, newer than)
|
||||
- `POST /user/logout`: Logs out a user.
|
||||
- Required headers:
|
||||
- `Authorization: token`
|
||||
|
||||
#### Authorized Endpoints
|
||||
|
||||
- `POST /user/create`: Creates a new user.
|
||||
- Required headers:
|
||||
- `Authorization: token`
|
||||
- `Content-Type: application/json`
|
||||
- Required payload fields:
|
||||
- `username`: User username.
|
||||
- `name`: User name.
|
||||
- `email`: User email.
|
||||
- `public_key`: User public key.
|
||||
- `POST /user/<username>/suspend`: Suspends a user.
|
||||
- Required headers:
|
||||
- `Authorization: token`
|
||||
- `POST /user/<username>/activate`: Activates a user.
|
||||
- Required headers:
|
||||
- `Authorization: token`
|
||||
- `POST /file/upload/metadata`: Uploads file metadata.
|
||||
- Required headers:
|
||||
- `Authorization: token`
|
||||
- `Content-Type: application/json`
|
||||
- Required payload fields:
|
||||
- `document_name`: Document name.
|
||||
- `key`: Document key.
|
||||
- `alg`: Document algorithm.
|
||||
- `nonce`: Document nonce.
|
||||
- `POST /file/upload/content`: Uploads file content, content-type must be `multipart/form-data`.
|
||||
- Required headers:
|
||||
- `Authorization: token`
|
||||
- `Content-Type: multipart/form-data`
|
||||
- Required payload fields:
|
||||
- `content`: Document content.
|
||||
- `GET /file/get/<document_handle>/metadata`: Downloads file metadata.
|
||||
- Required headers:
|
||||
- `Authorization: token`
|
||||
- `POST /file/delete/<document_handle>`: Deletes a file.
|
||||
- Required headers:
|
||||
- `Authorization: token
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
|
||||
from subject import main
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
#session file - username
|
||||
def activateSubject(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
parser.add_argument('username', nargs='?',default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.session or not args.username:
|
||||
logger.error("Need session file and username.")
|
||||
sys.exit(1)
|
||||
|
||||
if (not os.path.isfile(BASE_DIR + args.session)):
|
||||
logger.error("File '" + args.session + "' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
# Get session file content
|
||||
with open(BASE_DIR + args.session, 'r') as f:
|
||||
args.session = json.load(f)
|
||||
|
||||
try:
|
||||
req = requests.post(f'http://{state['REP_ADDRESS']}/user/' + args.username + '/activate', headers={'Authorization': args.session['token']})
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
activateSubject(sys.argv[1:])
|
|
@ -0,0 +1,88 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
|
||||
from subject import main
|
||||
|
||||
sys.path.append(os.path.abspath("../../"))
|
||||
from lib.symmetric_encryption import *
|
||||
from lib import digest
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# session file - document name - file
|
||||
def addDoc(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
parser.add_argument('name', nargs='?', default=None)
|
||||
parser.add_argument('file', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.session or not args.name or not args.file:
|
||||
logger.error("Need session file, document's name and file to upload.")
|
||||
sys.exit(1)
|
||||
|
||||
if (not os.path.isfile(os.path.join(BASE_DIR, args.session))):
|
||||
logger.error("File '" + args.session + "' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
if (not os.path.isfile(os.path.join(BASE_DIR, args.file))):
|
||||
logger.error("File '" + args.file + "' not found")
|
||||
sys.exit(1)
|
||||
|
||||
#Get session file content
|
||||
with open(os.path.join(BASE_DIR, args.session), 'r') as f:
|
||||
args.session = json.load(f)
|
||||
|
||||
#Encrypt content
|
||||
key, content, nonce = encrypt_file(BASE_DIR + args.file, BASE_DIR + 'encryptedText')
|
||||
|
||||
#Upload document metadata
|
||||
doc = {'document_name' : args.name, 'key' : key.hex(), 'alg' : 'AES-CFB', 'nonce' : nonce.hex() }
|
||||
|
||||
try:
|
||||
req = requests.post(f'http://{state['REP_ADDRESS']}/file/upload/metadata', json=json.dumps(doc),
|
||||
headers={'Authorization': args.session['token']})
|
||||
req.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
#Upload Document content
|
||||
file = {'file' : (BASE_DIR + args.file, content)}
|
||||
|
||||
try:
|
||||
req = requests.post(f'http://{state['REP_ADDRESS']}/file/upload/content',
|
||||
files=file,
|
||||
headers={'Authorization': args.session['token'], 'File-Checksum' : digest.get_hash(content)})
|
||||
req.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
os.remove(BASE_DIR + 'encryptedText')
|
||||
sys.exit(1)
|
||||
|
||||
#Delete temporary file
|
||||
os.remove(BASE_DIR + 'encryptedText')
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
addDoc(sys.argv[1:])
|
|
@ -0,0 +1,69 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
|
||||
from subject import main
|
||||
|
||||
sys.path.append(os.path.abspath('../../'))
|
||||
from lib import asymmetric_functs
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# session file - username - name - email - credentials file
|
||||
def addSubject(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
parser.add_argument('username', nargs='?', default=None)
|
||||
parser.add_argument('name', nargs='?', default=None)
|
||||
parser.add_argument('email', nargs='?', default=None)
|
||||
parser.add_argument('credentials', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.session or not args.username or not args.name or not args.email or not args.credentials:
|
||||
logger.error("Need session file, username, name, email and credentials file.")
|
||||
sys.exit(1)
|
||||
|
||||
if (not os.path.isfile(BASE_DIR + args.session)):
|
||||
logger.error("File '" + args.session + "' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
# Get session file content
|
||||
with open(BASE_DIR + args.session, 'r') as f:
|
||||
args.session = json.load(f)
|
||||
|
||||
if (not os.path.isfile(BASE_DIR + args.credentials)):
|
||||
logger.error("File '" + args.file + "' not found")
|
||||
sys.exit(1)
|
||||
|
||||
pubKey = asymmetric_functs.load_public_key(BASE_DIR + args.credentials)
|
||||
|
||||
subject = {'username' : args.username, 'full_name' : args.name, 'email' : args.email, 'public_key' : pubKey}
|
||||
|
||||
try:
|
||||
req = requests.post(f'http://{state['REP_ADDRESS']}/user/create', json=json.dumps(subject), headers={'Authorization': args.session['token']})
|
||||
req.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
addSubject(sys.argv[1:])
|
|
@ -0,0 +1,71 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
import argparse
|
||||
|
||||
sys.path.append(os.path.abspath("../"))
|
||||
from subject import main
|
||||
|
||||
sys.path.append(os.path.abspath('../../'))
|
||||
from lib import asymmetric_functs
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# Create organization
|
||||
# organization - username - name - email - public key file
|
||||
def createOrganization(args):
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('org', nargs='?', default=None)
|
||||
parser.add_argument('username', nargs='?', default=None)
|
||||
parser.add_argument('name', nargs='?', default=None)
|
||||
parser.add_argument('email', nargs='?', default=None)
|
||||
parser.add_argument('pubkey', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.org or not args.username or not args.name or not args.email or not args.pubkey:
|
||||
logger.error("Need organization, username, name, email and key file.")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate email
|
||||
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', args.email):
|
||||
logger.error("Need a valid email.")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate key file
|
||||
if (not os.path.isfile(BASE_DIR + args.pubkey)):
|
||||
logger.error("File '" + args.pubkey + "' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
# load public key from file
|
||||
pubKey = asymmetric_functs.load_public_key(BASE_DIR + args.pubkey)
|
||||
|
||||
input = {'name' : args.org, 'username' : args.username, 'full_name' : args.name, 'email' : args.email, 'public_key' : pubKey}
|
||||
|
||||
try:
|
||||
req = requests.post(f'http://{state['REP_ADDRESS']}/org/create', json=json.dumps(input))
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
createOrganization(sys.argv[1:])
|
|
@ -0,0 +1,64 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import json
|
||||
import requests
|
||||
|
||||
from subject import main
|
||||
|
||||
# Identity attributes
|
||||
# {'username' : '', 'full_name' : '', 'email' : '', public_key : '' }
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# org - username - password - credentials file - session file
|
||||
def createSession(args):
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('org', nargs='?', default=None)
|
||||
parser.add_argument('username', nargs='?', default=None)
|
||||
parser.add_argument('password', nargs='?', default=None)
|
||||
parser.add_argument('credentials', nargs='?', default=None)
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.org or not args.username or not args.password or not args.credentials or not args.session:
|
||||
logger.error("Need organization, username, password, credentials and session file")
|
||||
sys.exit(1)
|
||||
|
||||
if (not os.path.isfile(BASE_DIR + args.credentials)):
|
||||
logger.error("File '" + args.credentials + "' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
session = {'org' : args.org, 'username' : args.username, 'password' : args.password, 'credentials_file' : args.credentials}
|
||||
|
||||
#print( type(json.dumps(session)))
|
||||
|
||||
try:
|
||||
req = requests.post(f'http://{state['REP_ADDRESS']}/user/login', json=json.dumps(session))
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server")
|
||||
sys.exit(-1)
|
||||
|
||||
with open(BASE_DIR + args.session, 'w') as f:
|
||||
json.dump(req.json(), f)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
createSession(sys.argv[1:])
|
|
@ -0,0 +1,53 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import argparse
|
||||
import json
|
||||
|
||||
sys.path.append(os.path.abspath("../../"))
|
||||
from lib import symmetric_encryption
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
#send to stdout contents of decrypted file
|
||||
# encrypted file - encryption metadata
|
||||
def decryptFile(args):
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('encrypted', nargs='?', default=None)
|
||||
parser.add_argument('metadata', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.encrypted or not args.metadata:
|
||||
logger.error("Need encrypted file and it's metadata.")
|
||||
sys.exit(1)
|
||||
|
||||
# If first argument is not a file or not found
|
||||
if (not os.path.isfile(BASE_DIR + args.encrypted)):
|
||||
logger.error("File '" + args.encrypted + "' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
# if (not os.path.isfile(BASE_DIR + args.metadata)):
|
||||
# logger.error("File '" + args.metadata + "' not found.")
|
||||
# sys.exit(1)
|
||||
|
||||
#Decrypt file
|
||||
print(args.metadata)
|
||||
metadata = json.loads(args.metadata)
|
||||
|
||||
content = symmetric_encryption.decrypt_file(metadata['nonce'].encode(), metadata['key'].encode(), BASE_DIR + args.encrypted)
|
||||
|
||||
# Send decrypted content to stdout
|
||||
sys.stdout.write(content)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
decryptFile(sys.argv[1:])
|
|
@ -0,0 +1,60 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
|
||||
sys.path.append(os.path.abspath("../../"))
|
||||
from lib import digest
|
||||
|
||||
from subject import main
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# session file - document name
|
||||
def delDoc(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
parser.add_argument('name', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.session or not args.name:
|
||||
logger.error("Need session file and document's name.")
|
||||
sys.exit(1)
|
||||
|
||||
if (not os.path.isfile(BASE_DIR + args.session)):
|
||||
logger.error("File '" + args.session + "' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
# Get session file content
|
||||
with open(BASE_DIR + args.session, 'r') as f:
|
||||
args.session = json.load(f)
|
||||
|
||||
doc_name = digest.get_hash(bytes(args.name, encoding='utf-8'))
|
||||
|
||||
try:
|
||||
req = requests.post(f'http://{state['REP_ADDRESS']}/file/delete/' + doc_name, headers={'Authorization': args.session['token']})
|
||||
req.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
delDoc(sys.argv[1:])
|
|
@ -0,0 +1,92 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
|
||||
from subject import main
|
||||
|
||||
sys.path.append(os.path.abspath("../../"))
|
||||
from lib import digest
|
||||
from lib import symmetric_encryption
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# session file - document name - output file(optional)
|
||||
def getDoc(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
parser.add_argument('name', nargs='?', default=None)
|
||||
parser.add_argument('output', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.session or not args.name:
|
||||
logger.error("Need session file and document's name.")
|
||||
sys.exit(-1)
|
||||
|
||||
if (not os.path.isfile(BASE_DIR + args.session)):
|
||||
logger.error("File '" + args.session + "' not found.")
|
||||
sys.exit(-1)
|
||||
|
||||
# Get session file content
|
||||
with open(BASE_DIR + args.session, 'r') as f:
|
||||
args.session = json.load(f)
|
||||
|
||||
# Get Document metadata
|
||||
|
||||
doc_name = digest.get_hash(bytes(args.name, encoding='utf-8'))
|
||||
|
||||
try:
|
||||
metadata = requests.get(f'http://{state['REP_ADDRESS']}/file/get/' + doc_name + '/metadata', headers={'Authorization': args.session['token']})
|
||||
metadata.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(1)
|
||||
|
||||
metadata = metadata.json()
|
||||
|
||||
#Get file with file_handle provided by metadata
|
||||
|
||||
try:
|
||||
file = requests.get(f'http://{state['REP_ADDRESS']}/get/' + metadata['file_handle'] + '/content')
|
||||
file.raise_for_status()
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
file = file.json()
|
||||
|
||||
if not digest.get_hash(file) == metadata['file_handle']:
|
||||
logger.error("Files integrity is lost.")
|
||||
sys.exit(-1)
|
||||
|
||||
content = symmetric_encryption.decrypt_file(file)
|
||||
|
||||
if args.output:
|
||||
with open(BASE_DIR + args.output, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
else:
|
||||
sys.stdout.write(content)
|
||||
|
||||
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
getDoc(sys.argv[1:])
|
|
@ -0,0 +1,64 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
from lib import digest
|
||||
|
||||
from subject import main
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# session file - document name
|
||||
def getDocMetadata(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
parser.add_argument('name', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.session or not args.name:
|
||||
logger.error("Need session file and document's name.")
|
||||
sys.exit(1)
|
||||
|
||||
if (not os.path.isfile(BASE_DIR + args.session)):
|
||||
logger.error("File '" + args.session + "' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
# Get session file content
|
||||
with open(BASE_DIR + args.session, 'r') as f:
|
||||
args.session = json.load(f)
|
||||
|
||||
doc_name = digest.get_hash(bytes(args.name, encoding='utf-8'))
|
||||
|
||||
try:
|
||||
metadata = requests.get(f'http://{state['REP_ADDRESS']}/file/get/' + doc_name + '/metadata', headers={'Authorization': args.session['token']})
|
||||
metadata.raise_for_status()
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
metadata = metadata.json()
|
||||
|
||||
sys.stdout.write(json.dumps(metadata))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
getDocMetadata(sys.argv[1:])
|
|
@ -0,0 +1,64 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import json
|
||||
import requests
|
||||
import argparse
|
||||
|
||||
from subject import main
|
||||
|
||||
# Identity attributes
|
||||
# {'username' : '', 'full_name' : '', 'email' : '', public_key : '' }
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
#get file
|
||||
#file handle - file(optonal)
|
||||
def getFile(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('filehandle', nargs='?', default=None)
|
||||
parser.add_argument('file', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
if not args.filehandle:
|
||||
logger.error("Need a file handle.")
|
||||
sys.exit(1)
|
||||
#else:
|
||||
# if not os.path.isfile(BASE_DIR + args.filehandle):
|
||||
# logger.error("File '" + args.filehandle + "' not found" )
|
||||
# sys.exit(1)
|
||||
|
||||
#Get file
|
||||
try:
|
||||
file = requests.get(f'http://{state['REP_ADDRESS']}/file/get/' + args.filehandle + '/content')
|
||||
file.raise_for_status()
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
print(file)
|
||||
|
||||
if not args.file:
|
||||
sys.stdout.write(file.text)
|
||||
else:
|
||||
with open(BASE_DIR + args.file, "wb") as f:
|
||||
f.write(file.content)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
getFile(sys.argv[1:])
|
|
@ -0,0 +1,128 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
import datetime
|
||||
|
||||
from subject import main
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
def validDate(input):
|
||||
try:
|
||||
date = datetime.datetime.strptime(input,"%d/%m/%Y")
|
||||
except ValueError as err:
|
||||
logger.error("Date is invalid. Input format: DD/MM/YYYY")
|
||||
sys.exit(-1)
|
||||
|
||||
return date
|
||||
|
||||
#session file - [-s username] [-d nt/ot/et date]
|
||||
def list_docs(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
parser.add_argument("-s", '--username', nargs=1, help="Username")
|
||||
parser.add_argument("-dnt", '--newerThan', help="Date new than")
|
||||
parser.add_argument("-dot", '--olderThan', help="Date older than")
|
||||
parser.add_argument("-deq", '--equalTo', help="Date equal to")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check if session file is valid
|
||||
if args.session:
|
||||
if (not os.path.isfile(BASE_DIR + args.session)):
|
||||
logger.error("File '" + args.session + "' not found.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
logger.error("Need session file.")
|
||||
sys.exit(1)
|
||||
|
||||
# Get session file content
|
||||
with open(BASE_DIR + args.session, 'r') as f:
|
||||
args.session = json.load(f)
|
||||
|
||||
if args.newerThan:
|
||||
#Convert date str to datetime in seconds
|
||||
args.newerThan = validDate(args.newerThan)
|
||||
args.newerThan = int(args.newerThan.timestamp())
|
||||
|
||||
try:
|
||||
subjects = requests.get(f'http://{state['REP_ADDRESS']}/file/list',
|
||||
json=json.dumps({'username' : args.username,'datetime' : {'value' : args.newerThan, 'relation' : 'nt'}}),
|
||||
headers={'Authorization': args.session['token']})
|
||||
subjects.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
subjects = subjects.json()
|
||||
|
||||
elif args.equalTo:
|
||||
#Convert date str to datetime in seconds
|
||||
args.equalTo = validDate(args.equalTo)
|
||||
args.equalTo = int(args.equalTo.timestamp())
|
||||
|
||||
try:
|
||||
subjects = requests.get(f'http://{state['REP_ADDRESS']}/file/list',
|
||||
json=json.dumps({'username' : args.username,
|
||||
'datetime' : {'value' : args.equalTo, 'relation' : 'eq'}}),
|
||||
headers={'Authorization': args.session['token']})
|
||||
subjects.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
subjects = subjects.json()
|
||||
|
||||
elif args.olderThan:
|
||||
#Convert date str to datetime in seconds
|
||||
args.olderThan = validDate(args.olderThan)
|
||||
args.olderThan = int(args.olderThan.timestamp())
|
||||
|
||||
try:
|
||||
subjects = requests.get(f'http://{state['REP_ADDRESS']}/file/list',
|
||||
json=json.dumps({'username' : args.username,
|
||||
'datetime' : {'value' : args.olderThan, 'relation' : 'ot'}}),
|
||||
headers={'Authorization': args.session['token']})
|
||||
subjects.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
subjects = subjects.json()
|
||||
|
||||
else:
|
||||
try:
|
||||
subjects = requests.get(f'http://{state['REP_ADDRESS']}/file/list', json=json.dumps({}), headers={'Authorization': args.session['token']})
|
||||
subjects.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
subjects = subjects.json()
|
||||
|
||||
for s in subjects:
|
||||
sys.stdout.write(s['id'] + " - " + s['username'])
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
list_docs(sys.argv[1:])
|
|
@ -0,0 +1,34 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import json
|
||||
import requests
|
||||
|
||||
from subject import main
|
||||
|
||||
# Identity attributes
|
||||
# {'username' : '', 'full_name' : '', 'email' : '', public_key : '' }
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
def listOrganizations():
|
||||
try:
|
||||
orgs = requests.get(f'http://{state['REP_ADDRESS']}/org/list')
|
||||
orgs.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
for org in orgs.json():
|
||||
sys.stdout.write(str(org['id']) + " - " + org['name'])
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
listOrganizations()
|
|
@ -0,0 +1,74 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
|
||||
from subject import main
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# session file - username(optional)
|
||||
def list_subjects(args):
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
parser.add_argument('username', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check if session file is valid
|
||||
if args.session:
|
||||
if (not os.path.isfile(BASE_DIR + args.session)):
|
||||
logger.error("File '" + args.session + "' not found.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
logger.error("Need session file.")
|
||||
sys.exit(1)
|
||||
|
||||
# Get session file content
|
||||
with open(BASE_DIR + args.session, 'r') as f:
|
||||
args.session = json.load(f)
|
||||
|
||||
if args.username:
|
||||
try:
|
||||
subjects = requests.get(f'http://{state['REP_ADDRESS']}/user/list',
|
||||
json=json.dumps({'username' : args.username}),
|
||||
headers={'Authorization': args.session['token']})
|
||||
subjects.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
else:
|
||||
try:
|
||||
subjects = requests.get(f'http://{state['REP_ADDRESS']}/user/list',
|
||||
json=json.dumps({}),
|
||||
headers={'Authorization': args.session['token']})
|
||||
subjects.raise_for_status()
|
||||
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
for s,d in subjects.json().items():
|
||||
sys.stdout.write(s + " - " + d['username'] + "\n")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
list_subjects(sys.argv[1:])
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import argparse
|
||||
|
||||
sys.path.append(os.path.abspath("../../"))
|
||||
from lib import key_pair
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# Generate a key pair for a subject
|
||||
# password - file for public key, file for private key
|
||||
def generateKeyPair(args):
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('password', nargs='?', default=None)
|
||||
parser.add_argument('pubfile', nargs='?', default=None)
|
||||
parser.add_argument('privfile', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.password or not args.pubfile or not args.privfile:
|
||||
logger.error("Need password and file to store keys")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
#Generate the key pair
|
||||
key_pair.generate_key_pair(BASE_DIR + args.pubfile, BASE_DIR + args.privfile, 2048, args.password)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
generateKeyPair(sys.argv[1:])
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
|
||||
sys.path.append(os.path.abspath("../"))
|
||||
from subject import main
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# session file - username
|
||||
def suspendSubject(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('session', nargs='?', default=None)
|
||||
parser.add_argument('username', nargs='?',default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.session or not args.username:
|
||||
logger.error("Need session file and username.")
|
||||
sys.exit(1)
|
||||
|
||||
if (not os.path.isfile(BASE_DIR + args.session)):
|
||||
logger.error("File '" + args.session + "' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
# Get session file content
|
||||
with open(BASE_DIR + args.session, 'r') as f:
|
||||
args.session = json.load(f)
|
||||
|
||||
try:
|
||||
req = requests.post(f'http://{state['REP_ADDRESS']}/user/' + args.username + '/suspend', headers={'Authorization': args.session['token']})
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
suspendSubject(sys.argv[1:])
|
|
@ -0,0 +1,42 @@
|
|||
#!/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
import argparse
|
||||
|
||||
sys.path.append(os.path.abspath("../"))
|
||||
from subject import main
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
state = main(sys.argv)
|
||||
|
||||
def reset(args):
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
|
||||
parser.add_argument('password', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.password:
|
||||
logger.error("Need password")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
req = requests.post(f'http://{state['REP_ADDRESS']}/reset',json=json.dumps({'password' : args.password}))
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as errex:
|
||||
logger.error("Failed to obtain response from server.")
|
||||
sys.exit(-1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
reset(sys.argv[1:])
|
|
@ -0,0 +1,116 @@
|
|||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import json
|
||||
import requests
|
||||
|
||||
# Identity attributes
|
||||
# {'username' : '', 'full_name' : '', 'email' : '', public_key : '' }
|
||||
|
||||
logging.basicConfig(format='%(levelname)s\t- %(message)s')
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
def load_state():
|
||||
state = {}
|
||||
state_dir = os.path.join(os.path.expanduser('~'), '.sio')
|
||||
state_file = os.path.join(state_dir, 'state.json')
|
||||
|
||||
logger.debug('State folder: ' + state_dir)
|
||||
logger.debug('State file: ' + state_file)
|
||||
|
||||
if os.path.exists(state_file):
|
||||
logger.debug('Loading state')
|
||||
with open(state_file,'r') as f:
|
||||
state = json.loads(f.read())
|
||||
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
return state
|
||||
|
||||
def parse_env(state):
|
||||
if 'REP_ADDRESS' in os.environ:
|
||||
state['REP_ADDRESS'] = os.getenv('REP_ADDRESS')
|
||||
logger.debug('Setting REP_ADDRESS from Environment to: ' + state['REP_ADDRESS'])
|
||||
|
||||
if 'REP_PUB_KEY' in os.environ:
|
||||
rep_pub_key = os.getenv('REP_PUB_KEY')
|
||||
logger.debug('Loading REP_PUB_KEY fron: ' + state['REP_PUB_KEY'])
|
||||
if os.path.exists(rep_pub_key):
|
||||
with open(rep_pub_key, 'r') as f:
|
||||
state['REP_PUB_KEY'] = f.read()
|
||||
logger.debug('Loaded REP_PUB_KEY from Environment')
|
||||
return state
|
||||
|
||||
def parse_args(state):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-k", '--key', nargs=1, help="Path to the key file")
|
||||
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
|
||||
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
|
||||
parser.add_argument("-c", "--command", help="Command to execute")
|
||||
parser.add_argument('arg0', nargs='?', default=None)
|
||||
parser.add_argument('arg1', nargs='?', default=None)
|
||||
parser.add_argument('arg2', nargs='?', default=None)
|
||||
parser.add_argument('arg3', nargs='?', default=None)
|
||||
parser.add_argument('arg4', nargs='?', default=None)
|
||||
parser.add_argument('arg5', nargs='?', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.info('Setting log level to DEBUG')
|
||||
|
||||
if args.key:
|
||||
if not os.path.exists(args.key[0]) or not os.path.isfile(args.key[0]):
|
||||
logger.error(f'Key file not found or invalid: {args.key[0]}')
|
||||
sys.exit(1)
|
||||
|
||||
with open(args.key[0], 'r') as f:
|
||||
state['REP_PUB_KEY'] = f.read()
|
||||
logger.info('Overriding REP_PUB_KEY from command line')
|
||||
|
||||
if args.repo:
|
||||
state['REP_ADDRESS'] = args.repo[0]
|
||||
logger.info('Overriding REP_ADDRESS from command line')
|
||||
|
||||
#if args.command:
|
||||
# logger.info("Command: " + args.command)
|
||||
|
||||
return state#, {'command': args.command, 'arg0': args.arg0, 'arg1': args.arg1, 'arg2': args.arg2, 'arg3': args.arg3, 'arg4': args.arg4, 'arg5': args.arg5}
|
||||
|
||||
def save(state):
|
||||
state_dir = os.path.join(os.path.expanduser('~'), '.sio')
|
||||
state_file = os.path.join(state_dir, 'state.json')
|
||||
|
||||
if not os.path.exists(state_dir):
|
||||
logger.debug('Creating state folder')
|
||||
os.mkdir(state_dir)
|
||||
|
||||
with open(state_file, 'w') as f:
|
||||
f.write(json.dumps(state, indent=4))
|
||||
|
||||
#Main function for checking commands
|
||||
def main(args):
|
||||
|
||||
state = load_state()
|
||||
state = parse_env(state)
|
||||
#state, args = parse_args(state)
|
||||
state = parse_args(state)
|
||||
|
||||
if 'REP_ADDRESS' not in state:
|
||||
logger.error("Must define Repository Address")
|
||||
sys.exit(1)
|
||||
|
||||
# if 'REP_PUB_KEY' not in state:
|
||||
# logger.error("Must set the Repository Public Key")
|
||||
# sys.exit(1)
|
||||
|
||||
save(state)
|
||||
|
||||
return state
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
|
@ -0,0 +1,128 @@
|
|||
import json
|
||||
import os, subprocess, sys
|
||||
|
||||
import requests
|
||||
|
||||
DELIVERY_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
FILES_PATH = os.path.join(os.path.expanduser('~'), '.sio/')
|
||||
|
||||
# !!! database.db must be deleted/reset before running the tests !!!
|
||||
requests.post("http://localhost:5000/reset", json={"password": "123"})
|
||||
os.system(f"rm {FILES_PATH}*")
|
||||
|
||||
def test_address_set():
|
||||
# Initialize the server path on state.json
|
||||
process = subprocess.Popen(f"python3 {DELIVERY_PATH}/client/bin/subject.py -r localhost:5000 ", shell=True)
|
||||
process.wait()
|
||||
assert os.path.exists(os.path.join(FILES_PATH, 'state.json'))
|
||||
|
||||
|
||||
def test_rep_subject_credentials():
|
||||
# Test the rep_subject_create command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_subject_credentials password pub.pem priv.pem ", shell=True)
|
||||
process.wait()
|
||||
assert os.path.exists(os.path.join(FILES_PATH, 'pub.pem')) and os.path.exists(os.path.join(FILES_PATH, 'priv.pem'))
|
||||
|
||||
|
||||
def test_rep_create_org():
|
||||
# Test the rep_create_org command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_create_org org1 username name email@org.com pub.pem", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
|
||||
def test_rep_list_orgs():
|
||||
# Test the list_orgs command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_list_orgs", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
|
||||
def test_rep_create_session():
|
||||
# Test the rep_create_session command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_create_session org1 username password pub.pem session.json", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
|
||||
def test_rep_list_subjects():
|
||||
#Test the rep_list_subjects command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_list_subjects session.json", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
def test_rep_add_subject():
|
||||
# Test the rep_subject_create command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_subject_credentials password pub_extra.pem priv_extra.pem ", shell=True)
|
||||
process.wait()
|
||||
assert os.path.exists(os.path.join(FILES_PATH, 'pub_extra.pem')) and os.path.exists(os.path.join(FILES_PATH, 'priv_extra.pem'))
|
||||
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_add_subject session.json username2 name2 name2@any.com pub_extra.pem", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
|
||||
def test_rep_suspend_subject():
|
||||
# Test the rep_suspend_subject command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_suspend_subject session.json username2", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
|
||||
def test_rep_activate_subject():
|
||||
# Test the rep_activate_subject command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_activate_subject session.json username2", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
|
||||
def test_rep_add_doc():
|
||||
# Test the rep_add_doc command
|
||||
process = subprocess.Popen(f"dd if=/dev/urandom of={FILES_PATH}test.txt bs=1024 count=1000 ", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_add_doc session.json doc test.txt ", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
metadata = {}
|
||||
|
||||
def test_rep_get_doc_metadata():
|
||||
# Test the rep_get_doc_metadata command
|
||||
global metadata
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_get_doc_metadata session.json doc", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
process.wait()
|
||||
stdout, stderr = process.communicate()
|
||||
metadata = json.loads(stdout)
|
||||
assert process.returncode == 0
|
||||
|
||||
def test_rep_get_file():
|
||||
# Test the rep_get_file command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_get_file {metadata['file_handle']} file.txt ", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
def test_decrypt_file():
|
||||
# Test the rep_decrypt_file command
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_decrypt_file file.txt '{json.dumps(metadata)}'", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
def test_rep_get_doc_file():
|
||||
# Test the rep_get_doc_file
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_get_doc_file session.json doc ", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
def test_rep_delete_doc():
|
||||
# Test the rep_get_doc_file
|
||||
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_delete_doc session.json doc ", shell=True)
|
||||
process.wait()
|
||||
assert process.returncode == 0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
import sys, os
|
||||
|
||||
from cryptography.hazmat.primitives import serialization, hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
# -----------------
|
||||
# encrypt functions
|
||||
# -----------------
|
||||
|
||||
# function to generate a 256-bit symmetric key
|
||||
def generate_symmetric_key():
|
||||
return os.urandom(32)
|
||||
|
||||
|
||||
# function to encrypt data using a symmetric key
|
||||
def encrypt_symmetric(key, plain_text):
|
||||
# generate a random IV
|
||||
iv = os.urandom(16)
|
||||
|
||||
# cipher the data using AES in CFB mode
|
||||
cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=default_backend())
|
||||
encryptor = cipher.encryptor()
|
||||
ciphertext = encryptor.update(plain_text) + encryptor.finalize()
|
||||
|
||||
return iv + ciphertext
|
||||
|
||||
|
||||
# function that calls and combines the symmetric and asymmetric encryption
|
||||
def encrypt_hybrid(public_key, plaintext):
|
||||
# generate a random symmetric key
|
||||
symmetric_key = generate_symmetric_key()
|
||||
|
||||
encrypted_data = encrypt_symmetric(symmetric_key, plaintext)
|
||||
|
||||
# encrypt the symmetric key with the public key
|
||||
encrypted_symmetric_key = public_key.encrypt(
|
||||
symmetric_key,
|
||||
padding.OAEP(
|
||||
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
||||
algorithm=hashes.SHA256(),
|
||||
label=None
|
||||
)
|
||||
)
|
||||
|
||||
# combine the symmetric key and the encrypted data
|
||||
return encrypted_symmetric_key + encrypted_data
|
||||
|
||||
|
||||
# main function to encrypt the file
|
||||
def encrypt_file(public_key, original_file, encrypted_file):
|
||||
with open(original_file, 'rb') as f:
|
||||
plaintext = f.read()
|
||||
|
||||
encrypted_content = encrypt_hybrid(public_key, plaintext)
|
||||
|
||||
with open(encrypted_file, 'wb') as f:
|
||||
f.write(encrypted_content)
|
||||
|
||||
|
||||
# function to load a public key from a file
|
||||
def load_public_key(file):
|
||||
with open(file, 'rb') as key_file:
|
||||
public_key = serialization.load_pem_public_key(
|
||||
key_file.read(),
|
||||
)
|
||||
public_key_pem = public_key.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode('utf-8')
|
||||
|
||||
return public_key_pem
|
||||
|
||||
# -----------------
|
||||
# decrypt functions
|
||||
# -----------------
|
||||
|
||||
# function to decrypt data using a symmetric key
|
||||
def decrypt_symmetric(key, ciphertext):
|
||||
# generate a random IV
|
||||
iv = ciphertext[:16]
|
||||
|
||||
# decipher the data using AES in CFB mode
|
||||
ciphertext = ciphertext[16:]
|
||||
cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=default_backend())
|
||||
decryptor = cipher.decryptor()
|
||||
|
||||
return decryptor.update(ciphertext) + decryptor.finalize()
|
||||
|
||||
|
||||
# function that calls and combines the symmetric and asymmetric decryption
|
||||
def decrypt_hybrid(private_key, encrypted_data):
|
||||
# extract the encrypted symmetric key and the encrypted data (remember that the data is symmetric + asymmetric)
|
||||
encrypted_symmetric_key = encrypted_data[:private_key.key_size // 8]
|
||||
encrypted_data = encrypted_data[private_key.key_size // 8:]
|
||||
|
||||
# decrypt the symmetric key using the RSA private key
|
||||
symmetric_key = private_key.decrypt(
|
||||
encrypted_symmetric_key,
|
||||
padding.OAEP(
|
||||
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
||||
algorithm=hashes.SHA256(),
|
||||
label=None
|
||||
)
|
||||
)
|
||||
|
||||
# decrypt the data using the decrypted symmetric key
|
||||
return decrypt_symmetric(symmetric_key, encrypted_data)
|
||||
|
||||
|
||||
# main function to decrypt the file
|
||||
def decrypt_file(private_key, encrypted_file, decrypted_file=None):
|
||||
with open(encrypted_file, 'rb') as f:
|
||||
encrypted_content = f.read()
|
||||
|
||||
decrypted_content = decrypt_hybrid(private_key, encrypted_content)
|
||||
|
||||
if decrypted_file is None:
|
||||
return decrypted_content
|
||||
else:
|
||||
with open(decrypted_file, 'wb') as f:
|
||||
f.write(decrypted_content)
|
||||
|
||||
|
||||
# function to load a private key from a file
|
||||
def load_private_key(file, passwd=None):
|
||||
if passwd is not None:
|
||||
passwd = passwd.encode('utf-8')
|
||||
|
||||
try:
|
||||
with open(file, 'rb') as key_file:
|
||||
private_key = serialization.load_pem_private_key(
|
||||
key_file.read(),
|
||||
password=passwd,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ValueError("Error: The password is not valid.") from e
|
||||
|
||||
|
||||
return private_key
|
|
@ -0,0 +1,7 @@
|
|||
import cryptography.hazmat.primitives.hashes
|
||||
|
||||
|
||||
def get_hash(data):
|
||||
digest = cryptography.hazmat.primitives.hashes.Hash(cryptography.hazmat.primitives.hashes.SHA256())
|
||||
digest.update(data)
|
||||
return digest.finalize().hex()
|
|
@ -0,0 +1,34 @@
|
|||
import sys
|
||||
|
||||
from cryptography.hazmat.primitives import serialization, hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
||||
|
||||
|
||||
def generate_key_pair(pub_name, priv_name, key_size, passwd=None):
|
||||
|
||||
private_key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=int(key_size)
|
||||
)
|
||||
public_key = private_key.public_key()
|
||||
|
||||
with open(pub_name, 'wb') as f:
|
||||
f.write(public_key.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
))
|
||||
|
||||
if not passwd:
|
||||
with open(priv_name, 'wb') as f:
|
||||
f.write(private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()
|
||||
))
|
||||
else:
|
||||
with open(priv_name, 'wb') as f:
|
||||
f.write(private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.BestAvailableEncryption(passwd.encode())
|
||||
))
|
|
@ -0,0 +1,46 @@
|
|||
import os
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
# Function to encrypt a file using a salt
|
||||
def encrypt_file(input_file, output_file=None):
|
||||
key = os.urandom(16)
|
||||
iv = os.urandom(16)
|
||||
|
||||
cipher = Cipher(algorithms.AES(key), modes.CFB(iv))
|
||||
encryptor = cipher.encryptor()
|
||||
|
||||
with open(input_file, 'rb') as f:
|
||||
plaintext = f.read()
|
||||
|
||||
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
|
||||
ciphertext = iv + ciphertext
|
||||
|
||||
if output_file is not None:
|
||||
with open(output_file, 'wb') as f:
|
||||
f.write(ciphertext)
|
||||
|
||||
print(iv.hex())
|
||||
|
||||
return key, ciphertext, iv
|
||||
|
||||
|
||||
# Function to decrypt a file
|
||||
def decrypt_file(nonce, key, input_file, output_file=None):
|
||||
with open(input_file, 'rb') as f:
|
||||
encrypted_data = f.read()
|
||||
|
||||
ciphertext = encrypted_data
|
||||
|
||||
cipher = Cipher(algorithms.AES(key), modes.CFB(nonce))
|
||||
decryptor = cipher.decryptor()
|
||||
|
||||
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
||||
|
||||
if output_file is not None:
|
||||
with open(output_file, 'wb') as f:
|
||||
f.write(plaintext)
|
||||
|
||||
return plaintext.hex()
|
|
@ -0,0 +1,36 @@
|
|||
import os
|
||||
|
||||
from digest import *
|
||||
|
||||
def test_equal_string():
|
||||
string_one = "Hello, World!"
|
||||
string_two = "Hello, World!"
|
||||
|
||||
assert get_hash(bytes(string_one, 'utf-8')) == get_hash(bytes(string_two, 'utf-8'))
|
||||
|
||||
|
||||
def test_diff_string():
|
||||
string_one = "Hello, World!"
|
||||
string_two = "Hello, World"
|
||||
|
||||
assert get_hash(bytes(string_one, 'utf-8')) != get_hash(bytes(string_two, 'utf-8'))
|
||||
|
||||
|
||||
def test_equal_file():
|
||||
# create equal files
|
||||
os.system("dd if=/dev/zero of=test.txt bs=1024 count=1000 >/dev/null 2>&1")
|
||||
os.system("dd if=/dev/zero of=test2.txt bs=1024 count=1000 >/dev/null 2>&1")
|
||||
|
||||
assert get_hash(open("test.txt", "rb").read()) == get_hash(open("test2.txt", "rb").read())
|
||||
os.remove("test.txt")
|
||||
os.remove("test2.txt")
|
||||
|
||||
|
||||
def test_diff_file():
|
||||
# create different files
|
||||
os.system("dd if=/dev/urandom of=test.txt bs=1024 count=1000 >/dev/null 2>&1")
|
||||
os.system("dd if=/dev/urandom of=test2.txt bs=1024 count=1000 >/dev/null 2>&1")
|
||||
|
||||
assert get_hash(open("test.txt", "rb").read()) != get_hash(open("test2.txt", "rb").read())
|
||||
os.remove("test.txt")
|
||||
os.remove("test2.txt")
|
|
@ -0,0 +1,186 @@
|
|||
import os
|
||||
|
||||
from asymmetric_functs import *
|
||||
from key_pair import *
|
||||
|
||||
def test_encryption_no_pwd():
|
||||
# create a file to encrypt
|
||||
with open("test.txt", "w") as f:
|
||||
f.write("Hello, World!")
|
||||
|
||||
# generate a key pair
|
||||
generate_key_pair('public.pem', 'private.pem', '2048')
|
||||
|
||||
# load the public and private keys
|
||||
public_key = load_public_key("public.pem")
|
||||
private_key = load_private_key("private.pem")
|
||||
|
||||
# encrypt the file
|
||||
encrypt_file(public_key, "test.txt", "test.enc")
|
||||
|
||||
# decrypt the file
|
||||
decrypt_file(private_key, "test.enc", "test.dec")
|
||||
|
||||
# check that the decrypted file is the same as the original
|
||||
with open("test.dec", "r") as f:
|
||||
assert f.read() == "Hello, World!"
|
||||
|
||||
# cleanup
|
||||
os.remove("test.txt")
|
||||
os.remove("test.enc")
|
||||
os.remove("test.dec")
|
||||
os.remove("public.pem")
|
||||
os.remove("private.pem")
|
||||
|
||||
|
||||
def test_encryption_with_pwd():
|
||||
# create a file to encrypt
|
||||
with open("test.txt", "w") as f:
|
||||
f.write("Hello, World!")
|
||||
|
||||
# generate a key pair
|
||||
generate_key_pair('public.pem', 'private.pem', '2048', 'password')
|
||||
|
||||
# load the public and private keys
|
||||
public_key = load_public_key("public.pem")
|
||||
private_key = load_private_key("private.pem", 'password')
|
||||
|
||||
# encrypt the file
|
||||
encrypt_file(public_key, "test.txt", "test.enc")
|
||||
|
||||
# decrypt the file
|
||||
decrypt_file(private_key, "test.enc", "test.dec")
|
||||
|
||||
# check that the decrypted file is the same as the original
|
||||
with open("test.dec", "r") as f:
|
||||
assert f.read() == "Hello, World!"
|
||||
|
||||
# remove the files
|
||||
os.remove("test.txt")
|
||||
os.remove("test.enc")
|
||||
os.remove("test.dec")
|
||||
os.remove("public.pem")
|
||||
os.remove("private.pem")
|
||||
|
||||
|
||||
def test_load_private_key_wrong_pwd():
|
||||
# generate a key pair
|
||||
generate_key_pair('public.pem', 'private.pem', '2048', 'password')
|
||||
|
||||
# try to load the private key with the wrong password
|
||||
try:
|
||||
load_private_key("private.pem", 'wrong_password')
|
||||
except ValueError as e:
|
||||
assert str(e) == "Error: The password is not valid."
|
||||
|
||||
|
||||
def test_1mb_file_with_pwd():
|
||||
# create a 1mb file to encrypt
|
||||
os.system("dd if=/dev/urandom of=test.txt bs=1024 count=1000 >/dev/null 2>&1")
|
||||
|
||||
# generate a key pair
|
||||
generate_key_pair('public.pem', 'private.pem', '2048', 'password')
|
||||
|
||||
# load the public and private keys
|
||||
public_key = load_public_key("public.pem")
|
||||
private_key = load_private_key("private.pem", 'password')
|
||||
|
||||
# encrypt the file
|
||||
encrypt_file(public_key, "test.txt", "test.enc")
|
||||
|
||||
# decrypt the file
|
||||
decrypt_file(private_key, "test.enc", "test.dec")
|
||||
|
||||
# check that the decrypted file is the same as the original
|
||||
assert open("test.txt", "rb").read() == open("test.dec", "rb").read()
|
||||
|
||||
# remove the files
|
||||
os.remove("test.txt")
|
||||
os.remove("test.enc")
|
||||
os.remove("test.dec")
|
||||
os.remove("public.pem")
|
||||
os.remove("private.pem")
|
||||
|
||||
|
||||
def test_1mb_file_no_pwd():
|
||||
# create a 1mb file to encrypt
|
||||
os.system("dd if=/dev/urandom of=test.txt bs=1024 count=1000 >/dev/null 2>&1")
|
||||
|
||||
# generate a key pair
|
||||
generate_key_pair('public.pem', 'private.pem', '2048')
|
||||
|
||||
# load the public and private keys
|
||||
public_key = load_public_key("public.pem")
|
||||
private_key = load_private_key("private.pem")
|
||||
|
||||
# encrypt the file
|
||||
encrypt_file(public_key, "test.txt", "test.enc")
|
||||
|
||||
# decrypt the file
|
||||
decrypt_file(private_key, "test.enc", "test.dec")
|
||||
|
||||
# check that the decrypted file is the same as the original
|
||||
assert open("test.txt", "rb").read() == open("test.dec", "rb").read()
|
||||
|
||||
# remove the files
|
||||
os.remove("test.txt")
|
||||
os.remove("test.enc")
|
||||
os.remove("test.dec")
|
||||
os.remove("public.pem")
|
||||
os.remove("private.pem")
|
||||
|
||||
|
||||
def test_100mb_file_with_pwd():
|
||||
# create a 100mb file to encrypt
|
||||
os.system("dd if=/dev/urandom of=test.txt bs=1024 count=100000 >/dev/null 2>&1")
|
||||
|
||||
# generate a key pair
|
||||
generate_key_pair('public.pem', 'private.pem', '2048', 'password')
|
||||
|
||||
# load the public and private keys
|
||||
public_key = load_public_key("public.pem")
|
||||
private_key = load_private_key("private.pem", 'password')
|
||||
|
||||
# encrypt the file
|
||||
encrypt_file(public_key, "test.txt", "test.enc")
|
||||
|
||||
# decrypt the file
|
||||
decrypt_file(private_key, "test.enc", "test.dec")
|
||||
|
||||
# check that the decrypted file is the same as the original
|
||||
assert open("test.txt", "rb").read() == open("test.dec", "rb").read()
|
||||
|
||||
# remove the files
|
||||
os.remove("test.txt")
|
||||
os.remove("test.enc")
|
||||
os.remove("test.dec")
|
||||
os.remove("public.pem")
|
||||
os.remove("private.pem")
|
||||
|
||||
|
||||
def test_100mb_file_no_pwd():
|
||||
# create a 100mb file to encrypt
|
||||
os.system("dd if=/dev/urandom of=test.txt bs=1024 count=100000 >/dev/null 2>&1")
|
||||
|
||||
# generate a key pair
|
||||
generate_key_pair('public.pem', 'private.pem', '2048')
|
||||
|
||||
# load the public and private keys
|
||||
public_key = load_public_key("public.pem")
|
||||
private_key = load_private_key("private.pem")
|
||||
|
||||
# encrypt the file
|
||||
encrypt_file(public_key, "test.txt", "test.enc")
|
||||
|
||||
# decrypt the file
|
||||
decrypt_file(private_key, "test.enc", "test.dec")
|
||||
|
||||
# check that the decrypted file is the same as the original
|
||||
assert open("test.txt", "rb").read() == open("test.dec", "rb").read()
|
||||
|
||||
# remove the files
|
||||
os.remove("test.txt")
|
||||
os.remove("test.enc")
|
||||
os.remove("test.dec")
|
||||
os.remove("public.pem")
|
||||
os.remove("private.pem")
|
|
@ -0,0 +1,52 @@
|
|||
import os
|
||||
import sqlalchemy.exc
|
||||
from flask import Flask, request, jsonify
|
||||
from routes import org_bp, user_bp, file_bp
|
||||
from database import db_connection, db
|
||||
from models import Organization, User, File, Session
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
|
||||
app.config["SQLALCHEMY_AUTOCOMMIT"] = False
|
||||
app.config["SQLALCHEMY_AUTOFLUSH"] = False
|
||||
db_connection.init_app(app)
|
||||
with app.app_context():
|
||||
try:
|
||||
db_connection.session.query(Session).delete()
|
||||
db_connection.session.commit()
|
||||
except sqlalchemy.exc.OperationalError:
|
||||
pass
|
||||
db_connection.create_all()
|
||||
|
||||
app.register_blueprint(org_bp, url_prefix="/org")
|
||||
app.register_blueprint(user_bp, url_prefix="/user")
|
||||
app.register_blueprint(file_bp, url_prefix="/file")
|
||||
|
||||
|
||||
@app.route("/", methods=["GET"])
|
||||
def index():
|
||||
return jsonify({"message": "Welcome to the API"}), 200
|
||||
|
||||
|
||||
@app.route("/reset", methods=["POST"])
|
||||
def reset():
|
||||
password = request.json["password"]
|
||||
if password != "123":
|
||||
return jsonify({"error": "Invalid password"}), 403
|
||||
try:
|
||||
with app.app_context():
|
||||
db_connection.drop_all()
|
||||
db_connection.create_all()
|
||||
repos = os.path.join(os.path.dirname(os.path.abspath(__file__)), "repository")
|
||||
for repo in os.listdir(repos):
|
||||
if os.path.isdir(os.path.join(repos, repo)):
|
||||
for file in os.listdir(os.path.join(repos, repo)):
|
||||
os.remove(os.path.join(repos, repo, file))
|
||||
os.rmdir(os.path.join(repos, repo))
|
||||
except sqlalchemy.exc.OperationalError:
|
||||
return jsonify({"error": "Database error"}), 500
|
||||
return jsonify({"message": "Database reset"}), 200
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
|
@ -0,0 +1,4 @@
|
|||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db_connection = SQLAlchemy()
|
||||
db = db_connection.session
|
|
@ -0,0 +1,4 @@
|
|||
from .user import *
|
||||
from .org import *
|
||||
from .file import *
|
||||
from .session import *
|
|
@ -0,0 +1,32 @@
|
|||
from database import db_connection
|
||||
|
||||
|
||||
class File(db_connection.Model):
|
||||
__tablename__ = 'files'
|
||||
|
||||
id = db_connection.Column(db_connection.Integer, primary_key=True, index=True)
|
||||
file_handle = db_connection.Column(db_connection.String, unique=True, nullable=True)
|
||||
document_handle = db_connection.Column(db_connection.String, unique=True, nullable=False)
|
||||
name = db_connection.Column(db_connection.String, nullable=False)
|
||||
created_at = db_connection.Column(db_connection.Integer, nullable=False)
|
||||
key = db_connection.Column(db_connection.String, nullable=False)
|
||||
alg = db_connection.Column(db_connection.String, nullable=False)
|
||||
nonce = db_connection.Column(db_connection.String, nullable=False)
|
||||
org_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('organizations.id'), nullable=False)
|
||||
creator_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id'), nullable=False)
|
||||
org = db_connection.relationship('Organization', backref=db_connection.backref('org_files', uselist=False))
|
||||
creator = db_connection.relationship('User', backref=db_connection.backref('created_files', uselist=False))
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"file_handle": self.file_handle,
|
||||
"document_handle": self.document_handle,
|
||||
"name": self.name,
|
||||
"created_at": self.created_at,
|
||||
"key": self.key,
|
||||
"alg": self.alg,
|
||||
"nonce": self.nonce,
|
||||
"org": {"id": self.org.id, "name": self.org.name},
|
||||
"creator": {"id": self.creator.id, "username": self.creator.username}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
from database import db_connection
|
||||
|
||||
class Organization(db_connection.Model):
|
||||
__tablename__ = 'organizations'
|
||||
|
||||
id = db_connection.Column(db_connection.Integer, primary_key=True, index=True)
|
||||
name = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
|
||||
users = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
|
||||
users_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id'))
|
||||
manager = db_connection.relationship('User', backref=db_connection.backref('owned_organization', uselist=False))
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"manager": self.manager.to_dict(),
|
||||
"users": [{"id": user_id, "user_data": {
|
||||
"username": user_data["username"],
|
||||
"full_name": user_data["full_name"],
|
||||
"email": user_data["email"],
|
||||
"status": user_data["status"]
|
||||
}} for user_id, user_data in self.users.items()],
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
from database import db_connection
|
||||
|
||||
class Session(db_connection.Model):
|
||||
__tablename__ = 'sessions'
|
||||
|
||||
id = db_connection.Column(db_connection.Integer, primary_key=True)
|
||||
user_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id'))
|
||||
org_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('organizations.id'))
|
||||
token = db_connection.Column(db_connection.String(255), unique=True)
|
||||
created_at = db_connection.Column(db_connection.DateTime, server_default=db_connection.func.now())
|
||||
updated_at = db_connection.Column(db_connection.DateTime, server_default=db_connection.func.now(), server_onupdate=db_connection.func.now())
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"user_id": self.user_id,
|
||||
"org_id": self.org_id,
|
||||
"token": self.token,
|
||||
"created_at": self.created_at,
|
||||
"updated_at": self.updated_at
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
from flask_sqlalchemy import SQLAlchemy
|
||||
from database import db_connection
|
||||
|
||||
|
||||
class User(db_connection.Model):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = db_connection.Column(db_connection.Integer, primary_key=True, index=True)
|
||||
username = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
|
||||
full_name = db_connection.Column(db_connection.String, nullable=False)
|
||||
email = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
|
||||
public_keys = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
|
||||
orgs = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"username": self.username,
|
||||
"full_name": self.full_name,
|
||||
"email": self.email,
|
||||
"orgs": [{"id": org_id, "name": org_data["name"], "status": org_data["status"]} for org_id, org_data in self.orgs.items()],
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
cryptography
|
||||
flask
|
||||
flask_sqlalchemy
|
|
@ -0,0 +1,3 @@
|
|||
from .org import org_bp
|
||||
from .user import user_bp
|
||||
from .file import file_bp
|
|
@ -0,0 +1,190 @@
|
|||
import json
|
||||
|
||||
from flask import Blueprint, request, jsonify, send_file, Response
|
||||
|
||||
import utils
|
||||
from services import FileService, OrganizationService, UserService, SessionService
|
||||
|
||||
file_bp = Blueprint("file", __name__)
|
||||
upload_service = FileService()
|
||||
|
||||
|
||||
@file_bp.route("/get/<string:file_handle>/content", methods=["GET"])
|
||||
def file_get_content(file_handle: str):
|
||||
file = FileService.get_file_by_file_handle(file_handle)
|
||||
if not file:
|
||||
return jsonify({"error": "File not found"}), 404
|
||||
|
||||
file_content = FileService.get_file_content(file)
|
||||
return send_file(file_content, as_attachment=True, download_name=file.name)
|
||||
|
||||
|
||||
@file_bp.route("/get/<string:document_handle>/metadata", methods=["GET"])
|
||||
def file_get_metadata(document_handle: str):
|
||||
session_token = request.headers.get("Authorization")
|
||||
session = SessionService.validate_session(session_token)
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
|
||||
file = FileService.get_file_by_document_handle(document_handle)
|
||||
if not file:
|
||||
return jsonify({"error": "File not found"}), 404
|
||||
|
||||
return jsonify(file.to_dict())
|
||||
|
||||
|
||||
@file_bp.route("/upload/metadata", methods=["POST"])
|
||||
def file_upload_metadata():
|
||||
session_token = request.headers.get("Authorization")
|
||||
print(session_token)
|
||||
session = SessionService.validate_session(session_token)
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
data = request.json
|
||||
if type(data) is str:
|
||||
data = json.loads(data)
|
||||
if "document_name" not in data or "key" not in data or "alg" not in data or "nonce" not in data:
|
||||
return jsonify({"error": "Missing required fields"}), 400
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
|
||||
user = UserService.get_user(session.user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
file = upload_service.create_file(session.token, org, user, data["document_name"], data["key"], data["alg"], data["nonce"])
|
||||
return jsonify(file.to_dict()), 201
|
||||
|
||||
|
||||
@file_bp.route("/upload/content", methods=["POST"])
|
||||
def file_upload_content():
|
||||
session_token = request.headers.get("Authorization")
|
||||
if not session_token:
|
||||
return jsonify({"error": "No session token"}), 400
|
||||
|
||||
session = SessionService.validate_session(session_token)
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
if "file" not in request.files:
|
||||
return jsonify({"error": "No file data"}), 400
|
||||
|
||||
file = request.files.get("file")
|
||||
if file.filename == "":
|
||||
return jsonify({"error": "No file selected for uploading"}), 400
|
||||
|
||||
if not file:
|
||||
return jsonify({"error": "Invalid file data"}), 400
|
||||
|
||||
file_data = utils.get_hex_from_temp_file(file.stream)
|
||||
|
||||
file_sum = request.headers.get("File-Checksum")
|
||||
if not file_sum:
|
||||
return jsonify({"error": "No file checksum provided"}), 400
|
||||
|
||||
if file_sum != str(utils.get_hash(file_data)):
|
||||
return jsonify({"error": "File checksum mismatch"}), 400
|
||||
|
||||
file = upload_service.write_file(session_token, file_sum, file_data)
|
||||
if isinstance(file, tuple):
|
||||
return file
|
||||
|
||||
return jsonify(file.to_dict()), 201
|
||||
|
||||
|
||||
@file_bp.route("/list", methods=["GET"])
|
||||
def file_list():
|
||||
session_token = request.headers.get("Authorization")
|
||||
if not session_token:
|
||||
return jsonify({"error": "No session token"}), 400
|
||||
|
||||
session = SessionService.validate_session(session_token)
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
|
||||
data = request.json
|
||||
if type(data) is str:
|
||||
data = json.loads(data)
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
|
||||
if "datetime" in data:
|
||||
try:
|
||||
datetime_value = int(data["datetime"]["value"])
|
||||
datetime_relation = data["datetime"]["relation"]
|
||||
except ValueError:
|
||||
return jsonify({"error": "Invalid datetime value"}), 400
|
||||
|
||||
if "username" in data:
|
||||
user = UserService.get_user_by_username(data["username"])
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
files = FileService.list_files_in_org(org)
|
||||
return jsonify([file.to_dict() for file in files if file.creator_id == user.id and (
|
||||
utils.check_valid_time(file.created_at, datetime_value, datetime_relation)
|
||||
if "datetime" in data else True
|
||||
)])
|
||||
|
||||
files = FileService.list_files_in_org(org)
|
||||
return jsonify([file.to_dict() for file in files if (utils.check_valid_time(file.created_at, datetime_value, datetime_relation) if "datetime" in data else True)])
|
||||
|
||||
|
||||
@file_bp.route("/delete/<string:document_handle>", methods=["POST"])
|
||||
def file_delete(document_handle: str):
|
||||
session_token = request.headers.get("Authorization")
|
||||
if not session_token:
|
||||
return jsonify({"error": "No session token"}), 400
|
||||
|
||||
session = SessionService.validate_session(session_token)
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
|
||||
file = FileService.get_file_by_document_handle(document_handle)
|
||||
if not file:
|
||||
return jsonify({"error": "File not found"}), 404
|
||||
|
||||
if file.creator_id != session.user_id:
|
||||
return jsonify({"error": "Not authorized to delete file"}), 403
|
||||
|
||||
file = FileService.delete_file(file)
|
||||
return jsonify(file.to_dict())
|
||||
|
||||
|
||||
################################################
|
||||
|
||||
|
||||
@file_bp.route("/create_dummy", methods=["POST"])
|
||||
def file_create_dummy():
|
||||
session_token = request.headers.get("Authorization")
|
||||
if not session_token:
|
||||
return jsonify({"error": "No session token"}), 400
|
||||
|
||||
session = SessionService.validate_session(session_token)
|
||||
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
|
||||
user = UserService.get_user(session.user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
file = FileService.create_dummy_file(org, user)
|
||||
return jsonify(file.to_dict()), 201
|
|
@ -0,0 +1,32 @@
|
|||
import json
|
||||
from flask import Blueprint, request, jsonify
|
||||
from services import OrganizationService
|
||||
|
||||
org_bp = Blueprint("org", __name__)
|
||||
|
||||
@org_bp.route("/create", methods=["POST"])
|
||||
def org_create():
|
||||
data = request.json
|
||||
if type(data) is str:
|
||||
data = json.loads(data)
|
||||
if "name" not in data or "username" not in data or "full_name" not in data or "email" not in data or "public_key" not in data:
|
||||
return jsonify({"error": "Missing required fields"}), 400
|
||||
|
||||
existing_org = OrganizationService.get_organization_by_name(data["name"])
|
||||
if existing_org:
|
||||
return jsonify({"error": "Organization already exists"}), 400
|
||||
|
||||
org = OrganizationService.create_organization(
|
||||
name=data["name"],
|
||||
username=data["username"],
|
||||
full_name=data["full_name"],
|
||||
email=data["email"],
|
||||
public_key=data["public_key"]
|
||||
)
|
||||
|
||||
return jsonify(org.to_dict()), 201
|
||||
|
||||
@org_bp.route("/list", methods=["GET"])
|
||||
def org_list():
|
||||
orgs = OrganizationService.list_organizations()
|
||||
return jsonify([org.to_dict() for org in orgs])
|
|
@ -0,0 +1,152 @@
|
|||
import json
|
||||
import utils
|
||||
from flask import Blueprint, request, jsonify
|
||||
from services import UserService, SessionService, OrganizationService
|
||||
|
||||
user_bp = Blueprint("user", __name__)
|
||||
|
||||
@user_bp.route("/login", methods=["POST"])
|
||||
def user_login():
|
||||
data = request.json
|
||||
if type(data) is str:
|
||||
data = json.loads(data)
|
||||
|
||||
if "username" not in data or "org" not in data:
|
||||
return jsonify({"error": "Missing required fields"}), 400
|
||||
|
||||
user = UserService.get_user_by_username(data["username"])
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
org = OrganizationService.get_organization_by_name(data["org"])
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
|
||||
session = SessionService.create_session(user, org)
|
||||
return jsonify(session.to_dict()), 201
|
||||
|
||||
|
||||
@user_bp.route("/logout", methods=["POST"])
|
||||
def user_logout():
|
||||
session_token = request.headers.get("Authorization")
|
||||
if not session_token:
|
||||
return jsonify({"error": "No session token"}), 400
|
||||
|
||||
session = SessionService.get_session(session_token)
|
||||
if not session:
|
||||
return jsonify({"error": "Not authenticated"}), 401
|
||||
|
||||
SessionService.delete_session(session)
|
||||
return jsonify({"message": "Logged out"}), 200
|
||||
|
||||
|
||||
@user_bp.route("/list", methods=["GET"])
|
||||
def user_list():
|
||||
session_token = request.headers.get("Authorization")
|
||||
if not session_token:
|
||||
return jsonify({"error": "No session token"}), 400
|
||||
|
||||
session = SessionService.validate_session(session_token)
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
data = request.json
|
||||
if type(data) is str:
|
||||
data = json.loads(data)
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
|
||||
if "username" in data:
|
||||
user = UserService.get_user_by_username(data["username"])
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
return jsonify(user.to_dict()), 200
|
||||
|
||||
users = OrganizationService.get_users_in_organization(org)
|
||||
return jsonify(users), 200
|
||||
|
||||
|
||||
@user_bp.route("/create", methods=["POST"])
|
||||
def user_create():
|
||||
session_token = request.headers.get("Authorization")
|
||||
if not session_token:
|
||||
return jsonify({"error": "No session token"}), 400
|
||||
|
||||
session = SessionService.validate_session(session_token)
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
data = request.json
|
||||
if type(data) is str:
|
||||
data = json.loads(data)
|
||||
|
||||
if "username" not in data or "full_name" not in data or "email" not in data or "public_key" not in data:
|
||||
return jsonify({"error": "Missing required fields"}), 400
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
|
||||
if org.manager.id != session.user_id:
|
||||
return jsonify({"error": "Not authorized to create users"}), 403
|
||||
|
||||
user = UserService.get_user_by_username(data["username"])
|
||||
if not user:
|
||||
user = UserService.create_user(
|
||||
username=data["username"],
|
||||
full_name=data["full_name"],
|
||||
email=data["email"],
|
||||
public_key=data["public_key"],
|
||||
org=org
|
||||
)
|
||||
|
||||
return jsonify(user.to_dict()), 201
|
||||
|
||||
|
||||
@user_bp.route("/<string:username>/suspend", methods=["POST"])
|
||||
def user_suspend(username):
|
||||
session_token = request.headers.get("Authorization")
|
||||
if not session_token:
|
||||
return jsonify({"error": "No session token"}), 400
|
||||
|
||||
session = SessionService.validate_session(session_token)
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
if org.manager.id != session.user_id:
|
||||
return jsonify({"error": "Not authorized to suspend users"}), 403
|
||||
|
||||
user = UserService.get_user_by_username(username)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
return OrganizationService.suspend_user(org, user)
|
||||
|
||||
|
||||
@user_bp.route("/<string:username>/activate", methods=["POST"])
|
||||
def user_unsuspend(username):
|
||||
session_token = request.headers.get("Authorization")
|
||||
if not session_token:
|
||||
return jsonify({"error": "No session token"}), 400
|
||||
|
||||
session = SessionService.validate_session(session_token)
|
||||
if isinstance(session, tuple):
|
||||
return session
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
if org.manager.id != session.user_id:
|
||||
return jsonify({"error": "Not authorized to unsuspend users"}), 403
|
||||
|
||||
user = UserService.get_user_by_username(username)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
return OrganizationService.activate_user(org, user)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
from .orgs import OrganizationService
|
||||
from .users import UserService
|
||||
from .files import FileService
|
||||
from .sessions import SessionService
|
|
@ -0,0 +1,111 @@
|
|||
import os
|
||||
import io
|
||||
from datetime import datetime
|
||||
from typing import List, Type
|
||||
from flask import jsonify
|
||||
|
||||
from database import db
|
||||
from models import File, Organization, User
|
||||
from utils import get_hash
|
||||
|
||||
|
||||
class FileService:
|
||||
def __init__(self):
|
||||
self.current_requests = {}
|
||||
|
||||
def create_file(self, session_token: str, org: Organization, user: User, file_name: str, key: str, alg: str, nonce: str) -> File:
|
||||
file = File(
|
||||
file_handle = None,
|
||||
document_handle = get_hash(file_name),
|
||||
name = file_name,
|
||||
created_at = int(datetime.now().timestamp()),
|
||||
key = key,
|
||||
alg = alg,
|
||||
nonce = nonce,
|
||||
org_id = org.id,
|
||||
creator_id = user.id,
|
||||
org = org,
|
||||
creator = user
|
||||
)
|
||||
|
||||
db.add(file)
|
||||
db.commit()
|
||||
db.refresh(file)
|
||||
self.current_requests[session_token] = file.id
|
||||
|
||||
return file
|
||||
|
||||
|
||||
def write_file(self, session_token: str, file_handle: str, file_data: bytes) -> File | tuple:
|
||||
if session_token not in self.current_requests:
|
||||
return jsonify({"error": "No file upload request found"}), 400
|
||||
|
||||
file = db.query(File).filter(File.id == self.current_requests[session_token]).first()
|
||||
file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.org.name, file.document_handle)
|
||||
with open(file_path, "wb") as f:
|
||||
f.write(file_data)
|
||||
|
||||
file.file_handle = file_handle
|
||||
db.commit()
|
||||
db.refresh(file)
|
||||
|
||||
del self.current_requests[session_token]
|
||||
return file
|
||||
|
||||
|
||||
@staticmethod
|
||||
def create_dummy_file(org: Organization, user: User) -> File:
|
||||
file = File(
|
||||
file_handle = "dummy_file",
|
||||
document_handle = "org/dummy_file.txt",
|
||||
name = "dummy_file",
|
||||
created_at = int(datetime.now().timestamp()),
|
||||
org_id = 1,
|
||||
creator_id = 1,
|
||||
org = org,
|
||||
creator = user
|
||||
)
|
||||
|
||||
file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.document_handle)
|
||||
with open(file_path, "w") as f:
|
||||
f.write("Dummy file content")
|
||||
|
||||
db.add(file)
|
||||
db.commit()
|
||||
db.refresh(file)
|
||||
return file
|
||||
|
||||
@staticmethod
|
||||
def get_file(file_id: int) -> File | None:
|
||||
return db.query(File).filter(File.id == file_id).first()
|
||||
|
||||
@staticmethod
|
||||
def get_file_by_document_handle(document_handle: str) -> File | None:
|
||||
return db.query(File).filter(File.document_handle == document_handle).first()
|
||||
|
||||
@staticmethod
|
||||
def get_file_by_file_handle(file_handle: str) -> File | None:
|
||||
return db.query(File).filter(File.file_handle == file_handle).first()
|
||||
|
||||
@staticmethod
|
||||
def get_file_content(file: File) -> io.BytesIO:
|
||||
file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.org.name, file.document_handle)
|
||||
with open(file_path, "rb") as f:
|
||||
return io.BytesIO(f.read())
|
||||
|
||||
@staticmethod
|
||||
def list_files() -> list[Type[File]]:
|
||||
return db.query(File).all()
|
||||
|
||||
@staticmethod
|
||||
def list_files_in_org(org: Organization) -> list[Type[File]]:
|
||||
return db.query(File).filter(File.org_id == org.id).all()
|
||||
|
||||
@staticmethod
|
||||
def delete_file(file: File) -> File:
|
||||
file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.org.name, file.document_handle)
|
||||
os.remove(file_path)
|
||||
file.file_handle = None
|
||||
db.commit()
|
||||
db.refresh(file)
|
||||
return file
|
|
@ -0,0 +1,99 @@
|
|||
import os.path
|
||||
|
||||
from database import db
|
||||
from models import Organization, User
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
|
||||
class OrganizationService:
|
||||
@staticmethod
|
||||
def create_organization(name: str, username: str, full_name: str, email: str, public_key: str) -> Organization:
|
||||
from services import UserService
|
||||
user = UserService().get_user_by_username(username)
|
||||
if not user:
|
||||
user = UserService().create_user(username, full_name, email, public_key)
|
||||
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
repos = os.path.join(project_root, "repository")
|
||||
if not os.path.exists(os.path.join(repos, name)):
|
||||
os.mkdir(os.path.join(repos, name))
|
||||
|
||||
organization = Organization(
|
||||
name=name,
|
||||
manager=user,
|
||||
users={user.id: {
|
||||
"username": user.username,
|
||||
"full_name": user.full_name,
|
||||
"email": user.email,
|
||||
"status": "active"
|
||||
}}
|
||||
)
|
||||
|
||||
db.add(organization)
|
||||
db.commit()
|
||||
db.refresh(organization)
|
||||
|
||||
UserService().add_org_to_user(user, organization)
|
||||
UserService().add_public_key_to_user(user, organization, public_key)
|
||||
|
||||
return organization
|
||||
|
||||
@staticmethod
|
||||
def list_organizations():
|
||||
return db.query(Organization).all()
|
||||
|
||||
@staticmethod
|
||||
def get_organization(org_id: int) -> Organization | None:
|
||||
return db.query(Organization).filter(Organization.id == org_id).first()
|
||||
|
||||
@staticmethod
|
||||
def get_organization_by_name(name: str) -> Organization | None:
|
||||
return db.query(Organization).filter(Organization.name == name).first()
|
||||
|
||||
@staticmethod
|
||||
def get_users_in_organization(org: Organization) -> list[User]:
|
||||
return db.query(Organization).filter(Organization.id == org.id).first().users
|
||||
|
||||
@staticmethod
|
||||
def get_user_status(org: Organization, user_id: int) -> str:
|
||||
return db.query(Organization).filter(Organization.id == org.id).first().users[str(user_id)]["status"]
|
||||
|
||||
@staticmethod
|
||||
def add_user_to_organization(org: Organization, user: User) -> Organization:
|
||||
org.users[str(user.id)] = {
|
||||
"username": user.username,
|
||||
"full_name": user.full_name,
|
||||
"email": user.email,
|
||||
"status": "active"
|
||||
}
|
||||
flag_modified(org, "users")
|
||||
db.commit()
|
||||
db.refresh(org)
|
||||
return org
|
||||
|
||||
@staticmethod
|
||||
def suspend_user(org: Organization, user: User) -> tuple:
|
||||
if OrganizationService.get_user_status(org, user.id) != "active":
|
||||
return {"error": "User already suspended"}, 400
|
||||
|
||||
if org.manager.id == user.id:
|
||||
return {"error": "Cannot suspend manager"}, 400
|
||||
|
||||
org.users[str(user.id)]["status"] = "suspended"
|
||||
flag_modified(org, "users")
|
||||
db.commit()
|
||||
db.refresh(org)
|
||||
|
||||
return {"message": "User suspended"}, 200
|
||||
|
||||
@staticmethod
|
||||
def activate_user(org: Organization, user: User) -> tuple:
|
||||
if OrganizationService.get_user_status(org, user.id) != "suspended":
|
||||
return {"error": "User already active"}, 400
|
||||
|
||||
org.users[str(user.id)]["status"] = "active"
|
||||
flag_modified(org, "users")
|
||||
db.commit()
|
||||
db.refresh(org)
|
||||
|
||||
return {"message": "User activated"}, 200
|
|
@ -0,0 +1,48 @@
|
|||
import secrets
|
||||
from database import db
|
||||
from models import Session, User, Organization
|
||||
from flask import jsonify
|
||||
|
||||
|
||||
class SessionService:
|
||||
@staticmethod
|
||||
def create_session(user: User, org: Organization) -> Session:
|
||||
session = Session(
|
||||
user_id=user.id,
|
||||
org_id=org.id,
|
||||
token=secrets.token_hex(128)
|
||||
)
|
||||
db.add(session)
|
||||
db.commit()
|
||||
db.refresh(session)
|
||||
return session
|
||||
|
||||
@staticmethod
|
||||
def get_session(token: str) -> Session | None:
|
||||
return db.query(Session).filter(Session.token == token).first()
|
||||
|
||||
@staticmethod
|
||||
def delete_session(session: Session) -> None:
|
||||
db.delete(session)
|
||||
db.commit()
|
||||
|
||||
@staticmethod
|
||||
def validate_session(token: str) -> tuple | Session:
|
||||
from services import OrganizationService
|
||||
|
||||
if "Bearer" in token:
|
||||
token = token.split(" ")[1]
|
||||
|
||||
session = SessionService.get_session(token)
|
||||
if not session:
|
||||
return jsonify({"error": "Not authenticated"}), 401
|
||||
|
||||
org = OrganizationService.get_organization(session.org_id)
|
||||
if not org:
|
||||
return jsonify({"error": "Organization not found"}), 404
|
||||
|
||||
status = OrganizationService.get_user_status(org, session.user_id)
|
||||
if status != "active":
|
||||
return jsonify({"error": "User is not active"}), 403
|
||||
|
||||
return session
|
|
@ -0,0 +1,53 @@
|
|||
from database import db
|
||||
from models import User, Organization
|
||||
|
||||
|
||||
class UserService:
|
||||
@staticmethod
|
||||
def create_user(username: str, full_name: str, email: str, public_key: str, org: Organization = None) -> User:
|
||||
from services import OrganizationService
|
||||
user = User(
|
||||
username=username,
|
||||
full_name=full_name,
|
||||
email=email,
|
||||
public_keys={org.id: public_key} if org else {},
|
||||
orgs={org.id: {
|
||||
"name": org.name,
|
||||
"status": "active"
|
||||
}} if org else {}
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
if org:
|
||||
OrganizationService.add_user_to_organization(org, user)
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def get_user(user_id: int) -> User | None:
|
||||
return db.query(User).filter(User.id == user_id).first()
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_username(username: str) -> User | None:
|
||||
return db.query(User).filter(User.username == username).first()
|
||||
|
||||
@staticmethod
|
||||
def add_org_to_user(user: User, org: Organization) -> User:
|
||||
orgs = user.orgs.copy()
|
||||
orgs[org.id] = {
|
||||
"name": org.name,
|
||||
"status": "active"
|
||||
}
|
||||
user.orgs = orgs
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def add_public_key_to_user(user: User, org: Organization, public_key: str) -> User:
|
||||
public_keys = user.public_keys.copy()
|
||||
public_keys[org.id] = public_key
|
||||
user.public_keys = public_keys
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return user
|
|
@ -0,0 +1,106 @@
|
|||
### Reset database
|
||||
POST http://localhost:5000/reset
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"password": "123"
|
||||
}
|
||||
|
||||
### Create a new organization
|
||||
POST http://localhost:5000/org/create
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "org",
|
||||
"username": "username",
|
||||
"full_name": "Full Name",
|
||||
"email": "user@mail.com",
|
||||
"public_key": "null"
|
||||
}
|
||||
|
||||
### Login
|
||||
POST http://localhost:5000/user/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "username",
|
||||
"org": "org"
|
||||
}
|
||||
|
||||
> {% client.global.set("token", response.body["token"]) %}
|
||||
|
||||
### Upload dummy file metadata
|
||||
POST http://localhost:5000/file/upload/metadata
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"document_name": "dummy_file.txt",
|
||||
"key": "arfarf",
|
||||
"alg": "ftgtrg"
|
||||
}
|
||||
|
||||
> {% client.global.set("document_handle", response.body["document_handle"]) %}
|
||||
|
||||
|
||||
#### Upload dummy file content, through send file
|
||||
#POST http://localhost:5000/file/upload/content
|
||||
#Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
|
||||
#Authorization: {{token}}
|
||||
#
|
||||
#----WebKitFormBoundary7MA4YWxkTrZu0gW
|
||||
#Content-Disposition: form-data; name="file"; filename="dummy_file.txt"
|
||||
#Content-Type: text/plain
|
||||
#
|
||||
#This is a dummy file
|
||||
#----WebKitFormBoundary7MA4YWxkTrZu0gW
|
||||
|
||||
### List files (with no data)
|
||||
GET http://localhost:5000/file/list
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{}
|
||||
|
||||
### List files by user
|
||||
GET http://localhost:5000/file/list
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"username": "username"
|
||||
}
|
||||
|
||||
### List files by datetime
|
||||
GET http://localhost:5000/file/list
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"datetime": {
|
||||
"relation": "ot",
|
||||
"value": "1731863876"
|
||||
}
|
||||
}
|
||||
|
||||
### Get file metadata
|
||||
GET http://localhost:5000/file/get/{{document_handle}}/metadata
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
> {% client.global.set("file_handle", response.body["file_handle"]) %}
|
||||
|
||||
### Get file content
|
||||
GET http://localhost:5000/file/get/{{file_handle}}/content
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
### Delete dummy file
|
||||
POST http://localhost:5000/file/delete/{{document_handle}}
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
### Logout
|
||||
POST http://localhost:5000/user/logout
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
|
@ -0,0 +1,76 @@
|
|||
### Reset database
|
||||
POST http://localhost:5000/reset
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"password": "123"
|
||||
}
|
||||
|
||||
### Create a new organization
|
||||
POST http://localhost:5000/org/create
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "org",
|
||||
"username": "username",
|
||||
"full_name": "Full Name",
|
||||
"email": "user@mail.com",
|
||||
"public_key": "null"
|
||||
}
|
||||
|
||||
### Login
|
||||
POST http://localhost:5000/user/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "username",
|
||||
"org": "org",
|
||||
"public_key": "null"
|
||||
}
|
||||
|
||||
> {% client.global.set("token", response.body["token"]) %}
|
||||
|
||||
### List organizations
|
||||
GET http://localhost:5000/org/list
|
||||
|
||||
### Create a new user
|
||||
POST http://localhost:5000/user/create
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"username": "newuser",
|
||||
"full_name": "Full Name",
|
||||
"email": "newuser@mail.com",
|
||||
"public_key": "null2"
|
||||
}
|
||||
|
||||
### List users
|
||||
GET http://localhost:5000/user/list
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{}
|
||||
|
||||
### Suspend user
|
||||
POST http://localhost:5000/user/suspend
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"username": "newuser"
|
||||
}
|
||||
|
||||
### Activate user
|
||||
POST http://localhost:5000/user/activate
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"username": "newuser"
|
||||
}
|
||||
|
||||
### Logout
|
||||
POST http://localhost:5000/user/logout
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
|
@ -0,0 +1,2 @@
|
|||
from .checks import check_valid_time
|
||||
from .hashing import get_hash, get_hex_from_temp_file
|
|
@ -0,0 +1,9 @@
|
|||
def check_valid_time(time: int, relation_time: int, relation: str) -> bool:
|
||||
if relation == 'ot':
|
||||
return time < relation_time
|
||||
elif relation == 'nt':
|
||||
return time > relation_time
|
||||
elif relation == 'eq':
|
||||
return time == relation_time
|
||||
else:
|
||||
raise ValueError('Invalid relation: {}'.format(relation))
|
|
@ -0,0 +1,16 @@
|
|||
from tempfile import SpooledTemporaryFile
|
||||
import cryptography.hazmat.primitives.hashes
|
||||
|
||||
|
||||
def get_hash(data):
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
digest = cryptography.hazmat.primitives.hashes.Hash(cryptography.hazmat.primitives.hashes.SHA256())
|
||||
digest.update(data)
|
||||
return digest.finalize().hex()
|
||||
|
||||
|
||||
def get_hex_from_temp_file(temp_file: SpooledTemporaryFile) -> bytes:
|
||||
temp_file.seek(0)
|
||||
file_data = temp_file.read()
|
||||
return file_data
|
Loading…
Reference in New Issue