From 4d8518d7e09f1fd7db3aec01ffc44183adf23da7 Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Mon, 16 Sep 2024 17:15:45 +0530 Subject: [PATCH] [server] Allow basic and bearer auth for api access --- server/internal/api/api.go | 2 +- server/internal/api/auth/auth_api.go | 81 ++++++++++++++++++++++ server/internal/api/auth/auth_basic.go | 31 ++++----- server/internal/api/auth/auth_bearer.go | 46 ------------ server/internal/command/appcmd/resource.go | 12 +--- server/internal/core/app.go | 10 +-- server/internal/core/filesystem.go | 4 +- server/internal/db/permissions.sql.go | 2 +- server/sql/queries/permissions.sql | 26 +------ 9 files changed, 104 insertions(+), 110 deletions(-) create mode 100644 server/internal/api/auth/auth_api.go delete mode 100644 server/internal/api/auth/auth_bearer.go diff --git a/server/internal/api/api.go b/server/internal/api/api.go index 1e6a5b57..6de8ab4a 100644 --- a/server/internal/api/api.go +++ b/server/internal/api/api.go @@ -12,7 +12,7 @@ func Setup(r *gin.RouterGroup, a *core.App) { routes.SetupAuthRoutes(r, a) // Authenticated routes - r.Use(auth.CreateBearerAuthHandler(a)) + r.Use(auth.CreateApiAuthHandler(a)) routes.SetupResourceRoutes(r) routes.SetupUserRoutes(r) routes.SetupMobileRoutes(r) diff --git a/server/internal/api/auth/auth_api.go b/server/internal/api/auth/auth_api.go new file mode 100644 index 00000000..e47388a4 --- /dev/null +++ b/server/internal/api/auth/auth_api.go @@ -0,0 +1,81 @@ +package auth + +import ( + "encoding/base64" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/shroff/phylum/server/internal/api/errors" + "github.com/shroff/phylum/server/internal/core" +) + +type authHeaderType = int + +const ( + authHeaderNone = -1 + authHeaderUnknown = 0 + authHeaderBasic = 1 + authHeaderBearer = 2 +) + +const errCodeAuthRequred = "auth_required" +const errCodeCredentialsInvalid = "credentials_invalid" + +func CreateApiAuthHandler(a *core.App) func(c *gin.Context) { + return func(c *gin.Context) { + ctx := c.Request.Context() + if user, err := extractUserDetails(a, c); err != nil { + panic(err) + } else { + c.Set(keyUser, user) + c.Set(keyFileSystem, a.OpenFileSystem(ctx, user)) + } + } +} + +func extractUserDetails(a *core.App, c *gin.Context) (core.User, error) { + if header := c.Request.Header.Get("Authorization"); header == "" { + return core.User{}, errors.New(http.StatusUnauthorized, errCodeAuthRequred, "Authorization Required") + } else if auth, ok := checkAuthHeader(header, "basic"); ok { + if username, password, ok := decodeBasicAuth(auth); ok { + if user, err := a.VerifyUserPassword(c.Request.Context(), username, password); err == nil { + return user, nil + } else if errors.Is(err, core.ErrCredentialsInvalid) { + return core.User{}, errors.New(http.StatusUnauthorized, errCodeCredentialsInvalid, "Invalid Credentials") + } else { + return core.User{}, err + } + } + } else if token, ok := checkAuthHeader(header, "bearer"); ok { + if user, err := a.ReadAccessToken(c.Request.Context(), token); err == nil { + return user, nil + } else if errors.Is(err, core.ErrTokenExpired) || errors.Is(err, core.ErrTokenInvalid) { + return core.User{}, errors.New(http.StatusUnauthorized, errCodeCredentialsInvalid, "Invalid Credentials") + } else { + return core.User{}, err + } + } + return core.User{}, errors.New(http.StatusUnauthorized, errCodeAuthRequred, "Authorization scheme not recognized") +} + +func checkAuthHeader(header, prefix string) (string, bool) { + prefix = prefix + " " + if len(header) < len(prefix) || !strings.EqualFold(header[:len(prefix)], prefix) { + return "", false + } + return header[len(prefix):], true +} + +func decodeBasicAuth(auth string) (username, password string, ok bool) { + c, err := base64.StdEncoding.DecodeString(auth) + if err != nil { + return "", "", false + } + cs := string(c) + username, password, ok = strings.Cut(cs, ":") + if !ok { + return "", "", false + } + return username, password, true +} diff --git a/server/internal/api/auth/auth_basic.go b/server/internal/api/auth/auth_basic.go index adb89724..cd7ca7d5 100644 --- a/server/internal/api/auth/auth_basic.go +++ b/server/internal/api/auth/auth_basic.go @@ -1,33 +1,28 @@ package auth import ( - "errors" "net/http" "github.com/gin-gonic/gin" + "github.com/shroff/phylum/server/internal/api/errors" "github.com/shroff/phylum/server/internal/core" - "github.com/sirupsen/logrus" ) func CreateBasicAuthHandler(a *core.App) func(c *gin.Context) { return func(c *gin.Context) { - if username, pass, ok := c.Request.BasicAuth(); !ok { - c.Header("WWW-Authenticate", "Basic realm=\"Phylum WebDAV\"") - c.AbortWithStatus(http.StatusUnauthorized) - } else if user, err := a.VerifyUserPassword(c.Request.Context(), username, pass); err != nil { - if errors.Is(err, core.ErrCredentialsInvalid) { - c.Header("WWW-Authenticate", "Basic realm=\"Phylum WebDAV\"") - c.AbortWithStatus(http.StatusUnauthorized) - } else { - logrus.Warn(err) - c.AbortWithStatus(http.StatusInternalServerError) + authSuccess := false + if username, pass, ok := c.Request.BasicAuth(); ok { + if user, err := a.VerifyUserPassword(c.Request.Context(), username, pass); err == nil { + c.Set(keyUser, user) + c.Set(keyFileSystem, a.OpenFileSystem(c.Request.Context(), user)) + authSuccess = true + } else if !errors.Is(err, core.ErrCredentialsInvalid) { + panic(err) } - } else if fs, err := a.OpenFileSystem(c.Request.Context(), user); err != nil { - logrus.Warn(err) - c.AbortWithStatus(http.StatusInternalServerError) - } else { - c.Set(keyUser, user) - c.Set(keyFileSystem, fs) + } + if !authSuccess { + c.Header("WWW-Authenticate", "Basic realm=\"Phylum WebDAV\"") + panic(errors.New(http.StatusUnauthorized, errCodeAuthRequred, "Authorization Required")) } } } diff --git a/server/internal/api/auth/auth_bearer.go b/server/internal/api/auth/auth_bearer.go deleted file mode 100644 index 4e96234f..00000000 --- a/server/internal/api/auth/auth_bearer.go +++ /dev/null @@ -1,46 +0,0 @@ -package auth - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shroff/phylum/server/internal/api/errors" - "github.com/shroff/phylum/server/internal/core" - "github.com/sirupsen/logrus" -) - -const errCodeAuthRequred = "auth_required" -const errCodeTokenInvalid = "token_invalid" - -func CreateBearerAuthHandler(a *core.App) func(c *gin.Context) { - return func(c *gin.Context) { - ctx := c.Request.Context() - authHeader := c.GetHeader("Authorization") - if authHeader == "" { - panic(errors.New(401, errCodeAuthRequred, "Authorization Header Required")) - } - authParts := strings.Split(authHeader, " ") - if len(authParts) != 2 { - panic(errors.New(401, errCodeAuthRequred, "Authorization Header Malformed")) - } - if authParts[0] != "bearer" { - panic(errors.New(401, errCodeAuthRequred, "Authorization Type Not Recognized")) - } - - user, err := a.ReadAccessToken(ctx, authParts[1]) - if err != nil { - if errors.Is(err, core.ErrTokenExpired) || errors.Is(err, core.ErrTokenInvalid) { - panic(errors.New(401, errCodeTokenInvalid, "Authorization Token Invalid")) - } - panic(err) - } - if fs, err := a.OpenFileSystem(ctx, user); err != nil { - logrus.Warn(err) - c.AbortWithStatus(http.StatusInternalServerError) - } else { - c.Set(keyUser, user) - c.Set(keyFileSystem, fs) - } - } -} diff --git a/server/internal/command/appcmd/resource.go b/server/internal/command/appcmd/resource.go index cc384e70..be7876ea 100644 --- a/server/internal/command/appcmd/resource.go +++ b/server/internal/command/appcmd/resource.go @@ -263,23 +263,15 @@ func setupResourceChpermCommand() *cobra.Command { } func openFileSystemFromFlags(cmd *cobra.Command) { - var user core.User if value, err := cmd.Flags().GetString("user"); err != nil { logrus.Fatal(err) } else { - if u, err := core.Default.UserByEmail(context.Background(), value); err != nil { + if user, err := core.Default.UserByEmail(context.Background(), value); err != nil { logrus.Fatal(err) } else { - user = u + fs = core.Default.OpenFileSystem(context.Background(), user) } } - - var err error - fs, err = core.Default.OpenFileSystem(context.Background(), user) - if err != nil { - logrus.Fatal(err) - } - } func setupUserFlags(cmd *cobra.Command) { diff --git a/server/internal/core/app.go b/server/internal/core/app.go index b8c14ab6..f593b804 100644 --- a/server/internal/core/app.go +++ b/server/internal/core/app.go @@ -10,7 +10,7 @@ import ( "github.com/shroff/phylum/server/internal/storage" ) -const defaultUserEmail = "admin@phylum" +const defaultUserEmail = "phylum" type App struct { Debug bool @@ -38,11 +38,7 @@ func (a *App) setupAppData(ctx context.Context) error { u, err = a.populateData(ctx) } if err == nil { - if fs, err := OpenFileSystem(a.db, ctx, a.cs, u.ID, u.Root); err != nil { - return err - } else { - a.Rootfs = fs - } + a.Rootfs = OpenFileSystem(a.db, ctx, a.cs, u.ID, u.Root) } return err } @@ -104,6 +100,6 @@ func (a *App) populateData(ctx context.Context) (user db.User, e error) { return } -func (a App) OpenFileSystem(ctx context.Context, user User) (FileSystem, error) { +func (a App) OpenFileSystem(ctx context.Context, user User) FileSystem { return OpenFileSystem(a.db, ctx, a.cs, user.ID, user.Root) } diff --git a/server/internal/core/filesystem.go b/server/internal/core/filesystem.go index 7d3f88d8..5698ab42 100644 --- a/server/internal/core/filesystem.go +++ b/server/internal/core/filesystem.go @@ -55,14 +55,14 @@ type filesystem struct { user int32 } -func OpenFileSystem(dbh *db.DbHandler, ctx context.Context, cs storage.Storage, user int32, rootID uuid.UUID) (FileSystem, error) { +func OpenFileSystem(dbh *db.DbHandler, ctx context.Context, cs storage.Storage, user int32, rootID uuid.UUID) FileSystem { return filesystem{ db: dbh, ctx: ctx, cs: cs, rootID: rootID, user: user, - }, nil + } } func (f filesystem) RootID() uuid.UUID { diff --git a/server/internal/db/permissions.sql.go b/server/internal/db/permissions.sql.go index dd85fbb2..82214709 100644 --- a/server/internal/db/permissions.sql.go +++ b/server/internal/db/permissions.sql.go @@ -188,7 +188,7 @@ func (q *Queries) ResourceByPath(ctx context.Context, arg ResourceByPathParams) } const updatePermissionsForResource = `-- name: UpdatePermissionsForResource :exec -UPDATE resources SET permissions[$1::integer] = $2::int WHERE id = $3::uuid +UPDATE resources SET permissions[$1::int] = to_json($2::int) WHERE id = $3::uuid ` type UpdatePermissionsForResourceParams struct { diff --git a/server/sql/queries/permissions.sql b/server/sql/queries/permissions.sql index 7f3b8cc5..535a7f83 100644 --- a/server/sql/queries/permissions.sql +++ b/server/sql/queries/permissions.sql @@ -53,28 +53,4 @@ ON r.id = n.resid WHERE n.parent IS NULL; -- name: UpdatePermissionsForResource :exec -UPDATE resources SET permissions[@user_id::integer] = @permission::int WHERE id = @resource_id::uuid; - --- -- name: GetLocalPermissionsForResource :many --- SELECT p.user_id, CAST(p.permission AS INT) AS permission --- FROM permissions p --- WHERE p.resource_id = @resource_id::uuid; - - --- -- name: GetInheritedPermissionsForResource :many --- WITH RECURSIVE nodes(id, parent) AS ( --- SELECT r.id, r.parent --- FROM resources r --- WHERE r.id = @resource_id::uuid --- UNION ALL ( --- SELECT r.id, r.parent --- FROM resources r --- JOIN nodes n --- ON r.id = n.parent --- ) --- ) --- SELECT p.user_id, CAST(MAX(p.permission) AS INT) AS permission --- FROM permissions p --- JOIN nodes n --- ON n.id = p.resource_id --- GROUP BY p.user_id; +UPDATE resources SET permissions[@user_id::int] = to_json(@permission::int) WHERE id = @resource_id::uuid; \ No newline at end of file