From dd99e89b5dfe1d3ca6afb8adcf63e79cce98325f Mon Sep 17 00:00:00 2001 From: Micke Nordin Date: Fri, 9 Aug 2024 15:14:00 +0200 Subject: [PATCH] Client can send and server can receive --- src/ocm_cli/__init__.py | 200 ++++++++++++++++++++++++++++------------ 1 file changed, 141 insertions(+), 59 deletions(-) diff --git a/src/ocm_cli/__init__.py b/src/ocm_cli/__init__.py index 08db120..4df3fff 100755 --- a/src/ocm_cli/__init__.py +++ b/src/ocm_cli/__init__.py @@ -7,20 +7,115 @@ import argparse import sys import yaml import uuid -from http.server import BaseHTTPRequestHandler, HTTPServer +import json +from http.server import CGIHTTPRequestHandler, HTTPServer from os import environ as env -from os import path, makedirs -from urllib.parse import urlparse, quote_plus +from os import path, makedirs, stat +from urllib.parse import urlparse import pwd import requests -class OCMHandler(BaseHTTPRequestHandler): +class OCMHandler(CGIHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.cgi_directories = ["/ocm"] + def do_GET(self): - self.send_response(200) - self.end_headers() - self.wfile.write(b"Hello, world!") + if self.path in ["/.well-known/ocm", "/ocm-provider"]: + self.send_response(200) + self.end_headers() + self.wfile.write(self.discovery()) + elif self.path == "/ocm/webdav": + self.send_response(200) + self.end_headers() + self.wfile.write(b"Hello World!") + elif self.path == "/ocm/webapp": + self.send_response(200) + self.end_headers() + self.wfile.write(b"Hello World!") + elif self.path == "/ocm/datatx": + self.send_response(200) + self.end_headers() + self.wfile.write(b"Hello World!") + else: + self.send_response(404) + self.end_headers() + self.wfile.write(b"Not Found") + + def do_POST(self): + if self.path == "/ocm/shares": + length = int(self.headers.get('content-length')) + payload = json.loads(self.rfile.read(length)) + code, reply = self.validate_shares(payload) + self.send_response(code) + self.end_headers() + self.wfile.write(json.dumps(reply).encode("utf-8")) + elif self.path == "/ocm/webdav": + self.send_response(200) + self.end_headers() + self.wfile.write(b"{}") + elif self.path == "/ocm/webapp": + self.send_response(200) + self.end_headers() + self.wfile.write(b"{}") + elif self.path == "/ocm/datatx": + self.send_response(200) + self.end_headers + else: + self.send_response(404) + self.end_headers() + self.wfile.write(b"Not Found") + + def validate_shares(self, share) -> tuple[int, dict]: + # spec: https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1shares/post return (201, {}) + share_with = share.get("shareWith") + displayname = find_displayname(share_with) + if displayname is None: + return (400, {}) + reply = { + "recipientDisplayName": displayname + } + return (201, reply) + + def discovery(self): + # spec: https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1.well-known~1ocm/get + return json.dumps({ + "enabled": True, + "apiVersion": "1.1.0", + "endPoint": "http://127.0.0.1:8080/ocm", + "provider": "ocm-cli", + "resourceTypes": [ + { + "name": "file", + "shareTypes": [ + "user" + ], + "protocols": { + "webdav": "http://127.0.0.1:8080/ocm/webdav", + "datatx": "http://127.0.0.1:8080/ocm/datatx" + } + }, + { + "name": "folder", + "shareTypes": [ + "user" + ], + "protocols": { + "webdav": "http://127.0.0.1:8080/ocm/webdav", + "datatx": "http://127.0.0.1:8080/ocm/datatx" + } + } + ], + "capabilities": [ + "/invite-accepted" + ] + }).encode('utf-8') + + +def find_displayname(user: str) -> str: + return pwd.getpwnam(user).pw_gecos.strip(',') class OCM: @@ -28,7 +123,6 @@ class OCM: host: str server: HTTPServer dir: str - legacy_mode = False config_dir: str config: dict config_file: str @@ -37,7 +131,7 @@ class OCM: def __init__(self, dir: str = env["PWD"]): self.user = env["USER"] - self.fullname = pwd.getpwnam(self.user).pw_gecos.strip(',') + self.fullname = find_displayname(self.user) self.dir = dir self.config_dir = path.join(env["HOME"], ".config/ocm") self.config_file = path.join(self.config_dir, "config.yaml") @@ -58,28 +152,29 @@ class OCM: f.write(yaml.dump(self.config)) def get_share_payload(self, provider_id: str) -> dict: - if self.legacy_mode: - return { - "name": self.config[self.user][provider_id]["name"], - "owner": self.user + '@localhost.local', - "permission": - self.config[self.user][provider_id]["permission"], - "protocol": { - "name": "webdav", - "options": { - "sharedSecret": - self.config[self.user][provider_id]["token"], - "permissions": - '{http://open-cloud-mesh.org/ns}share-permissions' - } - }, - "providerId": provider_id, - "resourceType": "file", - # self.config[self.user][provider_id]["resourceType"], - "shareType": self.config[self.user][provider_id]["shareType"], - "shareWith": self.config[self.user][provider_id]["shareWith"], - } - return {} + name = self.config[self.user][provider_id]["name"], + name = name[0] + + owner = pwd.getpwuid(stat(name).st_uid).pw_name + return { + "shareWith": self.config[self.user][provider_id]["shareWith"], + "name": name, + "providerId": provider_id, + "owner": owner, + "sender": self.user, + "senderDisplayName": self.fullname, + "shareType": self.config[self.user][provider_id]["shareType"], + "resourceType": self.config[self.user][provider_id]["resourceType"], + "protocol": { + "name": "webdav", + "webdav": { + "sharedSecret": + self.config[self.user][provider_id]["token"], + "permissions": self.config[self.user][provider_id]["permissions"], + "uri": f'/{self.user}/{provider_id}' + } + }, + } def main(self): parser = argparse.ArgumentParser() @@ -110,11 +205,17 @@ class OCM: def receive(self): self.server.serve_forever() - def create_share(self, receiver, resource_type, name, share_endpoint) -> None: + def create_share( + self, + receiver, + resource_type, + name, + share_endpoint + ) -> None: provider_id = uuid.uuid4().hex self.config[self.user][provider_id] = { "name": name, - "permission": "read", + "permissions": ["read"], "resourceType": resource_type, "shareType": "user", "shareWith": receiver, @@ -122,7 +223,6 @@ class OCM: } self.write_config() payload = self.get_share_payload(provider_id) - print(payload) headers = { 'Content-Type': 'application/json', @@ -132,49 +232,31 @@ class OCM: share_endpoint, headers=headers, json=payload) - if share_req.status_code != 200: + if share_req.status_code != 201: print("Error: " + str(share_req.status_code)) print(share_req.text) sys.exit(1) share_data = share_req.json() - print(share_data) def share(self, file, directory, to): uparse = urlparse(to) - cloudid = f'{uparse.username}@{uparse.hostname}' - if not cloudid: - print("Username not specified") - sys.exit(1) - else: - print("Sharing with " + cloudid) + sharee = uparse.username + print("Sharing with " + sharee) discovery_endpoint = uparse.scheme + "://" + \ - uparse.hostname + "/.well-known/ocm" + uparse.hostname + f":{uparse.port}/.well-known/ocm" req = requests.get(discovery_endpoint) if req.status_code != 200: print("Could not find ocm-provider at " + discovery_endpoint) - print("Trying again with legacy endpoint") - # Fallback on legacy endpoint - discovery_endpoint = uparse.scheme + \ - '://' + uparse.hostname + "/ocm-provider" - req = requests.get(discovery_endpoint) - if req.status_code != 200: - print("Could not find ocm-provider at " + discovery_endpoint) - sys.exit(1) + sys.exit(1) data = req.json() share_endpoint = data['endPoint'] + "/shares" + print(share_endpoint) enabled = data['enabled'] if not enabled: print("OCM is not enabled on this server") sys.exit(1) - api_version = data['apiVersion'] resource_types = data['resourceTypes'] - if api_version == '1.0-proposal1': - self.legacy_mode = True - # provider = uparse.hostname - # else: - # provider = data['provider'] - file_or_folder = file resource_type = "file" if directory: @@ -191,7 +273,7 @@ class OCM: sys.exit(1) self.create_share( - cloudid, resource_type, file_or_folder, share_endpoint) + sharee, resource_type, file_or_folder, share_endpoint) if __name__ == "__main__":