diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a970f4619caf16f52750af0eb857c431359d0952 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# PyFed + +A robust, type-safe ActivityPub federation library for Python. + +## Features + +- Complete ActivityPub protocol implementation +- Type-safe models using Pydantic +- Flexible storage backends (SQL, Redis) +- Comprehensive security features +- Framework-agnostic design +- Async-first architecture + +## Installation + +```bash +pip install pyfed +``` + +#### Quick Start +```python +from pyfed.federation import FederationProtocol +from pyfed.models import APActivity, APActor + +# Initialize federation handler +federation = FederationProtocol() + +# Handle incoming activities +@federation.on_activity("Create") +async def handle_create(activity: APActivity): + await federation.store_object(activity.object) +``` + +#### Documentation +- [Getting Started](docs/getting-started.md) +- [Configuration Guide](docs/configuration.md) +- [Architecture Overview](docs/architecture.md) +- [Security Guide](docs/security.md) +- [API Reference](docs/api/) + +#### Requirements +- Python 3.9+ +- PostgreSQL (recommended) or SQLite +- Redis (optional, for caching) \ No newline at end of file diff --git a/documentation/api/models.md b/documentation/api/models.md new file mode 100644 index 0000000000000000000000000000000000000000..c0f0ee03c6708eabd679b897015cf93b100a0820 --- /dev/null +++ b/documentation/api/models.md @@ -0,0 +1,263 @@ +# PyFed Models API Reference + +## Overview +The models module provides Pydantic-based data models for ActivityPub objects, actors, activities, and collections. These models ensure type safety and validation while maintaining compliance with the ActivityPub protocol. + +## Core Concepts + +### Model Hierarchy +1. `ActivityPubBase`: Base class for all ActivityPub models +2. [APObject](cci:2://file:///Users/kene/Desktop/funkwhale-pyfed/main-lib/lastest-lib/pres_mode/pyfed/src/pyfed/models/objects.py:15:0-113:27): Base class for all ActivityPub objects +3. [APActor](cci:2://file:///Users/kene/Desktop/funkwhale-pyfed/main-lib/lastest-lib/pres_mode/pyfed/src/pyfed/models/actors.py:28:0-63:54): Base class for all ActivityPub actors +4. [APActivity](cci:2://file:///Users/kene/Desktop/funkwhale-pyfed/main-lib/lastest-lib/pres_mode/pyfed/src/pyfed/models/activities.py:13:0-41:112): Base class for all ActivityPub activities +5. [APCollection](cci:2://file:///Users/kene/Desktop/funkwhale-pyfed/main-lib/lastest-lib/pres_mode/pyfed/src/pyfed/models/collections.py:17:0-43:133): Base class for ActivityPub collections + +### Common Features +- Type validation via Pydantic +- URL validation for endpoints +- JSON-LD context handling +- Federation-ready serialization + +## Base Models + +### ActivityPubBase +The foundation class for all ActivityPub models. + +```python +class ActivityPubBase(BaseModel): + """Base model for all ActivityPub objects.""" + + context: Union[str, List[str]] = Field( + default="https://www.w3.org/ns/activitystreams", + alias="@context" + ) + id: Optional[HttpUrl] = None + type: str + +APObject +Base class for all ActivityPub objects. +class APObject(ActivityPubBase): + """Base class for ActivityPub objects.""" + + attachment: Optional[List[Union[APObject, APLink]]] = None + attributedTo: Optional[Union[str, List[str]]] = None + audience: Optional[List[str]] = None + content: Optional[str] = None + name: Optional[str] = None + published: Optional[datetime] = None + updated: Optional[datetime] = None + to: Optional[List[str]] = None + cc: Optional[List[str]] = None + tag: Optional[List[Dict[str, Any]]] = None + + def is_public(self) -> bool: + """Check if the object is public.""" + public_address = "https://www.w3.org/ns/activitystreams#Public" + return ( + (isinstance(self.to, list) and public_address in self.to) or + (isinstance(self.cc, list) and public_address in self.cc) + ) + + def get_mentions(self) -> List[str]: + """Extract mentions from the object's tags.""" + if not self.tag: + return [] + return [ + tag['href'] + for tag in self.tag + if isinstance(tag, dict) and tag.get('type') == "Mention" + ] + +Actor Models +APActor +Base class for all ActivityPub actors. +class APActor(APObject): + """Base class for ActivityPub actors.""" + + inbox: HttpUrl + outbox: HttpUrl + following: Optional[HttpUrl] = None + followers: Optional[HttpUrl] = None + liked: Optional[HttpUrl] = None + + @field_validator('inbox', 'outbox', 'following', 'followers', 'liked') + @classmethod + def validate_urls(cls, v): + """Validate actor endpoints are valid URLs.""" + try: + return HttpUrl(v) + except ValueError: + raise InvalidURLError(f"Invalid URL: {v}") + +Actor Types +APPerson +Represents a person in the federation. +class APPerson(APActor): + """Model for Person actors.""" + type: Literal["Person"] + +APGroup +Represents a group in the federation. +class APGroup(APActor): + """Model for Group actors.""" + type: Literal["Group"] + +APService +Represents a service in the federation. +class APService(APActor): + """Model for Service actors.""" + type: Literal["Service"] + +APOrganization +Represents an organization in the federation. +class APOrganization(APActor): + """Model for Organization actors.""" + type: Literal["Organization"] + +APApplication +Represents an application in the federation. +class APApplication(APActor): + """Model for Application actors.""" + type: Literal["Application"] + +Object Types +Content Objects +APNote +Represents a note or status update. +class APNote(APObject): + """Model for Note objects.""" + type: Literal["Note"] + content: str + +APArticle +Represents a full article or blog post. +class APArticle(APObject): + """Model for Article objects.""" + type: Literal["Article"] + content: str + summary: Optional[str] = None + +Media Objects +APImage +Represents an image object. +class APImage(APObject): + """Model for Image objects.""" + type: Literal["Image"] + url: Union[str, List[str]] + +APVideo +Represents a video object. +class APVideo(APObject): + """Model for Video objects.""" + type: Literal["Video"] + url: Union[str, List[str]] + +Collection Models +APCollection +Base class for collections. +class APCollection(APObject): + """Base class for ActivityPub collections.""" + + totalItems: Optional[int] = None + items: Optional[List[Union[APObject, Dict[str, Any]]]] = None + current: Optional[Union[str, "APCollectionPage"]] = None + first: Optional[Union[str, "APCollectionPage"]] = None + last: Optional[Union[str, "APCollectionPage"]] = None + +APOrderedCollection +Collection with ordered items. +class APOrderedCollection(APCollection): + """Model for ordered collections.""" + + type: Literal["OrderedCollection"] + orderedItems: Optional[List[Union[APObject, Dict[str, Any]]]] = None + +Activity Models +APActivity +Base class for all activities. +class APActivity(APObject): + """Base class for ActivityPub activities.""" + + actor: Union[str, APActor] + object: Optional[Union[str, APObject, Dict[str, Any]]] = None + +Common Activities +APCreate +Represents object creation.class APCreate(APActivity): + """Model for Create activities.""" + type: Literal["Create"] + object: Union[str, APObject, Dict[str, Any]] + +APUpdate +Represents object updates. +class APUpdate(APActivity): + """Model for Update activities.""" + type: Literal["Update"] + object: Union[str, APObject, Dict[str, Any]] + +APDelete +Represents object deletion. +class APDelete(APActivity): + """Model for Delete activities.""" + type: Literal["Delete"] + object: Union[str, APObject, Dict[str, Any]] + +Usage Examples +Creating an Actor +person = APPerson( + id="https://example.com/users/alice", + name="Alice", + inbox="https://example.com/users/alice/inbox", + outbox="https://example.com/users/alice/outbox", + following="https://example.com/users/alice/following", + followers="https://example.com/users/alice/followers" +) + +Creating a Note +note = APNote( + id="https://example.com/notes/1", + content="Hello, Federation!", + attributedTo="https://example.com/users/alice", + to=["https://www.w3.org/ns/activitystreams#Public"], + cc=["https://example.com/users/alice/followers"] +) + +Creating an Activity +create = APCreate( + id="https://example.com/activities/1", + actor="https://example.com/users/alice", + object=note, + to=note.to, + cc=note.cc +) + +Error Handling +Common Exceptions +class InvalidURLError(ValueError): + """Raised when a URL is invalid.""" + pass + +class ValidationError(Exception): + """Raised when model validation fails.""" + pass + +Validation Best Practices +Always validate URLs using the provided validators +Use type hints and Pydantic validators +Handle validation errors appropriately +Log validation failures for debugging + +Configuration +Model Configuration +class Config: + """Pydantic model configuration.""" + + allow_population_by_field_name = True + json_encoders = { + datetime: lambda v: v.isoformat() + "Z", + HttpUrl: str + } + +##### See Also +- [Security Guide](security.md) +- [ActivityPub Specification](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-activitypub) \ No newline at end of file diff --git a/documentation/architecture.md b/documentation/architecture.md new file mode 100644 index 0000000000000000000000000000000000000000..979043c0263d82cac8a5664310d858809987e131 --- /dev/null +++ b/documentation/architecture.md @@ -0,0 +1,290 @@ +# PyFed Architecture Guide + +## Overview +PyFed is a modular, type-safe ActivityPub federation library designed for Python applications. It provides a comprehensive implementation of the ActivityPub protocol with a focus on security, extensibility, and developer experience. + +## Core Architecture + +### Component Layers + +┌─────────────────────────────────────────────────────┐ +│ Application Layer │ +│ (Your ActivityPub Application) │ +├─────────────────────────────────────────────────────┤ +│ PyFed Library │ +├───────────┬───────────┬──────────────┬─────────────┤ +│ Servers │ Models │ Handlers │ Federation │ +├───────────┼───────────┼──────────────┼─────────────┤ +│ Security │ Storage │ Serializers │ Cache │ +└───────────┴───────────┴──────────────┴─────────────┘ + +### Core Components + +1. **Servers** + - Inbox/Outbox handlers + - Request processing + - Activity routing + - Federation endpoints + +2. **Models** + - ActivityPub objects + - Actors and activities + - Collections + - Type-safe validation + +3. **Handlers** + - Activity processing + - Content validation + - Federation logic + - State management + +4. **Federation** + - Protocol implementation + - Activity delivery + - Remote interaction + - Federation rules + +5. **Security** + - HTTP signatures + - Request validation + - Permission checks + - SSL/TLS handling + +6. **Storage** + - Activity persistence + - Object storage + - Collection management + - Query interfaces + +7. **Serializers** + - JSON-LD handling + - Data transformation + - Schema validation + - Format conversion + +8. **Cache** + - Object caching + - Request caching + - Performance optimization + - Cache invalidation + +## Data Flow + +### Incoming Activity Flow + +- Remote Server → Inbox Handler: Remote server sends an ActivityPub activity to your inbox endpoint +- Inbox Handler → Signature Check: Validates HTTP signatures using the sender's public key +- Signature Check → Activity Parser: Parses and validates the JSON activity data +- Activity Parser → Activity Handler: Routes to appropriate handler based on activity type +- Activity Handler → Storage: Stores the validated activity +- Storage → Activity Handler: Confirms storage completion +- Activity Handler → Response: Generates appropriate response (Accept/Reject) + + +### Outgoing Activity Flow + +- Local Actor → Outbox Handler: Local user/system initiates an activity +- Outbox Handler → Activity Creator: Creates the ActivityPub JSON object +- Activity Creator → Activity Signer: Signs the activity with the local actor's private key +- Activity Signer → Storage: Stores the signed activity +- Storage → Delivery Queue: Queues the activity for delivery +- Delivery Queue → Activity Sender: Manages delivery attempts and retries +- Activity Sender → Remote Server: Delivers the activity to recipient inboxes + + +## Key Design Principles + +### 1. Type Safety +- Comprehensive type hints +- Pydantic model validation +- Runtime type checking +- Schema enforcement + +### 2. Modularity +- Independent components +- Clear interfaces +- Pluggable backends +- Extensible design + +### 3. Security First +- Signature verification +- Request validation +- Permission checks +- Secure defaults + +### 4. Performance +- Efficient caching +- Async operations +- Batch processing +- Resource optimization + +## Component Details + +### Server Components +```python +# Server initialization +server = ActivityPubServer( + storage=SQLStorageBackend(), + delivery=ActivityDelivery(), + protocol=FederationProtocol(), + security=SecurityManager() +) + +# Request handling +@server.route("/inbox") +async def handle_inbox(request): + await server.inbox_handler.handle_request(request) +``` + +### Model System +```python +# Type-safe model definition +class APNote(APObject): + type: Literal["Note"] + content: str + attributedTo: Union[str, APActor] + to: List[str] + cc: Optional[List[str]] = None +``` + +### Handler System +```python +# Activity handler +class CreateHandler(ActivityHandler): + async def validate(self, activity: Dict[str, Any]) -> None: + # Validation logic + pass + + async def process(self, activity: Dict[str, Any]) -> None: + # Processing logic + pass +``` + +### Storage System +```python +# Storage operations +class SQLStorageBackend(BaseStorageBackend): + async def create_activity(self, activity: Dict[str, Any]) -> str: + # Store activity + pass + + async def get_object(self, object_id: str) -> Optional[Dict[str, Any]]: + # Retrieve object + pass +``` + +### Configuration +#### Basic Configuration + +```python +config = { + "server": { + "host": "0.0.0.0", + "port": 8000, + "debug": False + }, + "federation": { + "domain": "example.com", + "https_required": True, + "max_payload_size": 1048576 + }, + "security": { + "key_size": 2048, + "signature_algorithm": "rsa-sha256", + "signature_timeout": 300 + }, + "storage": { + "backend": "sql", + "dsn": "postgresql://user:pass@localhost/dbname" + }, + "cache": { + "backend": "redis", + "url": "redis://localhost", + "ttl": 3600 + } +} +``` + +#### Advanced Configuration + +```python +config = { + "federation": { + "delivery": { + "max_attempts": 5, + "retry_delay": 300, + "timeout": 30 + }, + "collections": { + "page_size": 20, + "max_items": 5000 + } + }, + "security": { + "allowed_algorithms": ["rsa-sha256", "hs2019"], + "key_rotation": { + "enabled": True, + "interval": 7776000 # 90 days + } + } +} +``` + +Integration Points +Application Integration +# Initialize PyFed +```python +pyfed = PyFed(config) +``` + +# Register handlers +```python +pyfed.register_handler("Create", CustomCreateHandler) +pyfed.register_handler("Follow", CustomFollowHandler) +``` + +# Start server +```python +await pyfed.start() +``` + +### Storage Integration + +```python +# Custom storage backend +class CustomStorage(BaseStorageBackend): + async def create_activity(self, activity): + # Custom storage logic + pass + + async def get_object(self, object_id): + # Custom retrieval logic + pass + +# Register storage +pyfed.use_storage(CustomStorage()) +``` + +Best Practices + +### Security +- Always verify HTTP signatures +- Validate all incoming activities +- Use HTTPS for all federation +- Implement rate limiting + +### Performance +- Use appropriate caching +- Implement batch processing +- Handle async operations properly +- Monitor resource usage + +### Reliability +- Implement retry logic +- Handle failures gracefully +- Log important operations +- Monitor system health + +#### See Also +- [Models API Reference](../api/models.md) +- [Security Guide](../security.md) \ No newline at end of file diff --git a/documentation/configuration.md b/documentation/configuration.md new file mode 100644 index 0000000000000000000000000000000000000000..2ccad64a9b39d866d38be733cd4bd0486310bd4a --- /dev/null +++ b/documentation/configuration.md @@ -0,0 +1,210 @@ +# PyFed Configuration Guide + +## Overview +PyFed provides flexible configuration options through environment variables, configuration files (YAML/JSON), and programmatic configuration. This guide covers all available configuration options and best practices. + +## Configuration Methods + +### 1. Environment Variables +All configuration options can be set using environment variables with the `PYFED_` prefix: + +```bash +# Core settings +PYFED_DOMAIN=example.com +PYFED_DEBUG=false + +# Database settings +PYFED_DATABASE_URL=postgresql://user:pass@localhost/dbname +PYFED_DB_MIN_CONNECTIONS=5 +PYFED_DB_MAX_CONNECTIONS=20 +PYFED_DB_TIMEOUT=30 + +# Security settings +PYFED_KEY_PATH=keys +PYFED_SIGNATURE_TTL=300 + +# Federation settings +PYFED_SHARED_INBOX=true +PYFED_DELIVERY_TIMEOUT=30 +PYFED_VERIFY_SSL=true + +# Media settings +PYFED_UPLOAD_PATH=uploads +PYFED_MAX_UPLOAD_SIZE=10000000 +``` + +## 2. Configuration File +Configuration can be loaded from YAML or JSON files: + +```yaml +# config.yaml +domain: example.com +debug: false + +storage: + provider: postgresql + database: + url: postgresql://user:pass@localhost/dbname + min_connections: 5 + max_connections: 20 + timeout: 30 + +security: + key_path: keys + signature_ttl: 300 + allowed_algorithms: + - rsa-sha256 + - hs2019 + +federation: + shared_inbox: true + delivery_timeout: 30 + verify_ssl: true + +media: + upload_path: uploads + max_size: 10000000 + allowed_types: + - image/jpeg + - image/png + - image/gif + - video/mp4 + - audio/mpeg +``` + +3. Programmatic Configuration +Configure PyFed directly in your code: + +```python +from pyfed.config import PyFedConfig, DatabaseConfig, SecurityConfig + +config = PyFedConfig( + domain="example.com", + storage=StorageConfig( + provider="postgresql", + database=DatabaseConfig( + url="postgresql://user:pass@localhost/dbname", + min_connections=5, + max_connections=20, + timeout=30 + ) + ), + security=SecurityConfig( + key_path="keys", + signature_ttl=300 + ) +) +``` + +Configuration Options +Core Settings +| Option | Environment Variable | Default | Description | +|--------|---------------------|---------|-------------| +| domain | PYFED_DOMAIN | localhost | Server domain name | +| debug | PYFED_DEBUG | false | Enable debug mode | + +Storage Settings +| Option | Environment Variable | Default | Description | +|--------|---------------------|---------|-------------| +| storage.provider | PYFED_STORAGE_PROVIDER | sqlite | Storage backend (sqlite/postgresql) | +| storage.database.url | PYFED_DATABASE_URL | sqlite:///pyfed.db | Database connection URL | +| storage.database.min_connections | PYFED_DB_MIN_CONNECTIONS | 5 | Minimum database connections | +| storage.database.max_connections | PYFED_DB_MAX_CONNECTIONS | 20 | Maximum database connections | +| storage.database.timeout | PYFED_DB_TIMEOUT | 30 | Database connection timeout | + +Security Settings +| Option | Environment Variable | Default | Description | +|--------|---------------------|---------|-------------| +| security.key_path | PYFED_KEY_PATH | keys | Path to key storage | +| security.signature_ttl | PYFED_SIGNATURE_TTL | 300 | Signature time-to-live (seconds) | +| security.allowed_algorithms | - | ["rsa-sha256"] | Allowed signature algorithms | + +Federation Settings +| Option | Environment Variable | Default | Description | +|--------|---------------------|---------|-------------| +| federation.shared_inbox | PYFED_SHARED_INBOX | true | Enable shared inbox | +| federation.delivery_timeout | PYFED_DELIVERY_TIMEOUT | 30 | Activity delivery timeout | +| federation.verify_ssl | PYFED_VERIFY_SSL | true | Verify SSL certificates | + +Media Settings +| Option | Environment Variable | Default | Description | +|--------|---------------------|---------|-------------| +| media.upload_path | PYFED_UPLOAD_PATH | uploads | Media upload directory | +| media.max_size | PYFED_MAX_UPLOAD_SIZE | 10000000 | Maximum upload size (bytes) | +| media.allowed_types | - | [image/*, video/mp4, audio/mpeg] | Allowed media types | + +Best Practices +1. Environment-Specific Configuration +Use different configuration files for development, testing, and production: + +# Development +config.dev.yaml + +# Testing +config.test.yaml + +# Production +config.prod.yaml + +2. Secure Secrets +Never commit sensitive information to version control +Use environment variables for secrets in production +Keep keys and credentials secure +3. Validation +PyFed validates all configuration options. Invalid configurations will raise ConfigError with detailed error messages. + +4. Logging +Configure logging for better debugging: + +```yaml +logging: + level: INFO + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + handlers: + - console + - file +``` + +5. Performance Tuning +Optimize database and cache settings based on your requirements: + +```yaml +storage: + database: + min_connections: 5 + max_connections: 20 + timeout: 30 + +cache: + backend: redis + url: redis://localhost + ttl: 3600 +``` + +Framework Integration +Flask Integration +from pyfed.integration.frameworks.flask import FlaskIntegration + +integration = FlaskIntegration(config) +await integration.initialize() + +Django Integration +from pyfed.integration.frameworks.django import DjangoIntegration + +integration = DjangoIntegration(config) +await integration.initialize() + +Error Handling +PyFed provides detailed error messages for configuration issues: + +```python +try: + config = PyFedConfig.from_file("config.yaml") +except ConfigError as e: + logger.error(f"Configuration error: {e}") +``` + +### See Also +- [Server API Reference](../api/server.md) +- [Models API Reference](../api/models.md) +- [Handlers API Reference](../api/handlers.md) \ No newline at end of file diff --git a/documentation/getting-started.md b/documentation/getting-started.md new file mode 100644 index 0000000000000000000000000000000000000000..30b35a877f5a224fd0029339ad4450044adf07c7 --- /dev/null +++ b/documentation/getting-started.md @@ -0,0 +1,242 @@ +# Getting Started with PyFed + +## Overview +PyFed is a powerful, type-safe ActivityPub federation library for Python applications. This guide will help you get started with integrating PyFed into your application. + +## Prerequisites +- Python 3.9 or higher +- PostgreSQL (recommended) or SQLite +- Redis (optional, for caching) + +## Installation + +```bash +pip install pyfed +``` + +Quick Start +1. Basic Setup +Create a new Python file (e.g., app.py): +```python +from pyfed import PyFedConfig +from pyfed.integration.frameworks.flask import FlaskIntegration +# or for Django: +# from pyfed.integration.frameworks.django import DjangoIntegration + +# Create configuration +config = PyFedConfig( + domain="example.com", + storage=StorageConfig( + provider="postgresql", + database=DatabaseConfig( + url="postgresql://user:pass@localhost/dbname", + min_connections=5, + max_connections=20 + ) + ), + security=SecurityConfig( + key_path="keys", + signature_ttl=300 + ) +) + +# Initialize integration +integration = FlaskIntegration(config) +await integration.initialize() +``` + +2. Define an Actor +```python +from pyfed.models import APPerson + +actor = APPerson( + id="https://example.com/users/alice", + name="Alice", + preferredUsername="alice", + inbox="https://example.com/users/alice/inbox", + outbox="https://example.com/users/alice/outbox", + followers="https://example.com/users/alice/followers", + following="https://example.com/users/alice/following" +) +``` + +3. Handle Activities +```python +from pyfed.handlers import CreateHandler, FollowHandler + +# Register activity handlers +@integration.handle_activity("Create") +async def handle_create(activity): + handler = CreateHandler() + await handler.handle(activity) + +@integration.handle_activity("Follow") +async def handle_follow(activity): + handler = FollowHandler() + await handler.handle(activity) +``` + +4. Send Activities +```python +# Create a Note +note = APNote( + id="https://example.com/notes/1", + attributedTo="https://example.com/users/alice", + content="Hello, ActivityPub world!", + to=["https://www.w3.org/ns/activitystreams#Public"] +) + +# Create and send activity +create_activity = APCreate( + actor="https://example.com/users/alice", + object=note, + to=["https://www.w3.org/ns/activitystreams#Public"] +) + +await integration.deliver_activity( + activity=create_activity.serialize(), + recipients=["https://example.com/users/bob"] +) +``` + +Configuration +Create a configuration file (config.yaml): +```yaml +domain: example.com +debug: false + +storage: + provider: postgresql + database: + url: postgresql://user:pass@localhost/dbname + min_connections: 5 + max_connections: 20 + +security: + key_path: keys + signature_ttl: 300 + allowed_algorithms: + - rsa-sha256 + +federation: + shared_inbox: true + delivery_timeout: 30 + verify_ssl: true + +media: + upload_path: uploads + max_size: 10000000 +``` + +Framework Integration +Flask Integration +```python +from flask import Flask +from pyfed.integration.frameworks.flask import FlaskIntegration + +app = Flask(__name__) +integration = FlaskIntegration(config) + +@app.route("/inbox", methods=["POST"]) +async def inbox(): + return await integration.handle_inbox(request) + +@app.route("/users/<username>", methods=["GET"]) +async def actor(username): + return await integration.handle_actor(username) + +if __name__ == "__main__": + app.run() +``` + +Django Integration + +```python +# settings.py +INSTALLED_APPS = [ + ... + 'pyfed.integration.frameworks.django', +] + +# urls.py +from django.urls import path +from pyfed.integration.frameworks.django import DjangoIntegration + +integration = DjangoIntegration(config) + +urlpatterns = [ + path('inbox/', integration.views['inbox'].as_view()), + path('users/<str:username>/', integration.views['actor'].as_view()), +] +``` + +#### Basic Features +##### 1. Activity Types +PyFed supports all standard ActivityPub activity types: + +- Create +- Update +- Delete +- Follow +- Like +- Announce +- Accept +- Reject +- Undo + +##### 2. Object Types +Common object types include: + +- Note +- Article +- Image +- Video +- Audio +- Person +- Group +- Organization + +##### 3. Collections +Manage collections of objects: + +- Collection +- OrderedCollection +- CollectionPage +- OrderedCollectionPage + +#### Next Steps +Read the Configuration Guide for detailed configuration options +Explore the API Reference for comprehensive documentation +Check the Architecture Guide to understand PyFed's design +Join our community for support and discussions + +#### Examples +Find more examples in our GitHub repository (in the [examples directory](../examples/README.md)): + +- Basic ActivityPub server + +Troubleshooting +Common Issues +1. Database Connection +```python +try: + await integration.initialize() +except StorageError as e: + logger.error(f"Database connection failed: {e}") +``` + +2. Activity Delivery +```python +try: + await integration.deliver_activity(activity, recipients) +except DeliveryError as e: + logger.error(f"Activity delivery failed: {e}") +``` + +3. Signature Verification +```python +try: + await integration.verify_signature(request) +except DeliveryError as e: + logger.error(f"Signature verification failed: {e}") +``` \ No newline at end of file diff --git a/documentation/guides/basic-server.md b/documentation/guides/basic-server.md new file mode 100644 index 0000000000000000000000000000000000000000..acad001b367353f27dbc3c2ec1466eec071fcd25 --- /dev/null +++ b/documentation/guides/basic-server.md @@ -0,0 +1,246 @@ +# Building a Basic ActivityPub Server with PyFed + +This guide walks you through creating a basic ActivityPub-compatible server using PyFed. You'll learn how to set up endpoints, handle activities, and implement federation. + +## Prerequisites + +- Python 3.9+ +- PyFed library installed (`pip install pyfed`) +- Basic understanding of ActivityPub protocol +- A domain name (for federation) + +## Basic Server Setup + +### 1. Project Structure + +Create a new project with the following structure: +myserver/ +├── config.yaml +├── main.py +├── handlers/ +│ └── init.py +└── models/ + └── init.py + +### 2. Basic Configuration + +Create `config.yaml`: +```yaml +domain: "example.com" +database: + url: "sqlite:///app.db" +keys: + path: "./keys" + rotation_interval: 30 +server: + host: "0.0.0.0" + port: 8000 +``` + +### 3. Basic Server Implementation + +In `main.py`, + +```python +from pyfed import Server, ActivityHandler +from pyfed.models import APActivity +from typing import Optional + +class MyActivityHandler(ActivityHandler): + async def handle_follow(self, activity: APActivity) -> Optional[APActivity]: + # Accept all follow requests + return { + "type": "Accept", + "actor": self.server.actor_id, + "object": activity + } + +async def main(): + # Create server instance + server = Server( + config_path="config.yaml", + handler_class=MyActivityHandler + ) + + # Initialize server + await server.initialize() + + # Start server + await server.run() + +if __name__ == "__main__": + import asyncio + asyncio.run(main()) +``` + +### Core Components +1. Actor Setup +Define your server's actor profile: + +```python +actor_info = { + "type": "Application", + "name": "My ActivityPub Server", + "summary": "A basic ActivityPub server using PyFed", + "preferredUsername": "myserver", + "inbox": "/inbox", + "outbox": "/outbox", + "followers": "/followers", + "following": "/following" +} + +server = Server( + config_path="config.yaml", + handler_class=MyActivityHandler, + actor_info=actor_info +) +``` + +2. Activity Handling +Implement custom activity handlers: + +```python +class MyActivityHandler(ActivityHandler): + async def handle_create(self, activity: Activity) -> Optional[Activity]: + # Handle Create activities + object_type = activity.get("object", {}).get("type") + if object_type == "Note": + await self.store_note(activity["object"]) + return None + + async def handle_like(self, activity: Activity) -> Optional[Activity]: + # Handle Like activities + await self.store_like(activity) + return None + + async def store_note(self, note): + # Store note in database + await self.server.db.notes.insert(note) + + async def store_like(self, like): + # Store like in database + await self.server.db.likes.insert(like) +``` + +3. Database Models +Create models in models/__init__.py: + +```python +from pyfed.models import BaseModel +from datetime import datetime +from typing import Optional + +class Note(BaseModel): + content: str + published: datetime + attributed_to: str + to: list[str] + cc: Optional[list[str]] = None + +class Like(BaseModel): + actor: str + object: str + published: datetime +``` + +Federation Features +1. Webfinger Support +Enable Webfinger for user discovery: + +```python +from pyfed.protocols import WebFinger + +webfinger = WebFinger(domain="example.com") +server.add_protocol(webfinger) +``` + +2. HTTP Signatures +Configure HTTP signatures for secure federation: + +```python +from pyfed.security import HTTPSignatureVerifier + +server.configure_signatures( + key_id="https://example.com/actor#main-key", + private_key_path="keys/private.pem" +) +``` + +3. Activity Distribution +Implement activity distribution: + +```python +class MyActivityHandler(ActivityHandler): + async def handle_create(self, activity: Activity) -> Optional[Activity]: + # Store the activity + await self.store_note(activity["object"]) + + # Distribute to followers + followers = await self.get_followers() + await self.distribute_activity(activity, followers) + + return None + + async def distribute_activity(self, activity: Activity, recipients: list[str]): + for recipient in recipients: + await self.server.deliver_activity( + activity=activity, + recipient=recipient + ) +``` + +## Running the Server +1. Generate keys: + +```bash +python -m pyfed.tools.keygen keys/ +``` + +2. Start the server: + +```bash +python main.py +``` + +3. Testing Federation +Test Webfinger: + +```bash +curl https://example.com/.well-known/webfinger?resource=acct:myserver@example.com +``` + +2. Test Actor Profile: + +```bash +curl -H "Accept: application/activity+json" https://example.com/actor +``` + +3. Send a Follow Activity: + +```bash +curl -X POST https://example.com/inbox \ + -H "Content-Type: application/activity+json" \ + -d '{ + "type": "Follow", + "actor": "https://other-server.com/users/alice", + "object": "https://example.com/actor" + }' + ``` + +Security Considerations +1. Always verify HTTP signatures +2. Validate activities before processing +3. Implement rate limiting +4. Use HTTPS in production +5. Regularly rotate keys + +Next Steps +- Implement more activity types +- Add user authentication +- Set up media handling +- Add moderation features +- Implement caching + +See Also +- [Security Guide](../security.md) +- [API Reference](../api) \ No newline at end of file diff --git a/documentation/security.md b/documentation/security.md new file mode 100644 index 0000000000000000000000000000000000000000..f6842b66aa44ba46686f0f8e1a6c84e90d8f1da4 --- /dev/null +++ b/documentation/security.md @@ -0,0 +1,287 @@ +# PyFed Security Guide + +## Overview +PyFed implements comprehensive security measures for ActivityPub federation, including: +- HTTP Signatures +- Key Management +- OAuth2 Authentication +- Request Validation +- Rate Limiting + +## Key Management + +### Key Generation and Storage +PyFed uses RSA key pairs for signing ActivityPub requests: + +```python +from pyfed.security import KeyManager + +key_manager = KeyManager( + domain="example.com", + keys_path="keys", + rotation_config={ + "rotation_interval": 30, # days + "key_overlap": 2, # days + "key_size": 2048 # bits + } +) + +# Initialize key manager +await key_manager.initialize() +``` + +## Key Rotation + +Keys are automatically rotated to maintain security: + +- Default rotation interval: 30 days +- Overlap period: 2 days (to handle in-flight requests) +- Automatic archival of expired keys +- Federation announcement of new keys + +## Key Storage Security + +- Private keys are stored with proper permissions +- Keys are stored in PEM format +- Separate directories for active and archived keys +- Metadata stored alongside keys for tracking + +## HTTP Signatures + +### Request Signing +All outgoing ActivityPub requests are signed: + +```python +from pyfed.security import HTTPSignatureVerifier + +# Create signature verifier +verifier = HTTPSignatureVerifier(key_manager) + +# Sign request +headers = await verifier.sign_request( + method="POST", + path="/inbox", + headers={"Content-Type": "application/activity+json"}, + body=activity_data +) +``` + +### Signature Verification +#### Incoming requests are verified: + +```python +# Verify request signature +is_valid = await verifier.verify_request( + method=request.method, + path=request.path, + headers=request.headers, + body=request.body +) +``` + +Signature Format +PyFed uses the standard HTTP Signatures format: + +```bash +Signature: keyId="https://example.com/keys/1234", + algorithm="rsa-sha256", + headers="(request-target) host date digest", + signature="base64..." +``` + +## OAuth2 Authentication +Token Generation +Client-to-server authentication using OAuth2: +```python +from pyfed.security import OAuth2Handler + +oauth = OAuth2Handler( + client_id="client_id", + client_secret="client_secret", + token_endpoint="https://example.com/oauth/token" +) + +# Create token +token = await oauth.create_token( + username="user", + password="pass", + scope="read write" +) +``` + +#### Token Verification +```python +# Verify token +try: + payload = await oauth.verify_token( + token="access_token", + required_scope="write" + ) +except AuthenticationError as e: + # Handle invalid token + pass +``` + +#### Token Management +- Automatic token refresh +- Token caching +- Scope validation +- Token revocation + +#### Request Validation +- Activity Validation +All incoming activities are validated: + +```python +from pyfed.handlers import ActivityHandler + +class CustomHandler(ActivityHandler): + async def validate(self, activity): + # Validate activity type + if activity.get('type') != 'Create': + raise ValidationError("Invalid activity type") + + # Validate required fields + if 'object' not in activity: + raise ValidationError("Missing object") + + # Validate permissions + if not await self.can_create(activity['actor']): + raise ValidationError("Unauthorized") + + # Validate content + await self._validate_content(activity['object']) +``` + +#### Content Validation +- Media type validation +- Size limits +- Content scanning +- Malware detection + +#### Rate Limiting +Configuration +```yaml +rate_limits: + inbox: + window: 3600 # seconds + max_requests: 1000 + media: + window: 3600 + max_requests: 100 + max_size: 10485760 # bytes +``` + +Implementation +```python +from pyfed.security import RateLimiter + +limiter = RateLimiter( + redis_url="redis://localhost", + config=rate_limit_config +) + +# Check rate limit +allowed = await limiter.check_limit( + key="inbox", + identifier=request.remote_addr +) +``` + +#### Best Practices + +##### 1. Key Management +- Regularly rotate keys (30 days recommended) +- Securely store private keys +- Monitor key usage and expiration +- Implement key backup procedures + +##### 2. Request Signing +Sign all outgoing requests +Verify all incoming signatures +Use strong algorithms (rsa-sha256) +Include relevant headers in signature + +3. Authentication +Use OAuth2 for C2S authentication +Implement proper scope validation +Securely store client secrets +Regular token rotation + +4. Content Security +Validate all incoming content +Implement upload limits +Scan for malware +Sanitize user content + +5. Rate Limiting +Implement per-endpoint limits +Use sliding windows +Account for bursts +Monitor abuse patterns + +### Security Headers +#### Recommended Headers +```python +security_headers = { + 'Content-Security-Policy': "default-src 'self'", + 'X-Frame-Options': 'DENY', + 'X-Content-Type-Options': 'nosniff', + 'X-XSS-Protection': '1; mode=block', + 'Referrer-Policy': 'strict-origin-when-cross-origin', + 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains' +} +``` + +### Monitoring and Logging +#### Security Events +```python +# Configure security logging +logging.config.dictConfig({ + 'handlers': { + 'security': { + 'class': 'logging.FileHandler', + 'filename': 'security.log', + 'formatter': 'detailed' + } + }, + 'loggers': { + 'pyfed.security': { + 'handlers': ['security'], + 'level': 'INFO' + } + } +}) +``` + +#### Metrics Collection +- Failed authentication attempts +- Signature verification failures +- Rate limit violations +- Key rotation events + +#### Error Handling +#### Security Exceptions +PyFed provides specific security exceptions: +- KeyManagementError +- SignatureError +- AuthenticationError +- ValidationError +- RateLimitError + +Example Error Handling +```python +try: + await verifier.verify_signature(request) +except SignatureError as e: + logger.warning(f"Signature verification failed: {e}") + return JsonResponse( + {"error": "Invalid signature"}, + status=401 + ) +``` + +See Also +[Configuration Guide](configuration.md) +[API Reference](api/) +[Architecture Guide](architecture.md) \ No newline at end of file