Card 04/ 09
Proxy Example: Checking Permissions Before a Document Is Deleted
The protection proxy lets an editor delete and tells a viewer no.
A permission check is a natural Proxy, and this example maps the template onto it as a protection proxy. A document store can read and delete documents. Anyone may read, and only an editor may delete. The store itself knows nothing about users; a proxy that holds the user's role stands in front of it.
| Template | Permissions example |
|---|---|
| Subject | DocumentStore, with read and delete |
| RealSubject | DatabaseDocumentStore, which holds the documents |
| Proxy | ProtectedDocumentStore, which holds the store and a role |
| Client | The loop that acts as an editor and then as a viewer |
from abc import ABC, abstractmethod
class DocumentStore(ABC):
@abstractmethod
def read(self, doc_id: str) -> str: ...
@abstractmethod
def delete(self, doc_id: str) -> None: ...
class DatabaseDocumentStore(DocumentStore):
def __init__(self) -> None:
self._documents = {"plan": "Q4 plan", "notes": "Meeting notes"}
def read(self, doc_id: str) -> str:
return self._documents[doc_id]
def delete(self, doc_id: str) -> None:
del self._documents[doc_id]
print(f"store: deleted {doc_id}")
class PermissionDenied(Exception):
pass
class ProtectedDocumentStore(DocumentStore):
def __init__(self, store: DocumentStore, role: str) -> None:
self._store = store
self._role = role
def read(self, doc_id: str) -> str:
return self._store.read(doc_id)
def delete(self, doc_id: str) -> None:
if self._role != "editor":
raise PermissionDenied(f"a {self._role} cannot delete {doc_id}")
self._store.delete(doc_id)
store = DatabaseDocumentStore()
for role in ["editor", "viewer"]:
documents = ProtectedDocumentStore(store, role)
print(f"{role} reads: {documents.read('notes')}")
try:
documents.delete("plan" if role == "editor" else "notes")
except PermissionDenied as error:
print(f"refused: {error}")editor reads: Meeting notes
store: deleted plan
viewer reads: Meeting notes
refused: a viewer cannot delete notesBoth users read the notes, because the proxy forwards read without a check. The editor's delete reaches the store, which prints that it deleted the plan. The viewer's delete never reaches it: the proxy raises PermissionDenied, and the store's own line does not appear.
The store can now be tested with no users at all, and a different rule, such as letting owners delete their own documents, is a change to the proxy alone. The caller still holds a DocumentStore, so code written before permissions existed works with the proxy unchanged.
← → move through sections and cards