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
45 changes: 37 additions & 8 deletions api/projects/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/semaphoreui/semaphore/db_lib"
"github.com/semaphoreui/semaphore/pkg/git"
"github.com/semaphoreui/semaphore/pkg/task_logger"
"github.com/semaphoreui/semaphore/services/server"
"github.com/semaphoreui/semaphore/util"
)

Expand Down Expand Up @@ -47,15 +48,37 @@ func GetRepositoryRefs(w http.ResponseWriter, r *http.Request) {
}

type RepositoryController struct {
keyInstaller db_lib.AccessKeyInstaller
keyInstaller db_lib.AccessKeyInstaller
encryptionService server.AccessKeyEncryptionService
}

func NewRepositoryController(keyInstaller db_lib.AccessKeyInstaller) *RepositoryController {
func NewRepositoryController(keyInstaller db_lib.AccessKeyInstaller, encryptionService server.AccessKeyEncryptionService) *RepositoryController {
return &RepositoryController{
keyInstaller: keyInstaller,
keyInstaller: keyInstaller,
encryptionService: encryptionService,
}
}

// loadSubmoduleCredentials returns the repository's per-host submodule
// credentials with each mapped AccessKey decrypted, ready to hand to
// db_lib.GitRepository -- Clone/Pull always update submodules, so any path
// that clones/pulls needs these to authenticate a submodule hosted with
// different credentials than the repository's own SSHKey.
func (c *RepositoryController) loadSubmoduleCredentials(r *http.Request, repo db.Repository) ([]db.RepositorySubmoduleCredential, error) {
creds, err := helpers.Store(r).GetRepositorySubmoduleCredentials(repo.ProjectID, repo.ID)
if err != nil {
return nil, err
}

for i := range creds {
if err := c.encryptionService.DeserializeSecret(&creds[i].AccessKey); err != nil {
return nil, err
}
}

return creds, nil
}

func (c *RepositoryController) GetRepositoryBranches(w http.ResponseWriter, r *http.Request) {
repo := helpers.GetFromContext(r, "repository").(db.Repository)

Expand Down Expand Up @@ -101,6 +124,12 @@ func (c *RepositoryController) GetRepositoryPlaybooks(w http.ResponseWriter, r *
return
}

submoduleCredentials, err := c.loadSubmoduleCredentials(r, repo)
if err != nil {
helpers.WriteError(w, err)
return
}

repoCopy := repo
repoCopy.GitBranch = branch
// Clone() does a single-branch clone (git clone --branch <branch>), so a
Expand All @@ -110,13 +139,13 @@ func (c *RepositoryController) GetRepositoryPlaybooks(w http.ResponseWriter, r *
// of failing to check out a branch that was never fetched.
branchHash := sha1.Sum([]byte(branch))
git := db_lib.GitRepository{
Repository: repoCopy,
TmpDirName: fmt.Sprintf("repository_%d_browse_%x", repo.ID, branchHash[:4]),
Client: db_lib.CreateDefaultGitClient(c.keyInstaller),
Logger: task_logger.NopLogger{},
Repository: repoCopy,
TmpDirName: fmt.Sprintf("repository_%d_browse_%x", repo.ID, branchHash[:4]),
SubmoduleCredentials: submoduleCredentials,
Client: db_lib.CreateDefaultGitClient(c.keyInstaller),
Logger: task_logger.NopLogger{},
}

var err error
if err = git.ValidateRepo(); err != nil {
err = git.Clone()
} else {
Expand Down
136 changes: 136 additions & 0 deletions api/projects/repository_submodule_credential.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package projects

import (
"fmt"
"net/http"

"github.com/semaphoreui/semaphore/api/helpers"
"github.com/semaphoreui/semaphore/db"
)

// SubmoduleCredentialMiddleware ensures a submodule credential exists on the
// repository in context and loads it into the request context.
func SubmoduleCredentialMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
repository := helpers.GetFromContext(r, "repository").(db.Repository)

credentialID, err := helpers.GetIntParam("submodule_credential_id", w, r)
if err != nil {
return
}

credential, err := helpers.Store(r).GetRepositorySubmoduleCredential(repository.ProjectID, repository.ID, credentialID)
if err != nil {
helpers.WriteError(w, err)
return
}

r = helpers.SetContextValue(r, "submoduleCredential", credential)
next.ServeHTTP(w, r)
})
}

// GetRepositorySubmoduleCredentials returns the submodule host/access-key
// mappings configured for the repository in context.
func GetRepositorySubmoduleCredentials(w http.ResponseWriter, r *http.Request) {
repository := helpers.GetFromContext(r, "repository").(db.Repository)

credentials, err := helpers.Store(r).GetRepositorySubmoduleCredentials(repository.ProjectID, repository.ID)
if err != nil {
helpers.WriteError(w, err)
return
}

helpers.WriteJSON(w, http.StatusOK, credentials)
}

// AddRepositorySubmoduleCredential creates a new submodule credential mapping
// for the repository in context.
func AddRepositorySubmoduleCredential(w http.ResponseWriter, r *http.Request) {
repository := helpers.GetFromContext(r, "repository").(db.Repository)

var credential db.RepositorySubmoduleCredential

if !helpers.Bind(w, r, &credential) {
return
}

credential.ProjectID = repository.ProjectID
credential.RepositoryID = repository.ID

newCredential, err := helpers.Store(r).CreateRepositorySubmoduleCredential(credential)
if err != nil {
helpers.WriteError(w, err)
return
}

helpers.EventLog(r, helpers.EventLogCreate, helpers.EventLogItem{
UserID: helpers.UserFromContext(r).ID,
ProjectID: repository.ProjectID,
ObjectType: db.EventRepository,
ObjectID: repository.ID,
Description: fmt.Sprintf("Submodule credential for host %s added to repository %s", credential.Host, repository.GitURL),
})

helpers.WriteJSON(w, http.StatusCreated, newCredential)
}

// UpdateRepositorySubmoduleCredential updates an existing submodule credential
// mapping.
func UpdateRepositorySubmoduleCredential(w http.ResponseWriter, r *http.Request) {
repository := helpers.GetFromContext(r, "repository").(db.Repository)
oldCredential := helpers.GetFromContext(r, "submoduleCredential").(db.RepositorySubmoduleCredential)

var credential db.RepositorySubmoduleCredential

if !helpers.Bind(w, r, &credential) {
return
}

if credential.ID != 0 && credential.ID != oldCredential.ID {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{
"error": "Submodule credential ID in body and URL must be the same",
})
return
}

credential.ID = oldCredential.ID
credential.ProjectID = repository.ProjectID
credential.RepositoryID = repository.ID

if err := helpers.Store(r).UpdateRepositorySubmoduleCredential(credential); err != nil {
helpers.WriteError(w, err)
return
}

helpers.EventLog(r, helpers.EventLogUpdate, helpers.EventLogItem{
UserID: helpers.UserFromContext(r).ID,
ProjectID: repository.ProjectID,
ObjectType: db.EventRepository,
ObjectID: repository.ID,
Description: fmt.Sprintf("Submodule credential for host %s updated on repository %s", credential.Host, repository.GitURL),
})

w.WriteHeader(http.StatusNoContent)
}

// RemoveRepositorySubmoduleCredential deletes a submodule credential mapping.
func RemoveRepositorySubmoduleCredential(w http.ResponseWriter, r *http.Request) {
repository := helpers.GetFromContext(r, "repository").(db.Repository)
credential := helpers.GetFromContext(r, "submoduleCredential").(db.RepositorySubmoduleCredential)

if err := helpers.Store(r).DeleteRepositorySubmoduleCredential(repository.ProjectID, repository.ID, credential.ID); err != nil {
helpers.WriteError(w, err)
return
}

helpers.EventLog(r, helpers.EventLogDelete, helpers.EventLogItem{
UserID: helpers.UserFromContext(r).ID,
ProjectID: repository.ProjectID,
ObjectType: db.EventRepository,
ObjectID: repository.ID,
Description: fmt.Sprintf("Submodule credential for host %s removed from repository %s", credential.Host, repository.GitURL),
})

w.WriteHeader(http.StatusNoContent)
}
Loading
Loading