Skip to content

chore(README) add a warning about legal compliance #803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docker-hub.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,5 @@ jobs:
name: Call argocd github webhook
run: |
data='{"ref": "'$GITHUB_REF'","repository": {"html_url":"'$GITHUB_SERVER_URL'/${{ secrets.DEPLOYMENT_REPO_URL }}"}}'
sig=$(echo -n ${data} | openssl dgst -sha1 -hmac "${{ secrets.ARGOCD_PREPROD_WEBHOOK_SECRET }}" | awk '{print "X-Hub-Signature: sha1="$2}')
sig=$(echo -n ${data} | openssl dgst -sha256 -hmac "${{ secrets.ARGOCD_PREPROD_WEBHOOK_SECRET }}" | awk '{print "X-Hub-Signature-256: sha256="$2}')
curl -X POST -H 'X-GitHub-Event:push' -H "Content-Type: application/json" -H "${sig}" --data "${data}" ${{ vars.ARGOCD_PREPROD_WEBHOOK_URL }}
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ and this project adheres to

## [Unreleased]

## [3.0.0] - 2025-03-28

## Added

- 📄(legal) Require contributors to sign a DCO #779

## Changed

- ♻️(frontend) Integrate UI kit #783
- 🏗️(y-provider) manage auth in y-provider app #804

## Fixed

- 🐛(backend) compute ancestor_links in get_abilities if needed #725
- 🔒️(back) restrict access to document accesses #801


## [2.6.0] - 2025-03-21

Expand Down Expand Up @@ -501,8 +506,9 @@ and this project adheres to
- ✨(frontend) Coming Soon page (#67)
- 🚀 Impress, project to manage your documents easily and collaboratively.

[unreleased]: https://github.com/numerique-gouv/impress/compare/v2.6.0...main
[v2.5.0]: https://github.com/numerique-gouv/impress/releases/v2.6.0
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.0.0...main
[v3.0.0]: https://github.com/numerique-gouv/impress/releases/v3.0.0
[v2.6.0]: https://github.com/numerique-gouv/impress/releases/v2.6.0
[v2.5.0]: https://github.com/numerique-gouv/impress/releases/v2.5.0
[v2.4.0]: https://github.com/numerique-gouv/impress/releases/v2.4.0
[v2.3.0]: https://github.com/numerique-gouv/impress/releases/v2.3.0
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Welcome to Docs! The open source document editor where your notes can become kno

## Why use Docs ❓

⚠️ **Note that Docs provides docs/pdf exporters by loading [two BlockNote packages](https://github.com/suitenumerique/docs/blob/main/src/frontend/apps/impress/package.json#L22C7-L23C53), which we use under the AGPL-3.0 licence. Until we comply with the terms of this license, we recommend that you don't run Docs as a commercial product, unless you are willing to sponsor [BlockNote](https://github.com/TypeCellOS/BlockNote).**

Docs is a collaborative text editor designed to address common challenges in knowledge building and sharing.

### Write
Expand Down
12 changes: 12 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ the following command inside your docker container:

## [Unreleased]

## [3.0.0] - 2025-03-28

We are not using the nginx auth request anymore to access the collaboration server (`yProvider`)
The authentication is now managed directly from the yProvider server.
You must remove the annotation `nginx.ingress.kubernetes.io/auth-url` from the `ingressCollaborationWS`.

This means as well that the yProvider server must be able to access the Django server.
To do so, you must set the `COLLABORATION_BACKEND_BASE_URL` environment variable to the `yProvider`
service.

## [2.2.0] - 2025-02-10

- AI features are now limited to users who are authenticated. Before this release, even anonymous
users who gained editor access on a document with link reach used to get AI feature.
IF you want anonymous users to keep access on AI features, you must now define the
Expand Down
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,15 @@ services:
context: .
dockerfile: ./src/frontend/servers/y-provider/Dockerfile
target: y-provider
command: ["yarn", "workspace", "server-y-provider", "run", "dev"]
working_dir: /app/frontend
restart: unless-stopped
env_file:
- env.d/development/common
ports:
- "4444:4444"
volumes:
- ./src/frontend/:/app/frontend

kc_postgresql:
image: postgres:14.3
Expand Down
48 changes: 0 additions & 48 deletions docker/files/etc/nginx/conf.d/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,6 @@ server {
server_name localhost;
charset utf-8;

# Proxy auth for collaboration server
location /collaboration/ws/ {
# Collaboration Auth request configuration
auth_request /collaboration-auth;
auth_request_set $authHeader $upstream_http_authorization;
auth_request_set $canEdit $upstream_http_x_can_edit;
auth_request_set $userId $upstream_http_x_user_id;

# Pass specific headers from the auth response
proxy_set_header Authorization $authHeader;
proxy_set_header X-Can-Edit $canEdit;
proxy_set_header X-User-Id $userId;

# Ensure WebSocket upgrade
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";

# Collaboration server
proxy_pass http://y-provider:4444;

# Set appropriate timeout for WebSocket
proxy_read_timeout 86400;
proxy_send_timeout 86400;

# Preserve original host and additional headers
proxy_set_header Host $host;
}

location /collaboration-auth {
proxy_pass http://app-dev:8000/api/v1.0/documents/collaboration-auth/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Original-URL $request_uri;

# Prevent the body from being passed
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-Method $request_method;
}

location /collaboration/api/ {
# Collaboration server
proxy_pass http://y-provider:4444;
proxy_set_header Host $host;
}

# Proxy auth for media
location /media/ {
# Auth request configuration
Expand Down
5 changes: 3 additions & 2 deletions env.d/development/common.dist
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ AI_API_KEY=password
AI_MODEL=llama

# Collaboration
COLLABORATION_API_URL=http://nginx:8083/collaboration/api/
COLLABORATION_API_URL=http://y-provider:4444/collaboration/api/
COLLABORATION_BACKEND_BASE_URL=http://app-dev:8000
COLLABORATION_SERVER_ORIGIN=http://localhost:3000
COLLABORATION_SERVER_SECRET=my-secret
COLLABORATION_WS_URL=ws://localhost:8083/collaboration/ws/
COLLABORATION_WS_URL=ws://localhost:4444/collaboration/ws/

# Frontend
FRONTEND_THEME=default
31 changes: 31 additions & 0 deletions src/backend/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@ class Meta:
read_only_fields = ["id", "email", "full_name", "short_name"]


class UserLightSerializer(UserSerializer):
"""Serialize users with limited fields."""

id = serializers.SerializerMethodField(read_only=True)
email = serializers.SerializerMethodField(read_only=True)

def get_id(self, _user):
"""Return always None. Here to have the same fields than in UserSerializer."""
return None

def get_email(self, _user):
"""Return always None. Here to have the same fields than in UserSerializer."""
return None

class Meta:
model = models.User
fields = ["id", "email", "full_name", "short_name"]
read_only_fields = ["id", "email", "full_name", "short_name"]


class BaseAccessSerializer(serializers.ModelSerializer):
"""Serialize template accesses."""

Expand Down Expand Up @@ -118,6 +138,17 @@ class Meta:
read_only_fields = ["id", "abilities"]


class DocumentAccessLightSerializer(DocumentAccessSerializer):
"""Serialize document accesses with limited fields."""

user = UserLightSerializer(read_only=True)

class Meta:
model = models.DocumentAccess
fields = ["id", "user", "team", "role", "abilities"]
read_only_fields = ["id", "team", "role", "abilities"]


class TemplateAccessSerializer(BaseAccessSerializer):
"""Serialize template accesses."""

Expand Down
87 changes: 29 additions & 58 deletions src/backend/core/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,18 +380,15 @@ class DocumentViewSet(
9. **Media Auth**: Authorize access to document media.
Example: GET /documents/media-auth/

10. **Collaboration Auth**: Authorize access to the collaboration server for a document.
Example: GET /documents/collaboration-auth/

11. **AI Transform**: Apply a transformation action on a piece of text with AI.
10. **AI Transform**: Apply a transformation action on a piece of text with AI.
Example: POST /documents/{id}/ai-transform/
Expected data:
- text (str): The input text.
- action (str): The transformation type, one of [prompt, correct, rephrase, summarize].
Returns: JSON response with the processed text.
Throttled by: AIDocumentRateThrottle, AIUserRateThrottle.

12. **AI Translate**: Translate a piece of text with AI.
11. **AI Translate**: Translate a piece of text with AI.
Example: POST /documents/{id}/ai-translate/
Expected data:
- text (str): The input text.
Expand Down Expand Up @@ -1207,17 +1204,6 @@ def _auth_get_url_params(self, pattern, fragment):
logger.debug("Failed to extract parameters from subrequest URL: %s", exc)
raise drf.exceptions.PermissionDenied() from exc

def _auth_get_document(self, pk):
"""
Retrieves the document corresponding to the given primary key (pk).
Raises PermissionDenied if the document is not found.
"""
try:
return models.Document.objects.get(pk=pk)
except models.Document.DoesNotExist as exc:
logger.debug("Document with ID '%s' does not exist", pk)
raise drf.exceptions.PermissionDenied() from exc

@drf.decorators.action(detail=False, methods=["get"], url_path="media-auth")
def media_auth(self, request, *args, **kwargs):
"""
Expand Down Expand Up @@ -1265,42 +1251,6 @@ def media_auth(self, request, *args, **kwargs):

return drf.response.Response("authorized", headers=request.headers, status=200)

@drf.decorators.action(detail=False, methods=["get"], url_path="collaboration-auth")
def collaboration_auth(self, request, *args, **kwargs):
"""
This view is used by an Nginx subrequest to control access to a document's
collaboration server.
"""
parsed_url = self._auth_get_original_url(request)
url_params = self._auth_get_url_params(
enums.COLLABORATION_WS_URL_PATTERN, parsed_url.query
)
document = self._auth_get_document(url_params["pk"])

abilities = document.get_abilities(request.user)
if not abilities.get(self.action, False):
logger.debug(
"User '%s' lacks permission for document '%s'",
request.user,
document.pk,
)
raise drf.exceptions.PermissionDenied()

if not settings.COLLABORATION_SERVER_SECRET:
logger.debug("Collaboration server secret is not defined")
raise drf.exceptions.PermissionDenied()

# Add the collaboration server secret token to the headers
headers = {
"Authorization": settings.COLLABORATION_SERVER_SECRET,
"X-Can-Edit": str(abilities["partial_update"]),
}

if request.user.is_authenticated:
headers["X-User-Id"] = str(request.user.id)

return drf.response.Response("authorized", headers=headers, status=200)

@drf.decorators.action(
detail=True,
methods=["post"],
Expand Down Expand Up @@ -1420,12 +1370,7 @@ def cors_proxy(self, request, *args, **kwargs):

class DocumentAccessViewSet(
ResourceAccessViewsetMixin,
drf.mixins.CreateModelMixin,
drf.mixins.DestroyModelMixin,
drf.mixins.ListModelMixin,
drf.mixins.RetrieveModelMixin,
drf.mixins.UpdateModelMixin,
viewsets.GenericViewSet,
viewsets.ModelViewSet,
):
"""
API ViewSet for all interactions with document accesses.
Expand Down Expand Up @@ -1457,6 +1402,32 @@ class DocumentAccessViewSet(
queryset = models.DocumentAccess.objects.select_related("user").all()
resource_field_name = "document"
serializer_class = serializers.DocumentAccessSerializer
is_current_user_owner_or_admin = False

def get_queryset(self):
"""Return the queryset according to the action."""
queryset = super().get_queryset()

if self.action == "list":
try:
document = models.Document.objects.get(pk=self.kwargs["resource_id"])
except models.Document.DoesNotExist:
return queryset.none()

roles = set(document.get_roles(self.request.user))
is_owner_or_admin = bool(roles.intersection(set(models.PRIVILEGED_ROLES)))
self.is_current_user_owner_or_admin = is_owner_or_admin
if not is_owner_or_admin:
# Return only the document owner access
queryset = queryset.filter(role__in=models.PRIVILEGED_ROLES)

return queryset

def get_serializer_class(self):
if self.action == "list" and not self.is_current_user_owner_or_admin:
return serializers.DocumentAccessLightSerializer

return super().get_serializer_class()

def perform_create(self, serializer):
"""Add a new access to the document and send an email to the new added user."""
Expand Down
1 change: 0 additions & 1 deletion src/backend/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
MEDIA_STORAGE_URL_EXTRACT = re.compile(
f"{settings.MEDIA_URL:s}({UUID_REGEX}/{ATTACHMENTS_FOLDER}/{UUID_REGEX}{FILE_EXT_REGEX})"
)
COLLABORATION_WS_URL_PATTERN = re.compile(rf"(?:^|&)room=(?P<pk>{UUID_REGEX})(?:&|$)")


# In Django's code base, `LANGUAGES` is set by default with all supported languages.
Expand Down
Loading
Loading