Skip to content

Commit

Permalink
Introduce create role endpoint
Browse files Browse the repository at this point in the history
* Currently supports only creating space roles
* Generate predictable role binding names by doing a sha256sum on the
  role name and user. The name is also prefixed with `cf-` to ensure it
  starts witha lowercase letter.
* The role will fail to create if the user does not have a role in the
  parent org namespace (as per the docs for POST /v3/roles)

Issue: #160

Co-authored-by: Mario Nitchev <marionitchev@gmail.com>
Co-authored-by: Kieron Browne <kbrowne@vmware.com>
  • Loading branch information
3 people committed Nov 5, 2021
1 parent 2b72a2d commit ed12d2d
Show file tree
Hide file tree
Showing 40 changed files with 1,428 additions and 140 deletions.
120 changes: 120 additions & 0 deletions api/apis/fake/cfrole_repository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 0 additions & 9 deletions api/apis/org_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"time"
Expand All @@ -16,7 +15,6 @@ import (
"code.cloudfoundry.org/cf-k8s-controllers/api/repositories/authorization"
"code.cloudfoundry.org/cf-k8s-controllers/controllers/webhooks/workloads"
"github.com/go-http-utils/headers"
"github.com/gorilla/mux"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -31,7 +29,6 @@ const (
var _ = Describe("OrgHandler", func() {
var (
ctx context.Context
router *mux.Router
orgHandler *apis.OrgHandler
orgRepoProvider *fake.OrgRepositoryProvider
orgRepo *fake.CFOrgRepository
Expand All @@ -47,14 +44,8 @@ var _ = Describe("OrgHandler", func() {
orgRepo = new(fake.CFOrgRepository)
orgRepoProvider.OrgRepoForRequestReturns(orgRepo, nil)

serverURL, err := url.Parse(defaultServerURL)
Expect(err).NotTo(HaveOccurred())

orgHandler = apis.NewOrgHandler(*serverURL, orgRepoProvider)
router = mux.NewRouter()
orgHandler.RegisterRoutes(router)

rr = httptest.NewRecorder()
})

Describe("Create Org", func() {
Expand Down
85 changes: 85 additions & 0 deletions api/apis/role_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package apis

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"

"code.cloudfoundry.org/cf-k8s-controllers/api/payloads"
"code.cloudfoundry.org/cf-k8s-controllers/api/presenter"
"code.cloudfoundry.org/cf-k8s-controllers/api/repositories"
"github.com/go-logr/logr"
"github.com/google/uuid"
"github.com/gorilla/mux"
controllerruntime "sigs.k8s.io/controller-runtime"
)

const (
RolesEndpoint = "/v3/roles"
)

//counterfeiter:generate -o fake -fake-name CFRoleRepository . CFRoleRepository

type CFRoleRepository interface {
CreateSpaceRole(ctx context.Context, role repositories.RoleRecord) (repositories.RoleRecord, error)
}

type RoleHandler struct {
logger logr.Logger
apiBaseURL url.URL
roleRepo CFRoleRepository
}

func NewRoleHandler(apiBaseURL url.URL, roleRepo CFRoleRepository) *RoleHandler {
return &RoleHandler{
logger: controllerruntime.Log.WithName("Role Handler"),
apiBaseURL: apiBaseURL,
roleRepo: roleRepo,
}
}

func (h *RoleHandler) roleCreateHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

var payload payloads.RoleCreate
rme := decodeAndValidateJSONPayload(r, &payload)
if rme != nil {
h.logger.Error(rme, "Failed to parse body")
writeErrorResponse(w, rme)

return
}

role := payload.ToRecord()
role.GUID = uuid.NewString()

record, err := h.roleRepo.CreateSpaceRole(r.Context(), role)
if err != nil {
if errors.Is(err, repositories.ErrorDuplicateRoleBinding) {
errorDetail := fmt.Sprintf("User '%s' already has '%s' role", role.User, role.Type)
h.logger.Info(errorDetail)
writeUnprocessableEntityError(w, errorDetail)
return
}
if errors.Is(err, repositories.ErrorMissingRoleBindingInParentOrg) {
h.logger.Info("no rolebinding in parent org", "space", role.Space, "user", role.User)
errorDetail := "Users cannot be assigned roles in a space if they do not have a role in that space's organization."
writeUnprocessableEntityError(w, errorDetail)
return
}
h.logger.Error(err, "Failed to create role", "Role Type", role.Type, "Space", role.Space, "User", role.User)
writeUnknownErrorResponse(w)
return
}

w.WriteHeader(http.StatusCreated)
roleResponse := presenter.ForCreateRole(record, h.apiBaseURL)
json.NewEncoder(w).Encode(roleResponse)
}

func (h *RoleHandler) RegisterRoutes(router *mux.Router) {
router.Path(RolesEndpoint).Methods("POST").HandlerFunc(h.roleCreateHandler)
}
Loading

0 comments on commit ed12d2d

Please sign in to comment.