Documentation for federated-star (#639)
Preview: * https://forgejo.codeberg.page/@docs_pull_639/docs/next/developer/federation-architecture/ * https://forgejo.codeberg.page/@docs_pull_639/docs/next/developer/threat-analysis/ * https://forgejo.codeberg.page/@docs_pull_639/docs/next/developer/adr/ Co-authored-by: patdyn <erik.seiert@meissa-gmbh.de> Co-authored-by: Clemens <clemens.geibel@meissa-gmbh.de.de> Reviewed-on: https://codeberg.org/forgejo/docs/pulls/639 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Reviewed-by: Panagiotis "Ivory" Vasilopoulos <git@n0toose.net> Co-authored-by: Michael Jerger <michael.jerger@meissa-gmbh.de> Co-committed-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
17
README.md
|
@ -74,6 +74,23 @@ name of the current branch from which the preview is run.
|
||||||
|
|
||||||
Modifications can be made to the docs while the dev server is running, and the preview will live-reload.
|
Modifications can be made to the docs while the dev server is running, and the preview will live-reload.
|
||||||
|
|
||||||
|
#### Mermaid image generation
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm run mermaid
|
||||||
|
```
|
||||||
|
|
||||||
|
This command generates svg images from [Mermaid](https://github.com/mermaid-js/mermaid) code.
|
||||||
|
|
||||||
|
Mermaid code should be stored in `docs/_mermaid/` with their path being relative to the markdown files where the diagrams are used.
|
||||||
|
|
||||||
|
The images are generated in `docs/_mermaid/_images/` in their respective relative paths, from where they can be referenced. The mermaid generator generates .svg out of [mermaid].md files. The generated .svg get a -[n].svg postfix, more than one diagram can be described in one [mermaid].md file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- [docs/\_mermaid/developer/adr/adr-how-to-trigger-activities.md](docs/_mermaid/developer/adr/adr-how-to-trigger-activities.md) generates
|
||||||
|
- [docs/\_mermaid/\_images/developer/adr/adr-how-to-trigger-activities-_n_.svg](docs/_mermaid/_images/developer/adr/)
|
||||||
|
|
||||||
#### Linting and formatting
|
#### Linting and formatting
|
||||||
|
|
||||||
We use two linters to check that all content is formatted in a consistent way.
|
We use two linters to check that all content is formatted in a consistent way.
|
||||||
|
|
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 11 KiB |
22
docs/_mermaid/developer/adr/adr-how-to-trigger-activities.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
U(User) -->|Press Star on UI| A(A: repository - follow C by incident)
|
||||||
|
A ~~~ B(B: follow A)
|
||||||
|
B ~~~ C(C: follow B)
|
||||||
|
C ~~~ A
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
U(User) -->|Press Star on UI| A(A: repository - follow C by incident)
|
||||||
|
A -->|federate Like Activity| B(B: follow A)
|
||||||
|
B -->|federate Like Activity| C(C: follow B)
|
||||||
|
C -->|federate Like Activity| A
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
U(User) -->|Press Star on UI| A(A: repository - follow C not allowed)
|
||||||
|
A -->|federate Like Activity| B(B: follow A)
|
||||||
|
A -->|federate Like Activity| C(C: follow B not allowed, has to follow A)
|
||||||
|
```
|
317
docs/_mermaid/developer/adr/adr-map-federated-person.md
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
namespace activitypub {
|
||||||
|
class Like {
|
||||||
|
ID ID
|
||||||
|
Type ActivityVocabularyType // Like
|
||||||
|
Actor Item
|
||||||
|
Object Item
|
||||||
|
}
|
||||||
|
class Actor {
|
||||||
|
ID
|
||||||
|
URL Item
|
||||||
|
Type ActivityVocabularyType // Person
|
||||||
|
Name NaturalLanguageValues
|
||||||
|
PreferredUsername NaturalLanguageValues
|
||||||
|
Inbox Item
|
||||||
|
Outbox Item
|
||||||
|
PublicKey PublicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace forgefed {
|
||||||
|
class ForgePerson {
|
||||||
|
}
|
||||||
|
class ForgeLike {
|
||||||
|
Actor PersonID
|
||||||
|
}
|
||||||
|
class ActorID {
|
||||||
|
ID string
|
||||||
|
Schema string
|
||||||
|
Path string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
UnvalidatedInput string
|
||||||
|
}
|
||||||
|
class PersonID {
|
||||||
|
AsLoginName() string // "ID-Host"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace forgejo {
|
||||||
|
class User {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
LowerName string
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
Passwd string
|
||||||
|
LoginName string
|
||||||
|
Type UserType
|
||||||
|
IsActive bool
|
||||||
|
IsAdmin bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor <|-- ForgePerson
|
||||||
|
Like <|-- ForgeLike
|
||||||
|
ActorID <|-- PersonID
|
||||||
|
|
||||||
|
ForgeLike *-- PersonID: Actor
|
||||||
|
|
||||||
|
PersonID -- User: mapped by AsLoginName() == LoginName
|
||||||
|
PersonID -- ForgePerson: links to
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
namespace activitypub {
|
||||||
|
class Like {
|
||||||
|
ID ID
|
||||||
|
Type ActivityVocabularyType // Like
|
||||||
|
Actor Item
|
||||||
|
Object Item
|
||||||
|
}
|
||||||
|
class Actor {
|
||||||
|
ID
|
||||||
|
URL Item
|
||||||
|
Type ActivityVocabularyType // Person
|
||||||
|
Name NaturalLanguageValues
|
||||||
|
PreferredUsername NaturalLanguageValues
|
||||||
|
Inbox Item
|
||||||
|
Outbox Item
|
||||||
|
PublicKey PublicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace forgefed {
|
||||||
|
class ForgePerson {
|
||||||
|
}
|
||||||
|
class ForgeLike {
|
||||||
|
Actor PersonID
|
||||||
|
}
|
||||||
|
class ActorID {
|
||||||
|
ID string
|
||||||
|
Schema string
|
||||||
|
Path string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
UnvalidatedInput string
|
||||||
|
}
|
||||||
|
class PersonID {
|
||||||
|
AsWebfinger() string // "ID@Host"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace user {
|
||||||
|
class User {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
LoginSource int64
|
||||||
|
LowerName string
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
Passwd string
|
||||||
|
LoginName string
|
||||||
|
Type UserType
|
||||||
|
IsActive bool
|
||||||
|
IsAdmin bool
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExternalLoginUser {
|
||||||
|
ExternalID string
|
||||||
|
LoginSourceID int64
|
||||||
|
RawData map[string]any
|
||||||
|
Provider string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace auth {
|
||||||
|
class Source {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
Type Type
|
||||||
|
Name string
|
||||||
|
IsActive bool
|
||||||
|
IsSyncEnabled bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor <|-- ForgePerson
|
||||||
|
Like <|-- ForgeLike
|
||||||
|
|
||||||
|
ActorID <|-- PersonID
|
||||||
|
ForgeLike *-- PersonID: Actor
|
||||||
|
PersonID -- ForgePerson: links to
|
||||||
|
PersonID -- ExternalLoginUser: mapped by AsLoginName() == ExternalID
|
||||||
|
|
||||||
|
User *-- ExternalLoginUser: ExternalLoginUser.UserID
|
||||||
|
User -- Source
|
||||||
|
ExternalLoginUser -- Source
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
namespace activitypub {
|
||||||
|
class Like {
|
||||||
|
ID ID
|
||||||
|
Type ActivityVocabularyType // Like
|
||||||
|
Actor Item
|
||||||
|
Object Item
|
||||||
|
}
|
||||||
|
class Actor {
|
||||||
|
ID
|
||||||
|
Type ActivityVocabularyType // Person
|
||||||
|
Name NaturalLanguageValues
|
||||||
|
PreferredUsername NaturalLanguageValues
|
||||||
|
Inbox Item
|
||||||
|
Outbox Item
|
||||||
|
PublicKey PublicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace forgefed {
|
||||||
|
class ForgePerson {
|
||||||
|
}
|
||||||
|
class ForgeLike {
|
||||||
|
Actor PersonID
|
||||||
|
}
|
||||||
|
class ActorID {
|
||||||
|
ID string
|
||||||
|
Schema string
|
||||||
|
Path string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
UnvalidatedInput string
|
||||||
|
}
|
||||||
|
class PersonID {
|
||||||
|
AsLoginName() string // "ID-Host"
|
||||||
|
AsWebfinger() string // "@ID@Host"
|
||||||
|
}
|
||||||
|
class FederationHost {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
HostFqdn string
|
||||||
|
}
|
||||||
|
|
||||||
|
class NodeInfo {
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace user {
|
||||||
|
class User {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
LowerName string
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
Passwd string
|
||||||
|
LoginName string
|
||||||
|
Type UserType
|
||||||
|
IsActive bool
|
||||||
|
IsAdmin bool
|
||||||
|
}
|
||||||
|
|
||||||
|
class FederatedUser {
|
||||||
|
ID int64
|
||||||
|
UserID int64
|
||||||
|
ExternalID string
|
||||||
|
FederationHost int64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor <|-- ForgePerson
|
||||||
|
Like <|-- ForgeLike
|
||||||
|
|
||||||
|
ActorID <|-- PersonID
|
||||||
|
ForgeLike *-- PersonID: Actor
|
||||||
|
ForgePerson -- PersonID: links to
|
||||||
|
FederationHost *-- NodeInfo
|
||||||
|
|
||||||
|
User *-- FederatedUser: FederatedUser.UserID
|
||||||
|
PersonID -- FederatedUser : mapped by PersonID.asWebfinger() == FederatedUser.externalID
|
||||||
|
FederatedUser -- FederationHost
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
namespace activitypub {
|
||||||
|
class Like {
|
||||||
|
ID ID
|
||||||
|
Type ActivityVocabularyType // Like
|
||||||
|
Actor Item
|
||||||
|
Object Item
|
||||||
|
}
|
||||||
|
class Actor {
|
||||||
|
ID
|
||||||
|
URL Item
|
||||||
|
Type ActivityVocabularyType // Person
|
||||||
|
Name NaturalLanguageValues
|
||||||
|
PreferredUsername NaturalLanguageValues
|
||||||
|
Inbox Item
|
||||||
|
Outbox Item
|
||||||
|
PublicKey PublicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace forgefed {
|
||||||
|
class ForgePerson {
|
||||||
|
}
|
||||||
|
class ForgeLike {
|
||||||
|
Actor PersonID
|
||||||
|
}
|
||||||
|
class ActorID {
|
||||||
|
ID string
|
||||||
|
Schema string
|
||||||
|
Path string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
UnvalidatedInput string
|
||||||
|
}
|
||||||
|
class PersonID {
|
||||||
|
AsLoginName() string // "ID-Host"
|
||||||
|
AsWebfinger() string // "@ID@Host"
|
||||||
|
}
|
||||||
|
class FederatedPerson {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
UserID int64
|
||||||
|
RawData map[string]any
|
||||||
|
ExternalID string
|
||||||
|
FederationHost int64
|
||||||
|
}
|
||||||
|
|
||||||
|
class FederationHost {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
HostFqdn string
|
||||||
|
}
|
||||||
|
|
||||||
|
class NodeInfo {
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace user {
|
||||||
|
class CommonUser {
|
||||||
|
<<Interface>>
|
||||||
|
}
|
||||||
|
class User {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
User ..<| CommonUser
|
||||||
|
|
||||||
|
Actor <|-- ForgePerson
|
||||||
|
Like <|-- ForgeLike
|
||||||
|
|
||||||
|
ActorID <|-- PersonID
|
||||||
|
ForgeLike *-- PersonID: Actor
|
||||||
|
|
||||||
|
PersonID -- ForgePerson: links to
|
||||||
|
PersonID -- FederatedPerson : mapped by PersonID.asWebfinger() == FederatedPerson.externalID
|
||||||
|
FederationHost *-- NodeInfo
|
||||||
|
FederatedPerson -- FederationHost
|
||||||
|
FederatedPerson ..<| CommonUser
|
||||||
|
```
|
116
docs/_mermaid/developer/federation-architecture.md
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
namespace activitypub {
|
||||||
|
class Activity {
|
||||||
|
ID ID
|
||||||
|
Type ActivityVocabularyType // Like
|
||||||
|
Actor Item
|
||||||
|
Object Item
|
||||||
|
}
|
||||||
|
class Actor {
|
||||||
|
ID
|
||||||
|
Type ActivityVocabularyType // Person
|
||||||
|
Name NaturalLanguageValues
|
||||||
|
PreferredUsername NaturalLanguageValues
|
||||||
|
Inbox Item
|
||||||
|
Outbox Item
|
||||||
|
PublicKey PublicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace forgefed {
|
||||||
|
class ForgePerson {
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
class ForgeLike {
|
||||||
|
Actor PersonID
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
class ActorID {
|
||||||
|
ID string
|
||||||
|
Schema string
|
||||||
|
Path string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Source string
|
||||||
|
UnvalidatedInput string
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
class PersonID {
|
||||||
|
AsLoginName() string // "ID-Host"
|
||||||
|
AsWebfinger() string // "@ID@Host"
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
class RepositoryID {
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
class FederationHost {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
HostFqdn string
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
class NodeInfo {
|
||||||
|
Source string
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor <|-- ForgePerson
|
||||||
|
Activity <|-- ForgeLike
|
||||||
|
|
||||||
|
ActorID <|-- PersonID
|
||||||
|
ActorID <|-- RepositoryID
|
||||||
|
ForgeLike *-- PersonID: Actor
|
||||||
|
ForgePerson -- PersonID: links to
|
||||||
|
FederationHost *-- NodeInfo
|
||||||
|
|
||||||
|
namespace user {
|
||||||
|
class User {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
LowerName string
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
Passwd string
|
||||||
|
LoginName string
|
||||||
|
Type UserType
|
||||||
|
IsActive bool
|
||||||
|
IsAdmin bool
|
||||||
|
NormalizedFederatedURI string
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
class FederatedUser {
|
||||||
|
ID int64
|
||||||
|
UserID int64
|
||||||
|
ExternalID string
|
||||||
|
FederationHost int64
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace repository {
|
||||||
|
class Repository {
|
||||||
|
<<Aggregate Root>>
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
class FollowingRepository {
|
||||||
|
ID int64
|
||||||
|
RepositoryID int64
|
||||||
|
ExternalID string
|
||||||
|
FederationHost int64
|
||||||
|
Validate() []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
User "1" *-- "1" FederatedUser: FederatedUser.UserID
|
||||||
|
PersonID -- FederatedUser : mapped by PersonID.ID == FederatedUser.externalID & FederationHost.ID
|
||||||
|
PersonID -- FederationHost : mapped by PersonID.Host == FederationHost.HostFqdn
|
||||||
|
FederatedUser -- FederationHost
|
||||||
|
|
||||||
|
Repository "1" *-- "n" FollowingRepository: FollowingRepository.RepositoryID
|
||||||
|
FollowingRepository -- FederationHost
|
||||||
|
```
|
|
@ -0,0 +1,46 @@
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant fs as foreign_repository_server
|
||||||
|
participant os as our_repository_server
|
||||||
|
|
||||||
|
fs ->> os: post /api/activitypub/repository-id/1/inbox {Like-Activity}
|
||||||
|
activate os
|
||||||
|
os ->> repository: load "1"
|
||||||
|
os ->> os: validate actor id inputs
|
||||||
|
activate os
|
||||||
|
os ->> FederationInfo: get by Host
|
||||||
|
os ->> os: if FederatonInfo not found
|
||||||
|
activate os
|
||||||
|
os ->> fs: get .well-known/nodeinfo
|
||||||
|
os ->> NodeInfoWellKnown: create & validate
|
||||||
|
os ->> fs: get api/v1/nodeinfo
|
||||||
|
os ->> NodeInfo: create & validate
|
||||||
|
os ->> FederationInfo: create
|
||||||
|
deactivate os
|
||||||
|
os ->> ForgeLike: validate
|
||||||
|
deactivate os
|
||||||
|
|
||||||
|
os ->> user: search for user with actor-id
|
||||||
|
os ->> os: create user if not found
|
||||||
|
activate os
|
||||||
|
os ->> fs: get /api/activitypub/user-id/{id from actor}
|
||||||
|
os ->> ForgePerson: validate
|
||||||
|
os ->> user: create user from ForgePerson
|
||||||
|
deactivate os
|
||||||
|
os ->> repository: execute star
|
||||||
|
os ->> FederationInfo: update latest activity
|
||||||
|
os -->> fs: 200 ok
|
||||||
|
deactivate os
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A(User) --> |stars a federated repository| B(foreign repository server)
|
||||||
|
B --> |Like Activity| C(our repository server)
|
||||||
|
C --> |get NodeInfoWellKnown| B
|
||||||
|
C --> |get NodeInfo| B
|
||||||
|
C --> |get Person Actor| B
|
||||||
|
C --> |cache/create federated user locally| D(our database)
|
||||||
|
C --> |cache/create NodeInfo locally| D(our database)
|
||||||
|
C --> |add star to repo locally| D
|
||||||
|
```
|
|
@ -0,0 +1,46 @@
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant fs as foreign_repository_server
|
||||||
|
participant os as our_repository_server
|
||||||
|
|
||||||
|
fs ->> os: post /api/activitypub/repository-id/1/inbox {Like-Activity}
|
||||||
|
activate os
|
||||||
|
os ->> repository: load "1"
|
||||||
|
os ->> os: validate actor id inputs
|
||||||
|
activate os
|
||||||
|
os ->> FederationInfo: get by Host
|
||||||
|
os ->> os: if FederatonInfo not found
|
||||||
|
activate os
|
||||||
|
os ->> fs: get .well-known/nodeinfo
|
||||||
|
os ->> NodeInfoWellKnown: create & validate
|
||||||
|
os ->> fs: get api/v1/nodeinfo
|
||||||
|
os ->> NodeInfo: create & validate
|
||||||
|
os ->> FederationInfo: create
|
||||||
|
deactivate os
|
||||||
|
os ->> ForgeLike: validate
|
||||||
|
deactivate os
|
||||||
|
|
||||||
|
os ->> user: search for user with actor-id
|
||||||
|
os ->> os: create user if not found
|
||||||
|
activate os
|
||||||
|
os ->> fs: get /api/activitypub/user-id/{id from actor}
|
||||||
|
os ->> ForgePerson: validate
|
||||||
|
os ->> user: create user from ForgePerson
|
||||||
|
deactivate os
|
||||||
|
os ->> repository: execute star
|
||||||
|
os ->> FederationInfo: update latest activity
|
||||||
|
os -->> fs: 200 ok
|
||||||
|
deactivate os
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A(User) --> |stars a federated repository| B(foreign repository server)
|
||||||
|
B --> |Like Activity| C(our repository server)
|
||||||
|
C --> |get NodeInfoWellKnown| B
|
||||||
|
C --> |get NodeInfo| B
|
||||||
|
C --> |get Person Actor| B
|
||||||
|
C --> |cache/create federated user locally| D(our database)
|
||||||
|
C --> |cache/create NodeInfo locally| D(our database)
|
||||||
|
C --> |add star to repo locally| D
|
||||||
|
```
|
93
docs/developer/adr/adr-activity-for-like.md
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
---
|
||||||
|
title: 'ADR: Activity for Like'
|
||||||
|
license: 'CC-BY-SA-4.0'
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Active
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
While implementing the star activity we have to take several decisions which will impact all other activities. Due to this relevance we will discuss decision with as many federation contributors as possible.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
We decided to use "2. Like Activity while source information comes from NodeInfo"
|
||||||
|
|
||||||
|
## Choices
|
||||||
|
|
||||||
|
### 1. Star Activity derived from AP Like with additional source information
|
||||||
|
|
||||||
|
```edn
|
||||||
|
# edn notation
|
||||||
|
{@context [
|
||||||
|
"as": "https://www.w3.org/ns/activitystreams#",
|
||||||
|
"forge": "https://forgefed.org/ns#",],
|
||||||
|
::as/id "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1/outbox/12345",
|
||||||
|
::as/type "Star",
|
||||||
|
::forge/source "forgejo",
|
||||||
|
::as/actor "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",
|
||||||
|
::as/object "https://codeberg.org/api/v1/activitypub/repository-id/12"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
# json notation
|
||||||
|
{"id": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1/outbox/12345",
|
||||||
|
"type": "Star",
|
||||||
|
"source": "forgejo",
|
||||||
|
"actor": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",
|
||||||
|
"object": "https://codeberg.org/api/v1/activitypub/repository-id/1",
|
||||||
|
"startTime": "2014-12-31T23:00:00-08:00",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This way of expressing stars will have the following features:
|
||||||
|
|
||||||
|
1. Actor & object may be dereferenced by (ap-)api
|
||||||
|
2. The activity can be referenced itself (e.g. in order to express a result of the triggered action)
|
||||||
|
3. Star is a special case of a Like. Star only happens in ForgeFed context. Different things should be named different ...
|
||||||
|
4. With the `source` given it would be more easy to distinguish the uri layout for object and actor id's and make implementation more straight forward
|
||||||
|
1. The `source` field reflects the software sending an activity. Values of it may be forgejo, gitlab, ...
|
||||||
|
2. Knowing the sending system will make it easier to interact with:
|
||||||
|
1. We know exactly how the actor can be dereferenced - names may be filled & used different (see: https://codeberg.org/meissa/forgejo/src/commit/7cac9806f8247963b1cdce3f2c5f5d1bc3763fbe/routers/api/v1/activitypub/repository.go#L180)
|
||||||
|
2. We know how we can validate the given references - valid uris will be different in details (see: https://codeberg.org/meissa/forgejo/src/commit/7cac9806f8247963b1cdce3f2c5f5d1bc3763fbe/models/forgefed/actor.go#L121)
|
||||||
|
5. startTime protects against The Reply Attack discussed in [threat-analysis][threat-analysis]
|
||||||
|
|
||||||
|
### 2. Like Activity while source information comes from NodeInfo
|
||||||
|
|
||||||
|
```json
|
||||||
|
# NodeInfo
|
||||||
|
{
|
||||||
|
"version": "2.1",
|
||||||
|
"software": {
|
||||||
|
"name": "gitea",
|
||||||
|
"version": "1.20.0+dev-2539-g5840cc6d3",
|
||||||
|
},
|
||||||
|
"protocols": [
|
||||||
|
"activitypub"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Like Activity
|
||||||
|
{"id": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1/outbox/12345",
|
||||||
|
"type": "Like",
|
||||||
|
"actor": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",
|
||||||
|
"object": "https://codeberg.org/api/v1/activitypub/repository-id/1",
|
||||||
|
"startTime": "2014-12-31T23:00:00-08:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This way of expressing stars will have the following features:
|
||||||
|
|
||||||
|
1. Actor & object may be dereferenced by (ap-)api
|
||||||
|
2. The activity can be referenced itself (e.g. in order to express a result of the triggered action)
|
||||||
|
3. With NodeInfo given it would be more easy to distinguish the uri layout for object and actor id's and make implementation more straight forward
|
||||||
|
1. The NodeInfo field reflects the software & version sending an activity. Values of may be gitea, forgejo, gitlab, ...
|
||||||
|
2. Knowing the sending system will it make easier to interact with:
|
||||||
|
1. We know exactly how the actor can be derefernced - names maybe filled & used different (see: https://codeberg.org/meissa/forgejo/src/commit/7cac9806f8247963b1cdce3f2c5f5d1bc3763fbe/routers/api/v1/activitypub/repository.go#L180)
|
||||||
|
2. We know how we can validate the given references - valid uris will be different in details (see: https://codeberg.org/meissa/forgejo/src/commit/7cac9806f8247963b1cdce3f2c5f5d1bc3763fbe/models/forgefed/actor.go#L121)
|
||||||
|
4. startTime protects against The Reply Attack discussed in [threat-analysis][threat-analysis]
|
||||||
|
|
||||||
|
5. [threat-analysis]: ../threat-analysis/threat-analysis-like-activity/
|
54
docs/developer/adr/adr-ddd-for-federation.md
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
title: 'ADR: DDD for Federation'
|
||||||
|
license: 'CC-BY-SA-4.0'
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
WIP
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
With federation we introduced a topic independent to existing gitea. This will give us the chance to choose other architecture principles.
|
||||||
|
|
||||||
|
Discussion see: https://codeberg.org/forgejo/discussions/issues/179
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
tbd
|
||||||
|
|
||||||
|
## Choices
|
||||||
|
|
||||||
|
### 1. Do federation stuff following Domain Driven Design in combination with Dependency Injection
|
||||||
|
|
||||||
|
**Let's do federation related stuff following DDD as whole. But let's keep the existing codebase as is.**
|
||||||
|
|
||||||
|
Reason for this proposal are:
|
||||||
|
|
||||||
|
1. Federation is new, so it might be possible to adjust the architecture for the new code. For existing (f3 code) we might find a way of migration and I offer help :-)
|
||||||
|
2. Keep the existing functionality in existing layout will keep compatibility for gitea contributions - as long as we can use them.
|
||||||
|
|
||||||
|
Code-Example can be found here: https://codeberg.org/forgejo/forgejo/pulls/4193
|
||||||
|
|
||||||
|
#### 1.1 Aspects and Properties
|
||||||
|
|
||||||
|
1. Entities and value structs: they contain as much domain logic as possible.
|
||||||
|
2. Aggregates: they are atomary units in terms of persistence. This, however, has still some room for improvement :)
|
||||||
|
3. Interfaces for infrastructure: infrastructure is encapsulated from domain logic by interfaces. This allows unit testing in combination with mocks.
|
||||||
|
4. Service: services provide logic that does not belong to a single aggregate or entity, or is using infrastructure.
|
||||||
|
5. Poor man's dependency injection: services contain infrastructure implementations. This allows to differentiate composition of used infrastructure between production or test usage. This also has room for improvement, because we do not have an application context singleton (as in Spring Boot).
|
||||||
|
|
||||||
|
#### 1.2 Expected Benefit
|
||||||
|
|
||||||
|
1. We can unittest all paths in service. Integration test has only to cover the most important paths like "Happy Path" or "Failure Path".
|
||||||
|
2. We can decide to separate `domain` from `domain_test`
|
||||||
|
3. We can split up the (in future probable) huge federation service along the domain aggregates `FederationHost`, `User`. This will lead to packages `domain/FederationHost` having a `FederationService` or `domain/User` having a `UserService`. This will help to keep the services focussed around one domain.
|
||||||
|
4. Refactorings of domain stays simple, as we have all paths under unit tests.
|
||||||
|
5. Aggregates are way to model out meaningful submodules in terms of domain. We can improve & sharpen this model in a iterative way. Therefor are Aggregates also the point defining the shape of used repositories and apis.
|
||||||
|
6. Aggregates bring the chance of performance improvements: If accessing db multiple times for one use case in my experience often leads to bad performance compared to "load all on start & save all at the end". But minimizing the times of accessing db stands against amount of data being loaded every time. A good & sharp modelled aggregate might reflect the sweet spot between both bad performing borders.
|
||||||
|
|
||||||
|
#### 1.3 See also
|
||||||
|
|
||||||
|
1. Wiki: https://en.wikipedia.org/wiki/Domain-driven_design
|
||||||
|
2. MartinFowler: https://martinfowler.com/bliki/DomainDrivenDesign.html
|
||||||
|
3. Distilled eBook: https://dl.ebooksworld.ir/motoman/AW.Domain-Driven.Design.Distilled.www.EBooksWorld.ir.pdf
|
87
docs/developer/adr/adr-how-to-trigger-activities.md
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
---
|
||||||
|
title: 'ADR: Trigger Activities'
|
||||||
|
license: 'CC-BY-SA-4.0'
|
||||||
|
---
|
||||||
|
|
||||||
|
# How to Trigger Activities
|
||||||
|
|
||||||
|
- [How to Trigger Activities](#how-to-trigger-activities)
|
||||||
|
- [Status](#status)
|
||||||
|
- [Context](#context)
|
||||||
|
- [Decision](#decision)
|
||||||
|
- [Choices](#choices)
|
||||||
|
- [1. Transient Federation without Constraints](#1-transient-federation-without-constraints)
|
||||||
|
- [Problem - Circularity And Inconsistency](#problem---circularity-and-inconsistency)
|
||||||
|
- [2. Direct Federation only](#2-direct-federation-only)
|
||||||
|
- [Discussion for option 2.](#discussion-for-option-2)
|
||||||
|
- [3. Transient Federation and Remember Processed](#3-transient-federation-and-remember-processed)
|
||||||
|
- [See also](#see-also)
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Proposal
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
While implementing the trigger of federated stars we have to handle the distribution of corresponding Like-Activities to the federated repositories.
|
||||||
|
|
||||||
|
This must be done consistently and without circularity, such that a repository starring by a user leads to exactly one added star in every federated repository.
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/adr/adr-how-to-trigger-activities-1.svg)
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
## Choices
|
||||||
|
|
||||||
|
### 1. Transient Federation without Constraints
|
||||||
|
|
||||||
|
In this case the star federation process would look like:
|
||||||
|
|
||||||
|
1. Repo on an instance receives a star (regardless of whether via UI or federation)
|
||||||
|
2. Instance federates star to all repos that are set as federated repositories.
|
||||||
|
|
||||||
|
#### Problem - Circularity And Inconsistency
|
||||||
|
|
||||||
|
Circular federation would lead to a infinite circular distribution of Like-Activities:
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/adr/adr-how-to-trigger-activities-2.svg)
|
||||||
|
|
||||||
|
1. Given a repo on the 3 instances A, B, C.
|
||||||
|
Repo on instance A has set repo on instance B as federation repo.
|
||||||
|
Repo on instance B has set repo on instance C as federation repo.
|
||||||
|
Repo on instance C has set repo on instance A as federation repo.
|
||||||
|
2. User stars repo on instance A via UI.
|
||||||
|
3. Instance A sends Like-Activity to repo on instance B.
|
||||||
|
4. Instance B creates local FederatedUser, stars the repo and sends Like-Activity to repo on instance C.
|
||||||
|
5. Instance C creates local FederatedUser, stars the repo and sends Like-Activity to repo on instance A.
|
||||||
|
6. Instance A creates local FederatedUser, since the Actor of the Like-Activity is the local FederatedUser created on instance C.
|
||||||
|
Thus, the repo on instance A gets another star by this user and sends Like-Activity to the repo on instance C.
|
||||||
|
7. The circular distribution of Like-Activities continues, since the actor is always the local FederatedUser of the sending instance.
|
||||||
|
|
||||||
|
### 2. Direct Federation only
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/adr/adr-how-to-trigger-activities-3.svg)
|
||||||
|
|
||||||
|
In this case the star federation process would look like:
|
||||||
|
|
||||||
|
1. Case: Repo on an instance receives a star by an authenticated user via UI/API:
|
||||||
|
1. Repository gets starred by the authenticated User.
|
||||||
|
2. Instance federates star to all repos that are set as federated repositories.
|
||||||
|
2. Case: Repo on an instance receives a star via a Like-Activity:
|
||||||
|
1. Instance creates FederatedUser and stars the repository.
|
||||||
|
2. No further star federation to federated repos is triggered.
|
||||||
|
|
||||||
|
#### Discussion for option 2.
|
||||||
|
|
||||||
|
1. pro
|
||||||
|
1. Prevent circular communication
|
||||||
|
2. Clear semantic also in case of "Who should authorize a digital signature"
|
||||||
|
|
||||||
|
### 3. Transient Federation and Remember Processed
|
||||||
|
|
||||||
|
In this case the star federation process would look like:
|
||||||
|
|
||||||
|
1. Repo on an instance receives a star (regardless of whether via UI or federation)
|
||||||
|
2. If this activity was not operated already in this instance, federate star to all repos that are set as federated repositories.
|
||||||
|
|
||||||
|
## See also
|
159
docs/developer/adr/adr-map-federated-person.md
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
---
|
||||||
|
title: 'ADR: Map federated Person'
|
||||||
|
license: 'CC-BY-SA-4.0'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Map federated Person
|
||||||
|
|
||||||
|
- [Map federated Person](#map-federated-person)
|
||||||
|
- [Status](#status)
|
||||||
|
- [Context](#context)
|
||||||
|
- [Decision](#decision)
|
||||||
|
- [Choices](#choices)
|
||||||
|
- [1. Map to plain Forgejo User](#1-map-to-plain-forgejo-user)
|
||||||
|
- [1. Pro](#1-pro)
|
||||||
|
- [1. Con](#1-con)
|
||||||
|
- [2. Map to User-\&-ExternalLoginUser](#2-map-to-user--externalloginuser)
|
||||||
|
- [2. Pro](#2-pro)
|
||||||
|
- [2. Con](#2-con)
|
||||||
|
- [3. Map to User-\&-FederatedUser](#3-map-to-user--federateduser)
|
||||||
|
- [3. Pro](#3-pro)
|
||||||
|
- [3. Con](#3-con)
|
||||||
|
- [4. Map to new FederatedPerson and introduce a common User interface](#4-map-to-new-federatedperson-and-introduce-a-common-user-interface)
|
||||||
|
- [4. Pro](#4-pro)
|
||||||
|
- [4. Con](#4-con)
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Active
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
While implementing federation we have to represent federated persons on a local instance.
|
||||||
|
|
||||||
|
A federated person should be able to execute local actions (as if he was a local user), ideally without too many code changes.
|
||||||
|
|
||||||
|
For being able to map the federated person reliable, the local representation has to carry a clear mapping to the original federated person.
|
||||||
|
|
||||||
|
We get actor information as `{"actor": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",}`. To find out whether this user is available locally without dereferencing the federated person every time is important for performance & system resilience.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
We decided to use option 3 "Map to User & FederatedUser".
|
||||||
|
|
||||||
|
Discussion can be found here: https://codeberg.org/forgejo/discussions/issues/101
|
||||||
|
|
||||||
|
## Choices
|
||||||
|
|
||||||
|
### 1. Map to plain Forgejo User
|
||||||
|
|
||||||
|
1. We map PersonId AsLoginName() (e.g. 13-some.instan.ce) to User.LoginName. Due to limitations of User.LoginName validation mapping may be affected by invalid characters.
|
||||||
|
2. Created User is limited:
|
||||||
|
1. non functional email is generated, email notification is false. At the moment we have problems with email whitelists at this point.
|
||||||
|
2. strong password is generated silently
|
||||||
|
3. User.Type is UserTypeRemoteUser
|
||||||
|
4. User is not Admin
|
||||||
|
5. User is not Active
|
||||||
|
|
||||||
|
#### 1. Pro
|
||||||
|
|
||||||
|
1. We can use Forgejo code (like star / unstar fkt.) without changes.
|
||||||
|
2. No new model & persistence is introduced, architectural change is small.
|
||||||
|
|
||||||
|
#### 1. Con
|
||||||
|
|
||||||
|
1. But we use fields against their semantic and see some problems / limitations for mapping arise.
|
||||||
|
1. generating email having the source fqdn is impacted by email whitelists.
|
||||||
|
2. loginName is used for mapping, but e.g. @ is not allowed.
|
||||||
|
3. password is generated headless.
|
||||||
|
2. Maybe the large User table gets even larger (see https://git.exozy.me/a/gitea/issues/2)
|
||||||
|
3. Occasional contributors may not understand the difference in level of trust implied by federated user. This may promote errors with security impact.
|
||||||
|
4. Understanding federated users entries being kind of cache would conflict with user table entries.
|
||||||
|
5. LoginNames may be occupied by federated users. This may leak information and increase attack surface.
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/adr/adr-map-federated-person-1.svg)
|
||||||
|
|
||||||
|
### 2. Map to User-&-ExternalLoginUser
|
||||||
|
|
||||||
|
1. We map PersonId.AsWebfinger() (e.g. 13@some.instan.ce) to ExternalLoginUser.ExternalID. LoginSourceID may be left Empty.
|
||||||
|
2. Created User is limited:
|
||||||
|
1. non functional email is generated, email notification is false.
|
||||||
|
2. strong password is generated silently
|
||||||
|
3. User.Type is UserTypeRemoteUser
|
||||||
|
4. User is not Admin
|
||||||
|
5. User is not Active
|
||||||
|
3. Created ExternalLoginUser is limited
|
||||||
|
1. Login via fediverse is not intended and will not work. This is distinct to the F3 usecase.
|
||||||
|
|
||||||
|
#### 2. Pro
|
||||||
|
|
||||||
|
1. We can use Forgejo code (like star / unstar fkt.) without changes.
|
||||||
|
2. No new model & persistence is introduced, architectural change is small. Comparable to option 1.
|
||||||
|
3. This option was taken by the F3-Export/Import-Feature
|
||||||
|
4. Mapping may be more reliable compared to option 1.
|
||||||
|
|
||||||
|
#### 2. Con
|
||||||
|
|
||||||
|
1. We use fields against their semantic (User.EMail, User.Password, User.LoginSource, ExternalLoginUser.Login\*) and see some problems / limitations for login functionality arise. Situation is worse than option 1.
|
||||||
|
1. generating email having the source fqdn is impacted by email whitelists.
|
||||||
|
2. password is generated headless.
|
||||||
|
3. TODO: How would we map/generate User.LoginName ?
|
||||||
|
4. TODO: How would we generate ExternalLoginUser.Login\* fields?
|
||||||
|
2. Getting a larger User table applies to this solution comparable to option 1.
|
||||||
|
3. Occasional contributors may not understand the difference in level of trust implied by federated user, this may promote errors with security impact.
|
||||||
|
4. Understanding federated users entries being kind of cache would conflict with user table entries.
|
||||||
|
5. LoginNames may be occupied by federated users. This may leak information and increase attack surface.
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/adr/adr-map-federated-person-2.svg)
|
||||||
|
|
||||||
|
### 3. Map to User-&-FederatedUser
|
||||||
|
|
||||||
|
1. We map PersonId.asWbfinger() to FederatedPerson.ExternalID (e.g. 13@some.instan.ce).
|
||||||
|
2. Created User is limited:
|
||||||
|
1. non functional email is generated, email notification is false.
|
||||||
|
2. strong password is generated silently
|
||||||
|
3. User.Type is UserTypeRemoteUser
|
||||||
|
4. User is not Admin
|
||||||
|
5. User is not Active
|
||||||
|
|
||||||
|
#### 3. Pro
|
||||||
|
|
||||||
|
1. We can use Forgejo code (like star / unstar fkt.) without changes.
|
||||||
|
2. Introduce FederatedUser as new model & persistence, architectural change is medium.
|
||||||
|
3. We will be able to have a reliable mapping. Better than option 1 & 2.
|
||||||
|
|
||||||
|
#### 3. Con
|
||||||
|
|
||||||
|
1. But we use fields (User.EMail, User.Password) against their semantic, but we probably can handle the problems arising. Situation is comparable to option 1.
|
||||||
|
1. generating email having the source fqdn is impacted by email whitelists.
|
||||||
|
2. password is generated headless.
|
||||||
|
3. TODO: How would we map/generate User.LoginName ?
|
||||||
|
2. Getting a larger User table applies to this solution comparable to option 1.
|
||||||
|
3. Occasional contributors may not understand the difference in level of trust implied by federated user, this may promote errors with security impact, comparable to option 1.
|
||||||
|
4. Getting a larger User table applies to this solution comparable to option 1.
|
||||||
|
5. Understanding federated users entries being kind of cache would conflict with user table entries.
|
||||||
|
6. LoginNames may be occupied by federated users. This may leak information and increase attack surface.
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/adr/adr-map-federated-person-3.svg)
|
||||||
|
|
||||||
|
### 4. Map to new FederatedPerson and introduce a common User interface
|
||||||
|
|
||||||
|
1. We map PersonId.asWbfinger() to FederatedPerson.ExternalID (e.g. 13@some.instan.ce).
|
||||||
|
2. We will have no semantic mismatch.
|
||||||
|
|
||||||
|
#### 4. Pro
|
||||||
|
|
||||||
|
1. We will be able to have a reliable mapping.
|
||||||
|
2. We will not use fields against their semantics.
|
||||||
|
3. We do not enhance user table with "cache entries". Forgejo stays scalable, no additional DOS surface.
|
||||||
|
4. Occasional contributors may understand a clear difference between user and federated user.
|
||||||
|
5. No LoginNames where occupied
|
||||||
|
6. Caching aspects of federated users (like refresh, evict) may be easier to implement.
|
||||||
|
|
||||||
|
#### 4. Con
|
||||||
|
|
||||||
|
1. We can use Forgejo code (like star / unstar fkt.) after refactorings only.
|
||||||
|
2. At every place of interaction we have to enhance persistence (e.g. a find may have to query two tables now) & introduce a common User interface.
|
||||||
|
3. We introduce new model & persistence.
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/adr/adr-map-federated-person-4.svg)
|
7
docs/developer/adr/index.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
title: 'Federation Architectural Decision Records'
|
||||||
|
---
|
||||||
|
|
||||||
|
- [ADR: Activity for Like](./adr-activity-for-like/)
|
||||||
|
- [ADR: Trigger Activities](./adr-how-to-trigger-activities/)
|
||||||
|
- [ADR: Map federated Person](./adr-map-federated-person/)
|
34
docs/developer/federation-architecture.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
title: Federation Architecture
|
||||||
|
license: 'CC-BY-SA-4.0'
|
||||||
|
---
|
||||||
|
|
||||||
|
While implementing federation in Forgejo we introduced some concepts from DomainDrivenDesign:
|
||||||
|
|
||||||
|
1. **Aggregate**: Aggregates are clusters of objects (entities or values) which are handled atomic when it comes to persistence.
|
||||||
|
2. **Validation**: Every object should express it's own validity, whenever someone is interested in
|
||||||
|
1. we collect as many invalidity information as possible in one shoot - so we return a list of validation issues if there are some.
|
||||||
|
2. Objects entering the lifetime are checked for validity on the borders (after loaded from and before stored to DB, after being newly created (New\* functions) or after loaded via web / REST).
|
||||||
|
|
||||||
|
Objects in forgefed package reflect Objects from ap or f3 lib but add some Forgejo specific enhancements like more specific validation.
|
||||||
|
|
||||||
|
## Federation Model
|
||||||
|
|
||||||
|
![diagram](../_mermaid/_images/developer/federation-architecture-1.svg)
|
||||||
|
|
||||||
|
## Normalized URI used as ID
|
||||||
|
|
||||||
|
In order to use URIs as ID we've to normalize URIs.
|
||||||
|
|
||||||
|
A normalized user URI looks like: `https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/1`
|
||||||
|
|
||||||
|
In order to normalize URIs we care:
|
||||||
|
|
||||||
|
1. Case (all to lower case): `https://federated-REPO.prod.meissa.de/api/v1/activitypub/user-id/1`
|
||||||
|
2. No relative path: `https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/../user-id/1`
|
||||||
|
3. No parameters: `https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/1?some-parameters=1`
|
||||||
|
4. No Webfinger: `https://user1@federated-repo.prod.meissa.de` (with following redirects)
|
||||||
|
5. No default api: `https://federated-repo.prod.meissa.de/api/activitypub/user-id/1`
|
||||||
|
6. No autorization: `https://user:password@federated-repo.prod.meissa.de/api/v1/activitypub/user-id/1`
|
||||||
|
7. No default ports: `https://federated-repo.prod.meissa.de:443/api/v1/activitypub/user-id/1`
|
||||||
|
8. Accept non default ports: `http://localhost:3000/api/v1/activitypub/user-id/1`
|
|
@ -16,6 +16,9 @@ their needs.
|
||||||
- [Development environment](./development-environment/)
|
- [Development environment](./development-environment/)
|
||||||
- [Interface customization](./customization/)
|
- [Interface customization](./customization/)
|
||||||
- [Architecture overview](./architecture/)
|
- [Architecture overview](./architecture/)
|
||||||
|
- [Federation Architecture](./federation-architecture/)
|
||||||
|
- [Federation Architectural Decision Records](./adr/)
|
||||||
|
- [Threat Analysis](./threat-analysis/)
|
||||||
- [Developer Certificate of Origin (DCO)](./dco/)
|
- [Developer Certificate of Origin (DCO)](./dco/)
|
||||||
- [code.forgejo.org](./code-forgejo-org/)
|
- [code.forgejo.org](./code-forgejo-org/)
|
||||||
- [next.forgejo.org](./next-forgejo-org/)
|
- [next.forgejo.org](./next-forgejo-org/)
|
||||||
|
|
6
docs/developer/threat-analysis/index.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
title: 'Threat Analysis'
|
||||||
|
---
|
||||||
|
|
||||||
|
- [Threat Analysis: Federated Like Activity](./threat-analysis-like-activity/)
|
||||||
|
- [Threat Analysis: Remote Login Propagation](./threat-analysis-remote-login-propagation/)
|
113
docs/developer/threat-analysis/threat-analysis-like-activity.md
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
---
|
||||||
|
title: 'Threat Analysis: Federated Like Activity'
|
||||||
|
license: 'CC-BY-SA-4.0'
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Background
|
||||||
|
|
||||||
|
### Control Flow
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/threat-analysis/threat-analysis-like-activity-1.svg)
|
||||||
|
|
||||||
|
### Data transferred
|
||||||
|
|
||||||
|
```
|
||||||
|
# NodeInfoWellKnown
|
||||||
|
{"links":[
|
||||||
|
{"href":"https://federated-repo.prod.meissa.de/api/v1/nodeinfo",
|
||||||
|
"rel":"http://nodeinfo.diaspora.software/ns/schema/2.1"}]}
|
||||||
|
|
||||||
|
# NodeInfo
|
||||||
|
{"version":"2.1",
|
||||||
|
"software":{"name":"gitea",
|
||||||
|
...}}
|
||||||
|
|
||||||
|
# LikeActivity
|
||||||
|
{"id": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1/outbox/12345",
|
||||||
|
"type": "Like",
|
||||||
|
"actor": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",
|
||||||
|
"object": "https://codeberg.org/api/v1/activitypub/repository-id/12"
|
||||||
|
"startTime": "2014-12-31T23:00:00-08:00"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Person
|
||||||
|
{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
|
||||||
|
"type":"Person",
|
||||||
|
"preferredUsername":"stargoose9",
|
||||||
|
"name": "goose going to star the repo",
|
||||||
|
"publicKey":{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10#main-key",
|
||||||
|
"owner":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
|
||||||
|
"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBoj...XAgMBAAE=\n-----END PUBLIC KEY-----\n"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/threat-analysis/threat-analysis-like-activity-2.svg)
|
||||||
|
|
||||||
|
## Analysis
|
||||||
|
|
||||||
|
### Assets
|
||||||
|
|
||||||
|
1. **Service Availability**: The availability of our or foreign servers.
|
||||||
|
2. **Instance Reputation**: We hope our project does not live on a spam instance.
|
||||||
|
3. **Project Reputation**: The reputation of an individual project.
|
||||||
|
|
||||||
|
### Actors
|
||||||
|
|
||||||
|
1. **Script Kiddies**: Boored teens, willing to do some illegal stuff without deep knowledge of tech details but broad knowledge across internet discussions. Able to do some bash / python scripting.
|
||||||
|
2. **Experienced Hacker**: Hacker with deep knowledge.
|
||||||
|
3. **Hacker**: Hacker with some knowledge.
|
||||||
|
4. **Malicious Fediverse Member**: Malicious Members of the fediverse, able to operate malicious forge instances.
|
||||||
|
5. **Malicious Forge Admin**: Admin of good reputation forge instance in the fediverse.
|
||||||
|
6. **Federated User**: Members of good reputation forge instance in the fediverse.
|
||||||
|
|
||||||
|
### Threat
|
||||||
|
|
||||||
|
1. **Knock foreign http server**: Script Kiddi sends a Like Activity containing an attack actor url `http://attacked.target/very/special/path` in place of actor. Our repository server sends a `get Person Actor` request to this url. The target receives a DenialdOfService attack. We loose CPU & instance reputation.
|
||||||
|
2. **Sql injection**: Experienced hacker sends a Like Activity containing an actor url pointing to an evil forgejo instance. Our repository server sends an `get Person Actor` request to this instance and gets a person having sth. like `; drop database;` in its name. If our server tries to create a new user out of this person, the db might be dropped.
|
||||||
|
3. **Malicious Activities**: Malicious Fediverse Member sends Star Activities containing non authorized Person Actors. The Actors listed as stargazer might get angry about this. The project may loose project reputation.
|
||||||
|
4. **DOS by Rate**: Experienced Hacker records activities sent and replays some of them. Without order of activities (i.e. timestamp) we can not decide wether we should execute the activity again. If the replayed activities are UnLike Activity we might loose stars.
|
||||||
|
5. **Replay**: Experienced Hacker records activities sends a massive amount of activities which leads to new user creation & storage loss. Our instance might fall out of service. See also [replay attack@wikipedia][2].
|
||||||
|
6. **Replay out of Order**: Experienced Hacker records activities sends again Unlike Activities happened but was succeeded by an Like. Our instance accept the Unlike and removes a star. Our repository gets rated unintended bad.
|
||||||
|
7. **DOS by Slowlories**: Experienced Hacker may craft their malicious server to keep connections open. Then they send a Like Activity with the actor URL pointing to that malicious server, and your background job keeps waiting for data. Then they send more such requests, until you exhaust your limit of file descriptors openable for your system and cause a DoS (by causing cascading failures all over the system, given file descriptors are used for about everything, from files, to sockets, to pipes). See also [Slowloris@wikipedia][1].
|
||||||
|
8. **Saturate by future StartTime**: Hacker sends an Activity having `startTime` in far future. Our Instance does no longer accept Activities till they have far far future `startTime` from the actors instance.
|
||||||
|
9. **Malicious Forge**: If a "Malicious Fediverse Member" deploys an 'federated' forge that sends the right amount of Like activities to not hit the rate limiter, an malicious user can modify the code of any 'federated' forge to ensure that if an foreign server tries to verify and activity, it will always succeed (such as creating users on demand, or simply mocking the data).
|
||||||
|
10. **Malicious Controlled Forge**: A "Malicious Forge Admin" of a good reputation instance may impersonate users on his instance and trigger federated activities.
|
||||||
|
11. **Side Chanel Malicious Activities**: A Owner of a good reputation instance may craft malicious activities with the hope not to get moderated.
|
||||||
|
|
||||||
|
### Mitigations
|
||||||
|
|
||||||
|
1. Validate object uri in order to send only requests to well defined endpoints.
|
||||||
|
2. xorm global SQL injection protection.
|
||||||
|
3. We accept only signed Activities
|
||||||
|
4. We accept only activities having an startTime & remember the last executed activity startTime.
|
||||||
|
5. We introduce (or have) rate limiting per IP.
|
||||||
|
6. We ensure, that outgoing HTTP requests have a reasonable timeout (if you didn't get that 500b JSON response after 10 seconds, you probably won't get it).
|
||||||
|
7. **Instance Level Moderation** (such as blocking other federated forges) can mitigate "Malicious Forge"
|
||||||
|
8. **User Level Moderation** (such as blocking other federated users) can mitigate "Side Chanel Malicious Activities"
|
||||||
|
|
||||||
|
### DREAD-Score
|
||||||
|
|
||||||
|
| Threat | Damage | Reproducibility | Exploitability | Affected Users | Discoverability | Mitigations |
|
||||||
|
| :----- | :------ | :-------------- | :------------- | :------------- | :-------------- | :---------- |
|
||||||
|
| 1. | ... tbd | | | | | |
|
||||||
|
| 2. | ... tbd | | | | | |
|
||||||
|
|
||||||
|
Threat Score with values between 1 - 6
|
||||||
|
|
||||||
|
- Damage – how severe would the damage be if the attack is successful? 6 is a very bad damage.
|
||||||
|
- Reproducibility – how easy would the attack be reproducible? 6 is very easy to reproduce.
|
||||||
|
- Exploitability – How much time, effort and experience are necessary to exploit the threat? 6 is very easy to make.
|
||||||
|
- Affected Users – if a threat were exploited, how many percentage of users would be affected?
|
||||||
|
- Discoverability – How easy can an attack be discovered? Does the attacker have to expect prosecution? 6 is very hard to discover / is not illegal
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
In addition to direct committer our special thanks goes to the experts joining our discussions:
|
||||||
|
|
||||||
|
- [kik](https://codeberg.org/oelmekki)
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
[1]: https://en.wikipedia.org/wiki/Slowloris_(computer_security)
|
||||||
|
[2]: https://en.wikipedia.org/wiki/Replay_attack
|
|
@ -0,0 +1,93 @@
|
||||||
|
---
|
||||||
|
title: 'Threat Analysis: Remote Login Propagation'
|
||||||
|
license: 'CC-BY-SA-4.0'
|
||||||
|
---
|
||||||
|
|
||||||
|
See also [blog: geballte sicherheit][1] for getting an idea about the analysis.
|
||||||
|
|
||||||
|
## Technical Background
|
||||||
|
|
||||||
|
### Control Flow
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/threat-analysis/threat-analysis-remote-login-propagation-1.svg)
|
||||||
|
|
||||||
|
### Data transferred
|
||||||
|
|
||||||
|
```
|
||||||
|
# NodeInfoWellKnown
|
||||||
|
{"links":[
|
||||||
|
{"href":"https://federated-repo.prod.meissa.de/api/v1/nodeinfo",
|
||||||
|
"rel":"http://nodeinfo.diaspora.software/ns/schema/2.1"}]}
|
||||||
|
|
||||||
|
# NodeInfo
|
||||||
|
{"version":"2.1",
|
||||||
|
"software":{"name":"gitea",
|
||||||
|
...}}
|
||||||
|
|
||||||
|
# LikeActivity
|
||||||
|
{"id": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1/outbox/12345",
|
||||||
|
"type": "Like",
|
||||||
|
"actor": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",
|
||||||
|
"object": "https://codeberg.org/api/v1/activitypub/repository-id/12"
|
||||||
|
"startTime": "2014-12-31T23:00:00-08:00"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Person
|
||||||
|
{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
|
||||||
|
"type":"Person",
|
||||||
|
"preferredUsername":"stargoose9",
|
||||||
|
"name": "goose going to star the repo",
|
||||||
|
"publicKey":{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10#main-key",
|
||||||
|
"owner":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
|
||||||
|
"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBoj...XAgMBAAE=\n-----END PUBLIC KEY-----\n"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
![diagram](../../_mermaid/_images/developer/threat-analysis/threat-analysis-remote-login-propagation-2.svg)
|
||||||
|
|
||||||
|
## Analysis
|
||||||
|
|
||||||
|
### Assets
|
||||||
|
|
||||||
|
1. **Service Availability**: The availability of our or foreign servers.
|
||||||
|
2. **Instance Reputation**: We hope our project does not live on a spam instance.
|
||||||
|
3. **Project Reputation**: The reputation of an individual project.
|
||||||
|
|
||||||
|
### Actors
|
||||||
|
|
||||||
|
1. **Script Kiddies**: Boored teens, willing to do some illegal stuff without deep knowledge of tech details but broad knowledge across internet discussions. Able to do some bash / python scripting.
|
||||||
|
2. **Experienced Hacker**: Hacker with deep knowledge.
|
||||||
|
3. **Hacker**: Hacker with some knowledge.
|
||||||
|
4. **Malicious Fediverse Member**: Malicious Members of the fediverse, able to operate malicious forge instances.
|
||||||
|
5. **Malicious Forge Admin**: Admin of good reputation forge instance in the fediverse.
|
||||||
|
6. **Federated User**: Members of good reputation forge instance in the fediverse.
|
||||||
|
|
||||||
|
### Threat
|
||||||
|
|
||||||
|
1. tbd
|
||||||
|
|
||||||
|
### Mitigations
|
||||||
|
|
||||||
|
1. tbd
|
||||||
|
|
||||||
|
### DREAD-Score
|
||||||
|
|
||||||
|
| Threat | Damage | Reproducibility | Exploitability | Affected Users | Discoverability | Mitigations |
|
||||||
|
| :----- | :------ | :-------------- | :------------- | :------------- | :-------------- | :---------- |
|
||||||
|
| 1. | ... tbd | | | | | |
|
||||||
|
| 2. | ... tbd | | | | | |
|
||||||
|
|
||||||
|
Threat Score with values between 1 - 6
|
||||||
|
|
||||||
|
- Damage – how severe would the damage be if the attack is successful? 6 is a very bad damage.
|
||||||
|
- Reproducibility – how easy would the attack be reproducible? 6 is very easy to reproduce.
|
||||||
|
- Exploitability – How much time, effort and experience are necessary to exploit the threat? 6 is very easy to make.
|
||||||
|
- Affected Users – if a threat were exploited, how many percentage of users would be affected?
|
||||||
|
- Discoverability – How easy can an attack be discovered? Does the attacker have to expect prosecution? 6 is very hard to discover / is not illegal
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
[1]: https://owasp.org/www-community/Threat_Modeling_Process
|
|
@ -13,7 +13,8 @@
|
||||||
"lint:prettier": "prettier --check .",
|
"lint:prettier": "prettier --check .",
|
||||||
"format:remark": "remark . --quiet --frail --output",
|
"format:remark": "remark . --quiet --frail --output",
|
||||||
"format:prettier": "prettier -w --cache .",
|
"format:prettier": "prettier -w --cache .",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install",
|
||||||
|
"mermaid": "./scripts/mermaid_image_generate.sh"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.md": "remark --quiet --frail",
|
"*.md": "remark --quiet --frail",
|
||||||
|
@ -39,6 +40,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@mermaid-js/mermaid-cli": "^10.9.1",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^14.0.0",
|
"lint-staged": "^14.0.0",
|
||||||
"prettier": "^3.0.2",
|
"prettier": "^3.0.2",
|
||||||
|
|
985
pnpm-lock.yaml
24
scripts/mermaid_image_generate.sh
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
current_branch=$(git branch --show-current)
|
||||||
|
repo_path=$(pwd)
|
||||||
|
mermaid_path=$repo_path/docs/_mermaid
|
||||||
|
mermaid_img_path=$repo_path/docs/_mermaid/_images
|
||||||
|
|
||||||
|
# Clean generated svg files
|
||||||
|
for file in $(find $mermaid_img_path -type f -name "*.svg" -printf "%p\n")
|
||||||
|
do
|
||||||
|
rm $file
|
||||||
|
done
|
||||||
|
|
||||||
|
# Generate svg files
|
||||||
|
for file in $(find $mermaid_path -path $mermaid_img_path -prune -o -type f -name "*.md" -printf "%p\n")
|
||||||
|
do
|
||||||
|
echo "MD FILE: $file"
|
||||||
|
dir_md=$(dirname "$file")
|
||||||
|
dir_relative=${dir_md#*docs/_mermaid/}
|
||||||
|
dir_output=$(echo "$mermaid_img_path/$dir_relative")
|
||||||
|
mkdir -p $dir_output
|
||||||
|
name=$(basename "$file" ".md")
|
||||||
|
$repo_path/node_modules/.bin/mmdc -i $file -o $(echo "$dir_output/$name.svg")
|
||||||
|
done
|
|
@ -3,6 +3,9 @@
|
||||||
current_branch=$(git branch --show-current)
|
current_branch=$(git branch --show-current)
|
||||||
repo_path=$(pwd)
|
repo_path=$(pwd)
|
||||||
|
|
||||||
|
# Generate mermaid images
|
||||||
|
./scripts/mermaid_image_generate.sh
|
||||||
|
|
||||||
# Clone the website repo, or make sure the current clone is up to date
|
# Clone the website repo, or make sure the current clone is up to date
|
||||||
if [ ! -e "./.preview" ];then
|
if [ ! -e "./.preview" ];then
|
||||||
git clone https://codeberg.org/forgejo/website.git .preview
|
git clone https://codeberg.org/forgejo/website.git .preview
|
||||||
|
|