plodding away on the accounts endpoint

This commit is contained in:
tsmethurst
2021-03-23 22:13:01 +01:00
parent 7139116e5d
commit 0ea69345b9
17 changed files with 657 additions and 173 deletions

View File

@ -19,13 +19,13 @@
package account
import (
"fmt"
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/gotosocial/gotosocial/internal/config"
"github.com/gotosocial/gotosocial/internal/db"
"github.com/gotosocial/gotosocial/internal/gtsmodel"
"github.com/gotosocial/gotosocial/internal/db/model"
"github.com/gotosocial/gotosocial/internal/module"
"github.com/gotosocial/gotosocial/internal/module/oauth"
"github.com/gotosocial/gotosocial/internal/router"
@ -56,19 +56,33 @@ func (m *accountModule) Route(r router.Router) error {
return nil
}
// AccountVerifyGETHandler serves a user's account details to them IF they reached this
// handler while in possession of a valid token, according to the oauth middleware.
func (m *accountModule) AccountVerifyGETHandler(c *gin.Context) {
s := sessions.Default(c)
userID, ok := s.Get(oauth.SessionAuthorizedUser).(string)
i, ok := c.Get(oauth.SessionAuthorizedUser)
fmt.Println(i)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "The access token is invalid"})
return
}
userID, ok := (i).(string)
if !ok || userID == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "The access token is invalid"})
return
}
acct := &gtsmodel.Account{}
acct := &model.Account{}
if err := m.db.GetAccountByUserID(userID, acct); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
return
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, acct.ToMastoSensitive())
acctSensitive, err := m.db.AccountToMastoSensitive(acct)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, acctSensitive)
}

View File

@ -0,0 +1,233 @@
/*
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 account
import (
"context"
"fmt"
"net/url"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/gotosocial/gotosocial/internal/config"
"github.com/gotosocial/gotosocial/internal/db"
"github.com/gotosocial/gotosocial/internal/db/model"
"github.com/gotosocial/gotosocial/internal/module/oauth"
"github.com/gotosocial/gotosocial/internal/router"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/suite"
"golang.org/x/crypto/bcrypt"
)
type AccountTestSuite struct {
suite.Suite
db db.DB
testAccountLocal *model.Account
testAccountRemote *model.Account
testUser *model.User
config *config.Config
}
// SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout
func (suite *AccountTestSuite) SetupSuite() {
c := config.Empty()
c.DBConfig = &config.DBConfig{
Type: "postgres",
Address: "localhost",
Port: 5432,
User: "postgres",
Password: "postgres",
Database: "postgres",
ApplicationName: "gotosocial",
}
suite.config = c
encryptedPassword, err := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)
if err != nil {
logrus.Panicf("error encrypting user pass: %s", err)
}
localAvatar, err := url.Parse("https://localhost:8080/media/aaaaaaaaa.png")
if err != nil {
logrus.Panicf("error parsing localavatar url: %s", err)
}
localHeader, err := url.Parse("https://localhost:8080/media/ffffffffff.png")
if err != nil {
logrus.Panicf("error parsing localheader url: %s", err)
}
acctID := uuid.NewString()
suite.testAccountLocal = &model.Account{
ID: acctID,
Username: "local_account_of_some_kind",
AvatarRemoteURL: localAvatar,
HeaderRemoteURL: localHeader,
DisplayName: "michael caine",
Fields: []model.Field{
{
Name: "come and ave a go",
Value: "if you think you're hard enough",
},
{
Name: "website",
Value: "https://imdb.com",
VerifiedAt: time.Now(),
},
},
Note: "My name is Michael Caine and i'm a local user.",
Discoverable: true,
}
avatarURL, err := url.Parse("http://example.org/accounts/avatars/000/207/122/original/089-1098-09.png")
if err != nil {
logrus.Panicf("error parsing avatarURL: %s", err)
}
headerURL, err := url.Parse("http://example.org/accounts/headers/000/207/122/original/111111111111.png")
if err != nil {
logrus.Panicf("error parsing avatarURL: %s", err)
}
suite.testAccountRemote = &model.Account{
ID: uuid.NewString(),
Username: "neato_bombeato",
Domain: "example.org",
AvatarFileName: "avatar.png",
AvatarContentType: "image/png",
AvatarFileSize: 1024,
AvatarUpdatedAt: time.Now(),
AvatarRemoteURL: avatarURL,
HeaderFileName: "avatar.png",
HeaderContentType: "image/png",
HeaderFileSize: 1024,
HeaderUpdatedAt: time.Now(),
HeaderRemoteURL: headerURL,
DisplayName: "one cool dude 420",
Fields: []model.Field{
{
Name: "pronouns",
Value: "he/they",
},
{
Name: "website",
Value: "https://imcool.edu",
VerifiedAt: time.Now(),
},
},
Note: "<p>I'm cool as heck!</p>",
Discoverable: true,
URI: "https://example.org/users/neato_bombeato",
URL: "https://example.org/@neato_bombeato",
LastWebfingeredAt: time.Now(),
InboxURL: "https://example.org/users/neato_bombeato/inbox",
OutboxURL: "https://example.org/users/neato_bombeato/outbox",
SharedInboxURL: "https://example.org/inbox",
FollowersURL: "https://example.org/users/neato_bombeato/followers",
FeaturedCollectionURL: "https://example.org/users/neato_bombeato/collections/featured",
}
suite.testUser = &model.User{
ID: uuid.NewString(),
EncryptedPassword: string(encryptedPassword),
Email: "user@example.org",
AccountID: acctID,
}
}
// SetupTest creates a postgres connection and creates the oauth_clients table before each test
func (suite *AccountTestSuite) SetupTest() {
log := logrus.New()
log.SetLevel(logrus.TraceLevel)
db, err := db.New(context.Background(), suite.config, log)
if err != nil {
logrus.Panicf("error creating database connection: %s", err)
}
suite.db = db
models := []interface{}{
&model.User{},
&model.Account{},
&model.Follow{},
&model.Status{},
}
for _, m := range models {
if err := suite.db.CreateTable(m); err != nil {
logrus.Panicf("db connection error: %s", err)
}
}
if err := suite.db.Put(suite.testAccountLocal); err != nil {
logrus.Panicf("could not insert test account into db: %s", err)
}
if err := suite.db.Put(suite.testUser); err != nil {
logrus.Panicf("could not insert test user into db: %s", err)
}
}
// TearDownTest drops the oauth_clients table and closes the pg connection after each test
func (suite *AccountTestSuite) TearDownTest() {
models := []interface{}{
&model.User{},
&model.Account{},
&model.Follow{},
&model.Status{},
}
for _, m := range models {
if err := suite.db.DropTable(m); err != nil {
logrus.Panicf("error dropping table: %s", err)
}
}
if err := suite.db.Stop(context.Background()); err != nil {
logrus.Panicf("error closing db connection: %s", err)
}
suite.db = nil
}
func (suite *AccountTestSuite) TestAPIInitialize() {
log := logrus.New()
log.SetLevel(logrus.TraceLevel)
r, err := router.New(suite.config, log)
if err != nil {
suite.FailNow(fmt.Sprintf("error mapping routes onto router: %s", err))
}
r.AttachMiddleware(func(c *gin.Context) {
c.Set(oauth.SessionAuthorizedUser, suite.testUser.ID)
})
acct := New(suite.config, suite.db)
acct.Route(r)
r.Start()
defer r.Stop(context.Background())
time.Sleep(10 * time.Second)
}
func TestAccountTestSuite(t *testing.T) {
suite.Run(t, new(AccountTestSuite))
}

View File

@ -20,7 +20,6 @@ package oauth
import (
"context"
"fmt"
"github.com/gotosocial/gotosocial/internal/db"
"github.com/gotosocial/oauth2/v4"
@ -43,7 +42,7 @@ func (cs *clientStore) GetByID(ctx context.Context, clientID string) (oauth2.Cli
ID: clientID,
}
if err := cs.db.GetByID(clientID, poc); err != nil {
return nil, fmt.Errorf("database error: %s", err)
return nil, err
}
return models.New(poc.ID, poc.Secret, poc.Domain, poc.UserID), nil
}

View File

@ -136,7 +136,7 @@ func (suite *PgClientStoreTestSuite) TestClientSetAndDelete() {
// try to get the deleted client; we should get an error
deletedClient, err := cs.GetByID(context.Background(), suite.testClientID)
suite.Assert().Nil(deletedClient)
suite.Assert().NotNil(err)
suite.Assert().EqualValues(db.ErrNoEntries{}, err)
}
func TestPgClientStoreTestSuite(t *testing.T) {

View File

@ -34,7 +34,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/gotosocial/gotosocial/internal/db"
"github.com/gotosocial/gotosocial/internal/gtsmodel"
"github.com/gotosocial/gotosocial/internal/db/model"
"github.com/gotosocial/gotosocial/internal/module"
"github.com/gotosocial/gotosocial/internal/router"
"github.com/gotosocial/gotosocial/pkg/mastotypes"
@ -47,10 +47,10 @@ import (
)
const (
appsPath = "/api/v1/apps"
authSignInPath = "/auth/sign_in"
oauthTokenPath = "/oauth/token"
oauthAuthorizePath = "/oauth/authorize"
appsPath = "/api/v1/apps"
authSignInPath = "/auth/sign_in"
oauthTokenPath = "/oauth/token"
oauthAuthorizePath = "/oauth/authorize"
SessionAuthorizedUser = "authorized_user"
)
@ -179,7 +179,7 @@ func (m *oauthModule) appsPOSTHandler(c *gin.Context) {
vapidKey := uuid.NewString()
// generate the application to put in the database
app := &gtsmodel.Application{
app := &model.Application{
Name: form.ClientName,
Website: form.Website,
RedirectURI: form.RedirectURIs,
@ -287,7 +287,7 @@ func (m *oauthModule) authorizeGETHandler(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "no client_id found in session"})
return
}
app := &gtsmodel.Application{
app := &model.Application{
ClientID: clientID,
}
if err := m.db.GetWhere("client_id", app.ClientID, app); err != nil {
@ -296,7 +296,7 @@ func (m *oauthModule) authorizeGETHandler(c *gin.Context) {
}
// we can also use the userid of the user to fetch their username from the db to greet them nicely <3
user := &gtsmodel.User{
user := &model.User{
ID: userID,
}
if err := m.db.GetByID(user.ID, user); err != nil {
@ -304,7 +304,7 @@ func (m *oauthModule) authorizeGETHandler(c *gin.Context) {
return
}
acct := &gtsmodel.Account{
acct := &model.Account{
ID: user.AccountID,
}
@ -413,7 +413,6 @@ func (m *oauthModule) oauthTokenMiddleware(c *gin.Context) {
if ti, err := m.oauthServer.ValidationBearerToken(c.Request); err == nil {
l.Tracef("authenticated user %s with bearer token, scope is %s", ti.GetUserID(), ti.GetScope())
c.Set(SessionAuthorizedUser, ti.GetUserID())
} else {
l.Trace("continuing with unauthenticated request")
}
@ -437,7 +436,7 @@ func (m *oauthModule) validatePassword(email string, password string) (userid st
}
// first we select the user from the database based on email address, bail if no user found for that email
gtsUser := &gtsmodel.User{}
gtsUser := &model.User{}
if err := m.db.GetWhere("email", email, gtsUser); err != nil {
l.Debugf("user %s was not retrievable from db during oauth authorization attempt: %s", email, err)

View File

@ -22,12 +22,11 @@ import (
"context"
"fmt"
"testing"
"time"
"github.com/google/uuid"
"github.com/gotosocial/gotosocial/internal/config"
"github.com/gotosocial/gotosocial/internal/db"
"github.com/gotosocial/gotosocial/internal/gtsmodel"
"github.com/gotosocial/gotosocial/internal/db/model"
"github.com/gotosocial/gotosocial/internal/router"
"github.com/gotosocial/oauth2/v4"
"github.com/sirupsen/logrus"
@ -40,9 +39,9 @@ type OauthTestSuite struct {
tokenStore oauth2.TokenStore
clientStore oauth2.ClientStore
db db.DB
testAccount *gtsmodel.Account
testApplication *gtsmodel.Application
testUser *gtsmodel.User
testAccount *model.Account
testApplication *model.Application
testUser *model.User
testClient *oauthClient
config *config.Config
}
@ -76,11 +75,11 @@ func (suite *OauthTestSuite) SetupSuite() {
acctID := uuid.NewString()
suite.testAccount = &gtsmodel.Account{
suite.testAccount = &model.Account{
ID: acctID,
Username: "test_user",
}
suite.testUser = &gtsmodel.User{
suite.testUser = &model.User{
EncryptedPassword: string(encryptedPassword),
Email: "user@example.org",
AccountID: acctID,
@ -90,7 +89,7 @@ func (suite *OauthTestSuite) SetupSuite() {
Secret: "some-secret",
Domain: fmt.Sprintf("%s://%s", c.Protocol, c.Host),
}
suite.testApplication = &gtsmodel.Application{
suite.testApplication = &model.Application{
Name: "a test application",
Website: "https://some-application-website.com",
RedirectURI: "http://localhost:8080",
@ -116,9 +115,9 @@ func (suite *OauthTestSuite) SetupTest() {
models := []interface{}{
&oauthClient{},
&oauthToken{},
&gtsmodel.User{},
&gtsmodel.Account{},
&gtsmodel.Application{},
&model.User{},
&model.Account{},
&model.Application{},
}
for _, m := range models {
@ -150,9 +149,9 @@ func (suite *OauthTestSuite) TearDownTest() {
models := []interface{}{
&oauthClient{},
&oauthToken{},
&gtsmodel.User{},
&gtsmodel.Account{},
&gtsmodel.Application{},
&model.User{},
&model.Account{},
&model.Application{},
}
for _, m := range models {
if err := suite.db.DropTable(m); err != nil {
@ -179,11 +178,10 @@ func (suite *OauthTestSuite) TestAPIInitialize() {
suite.FailNow(fmt.Sprintf("error mapping routes onto router: %s", err))
}
go r.Start()
time.Sleep(60 * time.Second)
// http://localhost:8080/oauth/authorize?client_id=a-known-client-id&response_type=code&redirect_uri=http://localhost:8080&scope=read
// curl -v -F client_id=a-known-client-id -F client_secret=some-secret -F redirect_uri=http://localhost:8080 -F code=[ INSERT CODE HERE ] -F grant_type=authorization_code localhost:8080/oauth/token
// curl -v -H "Authorization: Bearer [INSERT TOKEN HERE]" http://localhost:8080
r.Start()
if err := r.Stop(context.Background()); err != nil {
suite.FailNow(fmt.Sprintf("error stopping router: %s", err))
}
}
func TestOauthTestSuite(t *testing.T) {