diff --git a/private/apigen/endpoint.go b/private/apigen/endpoint.go index a0e128a63..0bf0e05d3 100644 --- a/private/apigen/endpoint.go +++ b/private/apigen/endpoint.go @@ -6,7 +6,6 @@ package apigen import ( "fmt" "net/http" - "path/filepath" "reflect" "regexp" "strings" @@ -348,7 +347,7 @@ func middlewareImports(m any) []string { // middlewareFields returns the list of fields of a middleware implementation. It panics if m isn't // a struct type, it has embedded fields, or it has unexported fields. -func middlewareFields(m any) []middlewareField { +func middlewareFields(api *API, m any) []middlewareField { fields := []middlewareField{} middlewareWalkFields(m, func(f reflect.StructField) { if f.Name == "_" { @@ -362,9 +361,10 @@ func middlewareFields(m any) []middlewareField { t = f.Type.Elem() } - typeref := t.Name() - if p := t.PkgPath(); p != "" { - typeref = fmt.Sprintf("%s%s.%s", psymbol, filepath.Base(p), typeref) + typeref := psymbol + t.Name() + if p := t.PkgPath(); p != "" && p != api.PackagePath { + pn, _ := importPath(p).PkgName() + typeref = fmt.Sprintf("%s%s.%s", psymbol, pn, t.Name()) } fields = append(fields, middlewareField{Name: f.Name, Type: typeref}) }) diff --git a/private/apigen/gogen.go b/private/apigen/gogen.go index 58a72520b..b9d1b74de 100644 --- a/private/apigen/gogen.go +++ b/private/apigen/gogen.go @@ -7,8 +7,9 @@ import ( "fmt" "go/format" "os" + "path/filepath" "reflect" - "sort" + "slices" "strings" "time" @@ -50,12 +51,12 @@ func (a *API) generateGo() ([]byte, error) { } imports := struct { - All map[string]bool - Standard []string - External []string - Internal []string + All map[importPath]bool + Standard []importPath + External []importPath + Internal []importPath }{ - All: make(map[string]bool), + All: make(map[importPath]bool), } i := func(paths ...string) { @@ -64,12 +65,13 @@ func (a *API) generateGo() ([]byte, error) { continue } - if _, ok := imports.All[path]; ok { + ipath := importPath(path) + if _, ok := imports.All[ipath]; ok { continue } - imports.All[path] = true + imports.All[ipath] = true - var slice *[]string + var slice *[]importPath switch { case !strings.Contains(path, "."): slice = &imports.Standard @@ -78,7 +80,7 @@ func (a *API) generateGo() ([]byte, error) { default: slice = &imports.External } - *slice = append(*slice, path) + *slice = append(*slice, ipath) } } @@ -170,12 +172,12 @@ func (a *API) generateGo() ([]byte, error) { autodefinedFields := map[string]string{"log": "*zap.Logger", "mon": "*monkit.Scope", "service": cname + "Service"} for _, m := range group.Middleware { - for _, f := range middlewareFields(m) { + for _, f := range middlewareFields(a, m) { if t, ok := autodefinedFields[f.Name]; ok { if t != f.Type { panic( fmt.Sprintf( - "middleware %q has a field with name %q and type %q which clashes with another defined field with the same but with type %q", + "middleware %q has a field with name %q and type %q which clashes with another defined field with the same name but with type %q", reflect.TypeOf(m).Name(), f.Name, f.Type, @@ -203,7 +205,7 @@ func (a *API) generateGo() ([]byte, error) { middlewareArgs := make([]string, 0, len(group.Middleware)) middlewareFieldsList := make([]string, 0, len(group.Middleware)) for _, m := range group.Middleware { - for _, f := range middlewareFields(m) { + for _, f := range middlewareFields(a, m) { if _, ok := autodedefined[f.Name]; !ok { middlewareArgs = append(middlewareArgs, fmt.Sprintf("%s %s", f.Name, f.Type)) middlewareFieldsList = append(middlewareFieldsList, fmt.Sprintf("%[1]s: %[1]s", f.Name)) @@ -339,12 +341,17 @@ func (a *API) generateGo() ([]byte, error) { pf("") pf("import (") - slices := [][]string{imports.Standard, imports.External, imports.Internal} - for sn, slice := range slices { - sort.Strings(slice) + all := [][]importPath{imports.Standard, imports.External, imports.Internal} + for sn, slice := range all { + slices.Sort(slice) for pn, path := range slice { - pf(`"%s"`, path) - if pn == len(slice)-1 && sn < len(slices)-1 { + if r, ok := path.PkgName(); ok { + pf(`%s "%s"`, r, path) + } else { + pf(`"%s"`, path) + } + + if pn == len(slice)-1 && sn < len(all)-1 { pf("") } } @@ -469,3 +476,20 @@ func handleBody(pf func(format string, a ...interface{}), body interface{}) { pf("}") pf("") } + +type importPath string + +// PkgName returns the name of the package based of the last part of the import +// path and false if the name isn't a rename, otherwise it returns true. +// +// The package name is renamed when the last part of the path contains hyphen +// (-) or dot (.) and the rename is this part with the hyphens and dots +// stripped. +func (i importPath) PkgName() (rename string, ok bool) { + b := filepath.Base(string(i)) + if strings.Contains(b, "-") || strings.Contains(b, ".") { + return strings.ReplaceAll(strings.ReplaceAll(b, "-", ""), ".", ""), true + } + + return b, false +} diff --git a/satellite/admin/back-office/gen/main.go b/satellite/admin/back-office/gen/main.go index dbeb41bfa..827b6167e 100644 --- a/satellite/admin/back-office/gen/main.go +++ b/satellite/admin/back-office/gen/main.go @@ -8,9 +8,11 @@ package main //go:generate go run $GOFILE import ( + "fmt" "os" "path" "path/filepath" + "strings" "storj.io/storj/private/apigen" backoffice "storj.io/storj/satellite/admin/back-office" @@ -35,6 +37,7 @@ func main() { }) group = api.Group("UserManagement", "users") + group.Middleware = append(group.Middleware, authMiddleware{}) group.Get("/{email}", &apigen.Endpoint{ Name: "Get user", @@ -45,6 +48,9 @@ func main() { apigen.NewParam("email", ""), }, Response: backoffice.User{}, + Settings: map[any]any{ + authPermsKey: []backoffice.Permission{backoffice.PermAccountView}, + }, }) modroot := findModuleRootDir() @@ -53,6 +59,37 @@ func main() { api.MustWriteDocs(filepath.Join(modroot, "satellite", "admin", "back-office", "api-docs.gen.md")) } +type authMiddleware struct { + //lint:ignore U1000 this field is used by the API generator to expose in the handler. + auth *backoffice.Authorizer +} + +func (a authMiddleware) Generate(api *apigen.API, group *apigen.EndpointGroup, ep *apigen.FullEndpoint) string { + perms := apigen.LoadSetting(authPermsKey, ep, []backoffice.Permission{}) + if len(perms) == 0 { + return "" + } + + verbs := make([]string, 0, len(perms)) + values := make([]any, 0, len(perms)) + for _, p := range perms { + verbs = append(verbs, "%d") + values = append(values, p) + } + + format := fmt.Sprintf(`if h.auth.IsRejected(w, r, %s) { + return + }`, strings.Join(verbs, ", ")) + + return fmt.Sprintf(format, values...) +} + +var _ apigen.Middleware = authMiddleware{} + +type tagAuthPerms struct{} + +var authPermsKey = tagAuthPerms{} + func findModuleRootDir() string { dir, err := os.Getwd() if err != nil { diff --git a/satellite/admin/back-office/handlers.gen.go b/satellite/admin/back-office/handlers.gen.go index ec286af73..e5388f96f 100644 --- a/satellite/admin/back-office/handlers.gen.go +++ b/satellite/admin/back-office/handlers.gen.go @@ -39,6 +39,7 @@ type UserManagementHandler struct { log *zap.Logger mon *monkit.Scope service UserManagementService + auth *Authorizer } func NewPlacementManagement(log *zap.Logger, mon *monkit.Scope, service PlacementManagementService, router *mux.Router) *PlacementManagementHandler { @@ -54,11 +55,12 @@ func NewPlacementManagement(log *zap.Logger, mon *monkit.Scope, service Placemen return handler } -func NewUserManagement(log *zap.Logger, mon *monkit.Scope, service UserManagementService, router *mux.Router) *UserManagementHandler { +func NewUserManagement(log *zap.Logger, mon *monkit.Scope, service UserManagementService, router *mux.Router, auth *Authorizer) *UserManagementHandler { handler := &UserManagementHandler{ log: log, mon: mon, service: service, + auth: auth, } usersRouter := router.PathPrefix("/back-office/api/v1/users").Subrouter() @@ -99,6 +101,10 @@ func (h *UserManagementHandler) handleGetUserByEmail(w http.ResponseWriter, r *h return } + if h.auth.IsRejected(w, r, 1) { + return + } + retVal, httpErr := h.service.GetUserByEmail(ctx, email) if httpErr.Err != nil { api.ServeError(h.log, w, httpErr.Status, httpErr.Err) diff --git a/satellite/admin/back-office/server.go b/satellite/admin/back-office/server.go index b088f4ef9..8c8f1ee9c 100644 --- a/satellite/admin/back-office/server.go +++ b/satellite/admin/back-office/server.go @@ -72,10 +72,18 @@ func NewServer( root = mux.NewRouter() } + auth := NewAuthorizer( + log, + config.UserGroupsRoleAdmin, + config.UserGroupsRoleViewer, + config.UserGroupsRoleCustomerSupport, + config.UserGroupsRoleFinanceManager, + ) + // API endpoints. // API generator already add the PathPrefix. NewPlacementManagement(log, mon, service, root) - NewUserManagement(log, mon, service, root) + NewUserManagement(log, mon, service, root, auth) root = root.PathPrefix(PathPrefix).Subrouter() // Static assets for the web interface.