diff --git a/internal/dao/project.go b/internal/dao/project.go new file mode 100644 index 0000000..c06b782 --- /dev/null +++ b/internal/dao/project.go @@ -0,0 +1,29 @@ +package dao + +import ( + "context" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +func GetProjectOrganization(ctx context.Context, db bun.IDB, id uuid.UUID) (string, string, error) { + // Could possibly union them later for some speedup + type projectOrg struct { + Project string + Organization string + } + var r projectOrg + err := db.NewSelect().Table("authsrv_project"). + ColumnExpr("authsrv_project.name as project"). + ColumnExpr("authsrv_organization.name as organization"). + Join(`JOIN authsrv_organization ON authsrv_project.organization_id=authsrv_organization.id`). + Where("authsrv_project.id = ?", id). + Where("authsrv_project.trash = ?", false). + Where("authsrv_organization.trash = ?", false). + Scan(ctx, &r) + if err != nil { + return "", "", err + } + return r.Project, r.Organization, nil +} diff --git a/pkg/auth/v3/middleware.go b/pkg/auth/v3/middleware.go index 2ec8daa..3eae9b8 100644 --- a/pkg/auth/v3/middleware.go +++ b/pkg/auth/v3/middleware.go @@ -3,19 +3,24 @@ package authv3 import ( "net/http" "regexp" + "strings" + "github.com/RafayLabs/rcloud-base/internal/dao" commonpbv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3" + "github.com/google/uuid" "github.com/uptrace/bun" "github.com/urfave/negroni" ) type authMiddleware struct { + db *bun.DB ac authContext opt Option } func NewAuthMiddleware(opt Option, db *bun.DB) negroni.Handler { return &authMiddleware{ + db: db, ac: NewAuthContext(db), opt: opt, } @@ -34,12 +39,48 @@ func (am *authMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, nex return } } + // Auth is primarily done via grpc endpoints, this is only used + // for endoints which do not go through grpc As of now, it is just + // prompt. + var proj string + var org string + + if strings.HasPrefix(r.URL.String(), "/v2/debug/prompt/project/") { + // /v2/debug/prompt/project/:project_id/cluster/:cluster_name + splits := strings.Split(r.URL.String(), "/") + if len(splits) > 5 { + projid, err := uuid.Parse(splits[5]) + if err != nil { + _log.Errorf("Failed to authenticate: unable to parse project uuid") + http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + // What gets sent for project is the id unlike most other + // api routes, so we have to fetch the name as well as the + // org info for casbin + proj, org, err = dao.GetProjectOrganization(r.Context(), am.db, projid) + if err != nil { + _log.Errorf("Failed to authenticate: unable to find project") + http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + } + } else { + // The middleware to only used with routes which does not have + // a grpc and so fail for any other requests. + _log.Errorf("Failed to authenticate: not a prompt request") + http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + req := &commonpbv3.IsRequestAllowedRequest{ Url: r.URL.String(), Method: r.Method, XSessionToken: r.Header.Get("X-Session-Token"), XApiKey: r.Header.Get("X-RAFAY-API-KEYID"), Cookie: r.Header.Get("Cookie"), + Project: proj, + Org: org, } res, err := am.ac.IsRequestAllowed(r.Context(), r, req) if err != nil { @@ -64,5 +105,4 @@ func (am *authMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, nex // status is unknown http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return }