---
# System prepended metadata

title: SemApps et ActivityPub

---

# SemApps et ActivityPub

## Partie 1 : Faut-il développer un seul serveur qui gère à la fois les protocoles ActivityPub et SOLiD/LDP ? 

### Problématique

Il y a actuellement deux projets proches: SemApps, dont le backend serait développé par Simon et Niko, et un serveur ActivityPub qui serait éventuellement développé en commun par Sébastien et Adu.

L'idée est de mutualiser le stockage en triple store, en passant pour les deux projets par un triple store Jena qui gère les WebACL.

Mais la question légitime qu'on peut se poser, c'est: ne pourrait-on pas greffer le serveur ActivityPub sur le backend SemApps (qui se veut le plus universel possible), afin de mutualiser le développement et réduire les coûts ?

### Qu'est-ce que SOLiD/LDP ?

* Le protocole LDP, qui est une des briques de SOLiD, a pour but de mettre à disposition des "serveurs universels" en read-write qui permettent à n'importe qui d'enregistrer et partager des données sémantiques (RDF/JSON-LD) mais aussi des données binaires (images...), tout en gardant le contrôle complet des droits sur ces données.

### Qu'est-ce qu'ActivityPub ?

* ActivityPub a été développé par le SocialWG du W3 dans le but de proposer un protocole pour faire du réseau social décentralisé. Il s'agissait d'offrir les fonctionnalités de base des réseaux sociaux, à savoir "follow", "like", "repost" ("announce" dans ActivityPub) et puis le fil personnalisé que les utilisateurs de Facebook, Twitter & co connaissent bien. L'idée c'est avant tout de pouvoir suivre les activités des acteurs qui nous intéressent, où qu'ils soient sur le web.

* ActivityPub est basé sur trois types d'entités:

  * Les [acteurs](https://www.w3.org/TR/activitystreams-vocabulary/#actor-types) (utilisateurs, organisations, etc.)
  * Les [activités](https://www.w3.org/TR/activitystreams-vocabulary/#activity-types) qui sont créées par ces acteurs
  * Les [objets](https://www.w3.org/TR/activitystreams-vocabulary/#object-types) qui sont attachés aux activités telles que `Create`, `Update` et `Delete`.

* Chaque acteur émet un flux d'activités que l'on peut retrouver dans son `Outbox`. 

* Un acteur dispose également d'une `Inbox` dans laquelle sont postées les activités des acteurs qu'il suit ou celles qui lui sont directement adressés (en principe, les messages privés).

* Les activités peuvent concerner la création / mise à jour d'objets, mais aussi des activités "sociales" (`Follow`, `Like`, `Announce`). Tout est enregistré dans ce flux d'activités. 

* Les activités sont toujours adressées à un ou plusieurs destinataires, via les champs `to`/`bto`/`cc`/`bcc`. Le destinataire peut être `https://www.w3.org/ns/activitystreams#Public` pour indiquer que l'activité est publique, ou alors ses propres followers, ou alors des acteurs en particulier (ce qui permet l'envoi de messages privés) 

* Pour identifier un acteur (et potentiellement vérifier son identité via le protocole HTTP signature), on utilise soit son URI, soit son adresse de type `@user@server.com`. Dans ce cas, on passe par le protocole [Webfinger](https://tools.ietf.org/html/rfc7033) pour retrouver l'URI de l'acteur.

### Quel est le serveur ActivityPub qu'on veut développer ?

Il faut noter que le serveur ActivityPub qu'on veut développer est déjà particulier, et va nécessiter de relever de nombreux défis:

- Communication entre le client et le serveur uniquement en ActivityPub, contrairement à toutes les implémentation existantes qui ont développé un protocole maison pour le C2S.
- Stockage en triple store, et non pas en BDD relationnelle comme toutes les implémentations existantes.
- Endpoint SparQL qui prend en compte les droits d'accès spécifiques (via WebACL)
- Gestion native de tous les types d'objets ActivityStreams + de toutes les extensions d'ontologies (serveur vraiment universel)

En plus de cela, nous avons dû développer des particularités propres au serveur de Reconnexion:
- Authentification CAS
- Gestion des webhooks entrants
- Notifications push sur mobile

### Quelles sont les limites d'ActivityPub ?

ActivityPub est un nouveau protocole qui a des limites qu'il s'agira, soit de contourner par des "hacks", soit de dépasser en proposant des extensions à faire valider par la communauté (Mastodon a déjà ajouté [3 extensions](https://docs.joinmastodon.org/development/activitypub/#extensions), elles ne cassent rien mais ne sont pas encore validées).

- Avec ActivityPub, on ne peut suivre qu'un acteur, pas un objet. Si un objet peut être modifié par plusieurs acteurs, mais qu'on en suit qu'un seul, on ne sera pas tenu au courant de toutes les modifications. Quelques solutions possibles:
    - On pourrait étendre les objets ActivityStreams avec une propriété `history` qui serait une collection d'activitées liées à l'objet. Il faudrait alors ajouter la possibilité de "suivre" n'importe quel objet.
    - Autre solution: créer un "super acteur" qui reposte toutes les activités de tous les acteurs. Il suffirait de s'y abonner pour être sûr d'être tenu au courant de toutes les modifications. Mais suivant les cas, cela ne sera pas toujours optimal en terme de performances.

- ActivityPub, tout comme SOLiD, ne dit rien sur la notion de versioning. Si on veut garder un historique des modifications (`Update`) dans les flux d'activités, il faudrait pouvoir gérer ça au niveau du triple store.
    - Deux solutions alternatives : enregistrer le JSON de l'objet à l'instant T (sorte de cache permanent) ou ne référencer que l'URI (mais on perd les données de modification)

- ActivityPub a une gestion des droits très basique. En lecture, on ne peut lire une activité (et l'objet associé ?) que s'il nous est adressé ou s'il est public. En écriture, rien n'est dit sur les droits d'un utilisateur à modifié un objet créé par un autre. A priori c'est possible, mais le mécanisme pour donner les droits d'accès n'est pas défini dans ActivityPub.

### Quelle est la compatibilité entre SOLID et un serveur ActivityPub ?

A priori, il n'y a eu aucune concertation entre le groupe qui a développé le standard ActivityPub et celui qui travaille sur SOLID, les deux standards répondant à différents objectifs.

Il se trouve que JSON-LD a été choisi pour ActivityPub, ce qui ouvre ce format à l'univers du web sémantique, mais jusqu'à maintenant, aucune implémentation n'a vraiment pris en compte cette dimension. Le JSON-LD est traité comme du JSON normal par les implémentations.

Voilà un récapitulatif des différences et éventuelles compatibilités :

|                |                                                             SOLiD/LDP                                                              |                                                                                                                              ActivityPub                                                                                                                              |                 Compatibilité                  |
|:-------------- |:----------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------:|
| **Auth**       |                                                             OIDC ou WebId-OIDC ou DID                                                              |                                      [Pas spécifié](https://www.w3.org/wiki/SocialCG/ActivityPub/Authentication_Authorization) pour C2S, [HTTP signature](https://tools.ietf.org/html/draft-cavage-http-signatures-10) pour S2S                                       |                       ?                        |
| **ID**         |                                                               WebID                                                                |                                                                                                           [Webfinger](https://tools.ietf.org/html/rfc7033)                                                                                                            |                       ?                        |
| **Containers** |                                                           Basic / Direct Containers                                                           |                                              [Collections (AS)](https://www.w3.org/TR/activitystreams-core/#collections), avec possibilité d'avoir des listes ordrées                                               |                       Voir si les listes ordrées sont compatibles avec LDP                        |
| **Lecture**    |                                                      Simple GET sur des URIs                                                       |                                                                                                                        Simple GET sur des URIs                                                                                                                        |                      Oui                       |
| **Ecriture**   | On peut potentiellement écrire dans n'importe quel container si on a les droits. On peut aussi modifier une ressource directement. | On ne peut poster que des activités dans son `Outbox`, tout le reste est géré en side-effects (`Create`, `Update`, `Like`, `Follow`...). Pour la fédération (S2S), on peut POSTer dans l'Inbox d'un autre serveur, mais il faut signer les messages pour qu'ils soient acceptés |                       Oui en théorie, mais si on peut modifier directement une ressource sans passer par l'`Outbox`, ça va être compliqué à gérer au niveau du triple store.                        |
| **ACL**        |                                                               WebACL                                                               |                                          Droits définis en fonction du destinataire de l'activité. Pas de notion de droit d'écriture : dans des réseaux sociaux simples comme Mastodon, seul le créateur d'une `Note` peut la modifier                                           | A priori on pourrait utiliser WebACL en background |

#### Et les Linked Data Notifications ?

Les Linked Data Notifications (LDN) ne sont pas prises en compte dans le tableau ci-dessus car elles sont incompatibles avec ActivityPub.

Les LDN permettent uniquement d'attacher une `Inbox` à une ressources existante. On peut ensuite POSTer des ressources sur cette `Inbox`. En lisant cette `Inbox`, on peut voir ces ressources sous forme de Containers LDP... ou pas (rien n'est spécifié quant à la gestion ACL). Il n'y a pas d'`Outbox` dans LDN, ni de notions de Followers. Il n'y a que des `Consumers` qui peuvent lire l'Inbox (en mode pull).

Avec ActivityPub, toutes les actions passent par des "side effects". On ne poste JAMAIS dans l'`inbox` de quelqu'un, ni dans la collection `followers`, ni dans la collection `liked`, etc. Tout passe par notre `outbox`. On y poste des activités de type Follow, Like, Create, Update, Delete, etc, et le serveur s'occupe ensuite de traiter les side-effets.

La seule exception est la fédération: lorsqu'un serveur envoie une activité vers un autre serveur, alors il poste dans l'`inbox` du destinataire. Cependant le serveur de destination doit vérifier (via la signature HTTP) la validité de cet envoi, on est donc dans un processus qui sort de SOLiD.

Je n'ai entendu qu'un cas d'usage de LDN qui me parait cohérent : si on a un document, on peut y attacher une Inbox, et n'importe qui peut poster des commentaires dans cette Inbox, qui s'afficheront éventuellement en-dessous du document.

#### Et les agents des serveurs SOLiD ?

Il a été évoqué l'idée qu'ActivityPub serait géré par des agents. Il faut cependant remarquer que ces agents n'ont fait l'objet d'aucune spécification dans le cadre de SOLiD. On peut bien sûr ajouter des side-effects à un serveur LDP, mais c'est quelque chose qui sera propre à chaque implémentation.

### Conclusion provisoire de Sébastien

Un serveur ActivityPub "greffé" sur un serveur SOLiD/LDP aurait a priori peu d'intérêt, puisqu'on ne pourrait reprendre pour le serveur ActivityPub presque aucun des protocoles de SOLiD. Tout devrait être redéveloppé, si ce n'est la connexion au triple store.

On pourrait bien sûr développer le serveur ActivityPub comme un "middleware" du serveur SOLiD en NodeJS, mais s'il s'agit juste de faire le pont, on peut aussi envisager un système de webhook très simple pour permettre au serveur SOLiD d'envoyer les données au serveur ActivityPub. On n'aurait alors pas la contrainte de développer dans le même langage.

Reste la question de la mutualisation de certaines parties du code, mais cela nécessite alors une concertation sur le langage utilisé.

-------------

## Partie 2: comment ajouter ActivityPub à SemApps ?

### Problématique

On a toujours évoqué l'idée qu'il faudrait que SemApps soit compatible ActivityPub, mais on gardait cette problématique pour plus tard, puisqu'on avait déjà assez de problèmes à régler.

Au vu des questions qui se posent ci-dessus, j'ai cependant pris le temps de lister les besoins qu'on pourrait avoir à l'avenir en terme d'ActivityPub, afin d'avoir déjà une vision de ce qu'il y aurait à faire, et je propose une implémentation via des webhooks.

### Besoins (user stories)

#### Etape 1: permettre à des serveurs ActivityPub extérieurs de suivre les activités d'un acteur sur SemApps

Il s'agit d'inclure SemApps dans le graphe global des données ActivityPub, pour permettre à n'importe quelle application ActivityPub de se connecter au flux d'activité qui est généré par un acteur sur une instance donnée.

- Permettre à tout utilisateur / organisation sur SemApps de devenir un acteur ActivityPub, avec implémentation des endpoints `/inbox`, `/outbox` et `/followers`
- Inscrire chacun des opérations CRUD de l'acteur dans son flux d'activités (Outbox)
- Traiter les requêtes `Follow` venant d'autres serveurs
- Faire suivre aux followers le flux d'activité, à condition que la ressource manipulée soit lisible par l'utilisateur connecté (un follower n'a pas forcément les droits de lecture sur tous les objets)

#### Etape 2: Rendre visible sur SemApps le flux d'activités des acteurs

Une fois qu'on a rendu public le flux d'activités d'un acteur, il ne serait pas forcément compliqué de le rendre visible directement sur SemApps, ou sur n'importe quelle autre interface. Il suffirait de lire l'`Outbox`.

- Sur la page d'un acteur (utilisateur / organisation), afficher son flux d'activité (`Outbox`), en filtrant selon les droits de l'utilisateur loggé (ou en ne montrant que les activités publiques si l'utilisateur n'est pas connecté)

> Note: A ce stade, on ne pourrait pas voir les anciennes versions des ressources modifiées, sauf si on implémente le versionning avant l'étape 4, par exemple dès la première version de SemApps.

#### Etape 3: Intégrer la notion de fil personnalisé à SemApps

Là on passe à une autre échelle, puisque SemApps prend les allures d'un réseau social, avec un fil personnalisé regroupant les activités de tous les acteurs qu'on suit.

- Permettre à un utilisateur SemApps de suivre un autre utilisateur / organisation, y compris sur une autre instance, en ajoutant un bouton "Suivre" sur la page de cet acteur.

- Ajouter à SemApps, ou sur une autre interface, la possibilité de voir sa propre Inbox, qui contiendrait les activités de tous les acteurs qu'on suit.

> Note: On pourrait aussi imaginer un bouton "Suggérer à des amis" qui permettrait d'envoyer à tous ses followers - ou à des utilisateurs en particulier - la suggestion de suivre un acteur. ~~Cela nécessiterait une petite extension d'ActivityPub, qui ne serait pas forcément difficile à mettre en oeuvre.~~ [L'activité `Offer` permet ce genre de choses](https://socialhub.activitypub.rocks/t/idea-for-a-new-suggest-activity/328/6).

#### Etape 4: Afficher l'historique d'une ressource

Afficher l'historique des activités d'une ressource peut-être précieux, surtout dans le cas d'objets qui sont fréquemment modifiés. On s'approcherait du fonctionnement d'un wiki, en donnant plus largement les droits de modifications sans risquer de perdre les données.

- Etendre les objets ActivityStreams pour ajouter une propriété `history`, qui contiendrait la liste ordonnée de toutes les activités liées à cette ressource (Follow, Update, Like...).

- Pouvoir voir une ressource à un instant T. Ou encore mieux: voir la différence d'une ressource entre deux instants.

#### Etape 5: Pouvoir suivre les activités liées à une ressource

Cela permettrait à un logiciel externe (type YesWiki) d'être tenu au courant de toutes les modifications d'une ressource, quel que soit l'acteur qui l'a modifiée. On pourrait ainsi garder un cache des ressources, sans avoir à les requêter à chaque fois pour savoir si elles ont été mises à jour. On pourrait même généraliser ce fonctionnement aux communications inter-instances SemApps.

- Etendre les objets ActivityStreams en ajoutant une propriété de type `subscribers` et ajouter une propriété de type `subscriptions` aux acteurs (cela éviterait de confondre avec l'activité `Follow` ?)

- Quand une activité est effectuée sur un objet (`Update`, `Delete`, `Like`), envoyer automatiquement toute l'activité aux followers, en signant le message dans le cas de fédération.

### Possible implémentation via des webhooks et un serveur "externe"

Il suffirait que SemApps implémente un système de webhooks sortant lors des opérations CRUD et qu'il envoie les donnée au serveur ActivityPub (qui gère déjà les webhooks entrant dans [sa version prototype actuelle](https://github.com/reconnexion/reconnexion-server))

On aurait alors deux serveurs indépendants, qui pourraient toutefois se baser sur une instance Jena unique (avec toutefois deux datasets, pour éviter les conflits de version)

| Serveur SOLiD/LDP | Serveur ActivityPub |
|:-----------------:|:-------------------:|
|   Jena Dataset    |    Jena Dataset     |

Avec trois container Docker, on a tout ficellé sur la même machine.

#### Etape 1

- A chaque création d'utilisateur ou d'organisation, créer un acteur (`User` / `Organisation`) correspondant sur le serveur ActivityPub

- A chaque opération CRUD, envoyer l'activité au webhook, en faisant attention de respecter les droits de lecture.

#### Etape 2 et 3

- Rien à faire de particulier, on se contente de lire les `inbox` et `outbox` des acteurs. Il faudra juste réfléchir à comment authentifier l'utilisateur connecté sur le serveur ActivityPub.

#### Etape 4 et 5

- On est dans de l'extension ActivityPub pure. Il n'y a pas plus de travail à faire en l'implémentant sur un serveur ActivityPub externe.