Use id to access orgs (#1873)

closes #1743 

fixes: setting secrets for own user namespace

- create org in database
- use orgID for org related APIs

Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
Anbraten
2023-07-21 19:45:32 +02:00
committed by GitHub
co-authored by 6543
parent aec2051071
commit e5d5ec8b47
51 changed files with 1261 additions and 392 deletions
+11 -2
View File
@@ -86,7 +86,7 @@ func HandleAuth(c *gin.Context) {
if len(config.Orgs) != 0 {
teams, terr := _forge.Teams(c, tmpuser)
if terr != nil || !config.IsMember(teams) {
log.Error().Msgf("cannot verify team membership for %s.", u.Login)
log.Error().Err(terr).Msgf("cannot verify team membership for %s.", u.Login)
c.Redirect(303, "/login?error=access_denied")
return
}
@@ -111,6 +111,15 @@ func HandleAuth(c *gin.Context) {
c.Redirect(http.StatusSeeOther, "/login?error=internal_error")
return
}
// if another user already have activated repos on behave of that user,
// the user was stored as org. now we adopt it to the user.
if org, err := _store.OrgFindByName(u.Login); err == nil && org != nil {
org.IsUser = true
if err := _store.OrgUpdate(org); err != nil {
log.Error().Err(err).Msgf("on user creation, could not mark org as user")
}
}
}
// update the user meta data and authorization data.
@@ -127,7 +136,7 @@ func HandleAuth(c *gin.Context) {
if len(config.Orgs) != 0 {
teams, terr := _forge.Teams(c, u)
if terr != nil || !config.IsMember(teams) {
log.Error().Msgf("cannot verify team membership for %s.", u.Login)
log.Error().Err(terr).Msgf("cannot verify team membership for %s.", u.Login)
c.Redirect(http.StatusSeeOther, "/login?error=access_denied")
return
}
+117 -9
View File
@@ -15,41 +15,149 @@
package api
import (
"errors"
"net/http"
"strconv"
"strings"
"github.com/rs/zerolog/log"
"github.com/woodpecker-ci/woodpecker/server"
"github.com/woodpecker-ci/woodpecker/server/model"
"github.com/woodpecker-ci/woodpecker/server/router/middleware/session"
"github.com/woodpecker-ci/woodpecker/server/store"
"github.com/woodpecker-ci/woodpecker/server/store/types"
"github.com/gin-gonic/gin"
)
// GetOrg
//
// @Summary Get organization by id
// @Router /orgs/{org_id} [get]
// @Produce json
// @Success 200 {array} Org
// @Tags Organization
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param org_id path string true "the organziation's id"
func GetOrg(c *gin.Context) {
_store := store.FromContext(c)
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
if err != nil {
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
return
}
org, err := _store.OrgGet(orgID)
if err != nil {
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, org)
}
// GetOrgPermissions
//
// @Summary Get the permissions of the current user in the given organization
// @Router /orgs/{owner}/permissions [get]
// @Router /orgs/{org_id}/permissions [get]
// @Produce json
// @Success 200 {array} OrgPerm
// @Tags Organization permissions
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the owner's name"
// @Param org_id path string true "the organziation's id"
func GetOrgPermissions(c *gin.Context) {
var (
err error
user = session.User(c)
owner = c.Param("owner")
)
user := session.User(c)
_store := store.FromContext(c)
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
if err != nil {
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
return
}
if user == nil {
c.JSON(http.StatusOK, &model.OrgPerm{})
return
}
perm, err := server.Config.Services.Membership.Get(c, user, owner)
org, err := _store.OrgGet(orgID)
if err != nil {
c.String(http.StatusInternalServerError, "Error getting membership for %q. %s", owner, err)
c.String(http.StatusInternalServerError, "Error getting org %d. %s", orgID, err)
return
}
if (org.IsUser && org.Name == user.Login) || user.Admin {
c.JSON(http.StatusOK, &model.OrgPerm{
Member: true,
Admin: true,
})
return
}
perm, err := server.Config.Services.Membership.Get(c, user, org.Name)
if err != nil {
c.String(http.StatusInternalServerError, "Error getting membership for %d. %s", orgID, err)
return
}
c.JSON(http.StatusOK, perm)
}
// LookupOrg
//
// @Summary Lookup organization by full-name
// @Router /org/lookup/{org_full_name} [get]
// @Produce json
// @Success 200 {object} Org
// @Tags Organizations
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param org_full_name path string true "the organizations full-name / slug"
func LookupOrg(c *gin.Context) {
_store := store.FromContext(c)
orgFullName := strings.TrimLeft(c.Param("org_full_name"), "/")
org, err := _store.OrgFindByName(orgFullName)
if err != nil {
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
// don't leak private org infos
if org.Private {
user := session.User(c)
if user == nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
if !user.Admin && org.Name != user.Login {
c.AbortWithStatus(http.StatusNotFound)
return
} else if !user.Admin {
perm, err := server.Config.Services.Membership.Get(c, user, org.Name)
if err != nil {
log.Error().Msgf("Failed to check membership: %v", err)
c.String(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
if perm == nil || !perm.Member {
c.AbortWithStatus(http.StatusNotFound)
return
}
}
}
c.JSON(http.StatusOK, org)
}
+62 -43
View File
@@ -16,6 +16,7 @@ package api
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/woodpecker-ci/woodpecker/server/router/middleware/session"
@@ -27,19 +28,23 @@ import (
// GetOrgSecret
//
// @Summary Get the named organization secret
// @Router /orgs/{owner}/secrets/{secret} [get]
// @Router /orgs/{org_id}/secrets/{secret} [get]
// @Produce json
// @Success 200 {object} Secret
// @Tags Organization secrets
// @Tags Organization secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the owner's name"
// @Param org_id path string true "the org's id"
// @Param secret path string true "the secret's name"
func GetOrgSecret(c *gin.Context) {
var (
owner = c.Param("owner")
name = c.Param("secret")
)
secret, err := server.Config.Services.Secrets.OrgSecretFind(owner, name)
name := c.Param("secret")
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
if err != nil {
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
return
}
secret, err := server.Config.Services.Secrets.OrgSecretFind(orgID, name)
if err != nil {
handleDbGetError(c, err)
return
@@ -50,19 +55,24 @@ func GetOrgSecret(c *gin.Context) {
// GetOrgSecretList
//
// @Summary Get the organization secret list
// @Router /orgs/{owner}/secrets [get]
// @Router /orgs/{org_id}/secrets [get]
// @Produce json
// @Success 200 {array} Secret
// @Tags Organization secrets
// @Tags Organization secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the owner's name"
// @Param org_id path string true "the org's id"
// @Param page query int false "for response pagination, page offset number" default(1)
// @Param perPage query int false "for response pagination, max items per page" default(50)
// @Param perPage query int false "for response pagination, max items per page" default(50)
func GetOrgSecretList(c *gin.Context) {
owner := c.Param("owner")
list, err := server.Config.Services.Secrets.OrgSecretList(owner, session.Pagination(c))
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
if err != nil {
c.String(http.StatusInternalServerError, "Error getting secret list for %q. %s", owner, err)
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
return
}
list, err := server.Config.Services.Secrets.OrgSecretList(orgID, session.Pagination(c))
if err != nil {
c.String(http.StatusInternalServerError, "Error getting secret list for %q. %s", orgID, err)
return
}
// copy the secret detail to remove the sensitive
@@ -79,20 +89,24 @@ func GetOrgSecretList(c *gin.Context) {
// @Router /orgs/{owner}/secrets [post]
// @Produce json
// @Success 200 {object} Secret
// @Tags Organization secrets
// @Tags Organization secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the owner's name"
// @Param org_id path string true "the org's id"
// @Param secretData body Secret true "the new secret"
func PostOrgSecret(c *gin.Context) {
owner := c.Param("owner")
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
if err != nil {
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
return
}
in := new(model.Secret)
if err := c.Bind(in); err != nil {
c.String(http.StatusBadRequest, "Error parsing org %q secret. %s", owner, err)
c.String(http.StatusBadRequest, "Error parsing org %q secret. %s", orgID, err)
return
}
secret := &model.Secret{
Owner: owner,
OrgID: orgID,
Name: in.Name,
Value: in.Value,
Events: in.Events,
@@ -100,11 +114,11 @@ func PostOrgSecret(c *gin.Context) {
PluginsOnly: in.PluginsOnly,
}
if err := secret.Validate(); err != nil {
c.String(http.StatusUnprocessableEntity, "Error inserting org %q secret. %s", owner, err)
c.String(http.StatusUnprocessableEntity, "Error inserting org %q secret. %s", orgID, err)
return
}
if err := server.Config.Services.Secrets.OrgSecretCreate(owner, secret); err != nil {
c.String(http.StatusInternalServerError, "Error inserting org %q secret %q. %s", owner, in.Name, err)
if err := server.Config.Services.Secrets.OrgSecretCreate(orgID, secret); err != nil {
c.String(http.StatusInternalServerError, "Error inserting org %q secret %q. %s", orgID, in.Name, err)
return
}
c.JSON(http.StatusOK, secret.Copy())
@@ -113,28 +127,30 @@ func PostOrgSecret(c *gin.Context) {
// PatchOrgSecret
//
// @Summary Update an organization secret
// @Router /orgs/{owner}/secrets/{secret} [patch]
// @Router /orgs/{org_id}/secrets/{secret} [patch]
// @Produce json
// @Success 200 {object} Secret
// @Tags Organization secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the owner's name"
// @Param org_id path string true "the org's id"
// @Param secret path string true "the secret's name"
// @Param secretData body Secret true "the update secret data"
// @Param secretData body Secret true "the update secret data"
func PatchOrgSecret(c *gin.Context) {
var (
owner = c.Param("owner")
name = c.Param("secret")
)
name := c.Param("secret")
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
if err != nil {
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
return
}
in := new(model.Secret)
err := c.Bind(in)
err = c.Bind(in)
if err != nil {
c.String(http.StatusBadRequest, "Error parsing secret. %s", err)
return
}
secret, err := server.Config.Services.Secrets.OrgSecretFind(owner, name)
secret, err := server.Config.Services.Secrets.OrgSecretFind(orgID, name)
if err != nil {
handleDbGetError(c, err)
return
@@ -151,11 +167,11 @@ func PatchOrgSecret(c *gin.Context) {
secret.PluginsOnly = in.PluginsOnly
if err := secret.Validate(); err != nil {
c.String(http.StatusUnprocessableEntity, "Error updating org %q secret. %s", owner, err)
c.String(http.StatusUnprocessableEntity, "Error updating org %q secret. %s", orgID, err)
return
}
if err := server.Config.Services.Secrets.OrgSecretUpdate(owner, secret); err != nil {
c.String(http.StatusInternalServerError, "Error updating org %q secret %q. %s", owner, in.Name, err)
if err := server.Config.Services.Secrets.OrgSecretUpdate(orgID, secret); err != nil {
c.String(http.StatusInternalServerError, "Error updating org %q secret %q. %s", orgID, in.Name, err)
return
}
c.JSON(http.StatusOK, secret.Copy())
@@ -164,19 +180,22 @@ func PatchOrgSecret(c *gin.Context) {
// DeleteOrgSecret
//
// @Summary Delete the named secret from an organization
// @Router /orgs/{owner}/secrets/{secret} [delete]
// @Router /orgs/{org_id}/secrets/{secret} [delete]
// @Produce plain
// @Success 200
// @Tags Organization secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the owner's name"
// @Param secret path string true "the secret's name"
// @Param org_id path string true "the org's id"
// @Param secret path string true "the secret's name"
func DeleteOrgSecret(c *gin.Context) {
var (
owner = c.Param("owner")
name = c.Param("secret")
)
if err := server.Config.Services.Secrets.OrgSecretDelete(owner, name); err != nil {
name := c.Param("secret")
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
if err != nil {
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
return
}
if err := server.Config.Services.Secrets.OrgSecretDelete(orgID, name); err != nil {
handleDbGetError(c, err)
return
}
+25
View File
@@ -119,6 +119,31 @@ func PostRepo(c *gin.Context) {
sig,
)
// find org of repo
var org *model.Org
org, err = _store.OrgFindByName(repo.Owner)
if err != nil && !errors.Is(err, types.RecordNotExist) {
c.String(http.StatusInternalServerError, err.Error())
return
}
// create an org if it doesn't exist yet
if errors.Is(err, types.RecordNotExist) {
org, err = forge.Org(c, user, repo.Owner)
if err != nil {
c.String(http.StatusInternalServerError, "Could not fetch organization from forge.")
return
}
err = _store.OrgCreate(org)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
}
repo.OrgID = org.ID
err = forge.Activate(c, user, repo, link)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())