From b12d676546a2581136b13efd3aed172eeedec3d2 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Sat, 13 Jul 2024 10:41:35 +0200 Subject: [PATCH] Allow login using multiple forges (#3822) --- cmd/server/server.go | 11 +- pipeline/rpc/proto/woodpecker.pb.go | 44 +- server/api/hook.go | 114 +++--- server/api/hook_test.go | 104 +++++ server/api/login.go | 28 +- server/services/config/mocks/service.go | 63 +++ server/services/config/service.go | 2 + server/services/environment/mocks/service.go | 57 +++ server/services/environment/service.go | 2 + server/services/manager.go | 2 +- server/services/mocks/manager.go | 12 +- server/services/registry/mocks/service.go | 399 +++++++++++++++++++ server/services/registry/service.go | 2 + server/services/secret/mocks/service.go | 399 +++++++++++++++++++ server/services/secret/service.go | 2 + server/store/datastore/user.go | 2 +- web/src/assets/locales/en.json | 1 + web/src/compositions/useAuthentication.ts | 4 +- web/src/views/Login.vue | 31 +- 19 files changed, 1182 insertions(+), 97 deletions(-) create mode 100644 server/api/hook_test.go create mode 100644 server/services/config/mocks/service.go create mode 100644 server/services/environment/mocks/service.go create mode 100644 server/services/registry/mocks/service.go create mode 100644 server/services/secret/mocks/service.go diff --git a/cmd/server/server.go b/cmd/server/server.go index 524b9f0af..a83d98a2c 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -265,13 +265,6 @@ func run(c *cli.Context) error { } func setupEvilGlobals(c *cli.Context, s store.Store) error { - // secrets - var err error - server.Config.Server.JWTSecret, err = setupJWTSecret(s) - if err != nil { - return fmt.Errorf("could not setup jwt secret: %w", err) - } - // services server.Config.Services.Queue = setupQueue(c, s) server.Config.Services.Logs = logging.New() @@ -319,6 +312,10 @@ func setupEvilGlobals(c *cli.Context, s store.Store) error { server.Config.Pipeline.Proxy.HTTPS = c.String("backend-https-proxy") // server configuration + server.Config.Server.JWTSecret, err = setupJWTSecret(s) + if err != nil { + return fmt.Errorf("could not setup jwt secret: %w", err) + } server.Config.Server.Cert = c.String("server-cert") server.Config.Server.Key = c.String("server-key") server.Config.Server.AgentToken = c.String("agent-secret") diff --git a/pipeline/rpc/proto/woodpecker.pb.go b/pipeline/rpc/proto/woodpecker.pb.go index 383314408..f66e9be5b 100644 --- a/pipeline/rpc/proto/woodpecker.pb.go +++ b/pipeline/rpc/proto/woodpecker.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.34.1 // protoc v4.25.1 // source: woodpecker.proto @@ -1312,7 +1312,7 @@ func file_woodpecker_proto_rawDescGZIP() []byte { } var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 21) -var file_woodpecker_proto_goTypes = []any{ +var file_woodpecker_proto_goTypes = []interface{}{ (*StepState)(nil), // 0: proto.StepState (*WorkflowState)(nil), // 1: proto.WorkflowState (*LogEntry)(nil), // 2: proto.LogEntry @@ -1380,7 +1380,7 @@ func file_woodpecker_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_woodpecker_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StepState); i { case 0: return &v.state @@ -1392,7 +1392,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WorkflowState); i { case 0: return &v.state @@ -1404,7 +1404,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LogEntry); i { case 0: return &v.state @@ -1416,7 +1416,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[3].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Filter); i { case 0: return &v.state @@ -1428,7 +1428,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[4].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Workflow); i { case 0: return &v.state @@ -1440,7 +1440,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[5].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*NextRequest); i { case 0: return &v.state @@ -1452,7 +1452,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[6].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InitRequest); i { case 0: return &v.state @@ -1464,7 +1464,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[7].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WaitRequest); i { case 0: return &v.state @@ -1476,7 +1476,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[8].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DoneRequest); i { case 0: return &v.state @@ -1488,7 +1488,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[9].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ExtendRequest); i { case 0: return &v.state @@ -1500,7 +1500,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[10].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdateRequest); i { case 0: return &v.state @@ -1512,7 +1512,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[11].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LogRequest); i { case 0: return &v.state @@ -1524,7 +1524,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[12].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Empty); i { case 0: return &v.state @@ -1536,7 +1536,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[13].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ReportHealthRequest); i { case 0: return &v.state @@ -1548,7 +1548,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[14].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RegisterAgentRequest); i { case 0: return &v.state @@ -1560,7 +1560,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[15].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*VersionResponse); i { case 0: return &v.state @@ -1572,7 +1572,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[16].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*NextResponse); i { case 0: return &v.state @@ -1584,7 +1584,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[17].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RegisterAgentResponse); i { case 0: return &v.state @@ -1596,7 +1596,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[18].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AuthRequest); i { case 0: return &v.state @@ -1608,7 +1608,7 @@ func file_woodpecker_proto_init() { return nil } } - file_woodpecker_proto_msgTypes[19].Exporter = func(v any, i int) any { + file_woodpecker_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AuthResponse); i { case 0: return &v.state diff --git a/server/api/hook.go b/server/api/hook.go index 0cbc100ea..654acc93d 100644 --- a/server/api/hook.go +++ b/server/api/hook.go @@ -104,18 +104,47 @@ func BlockTilQueueHasRunningItem(c *gin.Context) { func PostHook(c *gin.Context) { _store := store.FromContext(c) - _forge, err := server.Config.Services.Manager.ForgeByID(1) // TODO: get the forge for the specific repo somehow + // + // 1. Check if the webhook is valid and authorized + // + + var repo *model.Repo + + _, err := token.ParseRequest([]token.Type{token.HookToken}, c.Request, func(t *token.Token) (string, error) { + var err error + repo, err = getRepoFromToken(_store, t) + if err != nil { + return "", err + } + + return repo.Hash, nil + }) if err != nil { - log.Error().Err(err).Msg("Cannot get main forge") + msg := "failure to parse token from hook" + log.Error().Err(err).Msg(msg) + c.String(http.StatusBadRequest, msg) + return + } + + if repo == nil { + msg := "failure to get repo from token" + log.Error().Msg(msg) + c.String(http.StatusBadRequest, msg) + return + } + + _forge, err := server.Config.Services.Manager.ForgeFromRepo(repo) + if err != nil { + log.Error().Err(err).Int64("repo-id", repo.ID).Msgf("Cannot get forge with id: %d", repo.ForgeID) c.AbortWithStatus(http.StatusInternalServerError) return } // - // 1. Parse webhook + // 2. Parse the webhook data // - tmpRepo, tmpPipeline, err := _forge.Hook(c, c.Request) + repoFromForge, pipelineFromForge, err := _forge.Hook(c, c.Request) if err != nil { if errors.Is(err, &types.ErrIgnoreEvent{}) { msg := fmt.Sprintf("forge driver: %s", err) @@ -130,13 +159,13 @@ func PostHook(c *gin.Context) { return } - if tmpPipeline == nil { + if pipelineFromForge == nil { msg := "ignoring hook: hook parsing resulted in empty pipeline" log.Debug().Msg(msg) c.String(http.StatusOK, msg) return } - if tmpRepo == nil { + if repoFromForge == nil { msg := "failure to ascertain repo from hook" log.Debug().Msg(msg) c.String(http.StatusBadRequest, msg) @@ -144,21 +173,24 @@ func PostHook(c *gin.Context) { } // - // 2. Get related repo from store and take repo renaming into account + // 3. Check the repo from the token is matching the repo returned by the forge // - repo, err := _store.GetRepoNameFallback(tmpRepo.ForgeRemoteID, tmpRepo.FullName) - if err != nil { - log.Error().Err(err).Msgf("failure to get repo %s from store", tmpRepo.FullName) - handleDBError(c, err) + if repo.ForgeRemoteID != repoFromForge.ForgeRemoteID { + log.Warn().Msgf("ignoring hook: repo %s does not match the repo from the token", repo.FullName) + c.String(http.StatusBadRequest, "failure to parse token from hook") return } + + // + // 4. Check if the repo is active and has an owner + // + if !repo.IsActive { - log.Debug().Msgf("ignoring hook: repo %s is inactive", tmpRepo.FullName) + log.Debug().Msgf("ignoring hook: repo %s is inactive", repoFromForge.FullName) c.Status(http.StatusNoContent) return } - currentRepoFullName := repo.FullName if repo.UserID == 0 { log.Warn().Msgf("ignoring hook. repo %s has no owner.", repo.FullName) @@ -174,44 +206,10 @@ func PostHook(c *gin.Context) { forge.Refresh(c, _forge, _store, user) // - // 3. Check if the webhook is a valid and authorized one + // 4. Update the repo // - // get the token and verify the hook is authorized - parsedToken, err := token.ParseRequest([]token.Type{token.HookToken}, c.Request, func(_ *token.Token) (string, error) { - return repo.Hash, nil - }) - if err != nil { - msg := fmt.Sprintf("failure to parse token from hook for %s", repo.FullName) - log.Error().Err(err).Msg(msg) - c.String(http.StatusBadRequest, msg) - return - } - - // TODO: remove fallback for text full name in next major release - verifiedKey := parsedToken.Get("repo-id") == strconv.FormatInt(repo.ID, 10) || parsedToken.Get("text") == currentRepoFullName - if !verifiedKey { - verifiedKey, err = _store.HasRedirectionForRepo(repo.ID, repo.FullName) - if err != nil { - msg := "failure to verify token from hook. Could not check for redirections of the repo" - log.Error().Err(err).Msg(msg) - c.String(http.StatusInternalServerError, msg) - return - } - } - - if !verifiedKey { - msg := fmt.Sprintf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsedToken.Get("text")) - log.Debug().Msg(msg) - c.String(http.StatusForbidden, msg) - return - } - - // - // 4. Update repo - // - - if currentRepoFullName != tmpRepo.FullName { + if repo.FullName != repoFromForge.FullName { // create a redirection err = _store.CreateRedirection(&model.Redirection{RepoID: repo.ID, FullName: repo.FullName}) if err != nil { @@ -220,7 +218,7 @@ func PostHook(c *gin.Context) { } } - repo.Update(tmpRepo) + repo.Update(repoFromForge) err = _store.UpdateRepo(repo) if err != nil { c.String(http.StatusInternalServerError, err.Error()) @@ -231,7 +229,7 @@ func PostHook(c *gin.Context) { // 5. Check if pull requests are allowed for this repo // - if (tmpPipeline.Event == model.EventPull || tmpPipeline.Event == model.EventPullClosed) && !repo.AllowPull { + if (pipelineFromForge.Event == model.EventPull || pipelineFromForge.Event == model.EventPullClosed) && !repo.AllowPull { log.Debug().Str("repo", repo.FullName).Msg("ignoring hook: pull requests are disabled for this repo in woodpecker") c.Status(http.StatusNoContent) return @@ -241,10 +239,22 @@ func PostHook(c *gin.Context) { // 6. Finally create a pipeline // - pl, err := pipeline.Create(c, _store, repo, tmpPipeline) + pl, err := pipeline.Create(c, _store, repo, pipelineFromForge) if err != nil { handlePipelineErr(c, err) } else { c.JSON(http.StatusOK, pl) } } + +func getRepoFromToken(store store.Store, t *token.Token) (*model.Repo, error) { + // try to get the repo by the repo-id + repoID, err := strconv.ParseInt(t.Get("repo-id"), 10, 64) + if err == nil { + return store.GetRepo(repoID) + } + + // try to get the repo by the repo name or by its redirection + repoName := t.Get("text") + return store.GetRepoName(repoName) +} diff --git a/server/api/hook_test.go b/server/api/hook_test.go new file mode 100644 index 000000000..681edf0c8 --- /dev/null +++ b/server/api/hook_test.go @@ -0,0 +1,104 @@ +package api_test + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/franela/goblin" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "go.woodpecker-ci.org/woodpecker/v2/server" + "go.woodpecker-ci.org/woodpecker/v2/server/api" + mocks_forge "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" + "go.woodpecker-ci.org/woodpecker/v2/server/model" + mocks_config_service "go.woodpecker-ci.org/woodpecker/v2/server/services/config/mocks" + mocks_services "go.woodpecker-ci.org/woodpecker/v2/server/services/mocks" + "go.woodpecker-ci.org/woodpecker/v2/server/services/permissions" + mocks_registry_service "go.woodpecker-ci.org/woodpecker/v2/server/services/registry/mocks" + mocks_secret_service "go.woodpecker-ci.org/woodpecker/v2/server/services/secret/mocks" + mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v2/shared/token" +) + +func TestHook(t *testing.T) { + gin.SetMode(gin.TestMode) + + g := goblin.Goblin(t) + g.Describe("Hook", func() { + g.It("should handle a correct webhook payload", func() { + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + _configService := mocks_config_service.NewService(t) + _secretService := mocks_secret_service.NewService(t) + _registryService := mocks_registry_service.NewService(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + user := &model.User{ + ID: 123, + } + repo := &model.Repo{ + ID: 123, + ForgeRemoteID: "123", + Owner: "owner", + Name: "name", + IsActive: true, + UserID: user.ID, + Hash: "secret-123-this-is-a-secret", + } + pipeline := &model.Pipeline{ + ID: 123, + RepoID: repo.ID, + Event: model.EventPush, + } + + repoToken := token.New(token.HookToken) + repoToken.Set("repo-id", fmt.Sprintf("%d", repo.ID)) + signedToken, err := repoToken.Sign("secret-123-this-is-a-secret") + if err != nil { + g.Fail(err) + } + + header := http.Header{} + header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken)) + c.Request = &http.Request{ + Header: header, + URL: &url.URL{ + Scheme: "https", + }, + } + + _manager.On("ForgeFromRepo", repo).Return(_forge, nil) + _forge.On("Hook", mock.Anything, mock.Anything).Return(repo, pipeline, nil) + _store.On("GetRepo", repo.ID).Return(repo, nil) + _store.On("GetUser", user.ID).Return(user, nil) + _store.On("UpdateRepo", repo).Return(nil) + _store.On("CreatePipeline", mock.Anything).Return(nil) + _manager.On("ConfigServiceFromRepo", repo).Return(_configService) + _configService.On("Fetch", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) + _forge.On("Netrc", mock.Anything, mock.Anything).Return(&model.Netrc{}, nil) + _store.On("GetPipelineLastBefore", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) + _manager.On("SecretServiceFromRepo", repo).Return(_secretService) + _secretService.On("SecretListPipeline", repo, mock.Anything, mock.Anything).Return(nil, nil) + _manager.On("RegistryServiceFromRepo", repo).Return(_registryService) + _registryService.On("RegistryListPipeline", repo, mock.Anything).Return(nil, nil) + _manager.On("EnvironmentService").Return(nil) + _store.On("DeletePipeline", mock.Anything).Return(nil) + + api.PostHook(c) + + assert.Equal(g, http.StatusNoContent, c.Writer.Status()) + assert.Equal(g, "true", w.Header().Get("Pipeline-Filtered")) + }) + }) +} diff --git a/server/api/login.go b/server/api/login.go index aa883b79d..ebe6cf7b3 100644 --- a/server/api/login.go +++ b/server/api/login.go @@ -62,10 +62,10 @@ func HandleAuth(c *gin.Context) { code := c.Request.FormValue("code") state := c.Request.FormValue("state") isCallback := code != "" && state != "" - forgeID := int64(1) // TODO: replace with forge id when multiple forges are supported + var forgeID int64 if isCallback { // validate the state token - _, err := token.Parse([]token.Type{token.OAuthStateToken}, state, func(_ *token.Token) (string, error) { + stateToken, err := token.Parse([]token.Type{token.OAuthStateToken}, state, func(_ *token.Token) (string, error) { return server.Config.Server.JWTSecret, nil }) if err != nil { @@ -73,8 +73,29 @@ func HandleAuth(c *gin.Context) { c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=invalid_state") return } + + _forgeID := stateToken.Get("forge-id") + forgeID, err = strconv.ParseInt(_forgeID, 10, 64) + if err != nil { + log.Error().Err(err).Msg("forge-id of state token invalid") + c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=invalid_state") + return + } } else { // only generate a state token if not a callback var err error + + _forgeID := c.Request.FormValue("forge_id") + if _forgeID == "" { + forgeID = 1 // fallback to main forge + } else { + forgeID, err = strconv.ParseInt(_forgeID, 10, 64) + if err != nil { + log.Error().Err(err).Msg("forge-id of state token invalid") + c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=invalid_state") + return + } + } + jwtSecret := server.Config.Server.JWTSecret exp := time.Now().Add(stateTokenDuration).Unix() stateToken := token.New(token.OAuthStateToken) @@ -208,6 +229,7 @@ func HandleAuth(c *gin.Context) { user.Secret = userFromForge.Secret user.Email = userFromForge.Email user.Avatar = userFromForge.Avatar + user.ForgeID = forgeID user.ForgeRemoteID = userFromForge.ForgeRemoteID user.Login = userFromForge.Login user.Admin = user.Admin || server.Config.Permissions.Admins.IsAdmin(userFromForge) @@ -280,7 +302,7 @@ func GetLogout(c *gin.Context) { func DeprecatedGetLoginToken(c *gin.Context) { _store := store.FromContext(c) - _forge, err := server.Config.Services.Manager.ForgeByID(1) // TODO: get selected forge from auth request + _forge, err := server.Config.Services.Manager.ForgeByID(1) if err != nil { log.Error().Err(err).Msg("Cannot get main forge") c.AbortWithStatus(http.StatusInternalServerError) diff --git a/server/services/config/mocks/service.go b/server/services/config/mocks/service.go new file mode 100644 index 000000000..3f08d71c7 --- /dev/null +++ b/server/services/config/mocks/service.go @@ -0,0 +1,63 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + forge "go.woodpecker-ci.org/woodpecker/v2/server/forge" + + model "go.woodpecker-ci.org/woodpecker/v2/server/model" + + types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// Fetch provides a mock function with given fields: ctx, _a1, user, repo, pipeline, oldConfigData, restart +func (_m *Service) Fetch(ctx context.Context, _a1 forge.Forge, user *model.User, repo *model.Repo, pipeline *model.Pipeline, oldConfigData []*types.FileMeta, restart bool) ([]*types.FileMeta, error) { + ret := _m.Called(ctx, _a1, user, repo, pipeline, oldConfigData, restart) + + if len(ret) == 0 { + panic("no return value specified for Fetch") + } + + var r0 []*types.FileMeta + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, forge.Forge, *model.User, *model.Repo, *model.Pipeline, []*types.FileMeta, bool) ([]*types.FileMeta, error)); ok { + return rf(ctx, _a1, user, repo, pipeline, oldConfigData, restart) + } + if rf, ok := ret.Get(0).(func(context.Context, forge.Forge, *model.User, *model.Repo, *model.Pipeline, []*types.FileMeta, bool) []*types.FileMeta); ok { + r0 = rf(ctx, _a1, user, repo, pipeline, oldConfigData, restart) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.FileMeta) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, forge.Forge, *model.User, *model.Repo, *model.Pipeline, []*types.FileMeta, bool) error); ok { + r1 = rf(ctx, _a1, user, repo, pipeline, oldConfigData, restart) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/server/services/config/service.go b/server/services/config/service.go index 12ba46f21..9f857a796 100644 --- a/server/services/config/service.go +++ b/server/services/config/service.go @@ -22,6 +22,8 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/server/model" ) +//go:generate mockery --name Service --output mocks --case underscore + type Service interface { Fetch(ctx context.Context, forge forge.Forge, user *model.User, repo *model.Repo, pipeline *model.Pipeline, oldConfigData []*types.FileMeta, restart bool) (configData []*types.FileMeta, err error) } diff --git a/server/services/environment/mocks/service.go b/server/services/environment/mocks/service.go new file mode 100644 index 000000000..b7cb0776d --- /dev/null +++ b/server/services/environment/mocks/service.go @@ -0,0 +1,57 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + model "go.woodpecker-ci.org/woodpecker/v2/server/model" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// EnvironList provides a mock function with given fields: _a0 +func (_m *Service) EnvironList(_a0 *model.Repo) ([]*model.Environ, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for EnvironList") + } + + var r0 []*model.Environ + var r1 error + if rf, ok := ret.Get(0).(func(*model.Repo) ([]*model.Environ, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(*model.Repo) []*model.Environ); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Environ) + } + } + + if rf, ok := ret.Get(1).(func(*model.Repo) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/server/services/environment/service.go b/server/services/environment/service.go index f8ad14821..0b63471fd 100644 --- a/server/services/environment/service.go +++ b/server/services/environment/service.go @@ -16,6 +16,8 @@ package environment import "go.woodpecker-ci.org/woodpecker/v2/server/model" +//go:generate mockery --name Service --output mocks --case underscore + // Service defines a service for managing environment variables. type Service interface { EnvironList(*model.Repo) ([]*model.Environ, error) diff --git a/server/services/manager.go b/server/services/manager.go index 39ce07a48..3107f827d 100644 --- a/server/services/manager.go +++ b/server/services/manager.go @@ -46,7 +46,7 @@ type Manager interface { EnvironmentService() environment.Service ForgeFromRepo(repo *model.Repo) (forge.Forge, error) ForgeFromUser(user *model.User) (forge.Forge, error) - ForgeByID(id int64) (forge.Forge, error) + ForgeByID(forgeID int64) (forge.Forge, error) } type manager struct { diff --git a/server/services/mocks/manager.go b/server/services/mocks/manager.go index 0df118f81..1d94cdec8 100644 --- a/server/services/mocks/manager.go +++ b/server/services/mocks/manager.go @@ -68,9 +68,9 @@ func (_m *Manager) EnvironmentService() environment.Service { return r0 } -// ForgeByID provides a mock function with given fields: id -func (_m *Manager) ForgeByID(id int64) (forge.Forge, error) { - ret := _m.Called(id) +// ForgeByID provides a mock function with given fields: forgeID +func (_m *Manager) ForgeByID(forgeID int64) (forge.Forge, error) { + ret := _m.Called(forgeID) if len(ret) == 0 { panic("no return value specified for ForgeByID") @@ -79,10 +79,10 @@ func (_m *Manager) ForgeByID(id int64) (forge.Forge, error) { var r0 forge.Forge var r1 error if rf, ok := ret.Get(0).(func(int64) (forge.Forge, error)); ok { - return rf(id) + return rf(forgeID) } if rf, ok := ret.Get(0).(func(int64) forge.Forge); ok { - r0 = rf(id) + r0 = rf(forgeID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(forge.Forge) @@ -90,7 +90,7 @@ func (_m *Manager) ForgeByID(id int64) (forge.Forge, error) { } if rf, ok := ret.Get(1).(func(int64) error); ok { - r1 = rf(id) + r1 = rf(forgeID) } else { r1 = ret.Error(1) } diff --git a/server/services/registry/mocks/service.go b/server/services/registry/mocks/service.go new file mode 100644 index 000000000..ecce0a414 --- /dev/null +++ b/server/services/registry/mocks/service.go @@ -0,0 +1,399 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + model "go.woodpecker-ci.org/woodpecker/v2/server/model" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// GlobalRegistryCreate provides a mock function with given fields: _a0 +func (_m *Service) GlobalRegistryCreate(_a0 *model.Registry) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistryCreate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Registry) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GlobalRegistryDelete provides a mock function with given fields: _a0 +func (_m *Service) GlobalRegistryDelete(_a0 string) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistryDelete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GlobalRegistryFind provides a mock function with given fields: _a0 +func (_m *Service) GlobalRegistryFind(_a0 string) (*model.Registry, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistryFind") + } + + var r0 *model.Registry + var r1 error + if rf, ok := ret.Get(0).(func(string) (*model.Registry, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(string) *model.Registry); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Registry) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GlobalRegistryList provides a mock function with given fields: _a0 +func (_m *Service) GlobalRegistryList(_a0 *model.ListOptions) ([]*model.Registry, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistryList") + } + + var r0 []*model.Registry + var r1 error + if rf, ok := ret.Get(0).(func(*model.ListOptions) ([]*model.Registry, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(*model.ListOptions) []*model.Registry); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Registry) + } + } + + if rf, ok := ret.Get(1).(func(*model.ListOptions) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GlobalRegistryUpdate provides a mock function with given fields: _a0 +func (_m *Service) GlobalRegistryUpdate(_a0 *model.Registry) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistryUpdate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Registry) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrgRegistryCreate provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgRegistryCreate(_a0 int64, _a1 *model.Registry) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistryCreate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(int64, *model.Registry) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrgRegistryDelete provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgRegistryDelete(_a0 int64, _a1 string) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistryDelete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(int64, string) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrgRegistryFind provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgRegistryFind(_a0 int64, _a1 string) (*model.Registry, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistryFind") + } + + var r0 *model.Registry + var r1 error + if rf, ok := ret.Get(0).(func(int64, string) (*model.Registry, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(int64, string) *model.Registry); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Registry) + } + } + + if rf, ok := ret.Get(1).(func(int64, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrgRegistryList provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgRegistryList(_a0 int64, _a1 *model.ListOptions) ([]*model.Registry, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistryList") + } + + var r0 []*model.Registry + var r1 error + if rf, ok := ret.Get(0).(func(int64, *model.ListOptions) ([]*model.Registry, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(int64, *model.ListOptions) []*model.Registry); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Registry) + } + } + + if rf, ok := ret.Get(1).(func(int64, *model.ListOptions) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrgRegistryUpdate provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgRegistryUpdate(_a0 int64, _a1 *model.Registry) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistryUpdate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(int64, *model.Registry) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RegistryCreate provides a mock function with given fields: _a0, _a1 +func (_m *Service) RegistryCreate(_a0 *model.Repo, _a1 *model.Registry) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for RegistryCreate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Repo, *model.Registry) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RegistryDelete provides a mock function with given fields: _a0, _a1 +func (_m *Service) RegistryDelete(_a0 *model.Repo, _a1 string) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for RegistryDelete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Repo, string) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RegistryFind provides a mock function with given fields: _a0, _a1 +func (_m *Service) RegistryFind(_a0 *model.Repo, _a1 string) (*model.Registry, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for RegistryFind") + } + + var r0 *model.Registry + var r1 error + if rf, ok := ret.Get(0).(func(*model.Repo, string) (*model.Registry, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*model.Repo, string) *model.Registry); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Registry) + } + } + + if rf, ok := ret.Get(1).(func(*model.Repo, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RegistryList provides a mock function with given fields: _a0, _a1 +func (_m *Service) RegistryList(_a0 *model.Repo, _a1 *model.ListOptions) ([]*model.Registry, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for RegistryList") + } + + var r0 []*model.Registry + var r1 error + if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions) ([]*model.Registry, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions) []*model.Registry); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Registry) + } + } + + if rf, ok := ret.Get(1).(func(*model.Repo, *model.ListOptions) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RegistryListPipeline provides a mock function with given fields: _a0, _a1 +func (_m *Service) RegistryListPipeline(_a0 *model.Repo, _a1 *model.Pipeline) ([]*model.Registry, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for RegistryListPipeline") + } + + var r0 []*model.Registry + var r1 error + if rf, ok := ret.Get(0).(func(*model.Repo, *model.Pipeline) ([]*model.Registry, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*model.Repo, *model.Pipeline) []*model.Registry); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Registry) + } + } + + if rf, ok := ret.Get(1).(func(*model.Repo, *model.Pipeline) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RegistryUpdate provides a mock function with given fields: _a0, _a1 +func (_m *Service) RegistryUpdate(_a0 *model.Repo, _a1 *model.Registry) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for RegistryUpdate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Repo, *model.Registry) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/server/services/registry/service.go b/server/services/registry/service.go index 4198f2ff5..39e1e3fa9 100644 --- a/server/services/registry/service.go +++ b/server/services/registry/service.go @@ -16,6 +16,8 @@ package registry import "go.woodpecker-ci.org/woodpecker/v2/server/model" +//go:generate mockery --name Service --output mocks --case underscore + // Service defines a service for managing registries. type Service interface { RegistryListPipeline(*model.Repo, *model.Pipeline) ([]*model.Registry, error) diff --git a/server/services/secret/mocks/service.go b/server/services/secret/mocks/service.go new file mode 100644 index 000000000..37f301d53 --- /dev/null +++ b/server/services/secret/mocks/service.go @@ -0,0 +1,399 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + model "go.woodpecker-ci.org/woodpecker/v2/server/model" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// GlobalSecretCreate provides a mock function with given fields: _a0 +func (_m *Service) GlobalSecretCreate(_a0 *model.Secret) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecretCreate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Secret) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GlobalSecretDelete provides a mock function with given fields: _a0 +func (_m *Service) GlobalSecretDelete(_a0 string) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecretDelete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GlobalSecretFind provides a mock function with given fields: _a0 +func (_m *Service) GlobalSecretFind(_a0 string) (*model.Secret, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecretFind") + } + + var r0 *model.Secret + var r1 error + if rf, ok := ret.Get(0).(func(string) (*model.Secret, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(string) *model.Secret); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Secret) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GlobalSecretList provides a mock function with given fields: _a0 +func (_m *Service) GlobalSecretList(_a0 *model.ListOptions) ([]*model.Secret, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecretList") + } + + var r0 []*model.Secret + var r1 error + if rf, ok := ret.Get(0).(func(*model.ListOptions) ([]*model.Secret, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(*model.ListOptions) []*model.Secret); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Secret) + } + } + + if rf, ok := ret.Get(1).(func(*model.ListOptions) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GlobalSecretUpdate provides a mock function with given fields: _a0 +func (_m *Service) GlobalSecretUpdate(_a0 *model.Secret) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecretUpdate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Secret) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrgSecretCreate provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgSecretCreate(_a0 int64, _a1 *model.Secret) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgSecretCreate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(int64, *model.Secret) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrgSecretDelete provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgSecretDelete(_a0 int64, _a1 string) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgSecretDelete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(int64, string) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrgSecretFind provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgSecretFind(_a0 int64, _a1 string) (*model.Secret, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgSecretFind") + } + + var r0 *model.Secret + var r1 error + if rf, ok := ret.Get(0).(func(int64, string) (*model.Secret, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(int64, string) *model.Secret); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Secret) + } + } + + if rf, ok := ret.Get(1).(func(int64, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrgSecretList provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgSecretList(_a0 int64, _a1 *model.ListOptions) ([]*model.Secret, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgSecretList") + } + + var r0 []*model.Secret + var r1 error + if rf, ok := ret.Get(0).(func(int64, *model.ListOptions) ([]*model.Secret, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(int64, *model.ListOptions) []*model.Secret); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Secret) + } + } + + if rf, ok := ret.Get(1).(func(int64, *model.ListOptions) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrgSecretUpdate provides a mock function with given fields: _a0, _a1 +func (_m *Service) OrgSecretUpdate(_a0 int64, _a1 *model.Secret) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for OrgSecretUpdate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(int64, *model.Secret) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SecretCreate provides a mock function with given fields: _a0, _a1 +func (_m *Service) SecretCreate(_a0 *model.Repo, _a1 *model.Secret) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SecretCreate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Repo, *model.Secret) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SecretDelete provides a mock function with given fields: _a0, _a1 +func (_m *Service) SecretDelete(_a0 *model.Repo, _a1 string) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SecretDelete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Repo, string) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SecretFind provides a mock function with given fields: _a0, _a1 +func (_m *Service) SecretFind(_a0 *model.Repo, _a1 string) (*model.Secret, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SecretFind") + } + + var r0 *model.Secret + var r1 error + if rf, ok := ret.Get(0).(func(*model.Repo, string) (*model.Secret, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*model.Repo, string) *model.Secret); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Secret) + } + } + + if rf, ok := ret.Get(1).(func(*model.Repo, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SecretList provides a mock function with given fields: _a0, _a1 +func (_m *Service) SecretList(_a0 *model.Repo, _a1 *model.ListOptions) ([]*model.Secret, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SecretList") + } + + var r0 []*model.Secret + var r1 error + if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions) ([]*model.Secret, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions) []*model.Secret); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Secret) + } + } + + if rf, ok := ret.Get(1).(func(*model.Repo, *model.ListOptions) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SecretListPipeline provides a mock function with given fields: _a0, _a1 +func (_m *Service) SecretListPipeline(_a0 *model.Repo, _a1 *model.Pipeline) ([]*model.Secret, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SecretListPipeline") + } + + var r0 []*model.Secret + var r1 error + if rf, ok := ret.Get(0).(func(*model.Repo, *model.Pipeline) ([]*model.Secret, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*model.Repo, *model.Pipeline) []*model.Secret); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Secret) + } + } + + if rf, ok := ret.Get(1).(func(*model.Repo, *model.Pipeline) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SecretUpdate provides a mock function with given fields: _a0, _a1 +func (_m *Service) SecretUpdate(_a0 *model.Repo, _a1 *model.Secret) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SecretUpdate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*model.Repo, *model.Secret) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/server/services/secret/service.go b/server/services/secret/service.go index d505de0a1..c2b25ba0f 100644 --- a/server/services/secret/service.go +++ b/server/services/secret/service.go @@ -16,6 +16,8 @@ package secret import "go.woodpecker-ci.org/woodpecker/v2/server/model" +//go:generate mockery --name Service --output mocks --case underscore + // Service defines a service for managing secrets. type Service interface { SecretListPipeline(*model.Repo, *model.Pipeline) ([]*model.Secret, error) diff --git a/server/store/datastore/user.go b/server/store/datastore/user.go index 67427190c..c1900062e 100644 --- a/server/store/datastore/user.go +++ b/server/store/datastore/user.go @@ -30,7 +30,7 @@ func (s storage) GetUserRemoteID(remoteID model.ForgeRemoteID, login string) (*m user := new(model.User) err := wrapGet(sess.Where("forge_remote_id = ?", remoteID).Get(user)) if err != nil { - user, err = s.getUserLogin(sess, login) + return s.getUserLogin(sess, login) } return user, err } diff --git a/web/src/assets/locales/en.json b/web/src/assets/locales/en.json index c1538704b..d9087a016 100644 --- a/web/src/assets/locales/en.json +++ b/web/src/assets/locales/en.json @@ -1,5 +1,6 @@ { "cancel": "Cancel", + "login_with": "Login with {forge}", "login": "Login", "welcome": "Welcome to Woodpecker", "repos": "Repos", diff --git a/web/src/compositions/useAuthentication.ts b/web/src/compositions/useAuthentication.ts index d8bcb21fd..35e6b510e 100644 --- a/web/src/compositions/useAuthentication.ts +++ b/web/src/compositions/useAuthentication.ts @@ -7,11 +7,11 @@ export default () => user: useConfig().user, - authenticate(url?: string) { + authenticate(url?: string, forgeId?: number) { if (url !== undefined) { const config = useUserConfig(); config.setUserConfig('redirectUrl', url); } - window.location.href = `${useConfig().rootPath}/authorize`; + window.location.href = `${useConfig().rootPath}/authorize?forge_id=${forgeId}`; }, }) as const; diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index 9f60c192b..bc3a978f5 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -21,7 +21,16 @@

{{ $t('welcome') }}

- +
+ +
@@ -35,16 +44,30 @@ import { useRoute, useRouter } from 'vue-router'; import WoodpeckerLogo from '~/assets/logo.svg?component'; import Button from '~/components/atomic/Button.vue'; import Error from '~/components/atomic/Error.vue'; +import useApiClient from '~/compositions/useApiClient'; import useAuthentication from '~/compositions/useAuthentication'; +import type { Forge } from '~/lib/api/types'; const route = useRoute(); const router = useRouter(); const authentication = useAuthentication(); const i18n = useI18n(); +const apiClient = useApiClient(); -function doLogin() { +const forges = ref([]); + +function getHostFromUrl(forge: Forge) { + if (!forge.url) { + return forge.type.charAt(0).toUpperCase() + forge.type.slice(1); + } + + const url = new URL(forge.url); + return url.hostname; +} + +function doLogin(forgeId?: number) { const url = typeof route.query.url === 'string' ? route.query.url : ''; - authentication.authenticate(url); + authentication.authenticate(url, forgeId); } const authErrorMessages = { @@ -65,6 +88,8 @@ onMounted(async () => { return; } + forges.value = (await apiClient.getForges()) ?? []; + if (route.query.error) { const error = route.query.error as keyof typeof authErrorMessages; errorMessage.value = authErrorMessages[error] ?? error;