Skip to content

Commit

Permalink
clean up and refactor permissions logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Akopti8 committed Jun 13, 2024
1 parent df90a3a commit 9b21d30
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 85 deletions.
5 changes: 3 additions & 2 deletions auth/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type PostgresDB struct {
Handle *sql.DB
}

// Initialize the database and create tables if they do not exist.
// NewPostgresDB initializes the database and creates tables if they do not exist.
func NewPostgresDB() (*PostgresDB, error) {
connString, exist := os.LookupEnv("POSTGRES_CONN_STRING")
if !exist {
Expand All @@ -43,7 +43,7 @@ func NewPostgresDB() (*PostgresDB, error) {
return pgDB, nil
}

// Creates the necessary tables in the database.
// createTables creates the necessary tables in the database.
func (db *PostgresDB) createTables() error {
createPermissionsTable := `
CREATE TABLE IF NOT EXISTS permissions (
Expand All @@ -65,6 +65,7 @@ func (db *PostgresDB) createTables() error {
return nil
}

// GetUserAccessiblePrefixes retrieves the accessible prefixes for a user.
func (db *PostgresDB) GetUserAccessiblePrefixes(userEmail, bucket string, operations []string) ([]string, error) {
query := `
WITH unnested_permissions AS (
Expand Down
13 changes: 12 additions & 1 deletion blobstore/blobhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func NewBlobHandler(envJson string, authLvl int) (*BlobHandler, error) {
}

if len(bucketNames) > 0 {
config.S3Controllers = append(config.S3Controllers, S3Controller{Sess: sess, S3Svc: s3SVC, Buckets: bucketNames})
config.S3Controllers = append(config.S3Controllers, S3Controller{Sess: sess, S3Svc: s3SVC, Buckets: bucketNames, S3Mock: false})
}
}

Expand Down Expand Up @@ -307,6 +307,17 @@ func (bh *BlobHandler) PingWithAuth(c echo.Context) error {
return c.JSON(http.StatusOK, bucketHealth)
}

func (bh *BlobHandler) GetS3ReadPermissions(c echo.Context, bucket string) ([]string, bool, int, error) {
permissions, fullAccess, err := bh.GetUserS3ReadListPermission(c, bucket)
if err != nil {
return nil, false, http.StatusInternalServerError, fmt.Errorf("error fetching user permissions: %s", err.Error())
}
if !fullAccess && len(permissions) == 0 {
return nil, false, http.StatusForbidden, fmt.Errorf("user does not have read permission to read the %s bucket", bucket)
}
return permissions, fullAccess, http.StatusOK, nil
}

func (bh *BlobHandler) HandleCheckS3UserPermission(c echo.Context) error {
if bh.Config.AuthLevel == 0 {
log.Info("Checked user permissions successfully")
Expand Down
44 changes: 16 additions & 28 deletions blobstore/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type ListResult struct {
// CheckAndAdjustPrefix checks if the prefix is an object and adjusts the prefix accordingly.
// Returns the adjusted prefix, an error message (if any), and the HTTP status code.
func CheckAndAdjustPrefix(s3Ctrl *S3Controller, bucket, prefix string) (string, string, int) {
//As of 6/12/24, unsure why ./ is included here, may be needed for an edge case, but could also cause problems
// As of 6/12/24, unsure why ./ is included here, may be needed for an edge case, but could also cause problems
if prefix != "" && prefix != "./" && prefix != "/" {
isObject, err := s3Ctrl.KeyExists(bucket, prefix)
if err != nil {
Expand All @@ -41,19 +41,19 @@ func CheckAndAdjustPrefix(s3Ctrl *S3Controller, bucket, prefix string) (string,
if err != nil {
return "", fmt.Sprintf("error checking for object's metadata: %s", err.Error()), http.StatusInternalServerError
}
//this is because AWS considers empty prefixes with a .keep as an object, so we ignore and log
// This is because AWS considers empty prefixes with a .keep as an object, so we ignore and log
if *objMeta.ContentLength == 0 {
log.Infof("detected a zero byte directory marker within prefix: %s", prefix)
} else {
return "", fmt.Sprintf("`%s` is an object, not a prefix. please see options for keys or pass a prefix", prefix), http.StatusTeapot
return "", fmt.Sprintf("`%s` is an object, not a prefix. Please see options for keys or pass a prefix", prefix), http.StatusTeapot
}
}
prefix = strings.Trim(prefix, "/") + "/"
}
return prefix, "", http.StatusOK
}

// HandleListByPrefix handles the API endpoint for listing objects by prefix in S3 bucket.
// HandleListByPrefix handles the API endpoint for listing objects by prefix in an S3 bucket.
func (bh *BlobHandler) HandleListByPrefix(c echo.Context) error {
prefix := c.QueryParam("prefix")

Expand Down Expand Up @@ -93,16 +93,10 @@ func (bh *BlobHandler) HandleListByPrefix(c echo.Context) error {
prefix = adjustedPrefix

var result []string
permissions, fullAccess, err := bh.GetUserS3ReadListPermission(c, bucket)
permissions, fullAccess, statusCode, err := bh.GetS3ReadPermissions(c, bucket)
if err != nil {
errMsg := fmt.Errorf("error fetching user permissions: %s", err.Error())
log.Error(errMsg.Error())
return c.JSON(http.StatusInternalServerError, errMsg.Error())
}
if !fullAccess && len(permissions) == 0 {
errMsg := fmt.Errorf("user does not have read permission to read the %s bucket", bucket)
log.Error(errMsg.Error())
return c.JSON(http.StatusForbidden, errMsg.Error())
log.Error(err.Error())
return c.JSON(statusCode, err.Error())
}
processPage := func(page *s3.ListObjectsV2Output) error {
for _, cp := range page.CommonPrefixes {
Expand Down Expand Up @@ -153,16 +147,10 @@ func (bh *BlobHandler) HandleListByPrefixWithDetail(c echo.Context) error {

var results []ListResult
var count int
permissions, fullAccess, err := bh.GetUserS3ReadListPermission(c, bucket)
permissions, fullAccess, statusCode, err := bh.GetS3ReadPermissions(c, bucket)
if err != nil {
errMsg := fmt.Errorf("error fetching user permissions: %s", err.Error())
log.Error(errMsg.Error())
return c.JSON(http.StatusInternalServerError, errMsg.Error())
}
if !fullAccess && len(permissions) == 0 {
errMsg := fmt.Errorf("user does not have read permission to read the %s bucket", bucket)
log.Error(errMsg.Error())
return c.JSON(http.StatusForbidden, errMsg.Error())
log.Error(err.Error())
return c.JSON(statusCode, err.Error())
}
processPage := func(page *s3.ListObjectsV2Output) error {
for _, cp := range page.CommonPrefixes {
Expand Down Expand Up @@ -197,7 +185,6 @@ func (bh *BlobHandler) HandleListByPrefixWithDetail(c echo.Context) error {
}
results = append(results, file)
}
count++
}
return nil
}
Expand All @@ -214,9 +201,9 @@ func (bh *BlobHandler) HandleListByPrefixWithDetail(c echo.Context) error {
}

// GetList retrieves a list of objects in the specified S3 bucket with the given prefix.
// if delimiter is set to true then it is going to search for any objects within the prefix provided, if no object sare found it will
// return null even if there was prefixes within the user provided prefix. If delimiter is set to false then it will look for all prefixes
// that start with the user provided prefix.
// If delimiter is set to true, it will search for any objects within the prefix provided.
// If no objects are found, it will return null even if there were prefixes within the user-provided prefix.
// If delimiter is set to false, it will look for all prefixes that start with the user-provided prefix.
func (s3Ctrl *S3Controller) GetList(bucket, prefix string, delimiter bool) (*s3.ListObjectsV2Output, error) {
// Set up input parameters for the ListObjectsV2 API
input := &s3.ListObjectsV2Input{
Expand Down Expand Up @@ -252,8 +239,8 @@ func (s3Ctrl *S3Controller) GetList(bucket, prefix string, delimiter bool) (*s3.
return response, nil
}

// GetListWithCallBack is the same as GetList, except instead of returning the entire list at once, it gives you the option of processing page by page
// this method is safer than GetList as it avoid memory overload for large datasets since it does not store the entire list in memory but rather processes it on the go.
// GetListWithCallBack is the same as GetList, except instead of returning the entire list at once, it allows processing page by page.
// This method is safer than GetList as it avoids memory overload for large datasets by processing data on the go.
func (s3Ctrl *S3Controller) GetListWithCallBack(bucket, prefix string, delimiter bool, processPage func(*s3.ListObjectsV2Output) error) error {
input := &s3.ListObjectsV2Input{
Bucket: aws.String(bucket),
Expand All @@ -279,6 +266,7 @@ func (s3Ctrl *S3Controller) GetListWithCallBack(bucket, prefix string, delimiter
return err // Return any errors encountered in the pagination process
}

// isPermittedPrefix checks if the prefix is within the user's permissions.
func isPermittedPrefix(bucket, prefix string, permissions []string) bool {
prefixForChecking := fmt.Sprintf("/%s/%s", bucket, prefix)

Expand Down
36 changes: 9 additions & 27 deletions blobstore/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,10 @@ func (bh *BlobHandler) HandleGetSize(c echo.Context) error {
log.Error(errMsg.Error())
return c.JSON(http.StatusUnprocessableEntity, errMsg.Error())
}
permissions, fullAccess, err := bh.GetUserS3ReadListPermission(c, bucket)
permissions, fullAccess, statusCode, err := bh.GetS3ReadPermissions(c, bucket)
if err != nil {
errMsg := fmt.Errorf("error fetching user permissions: %s", err.Error())
log.Error(errMsg.Error())
return c.JSON(http.StatusInternalServerError, errMsg.Error())
}
if !fullAccess && len(permissions) == 0 {
errMsg := fmt.Errorf("user does not have read permission to read the %s bucket", bucket)
log.Error(errMsg.Error())
return c.JSON(http.StatusForbidden, errMsg.Error())
log.Error(err.Error())
return c.JSON(statusCode, err.Error())
}
if !fullAccess && !isPermittedPrefix(bucket, prefix, permissions) {
errMsg := fmt.Errorf("user does not have read permission to read this prefix %s", prefix)
Expand Down Expand Up @@ -118,16 +112,10 @@ func (bh *BlobHandler) HandleGetMetaData(c echo.Context) error {
log.Error(errMsg.Error())
return c.JSON(http.StatusUnprocessableEntity, errMsg.Error())
}
permissions, fullAccess, err := bh.GetUserS3ReadListPermission(c, bucket)
permissions, fullAccess, statusCode, err := bh.GetS3ReadPermissions(c, bucket)
if err != nil {
errMsg := fmt.Errorf("error fetching user permissions: %s", err.Error())
log.Error(errMsg.Error())
return c.JSON(http.StatusInternalServerError, errMsg.Error())
}
if !fullAccess && len(permissions) == 0 {
errMsg := fmt.Errorf("user does not have read permission to read the %s bucket", bucket)
log.Error(errMsg.Error())
return c.JSON(http.StatusForbidden, errMsg.Error())
log.Error(err.Error())
return c.JSON(statusCode, err.Error())
}

if !fullAccess && !isPermittedPrefix(bucket, key, permissions) {
Expand Down Expand Up @@ -167,16 +155,10 @@ func (bh *BlobHandler) HandleGetObjExist(c echo.Context) error {
return c.JSON(http.StatusUnprocessableEntity, errMsg.Error())
}

permissions, fullAccess, err := bh.GetUserS3ReadListPermission(c, bucket)
permissions, fullAccess, statusCode, err := bh.GetS3ReadPermissions(c, bucket)
if err != nil {
errMsg := fmt.Errorf("error fetching user permissions: %s", err.Error())
log.Error(errMsg.Error())
return c.JSON(http.StatusInternalServerError, errMsg.Error())
}
if !fullAccess && len(permissions) == 0 {
errMsg := fmt.Errorf("user does not have read permission to read the %s bucket", bucket)
log.Error(errMsg.Error())
return c.JSON(http.StatusForbidden, errMsg.Error())
log.Error(err.Error())
return c.JSON(statusCode, err.Error())
}

if !fullAccess && !isPermittedPrefix(bucket, key, permissions) {
Expand Down
12 changes: 3 additions & 9 deletions blobstore/object_content.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,10 @@ func (bh *BlobHandler) HandleObjectContents(c echo.Context) error {
log.Error(errMsg.Error())
return c.JSON(http.StatusUnprocessableEntity, errMsg.Error())
}
permissions, fullAccess, err := bh.GetUserS3ReadListPermission(c, bucket)
permissions, fullAccess, statusCode, err := bh.GetS3ReadPermissions(c, bucket)
if err != nil {
errMsg := fmt.Errorf("error fetching user permissions: %s", err.Error())
log.Error(errMsg.Error())
return c.JSON(http.StatusInternalServerError, errMsg.Error())
}
if !fullAccess && len(permissions) == 0 {
errMsg := fmt.Errorf("user does not have read permission to read the %s bucket", bucket)
log.Error(errMsg.Error())
return c.JSON(http.StatusForbidden, errMsg.Error())
log.Error(err.Error())
return c.JSON(statusCode, err.Error())
}

if !fullAccess && !isPermittedPrefix(bucket, key, permissions) {
Expand Down
25 changes: 7 additions & 18 deletions blobstore/presigned_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,10 @@ func (bh *BlobHandler) HandleGetPresignedDownloadURL(c echo.Context) error {
log.Error(errMsg.Error())
return c.JSON(http.StatusUnprocessableEntity, errMsg.Error())
}
permissions, fullAccess, err := bh.GetUserS3ReadListPermission(c, bucket)
permissions, fullAccess, statusCode, err := bh.GetS3ReadPermissions(c, bucket)
if err != nil {
errMsg := fmt.Errorf("error fetching user permissions: %s", err.Error())
log.Error(errMsg.Error())
return c.JSON(http.StatusInternalServerError, errMsg.Error())
}
if !fullAccess && len(permissions) == 0 {
errMsg := fmt.Errorf("user does not have read permission to read the %s bucket", bucket)
log.Error(errMsg.Error())
return c.JSON(http.StatusForbidden, errMsg.Error())
log.Error(err.Error())
return c.JSON(statusCode, err.Error())
}

if !fullAccess && !isPermittedPrefix(bucket, key, permissions) {
Expand Down Expand Up @@ -293,16 +287,11 @@ func (bh *BlobHandler) HandleGenerateDownloadScript(c echo.Context) error {
scriptBuilder.WriteString("REM 4. Initiate the Download: Double-click the renamed \".bat\" file to initiate the download process. Windows might display a warning message to protect your PC.\n")
scriptBuilder.WriteString("REM 5. Windows Defender SmartScreen (Optional): If you see a message like \"Windows Defender SmartScreen prevented an unrecognized app from starting,\" click \"More info\" and then click \"Run anyway\" to proceed with the download.\n\n")
scriptBuilder.WriteString(fmt.Sprintf("mkdir \"%s\"\n", basePrefix))
permissions, fullAccess, err := bh.GetUserS3ReadListPermission(c, bucket)

permissions, fullAccess, statusCode, err := bh.GetS3ReadPermissions(c, bucket)
if err != nil {
errMsg := fmt.Errorf("error fetching user permissions: %s", err.Error())
log.Error(errMsg.Error())
return c.JSON(http.StatusInternalServerError, errMsg.Error())
}
if !fullAccess && len(permissions) == 0 {
errMsg := fmt.Errorf("user does not have read permission to read the %s bucket", bucket)
log.Error(errMsg.Error())
return c.JSON(http.StatusForbidden, errMsg.Error())
log.Error(err.Error())
return c.JSON(statusCode, err.Error())
}
// Define the processPage function
processPage := func(page *s3.ListObjectsV2Output) error {
Expand Down

0 comments on commit 9b21d30

Please sign in to comment.