wowee some serious moving stuff around
This commit is contained in:
parent
cc424df169
commit
41e6e8ed10
|
@ -34,5 +34,5 @@ type ClientModule interface {
|
|||
// of functionalities and/or side effects to a router, by mapping routes and/or middlewares onto it--in other words, a REST API ;)
|
||||
// Unlike ClientAPIModule, federation API module is not intended to be interacted with by clients directly -- it is primarily a server-to-server interface.
|
||||
type FederationModule interface {
|
||||
Route(s router.Router) error
|
||||
Route(s router.Router) error
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/message"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/router"
|
||||
)
|
||||
|
||||
|
@ -39,17 +40,19 @@ const (
|
|||
|
||||
// Module implements the ClientAPIModule interface for
|
||||
type Module struct {
|
||||
config *config.Config
|
||||
processor message.Processor
|
||||
log *logrus.Logger
|
||||
config *config.Config
|
||||
db db.DB
|
||||
server oauth.Server
|
||||
log *logrus.Logger
|
||||
}
|
||||
|
||||
// New returns a new auth module
|
||||
func New(config *config.Config, processor message.Processor, log *logrus.Logger) api.ClientModule {
|
||||
func New(config *config.Config, db db.DB, server oauth.Server, log *logrus.Logger) api.ClientModule {
|
||||
return &Module{
|
||||
config: config,
|
||||
processor: processor,
|
||||
log: log,
|
||||
config: config,
|
||||
db: db,
|
||||
server: server,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import (
|
|||
// If user or account can't be found, then the handler won't *fail*, in case the server wants to allow
|
||||
// public requests that don't have a Bearer token set (eg., for public instance information and so on).
|
||||
func (m *Module) OauthTokenMiddleware(c *gin.Context) {
|
||||
l := m.log.WithField("func", "ValidatePassword")
|
||||
l := m.log.WithField("func", "OauthTokenMiddleware")
|
||||
l.Trace("entering OauthTokenMiddleware")
|
||||
|
||||
ti, err := m.server.ValidationBearerToken(c.Request)
|
||||
|
|
|
@ -85,6 +85,7 @@ func (m *FileServer) ServeFile(c *gin.Context) {
|
|||
FileName: fileName,
|
||||
})
|
||||
if err != nil {
|
||||
l.Debug(err)
|
||||
c.String(http.StatusNotFound, "404 page not found")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/message"
|
||||
|
@ -49,6 +50,7 @@ type ServeFileTestSuite struct {
|
|||
db db.DB
|
||||
log *logrus.Logger
|
||||
storage storage.Storage
|
||||
federator federation.Federator
|
||||
tc typeutils.TypeConverter
|
||||
processor message.Processor
|
||||
mediaHandler media.Handler
|
||||
|
@ -76,7 +78,8 @@ func (suite *ServeFileTestSuite) SetupSuite() {
|
|||
suite.db = testrig.NewTestDB()
|
||||
suite.log = testrig.NewTestLog()
|
||||
suite.storage = testrig.NewTestStorage()
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage)
|
||||
suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
|
||||
suite.tc = testrig.NewTestTypeConverter(suite.db)
|
||||
suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage)
|
||||
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/message"
|
||||
|
@ -52,6 +53,7 @@ type MediaCreateTestSuite struct {
|
|||
db db.DB
|
||||
log *logrus.Logger
|
||||
storage storage.Storage
|
||||
federator federation.Federator
|
||||
tc typeutils.TypeConverter
|
||||
mediaHandler media.Handler
|
||||
oauthServer oauth.Server
|
||||
|
@ -82,7 +84,8 @@ func (suite *MediaCreateTestSuite) SetupSuite() {
|
|||
suite.tc = testrig.NewTestTypeConverter(suite.db)
|
||||
suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage)
|
||||
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage)
|
||||
suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
|
||||
|
||||
// setup module being tested
|
||||
suite.mediaModule = mediamodule.New(suite.config, suite.processor, suite.log).(*mediamodule.Module)
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package status_test
|
||||
|
||||
import (
|
||||
|
@ -6,6 +24,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/api/client/status"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/message"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
|
@ -20,6 +39,7 @@ type StatusStandardTestSuite struct {
|
|||
db db.DB
|
||||
log *logrus.Logger
|
||||
tc typeutils.TypeConverter
|
||||
federator federation.Federator
|
||||
processor message.Processor
|
||||
storage storage.Storage
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
|||
mastoStatus, err := m.processor.StatusCreate(authed, form)
|
||||
if err != nil {
|
||||
l.Debugf("error processing status create: %s", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error":"bad request"})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,8 @@ func (suite *StatusCreateTestSuite) SetupTest() {
|
|||
suite.db = testrig.NewTestDB()
|
||||
suite.storage = testrig.NewTestStorage()
|
||||
suite.log = testrig.NewTestLog()
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage)
|
||||
suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
|
||||
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
|
||||
testrig.StandardDBSetup(suite.db)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -55,7 +55,8 @@ func (suite *StatusFaveTestSuite) SetupTest() {
|
|||
suite.db = testrig.NewTestDB()
|
||||
suite.storage = testrig.NewTestStorage()
|
||||
suite.log = testrig.NewTestLog()
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage)
|
||||
suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
|
||||
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
|
||||
testrig.StandardDBSetup(suite.db)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -55,7 +55,8 @@ func (suite *StatusFavedByTestSuite) SetupTest() {
|
|||
suite.db = testrig.NewTestDB()
|
||||
suite.storage = testrig.NewTestStorage()
|
||||
suite.log = testrig.NewTestLog()
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage)
|
||||
suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
|
||||
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
|
||||
testrig.StandardDBSetup(suite.db)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -45,7 +45,8 @@ func (suite *StatusGetTestSuite) SetupTest() {
|
|||
suite.db = testrig.NewTestDB()
|
||||
suite.storage = testrig.NewTestStorage()
|
||||
suite.log = testrig.NewTestLog()
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage)
|
||||
suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
|
||||
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
|
||||
testrig.StandardDBSetup(suite.db)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
@ -56,7 +57,6 @@ func (suite *StatusGetTestSuite) TearDownTest() {
|
|||
testrig.StandardStorageTeardown(suite.storage)
|
||||
}
|
||||
|
||||
|
||||
// Post a new status with some custom visibility settings
|
||||
func (suite *StatusGetTestSuite) TestPostNewStatus() {
|
||||
|
||||
|
|
|
@ -55,7 +55,8 @@ func (suite *StatusUnfaveTestSuite) SetupTest() {
|
|||
suite.db = testrig.NewTestDB()
|
||||
suite.storage = testrig.NewTestStorage()
|
||||
suite.log = testrig.NewTestLog()
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage)
|
||||
suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
|
||||
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
|
||||
testrig.StandardDBSetup(suite.db)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package federation
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UsersGETHandler should be served at https://example.org/users/:username.
|
||||
//
|
||||
// The goal here is to return the activitypub representation of an account
|
||||
// in the form of a vocab.ActivityStreamsPerson. This should only be served
|
||||
// to REMOTE SERVERS that present a valid signature on the GET request, on
|
||||
// behalf of a user, otherwise we risk leaking information about users publicly.
|
||||
//
|
||||
// And of course, the request should be refused if the account or server making the
|
||||
// request is blocked.
|
||||
func (m *Module) UsersGETHandler(c *gin.Context) {
|
||||
// l := m.log.WithFields(logrus.Fields{
|
||||
// "func": "UsersGETHandler",
|
||||
// "url": c.Request.RequestURI,
|
||||
// })
|
||||
// requestedUsername := c.Param(UsernameKey)
|
||||
// if requestedUsername == "" {
|
||||
// err := errors.New("no username specified in request")
|
||||
// l.Debug(err)
|
||||
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
// return
|
||||
// }
|
||||
|
||||
// // make sure this actually an AP request
|
||||
// format := c.NegotiateFormat(ActivityPubAcceptHeaders...)
|
||||
// if format == "" {
|
||||
// err := errors.New("could not negotiate format with given Accept header(s)")
|
||||
// l.Debug(err)
|
||||
// c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
||||
// return
|
||||
// }
|
||||
// l.Tracef("negotiated format: %s", format)
|
||||
|
||||
// // get the account the request is referring to
|
||||
// requestedAccount := >smodel.Account{}
|
||||
// if err := m.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||
// l.Errorf("database error getting account with username %s: %s", requestedUsername, err)
|
||||
// // we'll just return not authorized here to avoid giving anything away
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// // and create a transport for it
|
||||
// transport, err := m.federator.TransportController().NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey)
|
||||
// if err != nil {
|
||||
// l.Errorf("error creating transport for username %s: %s", requestedUsername, err)
|
||||
// // we'll just return not authorized here to avoid giving anything away
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// // authenticate the request
|
||||
// authentication, err := federation.AuthenticateFederatedRequest(transport, c.Request)
|
||||
// if err != nil {
|
||||
// l.Errorf("error authenticating GET user request: %s", err)
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// if !authentication.Authenticated {
|
||||
// l.Debug("request not authorized")
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// requestingAccount := >smodel.Account{}
|
||||
// if authentication.RequestingPublicKeyID != nil {
|
||||
// if err := m.db.GetWhere("public_key_uri", authentication.RequestingPublicKeyID.String(), requestingAccount); err != nil {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// authorization, err := federation.AuthorizeFederatedRequest
|
||||
|
||||
// person, err := m.tc.AccountToAS(requestedAccount)
|
||||
// if err != nil {
|
||||
// l.Errorf("error converting account to ap person: %s", err)
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// data, err := person.Serialize()
|
||||
// if err != nil {
|
||||
// l.Errorf("error serializing user: %s", err)
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// c.JSON(http.StatusOK, data)
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package federation
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
@ -24,10 +24,8 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/message"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/router"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
|
@ -51,20 +49,16 @@ var ActivityPubAcceptHeaders = []string{
|
|||
|
||||
// Module implements the FederationAPIModule interface
|
||||
type Module struct {
|
||||
federator federation.Federator
|
||||
config *config.Config
|
||||
db db.DB
|
||||
tc typeutils.TypeConverter
|
||||
processor message.Processor
|
||||
log *logrus.Logger
|
||||
}
|
||||
|
||||
// New returns a new auth module
|
||||
func New(db db.DB, federator federation.Federator, tc typeutils.TypeConverter, config *config.Config, log *logrus.Logger) api.FederationModule {
|
||||
func New(config *config.Config, processor message.Processor, log *logrus.Logger) api.FederationModule {
|
||||
return &Module{
|
||||
federator: federator,
|
||||
config: config,
|
||||
db: db,
|
||||
tc: tc,
|
||||
processor: processor,
|
||||
log: log,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// UsersGETHandler should be served at https://example.org/users/:username.
|
||||
//
|
||||
// The goal here is to return the activitypub representation of an account
|
||||
// in the form of a vocab.ActivityStreamsPerson. This should only be served
|
||||
// to REMOTE SERVERS that present a valid signature on the GET request, on
|
||||
// behalf of a user, otherwise we risk leaking information about users publicly.
|
||||
//
|
||||
// And of course, the request should be refused if the account or server making the
|
||||
// request is blocked.
|
||||
func (m *Module) UsersGETHandler(c *gin.Context) {
|
||||
l := m.log.WithFields(logrus.Fields{
|
||||
"func": "UsersGETHandler",
|
||||
"url": c.Request.RequestURI,
|
||||
})
|
||||
|
||||
requestedUsername := c.Param(UsernameKey)
|
||||
if requestedUsername == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
||||
return
|
||||
}
|
||||
|
||||
// make sure this actually an AP request
|
||||
format := c.NegotiateFormat(ActivityPubAcceptHeaders...)
|
||||
if format == "" {
|
||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": "could not negotiate format with given Accept header(s)"})
|
||||
return
|
||||
}
|
||||
l.Tracef("negotiated format: %s", format)
|
||||
|
||||
// get the account the request is referring to
|
||||
requestedAccount := >smodel.Account{}
|
||||
if err := m.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||
l.Errorf("database error getting account with username %s: %s", requestedUsername, err)
|
||||
// we'll just return not authorized here to avoid giving anything away
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// and create a transport for it
|
||||
transport, err := m.federator.TransportController().NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey)
|
||||
if err != nil {
|
||||
l.Errorf("error creating transport for username %s: %s", requestedUsername, err)
|
||||
// we'll just return not authorized here to avoid giving anything away
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// authenticate the request
|
||||
authentication, err := federation.AuthenticateFederatedRequest(transport, c.Request)
|
||||
if err != nil {
|
||||
l.Errorf("error authenticating GET user request: %s", err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
if !authentication.Authenticated {
|
||||
l.Debug("request not authorized")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
requestingAccount := >smodel.Account{}
|
||||
if authentication.RequestingPublicKeyID != nil {
|
||||
if err := m.db.GetWhere("public_key_uri", authentication.RequestingPublicKeyID.String(), requestingAccount); err != nil {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
authorization, err := federation.AuthorizeFederatedRequest
|
||||
|
||||
person, err := m.tc.AccountToAS(requestedAccount)
|
||||
if err != nil {
|
||||
l.Errorf("error converting account to ap person: %s", err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
data, err := person.Serialize()
|
||||
if err != nil {
|
||||
l.Errorf("error serializing user: %s", err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, data)
|
||||
}
|
|
@ -38,5 +38,5 @@ func (c *Clock) Now() time.Time {
|
|||
}
|
||||
|
||||
func NewClock() pub.Clock {
|
||||
return &Clock{}
|
||||
return &Clock{}
|
||||
}
|
||||
|
|
|
@ -26,33 +26,10 @@ import (
|
|||
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// commonBehavior implements the GTSCommonBehavior interface
|
||||
type commonBehavior struct {
|
||||
db db.DB
|
||||
log *logrus.Logger
|
||||
config *config.Config
|
||||
transportController transport.Controller
|
||||
}
|
||||
|
||||
// newCommonBehavior returns an implementation of the GTSCommonBehavior interface that uses the given db, log, config, and transportController.
|
||||
// This interface is a superset of the pub.CommonBehavior interface, so it can be used anywhere that interface would be used.
|
||||
func newCommonBehavior(db db.DB, log *logrus.Logger, config *config.Config, transportController transport.Controller) pub.CommonBehavior {
|
||||
return &commonBehavior{
|
||||
db: db,
|
||||
log: log,
|
||||
config: config,
|
||||
transportController: transportController,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
GOFED COMMON BEHAVIOR INTERFACE
|
||||
Contains functions required for both the Social API and Federating Protocol.
|
||||
|
@ -79,7 +56,7 @@ func newCommonBehavior(db db.DB, log *logrus.Logger, config *config.Config, tran
|
|||
// Finally, if the authentication and authorization succeeds, then
|
||||
// authenticated must be true and error nil. The request will continue
|
||||
// to be processed.
|
||||
func (c *commonBehavior) AuthenticateGetInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
|
||||
func (f *federator) AuthenticateGetInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
|
||||
// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through
|
||||
// the CLIENT API, not through the federation API, so we just do nothing here.
|
||||
return nil, false, nil
|
||||
|
@ -104,7 +81,7 @@ func (c *commonBehavior) AuthenticateGetInbox(ctx context.Context, w http.Respon
|
|||
// Finally, if the authentication and authorization succeeds, then
|
||||
// authenticated must be true and error nil. The request will continue
|
||||
// to be processed.
|
||||
func (c *commonBehavior) AuthenticateGetOutbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
|
||||
func (f *federator) AuthenticateGetOutbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
|
||||
// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through
|
||||
// the CLIENT API, not through the federation API, so we just do nothing here.
|
||||
return nil, false, nil
|
||||
|
@ -118,7 +95,7 @@ func (c *commonBehavior) AuthenticateGetOutbox(ctx context.Context, w http.Respo
|
|||
//
|
||||
// Always called, regardless whether the Federated Protocol or Social
|
||||
// API is enabled.
|
||||
func (c *commonBehavior) GetOutbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
|
||||
func (f *federator) GetOutbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
|
||||
// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through
|
||||
// the CLIENT API, not through the federation API, so we just do nothing here.
|
||||
return nil, nil
|
||||
|
@ -147,7 +124,7 @@ func (c *commonBehavior) GetOutbox(ctx context.Context, r *http.Request) (vocab.
|
|||
// Note that the library will not maintain a long-lived pointer to the
|
||||
// returned Transport so that any private credentials are able to be
|
||||
// garbage collected.
|
||||
func (c *commonBehavior) NewTransport(ctx context.Context, actorBoxIRI *url.URL, gofedAgent string) (pub.Transport, error) {
|
||||
func (f *federator) NewTransport(ctx context.Context, actorBoxIRI *url.URL, gofedAgent string) (pub.Transport, error) {
|
||||
|
||||
var username string
|
||||
var err error
|
||||
|
@ -167,16 +144,9 @@ func (c *commonBehavior) NewTransport(ctx context.Context, actorBoxIRI *url.URL,
|
|||
}
|
||||
|
||||
account := >smodel.Account{}
|
||||
if err := c.db.GetLocalAccountByUsername(username, account); err != nil {
|
||||
if err := f.db.GetLocalAccountByUsername(username, account); err != nil {
|
||||
return nil, fmt.Errorf("error getting account with username %s from the db: %s", username, err)
|
||||
}
|
||||
|
||||
return c.transportController.NewTransport(account.PublicKeyURI, account.PrivateKey)
|
||||
}
|
||||
|
||||
// GetUser returns the activitypub representation of the user specified in the path of r, eg https://example.org/users/example_user.
|
||||
// AuthenticateGetUser should be called first, to make sure the requester has permission to view the requested user.
|
||||
// The returned user should be a translation from a *gtsmodel.Account to a serializable ActivityStreamsPerson.
|
||||
func (c *commonBehavior) GetUser(ctx context.Context, r *http.Request) (vocab.ActivityStreamsPerson, error) {
|
||||
return nil, nil
|
||||
return f.transportController.NewTransport(account.PublicKeyURI, account.PrivateKey)
|
||||
}
|
||||
|
|
|
@ -28,34 +28,11 @@ import (
|
|||
"github.com/go-fed/activity/pub"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// federatingProtocol implements the go-fed federating protocol interface
|
||||
type federatingProtocol struct {
|
||||
db db.DB
|
||||
log *logrus.Logger
|
||||
config *config.Config
|
||||
transportController transport.Controller
|
||||
typeConverter typeutils.TypeConverter
|
||||
}
|
||||
|
||||
// newFederatingProtocol returns the gotosocial implementation of the GTSFederatingProtocol interface
|
||||
func newFederatingProtocol(db db.DB, log *logrus.Logger, config *config.Config, transportController transport.Controller, typeConverter typeutils.TypeConverter) pub.FederatingProtocol {
|
||||
return &federatingProtocol{
|
||||
db: db,
|
||||
log: log,
|
||||
config: config,
|
||||
transportController: transportController,
|
||||
typeConverter: typeConverter,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
GO FED FEDERATING PROTOCOL INTERFACE
|
||||
FederatingProtocol contains behaviors an application needs to satisfy for the
|
||||
|
@ -82,7 +59,7 @@ func newFederatingProtocol(db db.DB, log *logrus.Logger, config *config.Config,
|
|||
// PostInbox. In this case, the DelegateActor implementation must not
|
||||
// write a response to the ResponseWriter as is expected that the caller
|
||||
// to PostInbox will do so when handling the error.
|
||||
func (f *federatingProtocol) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) {
|
||||
func (f *federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) {
|
||||
l := f.log.WithFields(logrus.Fields{
|
||||
"func": "PostInboxRequestBodyHook",
|
||||
"useragent": r.UserAgent(),
|
||||
|
@ -115,7 +92,7 @@ func (f *federatingProtocol) PostInboxRequestBodyHook(ctx context.Context, r *ht
|
|||
// Finally, if the authentication and authorization succeeds, then
|
||||
// authenticated must be true and error nil. The request will continue
|
||||
// to be processed.
|
||||
func (f *federatingProtocol) AuthenticatePostInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
|
||||
func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
|
||||
l := f.log.WithFields(logrus.Fields{
|
||||
"func": "AuthenticatePostInbox",
|
||||
"useragent": r.UserAgent(),
|
||||
|
@ -133,12 +110,7 @@ func (f *federatingProtocol) AuthenticatePostInbox(ctx context.Context, w http.R
|
|||
return ctx, false, errors.New("requested account not parsebale from context")
|
||||
}
|
||||
|
||||
transport, err := f.transportController.NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey)
|
||||
if err != nil {
|
||||
return ctx, false, fmt.Errorf("error creating transport: %s", err)
|
||||
}
|
||||
|
||||
publicKeyOwnerURI, err := AuthenticateFederatedRequest(transport, r)
|
||||
publicKeyOwnerURI, err := f.AuthenticateFederatedRequest(requestedAccount.Username, r)
|
||||
if err != nil {
|
||||
l.Debugf("request not authenticated: %s", err)
|
||||
return ctx, false, fmt.Errorf("not authenticated: %s", err)
|
||||
|
@ -151,9 +123,9 @@ func (f *federatingProtocol) AuthenticatePostInbox(ctx context.Context, w http.R
|
|||
return ctx, false, fmt.Errorf("error getting requesting account with public key id %s: %s", publicKeyOwnerURI.String(), err)
|
||||
}
|
||||
|
||||
// we just don't know this account (yet) so try to dereference it
|
||||
// we don't know this account (yet) so let's dereference it right now
|
||||
// TODO: slow-fed
|
||||
person, err := DereferenceAccount(transport, publicKeyOwnerURI)
|
||||
person, err := f.DereferenceRemoteAccount(requestedAccount.Username, publicKeyOwnerURI)
|
||||
if err != nil {
|
||||
return ctx, false, fmt.Errorf("error dereferencing account with public key id %s: %s", publicKeyOwnerURI.String(), err)
|
||||
}
|
||||
|
@ -182,7 +154,7 @@ func (f *federatingProtocol) AuthenticatePostInbox(ctx context.Context, w http.R
|
|||
// Finally, if the authentication and authorization succeeds, then
|
||||
// blocked must be false and error nil. The request will continue
|
||||
// to be processed.
|
||||
func (f *federatingProtocol) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, error) {
|
||||
func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, error) {
|
||||
// TODO
|
||||
return false, nil
|
||||
}
|
||||
|
@ -206,7 +178,7 @@ func (f *federatingProtocol) Blocked(ctx context.Context, actorIRIs []*url.URL)
|
|||
//
|
||||
// Applications are not expected to handle every single ActivityStreams
|
||||
// type and extension. The unhandled ones are passed to DefaultCallback.
|
||||
func (f *federatingProtocol) FederatingCallbacks(ctx context.Context) (pub.FederatingWrappedCallbacks, []interface{}, error) {
|
||||
func (f *federator) FederatingCallbacks(ctx context.Context) (pub.FederatingWrappedCallbacks, []interface{}, error) {
|
||||
// TODO
|
||||
return pub.FederatingWrappedCallbacks{}, nil, nil
|
||||
}
|
||||
|
@ -218,7 +190,7 @@ func (f *federatingProtocol) FederatingCallbacks(ctx context.Context) (pub.Feder
|
|||
// Applications are not expected to handle every single ActivityStreams
|
||||
// type and extension, so the unhandled ones are passed to
|
||||
// DefaultCallback.
|
||||
func (f *federatingProtocol) DefaultCallback(ctx context.Context, activity pub.Activity) error {
|
||||
func (f *federator) DefaultCallback(ctx context.Context, activity pub.Activity) error {
|
||||
l := f.log.WithFields(logrus.Fields{
|
||||
"func": "DefaultCallback",
|
||||
"aptype": activity.GetTypeName(),
|
||||
|
@ -231,7 +203,7 @@ func (f *federatingProtocol) DefaultCallback(ctx context.Context, activity pub.A
|
|||
// an activity to determine if inbox forwarding needs to occur.
|
||||
//
|
||||
// Zero or negative numbers indicate infinite recursion.
|
||||
func (f *federatingProtocol) MaxInboxForwardingRecursionDepth(ctx context.Context) int {
|
||||
func (f *federator) MaxInboxForwardingRecursionDepth(ctx context.Context) int {
|
||||
// TODO
|
||||
return 0
|
||||
}
|
||||
|
@ -241,7 +213,7 @@ func (f *federatingProtocol) MaxInboxForwardingRecursionDepth(ctx context.Contex
|
|||
// delivery.
|
||||
//
|
||||
// Zero or negative numbers indicate infinite recursion.
|
||||
func (f *federatingProtocol) MaxDeliveryRecursionDepth(ctx context.Context) int {
|
||||
func (f *federator) MaxDeliveryRecursionDepth(ctx context.Context) int {
|
||||
// TODO
|
||||
return 0
|
||||
}
|
||||
|
@ -253,7 +225,7 @@ func (f *federatingProtocol) MaxDeliveryRecursionDepth(ctx context.Context) int
|
|||
//
|
||||
// The activity is provided as a reference for more intelligent
|
||||
// logic to be used, but the implementation must not modify it.
|
||||
func (f *federatingProtocol) FilterForwarding(ctx context.Context, potentialRecipients []*url.URL, a pub.Activity) ([]*url.URL, error) {
|
||||
func (f *federator) FilterForwarding(ctx context.Context, potentialRecipients []*url.URL, a pub.Activity) ([]*url.URL, error) {
|
||||
// TODO
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -266,7 +238,7 @@ func (f *federatingProtocol) FilterForwarding(ctx context.Context, potentialReci
|
|||
//
|
||||
// Always called, regardless whether the Federated Protocol or Social
|
||||
// API is enabled.
|
||||
func (f *federatingProtocol) GetInbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
|
||||
func (f *federator) GetInbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
|
||||
// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through
|
||||
// the CLIENT API, not through the federation API, so we just do nothing here.
|
||||
return nil, nil
|
||||
|
|
|
@ -19,11 +19,13 @@
|
|||
package federation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/message"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
)
|
||||
|
@ -32,35 +34,36 @@ import (
|
|||
type Federator interface {
|
||||
FederatingActor() pub.FederatingActor
|
||||
TransportController() transport.Controller
|
||||
FederatingProtocol() pub.FederatingProtocol
|
||||
CommonBehavior() pub.CommonBehavior
|
||||
AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error)
|
||||
pub.CommonBehavior
|
||||
pub.FederatingProtocol
|
||||
}
|
||||
|
||||
type federator struct {
|
||||
actor pub.FederatingActor
|
||||
processor message.Processor
|
||||
federatingProtocol pub.FederatingProtocol
|
||||
commonBehavior pub.CommonBehavior
|
||||
config *config.Config
|
||||
db db.DB
|
||||
clock pub.Clock
|
||||
typeConverter typeutils.TypeConverter
|
||||
transportController transport.Controller
|
||||
actor pub.FederatingActor
|
||||
log *logrus.Logger
|
||||
}
|
||||
|
||||
// NewFederator returns a new federator
|
||||
func NewFederator(db db.DB, transportController transport.Controller, config *config.Config, log *logrus.Logger, processor message.Processor, typeConverter typeutils.TypeConverter) Federator {
|
||||
func NewFederator(db db.DB, transportController transport.Controller, config *config.Config, log *logrus.Logger, typeConverter typeutils.TypeConverter) Federator {
|
||||
|
||||
clock := &Clock{}
|
||||
federatingProtocol := newFederatingProtocol(db, log, config, transportController, typeConverter)
|
||||
commonBehavior := newCommonBehavior(db, log, config, transportController)
|
||||
actor := newFederatingActor(commonBehavior, federatingProtocol, db.Federation(), clock)
|
||||
|
||||
return &federator{
|
||||
actor: actor,
|
||||
processor: processor,
|
||||
federatingProtocol: federatingProtocol,
|
||||
commonBehavior: commonBehavior,
|
||||
clock: clock,
|
||||
f := &federator{
|
||||
config: config,
|
||||
db: db,
|
||||
clock: &Clock{},
|
||||
typeConverter: typeConverter,
|
||||
transportController: transportController,
|
||||
log: log,
|
||||
}
|
||||
actor := newFederatingActor(f, f, db.Federation(), clock)
|
||||
f.actor = actor
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *federator) FederatingActor() pub.FederatingActor {
|
||||
|
@ -70,11 +73,3 @@ func (f *federator) FederatingActor() pub.FederatingActor {
|
|||
func (f *federator) TransportController() transport.Controller {
|
||||
return f.transportController
|
||||
}
|
||||
|
||||
func (f *federator) FederatingProtocol() pub.FederatingProtocol {
|
||||
return f.federatingProtocol
|
||||
}
|
||||
|
||||
func (f *federator) CommonBehavior() pub.CommonBehavior {
|
||||
return f.commonBehavior
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/message"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
|
@ -51,7 +50,6 @@ type ProtocolTestSuite struct {
|
|||
config *config.Config
|
||||
db db.DB
|
||||
log *logrus.Logger
|
||||
processor message.Processor
|
||||
storage storage.Storage
|
||||
typeConverter typeutils.TypeConverter
|
||||
accounts map[string]*gtsmodel.Account
|
||||
|
@ -65,7 +63,6 @@ func (suite *ProtocolTestSuite) SetupSuite() {
|
|||
suite.db = testrig.NewTestDB()
|
||||
suite.log = testrig.NewTestLog()
|
||||
suite.storage = testrig.NewTestStorage()
|
||||
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage)
|
||||
suite.typeConverter = testrig.NewTestTypeConverter(suite.db)
|
||||
suite.accounts = testrig.NewTestAccounts()
|
||||
suite.activities = testrig.NewTestActivities(suite.accounts)
|
||||
|
@ -92,7 +89,7 @@ func (suite *ProtocolTestSuite) TestPostInboxRequestBodyHook() {
|
|||
return nil, nil
|
||||
}))
|
||||
// setup module being tested
|
||||
federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.processor, suite.typeConverter)
|
||||
federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.typeConverter)
|
||||
|
||||
// setup request
|
||||
ctx := context.Background()
|
||||
|
@ -158,7 +155,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() {
|
|||
}))
|
||||
|
||||
// now setup module being tested, with the mock transport controller
|
||||
federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.processor, suite.typeConverter)
|
||||
federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.typeConverter)
|
||||
|
||||
// setup request
|
||||
ctx := context.Background()
|
||||
|
|
|
@ -101,15 +101,16 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca
|
|||
// Authenticate in this case is defined as just making sure that the http request is actually signed by whoever claims
|
||||
// to have signed it, by fetching the public key from the signature and checking it against the remote public key.
|
||||
//
|
||||
// The provided transport will be used to dereference the public key ID of the request signature. Ideally you should pass in a transport
|
||||
// with the credentials of the user *being requested*, so that the remote server can decide how to handle the request based on who's making it.
|
||||
// Ie., if the request on this server is for https://example.org/users/some_username then you should pass in a transport that's been initialized with
|
||||
// the keys belonging to local user 'some_username'. The remote server will then know that this is the user making the
|
||||
// dereferencing request, and they can decide to allow or deny the request depending on their settings.
|
||||
// The provided username will be used to generate a transport for making remote requests/derefencing the public key ID of the request signature.
|
||||
// Ideally you should pass in the username of the user *being requested*, so that the remote server can decide how to handle the request based on who's making it.
|
||||
// Ie., if the request on this server is for https://example.org/users/some_username then you should pass in the username 'some_username'.
|
||||
// The remote server will then know that this is the user making the dereferencing request, and they can decide to allow or deny the request depending on their settings.
|
||||
//
|
||||
// Note that it is also valid to pass in an empty string here, in which case the keys of the instance account will be used.
|
||||
//
|
||||
// Note that this function *does not* dereference the remote account that the signature key is associated with, but it will
|
||||
// return the owner of the public key, so that other functions can dereference it with that, as required.
|
||||
func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*url.URL, error) {
|
||||
func (f *federator) AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error) {
|
||||
verifier, err := httpsig.NewVerifier(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create http sig verifier: %s", err)
|
||||
|
@ -122,7 +123,12 @@ func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*ur
|
|||
return nil, fmt.Errorf("could not parse key id into a url: %s", err)
|
||||
}
|
||||
|
||||
// use the new transport to fetch the requesting public key from the remote server
|
||||
transport, err := f.GetTransportForUser(username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transport err: %s", err)
|
||||
}
|
||||
|
||||
// The actual http call to the remote server is made right here in the Dereference function.
|
||||
b, err := transport.Dereference(context.Background(), requestingPublicKeyID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error deferencing key %s: %s", requestingPublicKeyID.String(), err)
|
||||
|
@ -134,17 +140,13 @@ func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*ur
|
|||
return nil, fmt.Errorf("error getting key %s from response %s: %s", requestingPublicKeyID.String(), string(b), err)
|
||||
}
|
||||
|
||||
pkOwnerProp := requestingPublicKey.GetW3IDSecurityV1Owner()
|
||||
if pkOwnerProp == nil || !pkOwnerProp.IsIRI() {
|
||||
return nil, errors.New("publicKeyOwner property is not provided or it is not embedded as a value")
|
||||
}
|
||||
pkOwnerURI := pkOwnerProp.GetIRI()
|
||||
|
||||
// we should be able to get the actual key embedded in the vocab.W3IDSecurityV1PublicKey
|
||||
pkPemProp := requestingPublicKey.GetW3IDSecurityV1PublicKeyPem()
|
||||
if pkPemProp == nil || !pkPemProp.IsXMLSchemaString() {
|
||||
return nil, errors.New("publicKeyPem property is not provided or it is not embedded as a value")
|
||||
}
|
||||
|
||||
// and decode the PEM so that we can parse it as a golang public key
|
||||
pubKeyPem := pkPemProp.Get()
|
||||
block, _ := pem.Decode([]byte(pubKeyPem))
|
||||
if block == nil || block.Type != "PUBLIC KEY" {
|
||||
|
@ -162,14 +164,26 @@ func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*ur
|
|||
return nil, fmt.Errorf("error verifying key %s: %s", requestingPublicKeyID.String(), err)
|
||||
}
|
||||
|
||||
// all good!
|
||||
// all good! we just need the URI of the key owner to return
|
||||
pkOwnerProp := requestingPublicKey.GetW3IDSecurityV1Owner()
|
||||
if pkOwnerProp == nil || !pkOwnerProp.IsIRI() {
|
||||
return nil, errors.New("publicKeyOwner property is not provided or it is not embedded as a value")
|
||||
}
|
||||
pkOwnerURI := pkOwnerProp.GetIRI()
|
||||
|
||||
return pkOwnerURI, nil
|
||||
}
|
||||
|
||||
func DereferenceAccount(transport pub.Transport, id *url.URL) (vocab.ActivityStreamsPerson, error) {
|
||||
b, err := transport.Dereference(context.Background(), id)
|
||||
func (f *federator) DereferenceRemoteAccount(username string, remoteAccountID *url.URL) (vocab.ActivityStreamsPerson, error) {
|
||||
|
||||
transport, err := f.GetTransportForUser(username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error deferencing %s: %s", id.String(), err)
|
||||
return nil, fmt.Errorf("transport err: %s", err)
|
||||
}
|
||||
|
||||
b, err := transport.Dereference(context.Background(), remoteAccountID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error deferencing %s: %s", remoteAccountID.String(), err)
|
||||
}
|
||||
|
||||
m := make(map[string]interface{})
|
||||
|
@ -195,3 +209,25 @@ func DereferenceAccount(transport pub.Transport, id *url.URL) (vocab.ActivityStr
|
|||
|
||||
return nil, fmt.Errorf("type name %s not supported", t.GetTypeName())
|
||||
}
|
||||
|
||||
func (f *federator) GetTransportForUser(username string) (pub.Transport, error) {
|
||||
// We need an account to use to create a transport for dereferecing the signature.
|
||||
// If a username has been given, we can fetch the account with that username and use it.
|
||||
// Otherwise, we can take the instance account and use those credentials to make the request.
|
||||
ourAccount := >smodel.Account{}
|
||||
var u string
|
||||
if username == "" {
|
||||
u = f.config.Host
|
||||
} else {
|
||||
u = username
|
||||
}
|
||||
if err := f.db.GetLocalAccountByUsername(u, ourAccount); err != nil {
|
||||
return nil, fmt.Errorf("error getting account %s from db: %s", username, err)
|
||||
}
|
||||
|
||||
transport, err := f.TransportController().NewTransport(ourAccount.PublicKeyURI, ourAccount.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating transport for user %s: %s", username, err)
|
||||
}
|
||||
return transport, nil
|
||||
}
|
||||
|
|
|
@ -72,15 +72,15 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
|
|||
// build backend handlers
|
||||
mediaHandler := media.New(c, dbService, storageBackend, log)
|
||||
oauthServer := oauth.New(dbService, log)
|
||||
processor := message.NewProcessor(c, typeConverter, oauthServer, mediaHandler, storageBackend, dbService, log)
|
||||
transportController := transport.NewController(c, &federation.Clock{}, http.DefaultClient, log)
|
||||
federator := federation.NewFederator(dbService, transportController, c, log, typeConverter)
|
||||
processor := message.NewProcessor(c, typeConverter, federator, oauthServer, mediaHandler, storageBackend, dbService, log)
|
||||
if err := processor.Start(); err != nil {
|
||||
return fmt.Errorf("error starting processor: %s", err)
|
||||
}
|
||||
transportController := transport.NewController(c, &federation.Clock{}, http.DefaultClient, log)
|
||||
federator := federation.NewFederator(dbService, transportController, c, log, processor, typeConverter)
|
||||
|
||||
// build client api modules
|
||||
authModule := auth.New(c, processor, log)
|
||||
authModule := auth.New(c, dbService, oauthServer, log)
|
||||
accountModule := account.New(c, processor, log)
|
||||
appsModule := app.New(c, processor, log)
|
||||
mm := mediaModule.New(c, processor, log)
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package message
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (p *processor) GetAPUser(requestHeaders http.Header, username string) (interface{}, error) {
|
||||
|
||||
// // get the account the request is referring to
|
||||
// requestedAccount := >smodel.Account{}
|
||||
// if err := m.db.GetLocalAccountByUsername(username, requestedAccount); err != nil {
|
||||
// return nil, NewErrorNotAuthorized(fmt.Errorf("database error getting account with username %s: %s", username, err))
|
||||
// }
|
||||
|
||||
// // and create a transport for it
|
||||
// transport, err := p.federator.TransportController().NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey)
|
||||
// if err != nil {
|
||||
// l.Errorf("error creating transport for username %s: %s", requestedUsername, err)
|
||||
// // we'll just return not authorized here to avoid giving anything away
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// // authenticate the request
|
||||
// authentication, err := federation.AuthenticateFederatedRequest(transport, c.Request)
|
||||
// if err != nil {
|
||||
// l.Errorf("error authenticating GET user request: %s", err)
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// if !authentication.Authenticated {
|
||||
// l.Debug("request not authorized")
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// requestingAccount := >smodel.Account{}
|
||||
// if authentication.RequestingPublicKeyID != nil {
|
||||
// if err := m.db.GetWhere("public_key_uri", authentication.RequestingPublicKeyID.String(), requestingAccount); err != nil {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// authorization, err := federation.AuthorizeFederatedRequest
|
||||
|
||||
// person, err := m.tc.AccountToAS(requestedAccount)
|
||||
// if err != nil {
|
||||
// l.Errorf("error converting account to ap person: %s", err)
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// data, err := person.Serialize()
|
||||
// if err != nil {
|
||||
// l.Errorf("error serializing user: %s", err)
|
||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
// return
|
||||
// }
|
||||
|
||||
// c.JSON(http.StatusOK, data)
|
||||
return nil, nil
|
||||
}
|
|
@ -130,10 +130,12 @@ func (p *processor) MediaGet(authed *oauth.Auth, form *apimodel.GetContentReques
|
|||
return nil, NewErrorNotFound(fmt.Errorf("block status could not be established between accounts %s and %s: %s", form.AccountID, authed.Account.ID, err))
|
||||
}
|
||||
if blocked {
|
||||
return nil, NewErrorNotFound(fmt.Errorf("block exists between accounts %s and %s: %s", form.AccountID, authed.Account.ID))
|
||||
return nil, NewErrorNotFound(fmt.Errorf("block exists between accounts %s and %s", form.AccountID, authed.Account.ID))
|
||||
}
|
||||
}
|
||||
|
||||
// the way we store emojis is a little different from the way we store other attachments,
|
||||
// so we need to take different steps depending on the media type being requested
|
||||
content := &apimodel.Content{}
|
||||
var storagePath string
|
||||
switch mediaType {
|
||||
|
@ -155,7 +157,7 @@ func (p *processor) MediaGet(authed *oauth.Auth, form *apimodel.GetContentReques
|
|||
default:
|
||||
return nil, NewErrorNotFound(fmt.Errorf("media size %s not recognized for emoji", mediaSize))
|
||||
}
|
||||
case media.Attachment:
|
||||
case media.Attachment, media.Header, media.Avatar:
|
||||
a := >smodel.MediaAttachment{}
|
||||
if err := p.db.GetByID(wantedMediaID, a); err != nil {
|
||||
return nil, NewErrorNotFound(fmt.Errorf("attachment %s could not be taken from the db: %s", wantedMediaID, err))
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
|
@ -39,8 +40,12 @@ import (
|
|||
type Processor interface {
|
||||
// ToClientAPI returns a channel for putting in messages that need to go to the gts client API.
|
||||
ToClientAPI() chan ToClientAPI
|
||||
// FromClientAPI returns a channel for putting messages in that come from the client api going to the processor
|
||||
FromClientAPI() chan FromClientAPI
|
||||
// ToFederator returns a channel for putting in messages that need to go to the federator (activitypub).
|
||||
ToFederator() chan ToFederator
|
||||
// FromFederator returns a channel for putting messages in that come from the federator going into the processor
|
||||
FromFederator() chan FromFederator
|
||||
|
||||
/*
|
||||
API-FACING PROCESSING FUNCTIONS
|
||||
|
@ -86,53 +91,67 @@ type Processor interface {
|
|||
// processor just implements the Processor interface
|
||||
type processor struct {
|
||||
// federator pub.FederatingActor
|
||||
toClientAPI chan ToClientAPI
|
||||
toFederator chan ToFederator
|
||||
stop chan interface{}
|
||||
log *logrus.Logger
|
||||
config *config.Config
|
||||
tc typeutils.TypeConverter
|
||||
oauthServer oauth.Server
|
||||
mediaHandler media.Handler
|
||||
storage storage.Storage
|
||||
db db.DB
|
||||
toClientAPI chan ToClientAPI
|
||||
fromClientAPI chan FromClientAPI
|
||||
toFederator chan ToFederator
|
||||
fromFederator chan FromFederator
|
||||
federator federation.Federator
|
||||
stop chan interface{}
|
||||
log *logrus.Logger
|
||||
config *config.Config
|
||||
tc typeutils.TypeConverter
|
||||
oauthServer oauth.Server
|
||||
mediaHandler media.Handler
|
||||
storage storage.Storage
|
||||
db db.DB
|
||||
}
|
||||
|
||||
// NewProcessor returns a new Processor that uses the given federator and logger
|
||||
func NewProcessor(config *config.Config, tc typeutils.TypeConverter, oauthServer oauth.Server, mediaHandler media.Handler, storage storage.Storage, db db.DB, log *logrus.Logger) Processor {
|
||||
func NewProcessor(config *config.Config, tc typeutils.TypeConverter, federator federation.Federator, oauthServer oauth.Server, mediaHandler media.Handler, storage storage.Storage, db db.DB, log *logrus.Logger) Processor {
|
||||
return &processor{
|
||||
toClientAPI: make(chan ToClientAPI, 100),
|
||||
toFederator: make(chan ToFederator, 100),
|
||||
stop: make(chan interface{}),
|
||||
log: log,
|
||||
config: config,
|
||||
tc: tc,
|
||||
oauthServer: oauthServer,
|
||||
mediaHandler: mediaHandler,
|
||||
storage: storage,
|
||||
db: db,
|
||||
toClientAPI: make(chan ToClientAPI, 100),
|
||||
fromClientAPI: make(chan FromClientAPI, 100),
|
||||
toFederator: make(chan ToFederator, 100),
|
||||
fromFederator: make(chan FromFederator, 100),
|
||||
federator: federator,
|
||||
stop: make(chan interface{}),
|
||||
log: log,
|
||||
config: config,
|
||||
tc: tc,
|
||||
oauthServer: oauthServer,
|
||||
mediaHandler: mediaHandler,
|
||||
storage: storage,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *processor) ToClientAPI() chan ToClientAPI {
|
||||
return d.toClientAPI
|
||||
func (p *processor) ToClientAPI() chan ToClientAPI {
|
||||
return p.toClientAPI
|
||||
}
|
||||
|
||||
func (d *processor) ToFederator() chan ToFederator {
|
||||
return d.toFederator
|
||||
func (p *processor) FromClientAPI() chan FromClientAPI {
|
||||
return p.fromClientAPI
|
||||
}
|
||||
|
||||
func (p *processor) ToFederator() chan ToFederator {
|
||||
return p.toFederator
|
||||
}
|
||||
|
||||
func (p *processor) FromFederator() chan FromFederator {
|
||||
return p.fromFederator
|
||||
}
|
||||
|
||||
// Start starts the Processor, reading from its channels and passing messages back and forth.
|
||||
func (d *processor) Start() error {
|
||||
func (p *processor) Start() error {
|
||||
go func() {
|
||||
DistLoop:
|
||||
for {
|
||||
select {
|
||||
case clientMsg := <-d.toClientAPI:
|
||||
d.log.Infof("received message TO client API: %+v", clientMsg)
|
||||
case federatorMsg := <-d.toFederator:
|
||||
d.log.Infof("received message TO federator: %+v", federatorMsg)
|
||||
case <-d.stop:
|
||||
case clientMsg := <-p.toClientAPI:
|
||||
p.log.Infof("received message TO client API: %+v", clientMsg)
|
||||
case federatorMsg := <-p.toFederator:
|
||||
p.log.Infof("received message TO federator: %+v", federatorMsg)
|
||||
case <-p.stop:
|
||||
break DistLoop
|
||||
}
|
||||
}
|
||||
|
@ -142,8 +161,8 @@ func (d *processor) Start() error {
|
|||
|
||||
// Stop stops the processor cleanly, finishing handling any remaining messages before closing down.
|
||||
// TODO: empty message buffer properly before stopping otherwise we'll lose federating messages.
|
||||
func (d *processor) Stop() error {
|
||||
close(d.stop)
|
||||
func (p *processor) Stop() error {
|
||||
close(p.stop)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -154,9 +173,23 @@ type ToClientAPI struct {
|
|||
Activity interface{}
|
||||
}
|
||||
|
||||
// FromClientAPI wraps a message that travels from client API into the processor
|
||||
type FromClientAPI struct {
|
||||
APObjectType gtsmodel.ActivityStreamsObject
|
||||
APActivityType gtsmodel.ActivityStreamsActivity
|
||||
Activity interface{}
|
||||
}
|
||||
|
||||
// ToFederator wraps a message that travels from the processor into the federator
|
||||
type ToFederator struct {
|
||||
APObjectType gtsmodel.ActivityStreamsObject
|
||||
APActivityType gtsmodel.ActivityStreamsActivity
|
||||
Activity interface{}
|
||||
}
|
||||
|
||||
// FromFederator wraps a message that travels from the federator into the processor
|
||||
type FromFederator struct {
|
||||
APObjectType gtsmodel.ActivityStreamsObject
|
||||
APActivityType gtsmodel.ActivityStreamsActivity
|
||||
Activity interface{}
|
||||
}
|
||||
|
|
|
@ -23,10 +23,8 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/oauth2/v4"
|
||||
"github.com/superseriousbusiness/oauth2/v4/errors"
|
||||
"github.com/superseriousbusiness/oauth2/v4/manage"
|
||||
|
@ -66,82 +64,53 @@ type s struct {
|
|||
log *logrus.Logger
|
||||
}
|
||||
|
||||
// Auth wraps an authorized token, application, user, and account.
|
||||
// It is used in the functions GetAuthed and MustAuth.
|
||||
// Because the user might *not* be authed, any of the fields in this struct
|
||||
// might be nil, so make sure to check that when you're using this struct anywhere.
|
||||
type Auth struct {
|
||||
Token oauth2.TokenInfo
|
||||
Application *gtsmodel.Application
|
||||
User *gtsmodel.User
|
||||
Account *gtsmodel.Account
|
||||
}
|
||||
// New returns a new oauth server that implements the Server interface
|
||||
func New(database db.DB, log *logrus.Logger) Server {
|
||||
ts := newTokenStore(context.Background(), database, log)
|
||||
cs := NewClientStore(database)
|
||||
|
||||
// Authed is a convenience function for returning an Authed struct from a gin context.
|
||||
// In essence, it tries to extract a token, application, user, and account from the context,
|
||||
// and then sets them on a struct for convenience.
|
||||
//
|
||||
// If any are not present in the context, they will be set to nil on the returned Authed struct.
|
||||
//
|
||||
// If *ALL* are not present, then nil and an error will be returned.
|
||||
//
|
||||
// If something goes wrong during parsing, then nil and an error will be returned (consider this not authed).
|
||||
// Authed is like GetAuthed, but will fail if one of the requirements is not met.
|
||||
func Authed(c *gin.Context, requireToken bool, requireApp bool, requireUser bool, requireAccount bool) (*Auth, error) {
|
||||
ctx := c.Copy()
|
||||
a := &Auth{}
|
||||
var i interface{}
|
||||
var ok bool
|
||||
manager := manage.NewDefaultManager()
|
||||
manager.MapTokenStorage(ts)
|
||||
manager.MapClientStorage(cs)
|
||||
manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
|
||||
sc := &server.Config{
|
||||
TokenType: "Bearer",
|
||||
// Must follow the spec.
|
||||
AllowGetAccessRequest: false,
|
||||
// Support only the non-implicit flow.
|
||||
AllowedResponseTypes: []oauth2.ResponseType{oauth2.Code},
|
||||
// Allow:
|
||||
// - Authorization Code (for first & third parties)
|
||||
// - Client Credentials (for applications)
|
||||
AllowedGrantTypes: []oauth2.GrantType{
|
||||
oauth2.AuthorizationCode,
|
||||
oauth2.ClientCredentials,
|
||||
},
|
||||
AllowedCodeChallengeMethods: []oauth2.CodeChallengeMethod{oauth2.CodeChallengePlain},
|
||||
}
|
||||
|
||||
i, ok = ctx.Get(SessionAuthorizedToken)
|
||||
if ok {
|
||||
parsed, ok := i.(oauth2.TokenInfo)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse token from session context")
|
||||
srv := server.NewServer(sc, manager)
|
||||
srv.SetInternalErrorHandler(func(err error) *errors.Response {
|
||||
log.Errorf("internal oauth error: %s", err)
|
||||
return nil
|
||||
})
|
||||
|
||||
srv.SetResponseErrorHandler(func(re *errors.Response) {
|
||||
log.Errorf("internal response error: %s", re.Error)
|
||||
})
|
||||
|
||||
srv.SetUserAuthorizationHandler(func(w http.ResponseWriter, r *http.Request) (string, error) {
|
||||
userID := r.FormValue("userid")
|
||||
if userID == "" {
|
||||
return "", errors.New("userid was empty")
|
||||
}
|
||||
a.Token = parsed
|
||||
return userID, nil
|
||||
})
|
||||
srv.SetClientInfoHandler(server.ClientFormHandler)
|
||||
return &s{
|
||||
server: srv,
|
||||
log: log,
|
||||
}
|
||||
|
||||
i, ok = ctx.Get(SessionAuthorizedApplication)
|
||||
if ok {
|
||||
parsed, ok := i.(*gtsmodel.Application)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse application from session context")
|
||||
}
|
||||
a.Application = parsed
|
||||
}
|
||||
|
||||
i, ok = ctx.Get(SessionAuthorizedUser)
|
||||
if ok {
|
||||
parsed, ok := i.(*gtsmodel.User)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse user from session context")
|
||||
}
|
||||
a.User = parsed
|
||||
}
|
||||
|
||||
i, ok = ctx.Get(SessionAuthorizedAccount)
|
||||
if ok {
|
||||
parsed, ok := i.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse account from session context")
|
||||
}
|
||||
a.Account = parsed
|
||||
}
|
||||
|
||||
if requireToken && a.Token == nil {
|
||||
return nil, errors.New("token not supplied")
|
||||
}
|
||||
if requireApp && a.Application == nil {
|
||||
return nil, errors.New("application not supplied")
|
||||
}
|
||||
if requireUser && a.User == nil {
|
||||
return nil, errors.New("user not supplied")
|
||||
}
|
||||
if requireAccount && a.Account == nil {
|
||||
return nil, errors.New("account not supplied")
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// HandleTokenRequest wraps the oauth2 library's HandleTokenRequest function
|
||||
|
@ -199,52 +168,3 @@ func (s *s) GenerateUserAccessToken(ti oauth2.TokenInfo, clientSecret string, us
|
|||
s.log.Tracef("obtained user-level access token: %+v", accessToken)
|
||||
return accessToken, nil
|
||||
}
|
||||
|
||||
// New returns a new oauth server that implements the Server interface
|
||||
func New(database db.DB, log *logrus.Logger) Server {
|
||||
ts := newTokenStore(context.Background(), database, log)
|
||||
cs := NewClientStore(database)
|
||||
|
||||
manager := manage.NewDefaultManager()
|
||||
manager.MapTokenStorage(ts)
|
||||
manager.MapClientStorage(cs)
|
||||
manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
|
||||
sc := &server.Config{
|
||||
TokenType: "Bearer",
|
||||
// Must follow the spec.
|
||||
AllowGetAccessRequest: false,
|
||||
// Support only the non-implicit flow.
|
||||
AllowedResponseTypes: []oauth2.ResponseType{oauth2.Code},
|
||||
// Allow:
|
||||
// - Authorization Code (for first & third parties)
|
||||
// - Client Credentials (for applications)
|
||||
AllowedGrantTypes: []oauth2.GrantType{
|
||||
oauth2.AuthorizationCode,
|
||||
oauth2.ClientCredentials,
|
||||
},
|
||||
AllowedCodeChallengeMethods: []oauth2.CodeChallengeMethod{oauth2.CodeChallengePlain},
|
||||
}
|
||||
|
||||
srv := server.NewServer(sc, manager)
|
||||
srv.SetInternalErrorHandler(func(err error) *errors.Response {
|
||||
log.Errorf("internal oauth error: %s", err)
|
||||
return nil
|
||||
})
|
||||
|
||||
srv.SetResponseErrorHandler(func(re *errors.Response) {
|
||||
log.Errorf("internal response error: %s", re.Error)
|
||||
})
|
||||
|
||||
srv.SetUserAuthorizationHandler(func(w http.ResponseWriter, r *http.Request) (string, error) {
|
||||
userID := r.FormValue("userid")
|
||||
if userID == "" {
|
||||
return "", errors.New("userid was empty")
|
||||
}
|
||||
return userID, nil
|
||||
})
|
||||
srv.SetClientInfoHandler(server.ClientFormHandler)
|
||||
return &s{
|
||||
server: srv,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package oauth
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/oauth2/v4"
|
||||
"github.com/superseriousbusiness/oauth2/v4/errors"
|
||||
)
|
||||
|
||||
// Auth wraps an authorized token, application, user, and account.
|
||||
// It is used in the functions GetAuthed and MustAuth.
|
||||
// Because the user might *not* be authed, any of the fields in this struct
|
||||
// might be nil, so make sure to check that when you're using this struct anywhere.
|
||||
type Auth struct {
|
||||
Token oauth2.TokenInfo
|
||||
Application *gtsmodel.Application
|
||||
User *gtsmodel.User
|
||||
Account *gtsmodel.Account
|
||||
}
|
||||
|
||||
// Authed is a convenience function for returning an Authed struct from a gin context.
|
||||
// In essence, it tries to extract a token, application, user, and account from the context,
|
||||
// and then sets them on a struct for convenience.
|
||||
//
|
||||
// If any are not present in the context, they will be set to nil on the returned Authed struct.
|
||||
//
|
||||
// If *ALL* are not present, then nil and an error will be returned.
|
||||
//
|
||||
// If something goes wrong during parsing, then nil and an error will be returned (consider this not authed).
|
||||
// Authed is like GetAuthed, but will fail if one of the requirements is not met.
|
||||
func Authed(c *gin.Context, requireToken bool, requireApp bool, requireUser bool, requireAccount bool) (*Auth, error) {
|
||||
ctx := c.Copy()
|
||||
a := &Auth{}
|
||||
var i interface{}
|
||||
var ok bool
|
||||
|
||||
i, ok = ctx.Get(SessionAuthorizedToken)
|
||||
if ok {
|
||||
parsed, ok := i.(oauth2.TokenInfo)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse token from session context")
|
||||
}
|
||||
a.Token = parsed
|
||||
}
|
||||
|
||||
i, ok = ctx.Get(SessionAuthorizedApplication)
|
||||
if ok {
|
||||
parsed, ok := i.(*gtsmodel.Application)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse application from session context")
|
||||
}
|
||||
a.Application = parsed
|
||||
}
|
||||
|
||||
i, ok = ctx.Get(SessionAuthorizedUser)
|
||||
if ok {
|
||||
parsed, ok := i.(*gtsmodel.User)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse user from session context")
|
||||
}
|
||||
a.User = parsed
|
||||
}
|
||||
|
||||
i, ok = ctx.Get(SessionAuthorizedAccount)
|
||||
if ok {
|
||||
parsed, ok := i.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse account from session context")
|
||||
}
|
||||
a.Account = parsed
|
||||
}
|
||||
|
||||
if requireToken && a.Token == nil {
|
||||
return nil, errors.New("token not supplied")
|
||||
}
|
||||
if requireApp && a.Application == nil {
|
||||
return nil, errors.New("application not supplied")
|
||||
}
|
||||
if requireUser && a.User == nil {
|
||||
return nil, errors.New("user not supplied")
|
||||
}
|
||||
if requireAccount && a.Account == nil {
|
||||
return nil, errors.New("account not supplied")
|
||||
}
|
||||
return a, nil
|
||||
}
|
|
@ -35,7 +35,7 @@ func (s *inMemStorage) RetrieveFileFrom(path string) ([]byte, error) {
|
|||
l := s.log.WithField("func", "RetrieveFileFrom")
|
||||
l.Debugf("retrieving from path %s", path)
|
||||
d, ok := s.stored[path]
|
||||
if !ok {
|
||||
if !ok || len(d) == 0 {
|
||||
return nil, fmt.Errorf("no data found at path %s", path)
|
||||
}
|
||||
return d, nil
|
||||
|
|
|
@ -22,9 +22,9 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
)
|
||||
|
||||
func (c *converter) AccountToMastoSensitive(a *gtsmodel.Account) (*model.Account, error) {
|
||||
|
|
|
@ -50,10 +50,7 @@ var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logr
|
|||
dbService := NewTestDB()
|
||||
router := NewTestRouter()
|
||||
storageBackend := NewTestStorage()
|
||||
processor := NewTestProcessor(dbService, storageBackend)
|
||||
if err := processor.Start(); err != nil {
|
||||
return fmt.Errorf("error starting processor: %s", err)
|
||||
}
|
||||
|
||||
typeConverter := NewTestTypeConverter(dbService)
|
||||
transportController := NewTestTransportController(NewMockHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
r := ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
|
@ -62,13 +59,17 @@ var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logr
|
|||
Body: r,
|
||||
}, nil
|
||||
}))
|
||||
federator := federation.NewFederator(dbService, transportController, c, log, processor, typeConverter)
|
||||
federator := federation.NewFederator(dbService, transportController, c, log, typeConverter)
|
||||
processor := NewTestProcessor(dbService, storageBackend, federator)
|
||||
if err := processor.Start(); err != nil {
|
||||
return fmt.Errorf("error starting processor: %s", err)
|
||||
}
|
||||
|
||||
StandardDBSetup(dbService)
|
||||
StandardStorageSetup(storageBackend, "./testrig/media")
|
||||
|
||||
// build client api modules
|
||||
authModule := auth.New(c, processor, log)
|
||||
authModule := auth.New(c, dbService, NewTestOauthServer(dbService), log)
|
||||
accountModule := account.New(c, processor, log)
|
||||
appsModule := app.New(c, processor, log)
|
||||
mm := mediaModule.New(c, processor, log)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package testrig
|
||||
|
||||
import (
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
)
|
||||
|
||||
func NewTestFederator(db db.DB, tc transport.Controller) federation.Federator {
|
||||
return federation.NewFederator(db, tc, NewTestConfig(), NewTestLog(), NewTestTypeConverter(db))
|
||||
}
|
|
@ -20,11 +20,12 @@ package testrig
|
|||
|
||||
import (
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/message"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||
)
|
||||
|
||||
// NewTestProcessor returns a Processor suitable for testing purposes
|
||||
func NewTestProcessor(db db.DB, storage storage.Storage) message.Processor {
|
||||
return message.NewProcessor(NewTestConfig(), NewTestTypeConverter(db), NewTestOauthServer(db), NewTestMediaHandler(db, storage), storage, db, NewTestLog())
|
||||
func NewTestProcessor(db db.DB, storage storage.Storage, federator federation.Federator) message.Processor {
|
||||
return message.NewProcessor(NewTestConfig(), NewTestTypeConverter(db), federator, NewTestOauthServer(db), NewTestMediaHandler(db, storage), storage, db, NewTestLog())
|
||||
}
|
||||
|
|
|
@ -691,25 +691,26 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment {
|
|||
func NewTestEmojis() map[string]*gtsmodel.Emoji {
|
||||
return map[string]*gtsmodel.Emoji{
|
||||
"rainbow": {
|
||||
ID: "a96ec4f3-1cae-47e4-a508-f9d66a6b221b",
|
||||
Shortcode: "rainbow",
|
||||
Domain: "",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
ImageRemoteURL: "",
|
||||
ImageStaticRemoteURL: "",
|
||||
ImageURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
|
||||
ImagePath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
|
||||
ImageStaticURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
|
||||
ImageStaticPath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
|
||||
ImageContentType: "image/png",
|
||||
ImageFileSize: 36702,
|
||||
ImageStaticFileSize: 10413,
|
||||
ImageUpdatedAt: time.Now(),
|
||||
Disabled: false,
|
||||
URI: "http://localhost:8080/emoji/a96ec4f3-1cae-47e4-a508-f9d66a6b221b",
|
||||
VisibleInPicker: true,
|
||||
CategoryID: "",
|
||||
ID: "a96ec4f3-1cae-47e4-a508-f9d66a6b221b",
|
||||
Shortcode: "rainbow",
|
||||
Domain: "",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
ImageRemoteURL: "",
|
||||
ImageStaticRemoteURL: "",
|
||||
ImageURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
|
||||
ImagePath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
|
||||
ImageStaticURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
|
||||
ImageStaticPath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
|
||||
ImageContentType: "image/png",
|
||||
ImageStaticContentType: "image/png",
|
||||
ImageFileSize: 36702,
|
||||
ImageStaticFileSize: 10413,
|
||||
ImageUpdatedAt: time.Now(),
|
||||
Disabled: false,
|
||||
URI: "http://localhost:8080/emoji/a96ec4f3-1cae-47e4-a508-f9d66a6b221b",
|
||||
VisibleInPicker: true,
|
||||
CategoryID: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
package testrig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-fed/activity/pub"
|
||||
|
@ -41,7 +43,22 @@ func NewTestTransportController(client pub.HttpClient) transport.Controller {
|
|||
|
||||
// NewMockHTTPClient returns a client that conforms to the pub.HttpClient interface,
|
||||
// but will always just execute the given `do` function, allowing responses to be mocked.
|
||||
//
|
||||
// If 'do' is nil, then a no-op function will be used instead, that just returns status 200.
|
||||
//
|
||||
// Note that you should never ever make ACTUAL http calls with this thing.
|
||||
func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error)) pub.HttpClient {
|
||||
if do == nil {
|
||||
return &mockHTTPClient{
|
||||
do: func(req *http.Request) (*http.Response, error) {
|
||||
r := ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: r,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
return &mockHTTPClient{
|
||||
do: do,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue