Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configs/migrations/0006_add_groups_table.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS public.groups CASCADE;
41 changes: 41 additions & 0 deletions configs/migrations/0006_add_groups_table.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
CREATE TABLE public.groups (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone,
user_id bigint NOT NULL,
client_id bigint NOT NULL,
tags text[] NOT NULL
);


CREATE SEQUENCE public.groups_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;


ALTER SEQUENCE public.groups_id_seq OWNED BY public.groups.id;


ALTER TABLE ONLY public.groups ALTER COLUMN id SET DEFAULT nextval('public.groups_id_seq'::regclass);


ALTER TABLE ONLY public.groups
ADD CONSTRAINT groups_user_client_key UNIQUE (user_id, client_id);


ALTER TABLE ONLY public.groups
ADD CONSTRAINT groups_pkey PRIMARY KEY (id);


ALTER TABLE ONLY public.groups
ADD CONSTRAINT fk_groups_user FOREIGN KEY (user_id) REFERENCES public.users(id);


ALTER TABLE ONLY public.groups
ADD CONSTRAINT fk_groups_client FOREIGN KEY (client_id) REFERENCES public.clients(id);


CREATE INDEX idx_groups_user_client ON public.groups USING btree (user_id, client_id);
1 change: 1 addition & 0 deletions configs/migrations/0007_rename_index_for_settings.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER INDEX public.idx_settings_user_realm_category_property RENAME TO idx_settings_address;
1 change: 1 addition & 0 deletions configs/migrations/0007_rename_index_for_settings.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER INDEX public.idx_settings_address RENAME TO idx_settings_user_realm_category_property;
91 changes: 11 additions & 80 deletions internal/api/api_test.go
Original file line number Diff line number Diff line change
@@ -1,100 +1,31 @@
package api

import (
"net/http"
"testing"

"github.com/brianvoe/gofakeit/v7"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/suite"

"github.com/earaujoassis/space/internal/ioc"
"github.com/earaujoassis/space/internal/models"
"github.com/earaujoassis/space/internal/security"
"github.com/earaujoassis/space/internal/shared"
"github.com/earaujoassis/space/test/factory"
"github.com/earaujoassis/space/test/unit"
"github.com/earaujoassis/space/test/utils"
)

type ApiHandlerTestSuite struct {
unit.BaseTestSuite
type ApiTestSuite struct {
unit.ApiBaseTestSuite
Router *gin.Engine
}

func (s *ApiHandlerTestSuite) SetupSuite() {
s.BaseTestSuite.SetupSuite()
func (s *ApiTestSuite) SetupSuite() {
s.ApiBaseTestSuite.SetupSuite()
gin.SetMode(gin.TestMode)
s.Router = s.setupRouter()
s.Router = s.SetupRouter()
}

func (s *ApiHandlerTestSuite) SetupTest() {
s.BaseTestSuite.SetupTest()
s.Router = s.setupRouter()
func (s *ApiTestSuite) SetupTest() {
s.ApiBaseTestSuite.SetupTest()
s.Router = s.SetupRouter()
ExposeRoutes(s.Router)
}

func (s *ApiHandlerTestSuite) setupRouter() *gin.Engine {
router := gin.New()
security.SetTrustedProxies(router)
store := cookie.NewStore([]byte(s.Config.SessionSecret))
store.Options(sessions.Options{Secure: false, HttpOnly: true})
router.Use(sessions.Sessions("space.session", store))
router.Use(ioc.InjectAppContext(s.BaseTestSuite.AppCtx))
router.GET("/set-session", func(c *gin.Context) {
var user models.User

admin := c.Query("admin") == "true"
session := sessions.Default(c)
repositories := ioc.GetRepositories(c)
if admin {
user = s.Factory.NewUserWithOption(factory.UserOptions{Admin: true}).Model
} else {
user = s.Factory.NewUserWithOption(factory.UserOptions{Admin: false}).Model
}
client := repositories.Clients().FindOrCreate(models.DefaultClient)
applicationSession := models.Session{
User: user,
Client: client,
IP: gofakeit.IPv4Address(),
UserAgent: gofakeit.UserAgent(),
Scopes: models.PublicScope,
TokenType: models.ApplicationToken,
}
err := repositories.Sessions().Create(&applicationSession)
s.Require().NoError(err)
session.Set(shared.CookieSessionKey, applicationSession.Token)
session.Save()
c.String(200, "Session set")
})
router.RedirectTrailingSlash = false
ExposeRoutes(router)
return router
}

func (s *ApiHandlerTestSuite) createSessionCookie(admin bool) *http.Cookie {
var path string

if admin {
path = "/set-session?admin=true"
} else {
path = "/set-session?admin=false"
}
w := s.PerformRequest(s.Router, "GET", path, nil, nil, nil)
r := utils.ParseResponse(w.Result(), nil)
s.Equal(200, w.Code)
s.Contains(r.Body, "Session set")

for _, cookie := range w.Result().Cookies() {
if cookie.Name == "space.session" {
return cookie
}
}

return nil
}

func TestApiSuite(t *testing.T) {
suite.Run(t, new(ApiHandlerTestSuite))
func TestClientsSuite(t *testing.T) {
suite.Run(t, new(ApiTestSuite))
}
32 changes: 32 additions & 0 deletions internal/api/clients/clients_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package clients

import (
"testing"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/suite"

"github.com/earaujoassis/space/test/unit"
)

type ClientsTestSuite struct {
unit.ApiBaseTestSuite
Router *gin.Engine
}

func (s *ClientsTestSuite) SetupSuite() {
s.ApiBaseTestSuite.SetupSuite()
gin.SetMode(gin.TestMode)
s.Router = s.SetupRouter()
}

func (s *ClientsTestSuite) SetupTest() {
s.ApiBaseTestSuite.SetupTest()
s.Router = s.SetupRouter()
group := s.Router.Group("/api")
ExposeRoutes(group)
}

func TestClientsSuite(t *testing.T) {
suite.Run(t, new(ClientsTestSuite))
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package api
package clients

import (
"fmt"
Expand All @@ -8,23 +8,11 @@ import (

"github.com/earaujoassis/space/internal/ioc"
"github.com/earaujoassis/space/internal/models"
"github.com/earaujoassis/space/internal/shared"
"github.com/earaujoassis/space/internal/utils"
)

func clientsCreateHandler(c *gin.Context) {
func createHandler(c *gin.Context) {
repositories := ioc.GetRepositories(c)
action := c.MustGet("Action").(models.Action)
user := c.MustGet("User").(models.User)
if user.ID != action.UserID || !user.Admin {
c.Header("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"%s\"", c.Request.RequestURI))
c.JSON(http.StatusUnauthorized, utils.H{
"_status": "error",
"_message": "Client was not created",
"error": shared.AccessDenied,
})
return
}

client := models.Client{
Name: c.PostForm("name"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package api
package clients

import (
"fmt"
Expand All @@ -11,30 +11,28 @@ import (
"github.com/earaujoassis/space/test/utils"
)

func (s *ApiHandlerTestSuite) TestClientsCreateHandlerWithoutHeader() {
func (s *ClientsTestSuite) TestCreateHandlerWithoutHeader() {
w := s.PerformRequest(s.Router, "POST", "/api/clients", nil, nil, nil)
r := utils.ParseResponse(w.Result(), nil)
s.Require().Equal(400, w.Code)
s.Contains(r.Body, "missing X-Requested-By header attribute or Origin header does not comply with the same-origin policy")
}

func (s *ApiHandlerTestSuite) TestClientsCreateHandlerByUnauthenticatedUser() {
func (s *ClientsTestSuite) TestCreateHandlerByUnauthenticatedUser() {
header := &http.Header{
"X-Requested-By": []string{"SpaceApi"},
}

w := s.PerformRequest(s.Router, "POST", "/api/clients", header, nil, nil)
r := utils.ParseResponse(w.Result(), nil)
s.Require().Equal(401, w.Code)
s.Contains(r.Body, "User must be authenticated")
}

func (s *ApiHandlerTestSuite) TestClientsCreateHandlerWithoutActionGrant() {
func (s *ClientsTestSuite) TestCreateHandlerWithoutActionGrant() {
header := &http.Header{
"X-Requested-By": []string{"SpaceApi"},
}

cookie := s.createSessionCookie(true)
cookie := s.CreateSessionCookie(true)
s.NotNil(cookie)

w := s.PerformRequest(s.Router, "POST", "/api/clients", header, cookie, nil)
Expand All @@ -44,8 +42,8 @@ func (s *ApiHandlerTestSuite) TestClientsCreateHandlerWithoutActionGrant() {
s.Equal("must use valid token field", r.JSON["error"])
}

func (s *ApiHandlerTestSuite) TestClientsCreateHandlerByAdminUser() {
cookie := s.createSessionCookie(true)
func (s *ClientsTestSuite) TestCreateHandlerByAdminUser() {
cookie := s.CreateSessionCookie(true)
s.NotNil(cookie)
user := s.Factory.GetAvailableUser()
actionToken := s.Factory.NewAction(user).Model.Token
Expand Down Expand Up @@ -82,8 +80,8 @@ func (s *ApiHandlerTestSuite) TestClientsCreateHandlerByAdminUser() {
s.Require().Equal(204, w.Code)
}

func (s *ApiHandlerTestSuite) TestClientsCreateHandlerByCommonUser() {
cookie := s.createSessionCookie(false)
func (s *ClientsTestSuite) TestCreateHandlerByCommonUser() {
cookie := s.CreateSessionCookie(false)
s.NotNil(cookie)
user := s.Factory.GetAvailableUser()
actionToken := s.Factory.NewAction(user).Model.Token
Expand All @@ -96,7 +94,6 @@ func (s *ApiHandlerTestSuite) TestClientsCreateHandlerByCommonUser() {

w := s.PerformRequest(s.Router, "POST", "/api/clients", header, cookie, nil)
r := utils.ParseResponse(w.Result(), nil)
s.Require().Equal(401, w.Code)
s.Require().Equal(403, w.Code)
s.True(r.HasKeyInJSON("error"))
s.Equal("access_denied", r.JSON["error"])
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package api
package clients

import (
"fmt"
Expand All @@ -10,24 +10,11 @@ import (
"github.com/earaujoassis/space/internal/ioc"
"github.com/earaujoassis/space/internal/models"
"github.com/earaujoassis/space/internal/security"
"github.com/earaujoassis/space/internal/shared"
"github.com/earaujoassis/space/internal/utils"
)

func clientsCredentialsHandler(c *gin.Context) {
func credentialsHandler(c *gin.Context) {
clientUUID := c.Param("client_id")
repositories := ioc.GetRepositories(c)
user := c.MustGet("User").(models.User)
if !user.Admin {
c.Header("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"%s\"", c.Request.RequestURI))
c.JSON(http.StatusUnauthorized, utils.H{
"_status": "error",
"_message": "Client credentials are not available",
"error": shared.AccessDenied,
})
return
}

if !security.ValidUUID(clientUUID) {
c.JSON(http.StatusBadRequest, utils.H{
"_status": "error",
Expand All @@ -37,6 +24,7 @@ func clientsCredentialsHandler(c *gin.Context) {
return
}

repositories := ioc.GetRepositories(c)
client := repositories.Clients().FindByUUID(clientUUID)
// For security reasons, the client's secret is regenerated
clientSecret := models.GenerateRandomString(64)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package api
package clients

import (
"fmt"
Expand All @@ -7,12 +7,12 @@ import (
"github.com/earaujoassis/space/test/utils"
)

func (s *ApiHandlerTestSuite) TestClientsCredentialsHandlerByAdminUser() {
func (s *ClientsTestSuite) TestCredentialsHandlerByAdminUser() {
header := &http.Header{
"X-Requested-By": []string{"SpaceApi"},
}

cookie := s.createSessionCookie(true)
cookie := s.CreateSessionCookie(true)
s.NotNil(cookie)
user := s.Factory.GetAvailableUser()
actionToken := s.Factory.NewAction(user).Model.Token
Expand All @@ -30,7 +30,6 @@ func (s *ApiHandlerTestSuite) TestClientsCredentialsHandlerByAdminUser() {
r := utils.ParseResponse(w.Result(), nil)
s.Require().Equal(401, w.Code)
s.True(r.HasKeyInJSON("error"))
s.Equal("User must be authenticated", r.JSON["_message"])

header = &http.Header{
"X-Requested-By": []string{"SpaceApi"},
Expand All @@ -41,7 +40,6 @@ func (s *ApiHandlerTestSuite) TestClientsCredentialsHandlerByAdminUser() {
r = utils.ParseResponse(w.Result(), nil)
s.Require().Equal(401, w.Code)
s.True(r.HasKeyInJSON("error"))
s.Equal("User must be authenticated", r.JSON["_message"])

w = s.PerformRequest(s.Router, "GET", path, nil, cookie, nil)
r = utils.ParseResponse(w.Result(), nil)
Expand All @@ -63,8 +61,8 @@ func (s *ApiHandlerTestSuite) TestClientsCredentialsHandlerByAdminUser() {
s.Equal("Client credentials are not available", r.JSON["_message"])
}

func (s *ApiHandlerTestSuite) TestClientsCredentialsHandlerByCommonUser() {
cookie := s.createSessionCookie(false)
func (s *ClientsTestSuite) TestCredentialsHandlerByCommonUser() {
cookie := s.CreateSessionCookie(false)
s.NotNil(cookie)
user := s.Factory.GetAvailableUser()
actionToken := s.Factory.NewAction(user).Model.Token
Expand All @@ -80,7 +78,6 @@ func (s *ApiHandlerTestSuite) TestClientsCredentialsHandlerByCommonUser() {

w := s.PerformRequest(s.Router, "GET", path, header, cookie, nil)
r := utils.ParseResponse(w.Result(), nil)
s.Require().Equal(401, w.Code)
s.Require().Equal(403, w.Code)
s.True(r.HasKeyInJSON("error"))
s.Equal("access_denied", r.JSON["error"])
}
Loading