Verified Commit 3ba2b3bb authored by Loïc Dachary's avatar Loïc Dachary
Browse files

activitypub: implement Repository


Signed-off-by: Loïc Dachary's avatarLoïc Dachary <loic@dachary.org>
parent 24e1c3dc
Pipeline #632 passed with stage
in 22 minutes and 4 seconds
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integrations
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"sort"
"testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/migrations"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/stretchr/testify/assert"
)
func TestActivityPubRepository(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
AllowedDomains := setting.Migrations.AllowedDomains
setting.Migrations.AllowedDomains = "loopback"
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
setting.Migrations.AllowLocalNetworks = true
setting.Federation.Enabled = true
setting.Database.LogSQL = true
AppVer := setting.AppVer
setting.AppVer = "1.15"
defer func() {
setting.Migrations.AllowedDomains = AllowedDomains
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
setting.Federation.Enabled = false
setting.Database.LogSQL = false
setting.AppVer = AppVer
}()
assert.NoError(t, migrations.Init())
username := "user2"
reponame := "repo1"
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
assert.False(t, repo1.HasProjectBase())
req := NewRequestf(t, "GET", fmt.Sprintf("/api/v1/activitypub/repos/%s/%s", username, reponame))
resp := MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), "@context")
var m map[string]interface{}
_ = json.Unmarshal(resp.Body.Bytes(), &m)
var repository vocab.ForgeFedRepository
resolver, _ := streams.NewJSONResolver(func(c context.Context, r vocab.ForgeFedRepository) error {
repository = r
return nil
})
ctx := context.Background()
err := resolver.Resolve(ctx, m)
assert.NoError(t, err)
assert.Equal(t, "Repository", repository.GetTypeName())
assert.Equal(t, reponame, repository.GetActivityStreamsName().Begin().GetXMLSchemaString())
keyID := repository.GetJSONLDId().GetIRI().String()
assert.Regexp(t, fmt.Sprintf("activitypub/repos/%s/%s$", username, reponame), keyID)
urlIter := repository.GetActivityStreamsUrl().Begin()
assert.Regexp(t, fmt.Sprintf("^ssh:.*/%s/%s.projectbase.git", username, reponame), urlIter.GetIRI().String())
urlIter = urlIter.Next()
assert.Regexp(t, fmt.Sprintf("^http.*/%s/%s.projectbase.git", username, reponame), urlIter.GetIRI().String())
assert.True(t, repo1.HasProjectBase())
gitRepo, err := git.OpenRepository(repo1.ProjectBasePath())
fmt.Println(repo1.ProjectBasePath())
assert.NoError(t, err)
defer gitRepo.Close()
files := []string{"repo.yml", "issue.yml", "comments"}
filelist, err := gitRepo.LsTree("HEAD", files...)
assert.NoError(t, err)
sort.Strings(filelist)
var expected = append(files, "")
sort.Strings(expected)
assert.Equal(t, expected, filelist)
})
}
func TestActivityPubMissingRepository(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
setting.Federation.Enabled = true
defer func() {
setting.Federation.Enabled = false
}()
req := NewRequestf(t, "GET", "/api/v1/activitypub/repos/nonexistentuser/nonexistentrepo")
resp := MakeRequest(t, req, http.StatusNotFound)
assert.Contains(t, resp.Body.String(), "GetUserByName")
})
}
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package activitypub
import (
"net/http"
"net/url"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
projectbase_service "code.gitea.io/gitea/services/projectbase"
"github.com/go-fed/activity/streams"
)
// Repository function
func Repository(ctx *context.APIContext) {
// swagger:operation GET /activitypub/repos/{username}/{reponame} activitypub activitypubRepository
// ---
// summary: Returns the repository
// produces:
// - application/json
// parameters:
// - name: username
// in: path
// description: username of the user
// type: string
// required: true
// - name: reponame
// in: path
// description: name of the repo
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/ActivityPub"
repository := ctx.Repo.Repository
if err := projectbase_service.InitProjectBase(repository); err != nil {
ctx.Error(http.StatusInternalServerError, "InitProjectBase", err)
}
//
// Transient token
//
token := &models.AccessToken{
UID: repository.Owner.ID,
Name: "projectbase",
}
if err := models.NewAccessToken(token); err != nil {
ctx.Error(http.StatusInternalServerError, "NewAccessToken", err)
}
defer func() {
if err := models.DeleteAccessTokenByID(token.ID, token.UID); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteAccessTokenByID", err)
}
}()
if err := projectbase_service.UpdateProjectBase(token.TokenHash, repository); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateProjectBase", err)
}
r := streams.NewForgeFedRepository()
id := streams.NewJSONLDIdProperty()
link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/")
idIRI, _ := url.Parse(link)
id.SetIRI(idIRI)
r.SetJSONLDId(id)
name := streams.NewActivityStreamsNameProperty()
name.AppendXMLSchemaString(ctx.Params("reponame"))
r.SetActivityStreamsName(name)
projectbase := repository.ProjectBaseCloneLink()
urlProp := streams.NewActivityStreamsUrlProperty()
urlObject, _ := url.Parse(projectbase.SSH)
urlProp.AppendIRI(urlObject)
urlObject, _ = url.Parse(projectbase.HTTPS)
urlProp.AppendIRI(urlObject)
r.SetActivityStreamsUrl(urlProp)
var jsonmap map[string]interface{}
jsonmap, _ = streams.Serialize(r)
ctx.JSON(http.StatusOK, jsonmap)
}
......@@ -603,6 +603,9 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
m.Get("", activitypub.Person)
})
m.Post("/user/{username}/inbox", activitypub.ReqSignature(), activitypub.PersonInbox)
m.Group("/repos/{username}/{reponame}", func() {
m.Get("", activitypub.Repository)
}, repoAssignment())
})
}
m.Get("/signing-key.gpg", misc.SigningKey)
......
......@@ -23,6 +23,39 @@
},
"basePath": "{{AppSubUrl | JSEscape | Safe}}/api/v1",
"paths": {
"/activitypub/repos/{username}/{reponame}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"activitypub"
],
"summary": "Returns the repository",
"operationId": "activitypubRepository",
"parameters": [
{
"type": "string",
"description": "username of the user",
"name": "username",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "reponame",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/ActivityPub"
}
}
}
},
"/activitypub/user/{username}": {
"get": {
"produces": [
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment