[Generated on 2025-01-05 by Claude Sonnet 4.5 with access to the Gramps Web API repsotitory.]
Role-Based Access Control (RBAC):
- 6 user roles: Guest (0), Member (1), Contributor (2), Editor (3), Owner (4), Admin (5)
- Roles grant hierarchical permissions with each level inheriting from the previous
- Permissions stored in JWT claims for stateless authorization
Privacy Control:
PERM_VIEW_PRIVATE: Enables/disables viewing of Gramps objects marked private- When disabled, users get a
ModifiedPrivateProxyDbwrapping the database - Based on Gramps' native
PrivateProxyDbwhich filters private records
Object-Level Permissions:
PERM_ADD_OBJ: Add new genealogical objects (Contributor+)PERM_EDIT_OBJ: Edit existing objects (Editor+)PERM_DEL_OBJ: Delete objects (Editor+)
Current Limitations:
- Binary privacy: either see all private records or none
- No per-object-type permissions (e.g., edit people but not sources)
- No relationship-based access (e.g., view only close relatives)
- No branch-specific editing rights
- Relationship-Based Viewing: View details for people within N generations
- Branch-Specific Editing: Edit only a specific lineage/branch
- Graduated Privacy Levels: More than just public/private
- Context-Aware Access: Different rights for different parts of the tree
Approach: Build custom proxy classes extending Gramps' existing proxy system
Implementation:
- Create
RelationshipProxyDbwrapping the base database - Filter objects based on relationship calculations at query time
- Similar to existing
PrivateProxyDbbut with genealogical logic
Pros:
- Consistent with current architecture
- Leverages proven Gramps proxy patterns
- Minimal changes to API endpoints
- Database-agnostic (works with all filters/exports)
Cons:
- Performance overhead for complex relationship calculations
- Relationship computation on every database access
- Limited flexibility for non-relationship-based rules
Complexity: Medium Performance Impact: Medium-High (relationship calculations are expensive)
Approach: Store explicit user-object permissions in a database table
Implementation:
- New
user_object_permissionstable with columns: user_id, object_handle, object_type, permission_level - Pre-compute and cache accessible objects per user
- Check permissions in middleware before returning data
Pros:
- Fast lookups (indexed database queries)
- Maximum flexibility (any permission pattern)
- Can handle complex rules once computed
- Explicit audit trail
Cons:
- Significant storage overhead (potentially millions of rows)
- Complex maintenance (updates when objects added/modified)
- Relationship changes require permission recalculation
- Requires schema migrations
Complexity: High Performance Impact: Low (after initial computation)
Approach: Store Gramps filter definitions per user role/permission level
Implementation:
- Extend user table with
viewing_filterandediting_filterJSON columns - Filters use Gramps' native filter engine (already supports relationship rules)
- Apply user's filter when calling
get_db_handle() - Reuse existing filter infrastructure (ancestors, descendants, etc.)
Pros:
- Leverages existing, well-tested Gramps filter system
- Filters already support relationship-based rules
- User-configurable without code changes
- Works with existing
FilterProxyDbinfrastructure
Cons:
- Filter complexity can be hard for admins to configure
- Some performance impact (filter evaluation at query time)
- Limited by Gramps filter capabilities
Complexity: Medium Performance Impact: Medium
Approach: Define permissions based on attributes of user, object, and context
Implementation:
- Policy engine evaluating rules like:
- "User can view Person if relationship_distance <= user.max_viewing_distance"
- "User can edit Person if person.handle in user.editable_branch_roots + descendants"
- Policies stored as evaluatable expressions (e.g., Python expressions or policy DSL)
Pros:
- Most flexible and powerful approach
- Supports complex conditional logic
- Industry-standard pattern (similar to AWS IAM policies)
- Can combine multiple criteria
Cons:
- Most complex to implement
- Significant learning curve for administrators
- Policy language/engine required
- Potential security risks if not carefully implemented
Complexity: Very High Performance Impact: Medium
This combines the strengths of the existing architecture with proven Gramps functionality.
Challenge: When filtering People, what happens to associated objects?
Example Scenarios:
- Person passes filter → should their birth event be visible?
- Person fails filter → can you still see an event where they're a witness?
- Can you view a note attached to a filtered-out person?
Solution: Transitive Inclusion with ReferencedBySelectionProxyDb
Gramps already provides ReferencedBySelectionProxyDb which:
- Takes a set of "approved" object handles
- Includes ALL objects referenced by those approved objects
- Used in exports with person filters
Implementation Strategy:
class FilteredProxyDb(ProxyDbBase):
def __init__(self, db, person_filter):
super().__init__(db)
# Apply person filter to get approved people
approved_people = apply_person_filter(db, person_filter)
# Use ReferencedBySelectionProxyDb for transitive inclusion
self._proxy = ReferencedBySelectionProxyDb(
db,
person_list=approved_people,
note_list=[], # Will be auto-included if referenced
event_list=[],
source_list=[],
# ...
)
# Delegate all methods to self._proxyAccess Rules:
- People: Only those matching the filter
- Events: Included if referenced by filtered people (birth, death, etc.)
- Families: Included if either parent matches filter
- Notes: Included if attached to any included object
- Sources/Citations: Included if cited by any included object
- Media: Included if referenced by any included object
- Places: Included if used in any included event
Edge Cases:
| Scenario | Behavior |
|---|---|
| Person A matches filter, married to Person B who doesn't | Family visible, B appears as spouse but B's details hidden when accessing directly |
| Event has multiple participants, only some match filter | Event visible, but participant list filtered |
| Note shared between filtered and non-filtered people | Note visible (conservative approach) |
Configuration Options:
For maximum flexibility, add a policy setting:
# In user configuration
related_objects_policy: str = "transitive" # or "strict"- "transitive" (recommended): Include all objects referenced by filtered people
- "strict": Also filter related objects (e.g., only show events for filtered people)
Why Transitive is Better:
- Prevents broken references in UI
- Maintains data integrity and context
- Matches user expectations (if I can see a person, I expect to see their birth event)
- Consistent with Gramps export behavior
-
Extend User Model
class User: viewing_filter_json: str # Serialized Gramps filter editing_filter_json: str # For write permissions privacy_level: int # 0=all, 1=non-private, 2=custom related_objects_policy: str # "transitive" (default) or "strict"
-
Create
FilteredProxyDbClass- Extends
ModifiedPrivateProxyDb - Applies user's viewing filter to People
- Uses
ReferencedBySelectionProxyDbfor transitive inclusion - Caches relationship calculations per request
- Extends
-
Update
get_db_handle()Logicdef get_db_handle(readonly=True): claims = get_jwt() if claims.get('viewing_filter'): filter_def = claims['viewing_filter'] return FilteredProxyDb(base_db, filter_def) elif not claims.get(PERM_VIEW_PRIVATE): return ModifiedPrivateProxyDb(base_db) return base_db
-
Filter Builder UI for admins
- Preset templates: "Descendants of X within N generations"
- Visual filter editor leveraging existing Gramps UI patterns
-
User Management Extensions
- Assign filters to users/roles
- "Anchor person" field (which person is the user in the tree?)
-
API Endpoints
PUT /users/{username}/viewing-filterGET /users/{username}/accessible-people(preview)
-
Multi-Level Privacy
- Extend privacy flag from boolean to 0-3 levels
- Filter by privacy level: "show level 0-1 only"
-
Branch-Specific Editing
- Separate
editing_filter_jsonfield - Apply to write operations (POST/PUT/DELETE)
- Validation: ensure user can view what they're editing
- Separate
-
Performance Optimization
- Cache relationship calculations in Redis
- Pre-compute common filter results
- Lazy-load filtered object lists
Options:
- A) JSON representation of Gramps filter rules ✓ RECOMMENDED
- B) Custom DSL
- C) Python code snippets
Rationale: JSON format allows validation, is language-agnostic for frontend, and maps directly to existing Gramps filter system.
Options:
- A) Always compute filters dynamically ✓ RECOMMENDED for MVP
- B) Pre-compute and cache filtered object lists
- C) Hybrid: cache for common filters, compute for custom
Rationale: Start with dynamic computation for correctness, add caching later based on profiling. Premature optimization here risks bugs.
Options:
- A) Maintain current privacy system, add filters alongside ✓ RECOMMENDED
- B) Migrate all users to filter-based system
- C) Parallel systems with toggle
Rationale: Don't break existing deployments. Null viewing_filter_json → use legacy PERM_VIEW_PRIVATE.
Options:
- A) Separate viewing and editing filters ✓ RECOMMENDED
- B) Same filter for both (edit implies view)
- C) Edit requires superset of view permissions
Rationale: Different use cases: users who can view extended family but only edit immediate family.
Requirement: Member can view people within 3 generations
Implementation:
{
"rules": [
{
"name": "IsLessThanNthGenerationAncestorOf",
"values": ["<USER_PERSON_ID>", "4"]
},
{
"name": "IsLessThanNthGenerationDescendantOf",
"values": ["<USER_PERSON_ID>", "4"]
}
],
"operator": "or"
}Admin workflow:
- Identify user's person record in tree
- Select "View close relatives" template
- Set generations = 3
- Save to user's
viewing_filter_json
Requirement: Contributor can edit descendants of their grandparent
Implementation:
{
"editing_filter_json": {
"rules": [
{
"name": "IsDescendantOf",
"values": ["<BRANCH_ROOT_ID>", "1"]
}
]
}
}Middleware check on POST/PUT/DELETE:
def require_edit_permission(handle, obj_type):
if has_permissions([PERM_EDIT_OBJ]):
# No restrictions for Editors+
return
user_id = get_jwt_identity()
editing_filter = get_user_editing_filter(user_id)
if not editing_filter:
abort(403)
if not object_matches_filter(handle, obj_type, editing_filter):
abort(403, "Object not in your editable branch")Requirement: Different privacy tiers (public → family → close family → private)
Implementation:
- Database: Add
privacy_levelinteger field to objects (0-3) - User setting:
max_privacy_level(Guest=0, Member=1, Contributor=2, Editor+=3) - Proxy filter: Filter objects where
obj.privacy_level <= user.max_privacy_level
Migration: Current boolean private → privacy_level = 3 if private else 0
-
JWT Token Size: Embedding full filters in JWT could exceed size limits
- Solution: Store filter ID in JWT, fetch full filter server-side
-
Filter Injection: Malicious filter JSON could compromise security
- Solution: Validate filter JSON against schema before saving
- Solution: Use read-only filter evaluation (no dynamic code execution)
-
Performance DoS: Complex filters could overload server
- Solution: Set max filter complexity (e.g., max 10 rules)
- Solution: Timeout filter evaluation after N seconds
-
Write Authorization: Ensure edit filters are properly enforced
- Solution: Double-check permissions in write operations (defense in depth)
- Solution: Audit log all permission changes
-
Information Leakage via Related Objects
- Risk: User accesses filtered-out person via shared note/event
- Solution: Transitive policy prevents direct access, only shows object in context
- Solution: API endpoints check object type and enforce primary filter
-
Family Relationship Exposure
- Risk: Seeing family record reveals existence of filtered-out spouse
- Solution: Proxy database can redact spouse details in family objects
- Alternative: Family only visible if both spouses pass filter (stricter)
Phase 1 - Preparation:
- Database migration adds new columns (default NULL)
- Existing behavior unchanged (NULL filters = use legacy permissions)
Phase 2 - Opt-In:
- Admins can assign filters to specific users
- Both systems work in parallel
Phase 3 - Full Migration (Optional):
- Convert
PERM_VIEW_PRIVATEto filter equivalent - Deprecate legacy permission flags
Based on typical Gramps database with 10,000 people:
| Operation | Current | Filter-Based (uncached) | With Caching |
|---|---|---|---|
| Get person list | 50ms | 150ms | 60ms |
| Get single person | 5ms | 8ms | 5ms |
| Relationship calc | N/A | 200ms (initial) | 10ms |
| Search people | 100ms | 200ms | 110ms |
Mitigation strategies:
- Cache relationship graphs per session
- Use lazy evaluation (only filter when accessing)
- Index commonly used relationship paths
If full implementation is too complex, consider these incremental improvements:
- Add
PERM_VIEW_LIVINGpermission - Use Gramps' existing
LivingProxyDb - Restricts viewing to deceased only
Effort: 1 day Benefit: Privacy for living relatives
- Add enum:
viewing_scope: "all" | "non-private" | "living-only" | "deceased-only" - Map to existing proxy classes
- No custom filters, just fixed options
Effort: 3 days Benefit: Simple multi-level access without complexity
- Add
home_person_handleto User model - New permission
PERM_VIEW_EXTENDED_FAMILY - If set, only show people within N degrees
Effort: 1 week Benefit: Relationship-based viewing without full filter system
Before proceeding with implementation, please clarify:
-
Priority Use Cases: Which is most important?
- Viewing restrictions (relationship-based)?
- Editing restrictions (branch-based)?
- Privacy levels (graduated access)?
-
User Base: How many users per tree typically?
- Affects caching strategy and storage considerations
-
Performance Requirements: What's acceptable latency for filtered queries?
- Helps choose between caching strategies
-
Administrative Complexity: Who will manage these permissions?
- Affects UI complexity requirements
-
Backward Compatibility: Must all existing installations work unchanged?
- Determines migration approach
-
Timeline: What's the target timeframe for MVP?
- Helps scope initial implementation
-
Related Objects Policy: For filtered people, should associated objects (events, notes, sources) be:
- A) Always included (transitive - recommended for usability)
- B) Also filtered (stricter privacy, but may break context)
- C) Configurable per user/role?
-
Information Disclosure Tolerance: Is it acceptable for users to:
- Know filtered-out people exist (via family relationships)?
- See event/note titles but not details for filtered objects?
- Or should filtered objects be completely invisible?
The Filter-Based Permissions (Option 3) approach offers the best balance of:
- Leveraging existing Gramps infrastructure
- Flexibility for various use cases
- Reasonable implementation complexity
- Good performance with caching
Starting with the "Quick Win" solutions can provide immediate value while the full system is developed.
Recommended Next Steps:
- Review and discuss this document
- Prioritize use cases
- Implement one "Quick Win" for immediate improvement
- Begin Phase 1 of full filter-based system
- Iterate based on user feedback