Skip to main content
Glama
cbcoutinho

Nextcloud MCP Server

by cbcoutinho
test_oauth_file_permissions.py15.8 kB
""" Multi-user OAuth tests for Nextcloud WebDAV file permissions. Tests verify that the MCP server respects Nextcloud file sharing permissions when accessed via OAuth authentication with different users. All operations (file creation, sharing, access) are performed through MCP tools to ensure the MCP server properly supports multi-user scenarios. """ import json import logging import pytest logger = logging.getLogger(__name__) pytestmark = [pytest.mark.integration, pytest.mark.oauth] async def test_file_share_read_permissions( alice_mcp_client, bob_mcp_client, diana_mcp_client ): """ Test that shared files respect read permissions. Scenario: 1. Alice creates a file via MCP 2. Alice shares the file with Bob (read-only) via MCP 3. Bob can read the file via MCP tools 4. Diana cannot access the file (no share) """ file_path = "/alice_shared_file_read.txt" file_content = "This file is shared with Bob for reading only." # Alice creates a file logger.info(f"Alice creating file: {file_path}") result = await alice_mcp_client.call_tool( "nc_webdav_write_file", arguments={"path": file_path, "content": file_content}, ) assert not result.isError, f"Alice failed to create file: {result.content}" share_id = None try: # Alice shares the file with bob (read-only, permissions=1) logger.info("Alice sharing file with bob (read-only)...") result = await alice_mcp_client.call_tool( "nc_share_create", arguments={ "path": file_path, "share_with": "bob", "share_type": 0, "permissions": 1, }, ) assert not result.isError, f"Alice failed to create share: {result.content}" share_data = json.loads(result.content[0].text) share_id = share_data["id"] logger.info(f"Created share {share_id}") # Test: Bob reads the file via MCP logger.info("Bob attempting to read file via MCP...") result = await bob_mcp_client.call_tool( "nc_webdav_read_file", arguments={"path": file_path} ) # Bob should be able to read the shared file if not result.isError: response_data = json.loads(result.content[0].text) logger.info( f"Bob successfully read file: {response_data.get('content', '')[:50]}..." ) assert "content" in response_data assert file_content in response_data["content"] else: logger.warning(f"Bob could not read file: {result.content}") # This might fail if the share path is different for bob # Test: Diana attempts to read the file logger.info("Diana attempting to read file via MCP...") result = await diana_mcp_client.call_tool( "nc_webdav_read_file", arguments={"path": file_path} ) # Diana should NOT be able to read (no share) if result.isError: logger.info("Diana correctly denied access to unshared file") else: logger.warning("Diana unexpectedly could read unshared file") finally: # Cleanup - Alice deletes the share and file if share_id: logger.info(f"Alice deleting share {share_id}") await alice_mcp_client.call_tool( "nc_share_delete", arguments={"share_id": share_id} ) logger.info(f"Alice deleting file {file_path}") await alice_mcp_client.call_tool( "nc_webdav_delete_resource", arguments={"path": file_path} ) async def test_file_share_write_permissions( alice_mcp_client, charlie_mcp_client, bob_mcp_client ): """ Test that shared files respect write permissions. Scenario: 1. Alice creates a file via MCP 2. Alice shares the file with Charlie (edit permission) via MCP 3. Alice shares the file with Bob (read-only) via MCP 4. Charlie can edit the file via MCP tools 5. Bob cannot edit the file """ file_path = "/alice_shared_file_write.txt" file_content = "This file is shared with Charlie for editing." logger.info(f"Alice creating file: {file_path}") result = await alice_mcp_client.call_tool( "nc_webdav_write_file", arguments={"path": file_path, "content": file_content}, ) assert not result.isError, f"Alice failed to create file: {result.content}" charlie_share_id = None bob_share_id = None try: # Alice shares with Charlie (read+write, permissions=3) logger.info("Alice sharing file with Charlie (edit permission)...") result = await alice_mcp_client.call_tool( "nc_share_create", arguments={ "path": file_path, "share_with": "charlie", "share_type": 0, "permissions": 3, }, ) assert not result.isError, ( f"Alice failed to share with Charlie: {result.content}" ) charlie_share_data = json.loads(result.content[0].text) charlie_share_id = charlie_share_data["id"] logger.info(f"Created share {charlie_share_id} for Charlie") # Alice shares with Bob (read-only, permissions=1) logger.info("Alice sharing file with Bob (read-only)...") result = await alice_mcp_client.call_tool( "nc_share_create", arguments={ "path": file_path, "share_with": "bob", "share_type": 0, "permissions": 1, }, ) assert not result.isError, f"Alice failed to share with Bob: {result.content}" bob_share_data = json.loads(result.content[0].text) bob_share_id = bob_share_data["id"] logger.info(f"Created share {bob_share_id} for Bob") # Test: Charlie can write to the file logger.info("Charlie attempting to write to file via MCP...") updated_content = f"{file_content}\nCharlie added this line." result = await charlie_mcp_client.call_tool( "nc_webdav_write_file", arguments={"path": file_path, "content": updated_content}, ) if not result.isError: logger.info("Charlie successfully wrote to file") else: logger.warning(f"Charlie could not write to file: {result.content}") # Test: Bob attempts to write (should fail) logger.info("Bob attempting to write to file via MCP...") result = await bob_mcp_client.call_tool( "nc_webdav_write_file", arguments={"path": file_path, "content": "Bob tries to overwrite this."}, ) # Bob should be denied if result.isError: logger.info("Bob correctly denied write access") else: logger.warning("Bob unexpectedly succeeded in writing (permissions issue?)") finally: # Cleanup - Alice deletes shares and file if charlie_share_id: logger.info(f"Alice deleting Charlie's share {charlie_share_id}") await alice_mcp_client.call_tool( "nc_share_delete", arguments={"share_id": charlie_share_id} ) if bob_share_id: logger.info(f"Alice deleting Bob's share {bob_share_id}") await alice_mcp_client.call_tool( "nc_share_delete", arguments={"share_id": bob_share_id} ) logger.info(f"Alice deleting file {file_path}") await alice_mcp_client.call_tool( "nc_webdav_delete_resource", arguments={"path": file_path} ) async def test_file_list_permissions(alice_mcp_client, bob_mcp_client): """ Test that file listing respects share permissions. Scenario: 1. Alice creates her private file via MCP 2. Bob creates his private file via MCP 3. Alice creates a file and shares it with Bob via MCP 4. Alice can list her own files + shared files 5. Bob can list his own files + shared files from Alice """ alice_file = "/alice_private_file.txt" bob_file = "/bob_private_file.txt" shared_file = "/alice_shared_with_bob.txt" # Alice creates her private file logger.info(f"Alice creating private file: {alice_file}") result = await alice_mcp_client.call_tool( "nc_webdav_write_file", arguments={"path": alice_file, "content": "Alice's private file"}, ) assert not result.isError, f"Alice failed to create file: {result.content}" # Bob creates his private file logger.info(f"Bob creating private file: {bob_file}") result = await bob_mcp_client.call_tool( "nc_webdav_write_file", arguments={"path": bob_file, "content": "Bob's private file"}, ) assert not result.isError, f"Bob failed to create file: {result.content}" # Alice creates a shared file logger.info(f"Alice creating shared file: {shared_file}") result = await alice_mcp_client.call_tool( "nc_webdav_write_file", arguments={"path": shared_file, "content": "Shared file content"}, ) assert not result.isError, f"Alice failed to create shared file: {result.content}" share_id = None try: # Alice shares the file with Bob logger.info("Alice sharing file with Bob...") result = await alice_mcp_client.call_tool( "nc_share_create", arguments={ "path": shared_file, "share_with": "bob", "share_type": 0, "permissions": 1, }, ) assert not result.isError, f"Alice failed to create share: {result.content}" share_data = json.loads(result.content[0].text) share_id = share_data["id"] # Test: Alice lists files in root logger.info("Alice listing files via MCP...") result = await alice_mcp_client.call_tool( "nc_webdav_list_directory", arguments={"path": "/"} ) if not result.isError: response_data = json.loads(result.content[0].text) # Extract files from DirectoryListing response files = response_data.get("files", []) file_names = [f["name"] for f in files] logger.info(f"Alice can see files: {file_names}") # Alice should see her own files # Note: Exact assertions depend on test isolation else: logger.warning(f"Alice could not list files: {result.content}") # Test: Bob lists files in root logger.info("Bob listing files via MCP...") result = await bob_mcp_client.call_tool( "nc_webdav_list_directory", arguments={"path": "/"} ) if not result.isError: response_data = json.loads(result.content[0].text) # Extract files from DirectoryListing response files = response_data.get("files", []) file_names = [f["name"] for f in files] logger.info(f"Bob can see files: {file_names}") # Bob should see his own file, but not Alice's private file # Bob may see shared files in his shared folder or via different path else: logger.warning(f"Bob could not list files: {result.content}") finally: # Cleanup if share_id: logger.info(f"Alice deleting share {share_id}") await alice_mcp_client.call_tool( "nc_share_delete", arguments={"share_id": share_id} ) logger.info("Cleaning up Alice's files...") await alice_mcp_client.call_tool( "nc_webdav_delete_resource", arguments={"path": alice_file} ) await alice_mcp_client.call_tool( "nc_webdav_delete_resource", arguments={"path": shared_file} ) logger.info("Cleaning up Bob's files...") await bob_mcp_client.call_tool( "nc_webdav_delete_resource", arguments={"path": bob_file} ) async def test_folder_share_permissions(alice_mcp_client, bob_mcp_client): """ Test that folder sharing works correctly. Scenario: 1. Alice creates a folder via MCP 2. Alice creates files in the folder via MCP 3. Alice shares the folder with Bob via MCP 4. Bob can access files in the shared folder via MCP """ folder_path = "/alice_shared_folder" file_in_folder = f"{folder_path}/document.txt" file_content = "This is a document in Alice's shared folder" # Alice creates folder logger.info(f"Alice creating folder: {folder_path}") result = await alice_mcp_client.call_tool( "nc_webdav_create_directory", arguments={"path": folder_path} ) assert not result.isError, f"Alice failed to create folder: {result.content}" # Alice creates file in folder logger.info(f"Alice creating file in folder: {file_in_folder}") result = await alice_mcp_client.call_tool( "nc_webdav_write_file", arguments={"path": file_in_folder, "content": file_content}, ) assert not result.isError, f"Alice failed to create file: {result.content}" share_id = None try: # Alice shares the folder with Bob logger.info("Alice sharing folder with Bob...") result = await alice_mcp_client.call_tool( "nc_share_create", arguments={ "path": folder_path, "share_with": "bob", "share_type": 0, "permissions": 1, }, ) assert not result.isError, f"Alice failed to create share: {result.content}" share_data = json.loads(result.content[0].text) share_id = share_data["id"] logger.info(f"Created folder share {share_id}") # Test: Bob lists the shared folder logger.info("Bob attempting to list shared folder via MCP...") result = await bob_mcp_client.call_tool( "nc_webdav_list_directory", arguments={"path": folder_path} ) if not result.isError: response_data = json.loads(result.content[0].text) # Extract files from DirectoryListing response files = response_data.get("files", []) logger.info(f"Bob can see {len(files)} files in shared folder") # Bob should see the file in the shared folder file_names = [f["name"] for f in files] assert "document.txt" in file_names, ( "Bob should see the file in shared folder" ) else: logger.warning(f"Bob could not list shared folder: {result.content}") # Test: Bob reads the file in the shared folder logger.info("Bob attempting to read file in shared folder via MCP...") result = await bob_mcp_client.call_tool( "nc_webdav_read_file", arguments={"path": file_in_folder} ) if not result.isError: response_data = json.loads(result.content[0].text) logger.info("Bob successfully read file in shared folder") assert "content" in response_data assert file_content in response_data["content"] else: logger.warning( f"Bob could not read file in shared folder: {result.content}" ) finally: # Cleanup - Alice deletes the share and folder if share_id: logger.info(f"Alice deleting share {share_id}") await alice_mcp_client.call_tool( "nc_share_delete", arguments={"share_id": share_id} ) logger.info("Alice cleaning up test folder...") await alice_mcp_client.call_tool( "nc_webdav_delete_resource", arguments={"path": folder_path} )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cbcoutinho/nextcloud-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server