[server] Allow basic and bearer auth for api access

This commit is contained in:
Abhishek Shroff
2024-09-16 17:15:45 +05:30
parent 70965ded60
commit 4d8518d7e0
9 changed files with 104 additions and 110 deletions

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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"))
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;