From 4c129e776c2b9094040d72143b8befc1db81220e Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 16 Nov 2024 12:51:17 +0300 Subject: [PATCH] Some improvements for ABAC --- enshi_back/ABAC/blogsPolicies/blogPolicies.go | 1 + .../blogsPolicies/blogRules/createRule.go | 22 ++++++ .../{authorizedRule.go => AuthorizedRule.go} | 6 +- enshi_back/ABAC/globalRules/IsAdminRule.go | 33 +++++++++ .../ABAC/globalRules/IsOwnerOfTheBlogRule.go | 39 ++++++++++ .../ABAC/globalRules/IsOwnerOfThePostRule.go | 39 ++++++++++ enshi_back/ABAC/postsPolicies/postPolicy.go | 24 ++----- .../postsPolicies/postRules/createRule.go | 17 ++++- .../postsPolicies/postRules/deleteRule.go | 32 ++++----- .../ABAC/postsPolicies/postRules/readRule.go | 2 +- .../postsPolicies/postRules/updateRule.go | 24 +++---- enshi_back/ABAC/rules/CheckRule.go | 72 +++++++++++++++++++ enshi_back/db/go_queries/blogs_queries.sql.go | 20 ++++++ enshi_back/db/queries/blogs_queries.sql | 5 ++ enshi_back/main.go | 4 +- enshi_back/middleware/postsMiddleware.go | 12 +++- enshi_back/routes/postsRoutes/updatePost.go | 10 +-- enshi_back/{utils => routes}/routesSetup.go | 9 ++- 18 files changed, 299 insertions(+), 72 deletions(-) create mode 100644 enshi_back/ABAC/blogsPolicies/blogPolicies.go create mode 100644 enshi_back/ABAC/blogsPolicies/blogRules/createRule.go rename enshi_back/ABAC/globalRules/{authorizedRule.go => AuthorizedRule.go} (78%) create mode 100644 enshi_back/ABAC/globalRules/IsAdminRule.go create mode 100644 enshi_back/ABAC/globalRules/IsOwnerOfTheBlogRule.go create mode 100644 enshi_back/ABAC/globalRules/IsOwnerOfThePostRule.go create mode 100644 enshi_back/ABAC/rules/CheckRule.go rename enshi_back/{utils => routes}/routesSetup.go (98%) diff --git a/enshi_back/ABAC/blogsPolicies/blogPolicies.go b/enshi_back/ABAC/blogsPolicies/blogPolicies.go new file mode 100644 index 0000000..d1e3971 --- /dev/null +++ b/enshi_back/ABAC/blogsPolicies/blogPolicies.go @@ -0,0 +1 @@ +package blogspolicies diff --git a/enshi_back/ABAC/blogsPolicies/blogRules/createRule.go b/enshi_back/ABAC/blogsPolicies/blogRules/createRule.go new file mode 100644 index 0000000..8c25f3c --- /dev/null +++ b/enshi_back/ABAC/blogsPolicies/blogRules/createRule.go @@ -0,0 +1,22 @@ +package blogrules + +import ( + globalrules "enshi/ABAC/globalRules" + "enshi/ABAC/rules" + + "github.com/gin-gonic/gin" +) + +func BlogCreateRule(c *gin.Context) (bool, []error) { + rulesToCheck := []rules.RuleFunction{ + globalrules.AuthorizedRule, + } + + isAllowed, errors := rules.CheckRules( + c, + rulesToCheck, + rules.ALL_RULES_MUST_BE_COMPLETED, + ) + + return isAllowed, errors +} diff --git a/enshi_back/ABAC/globalRules/authorizedRule.go b/enshi_back/ABAC/globalRules/AuthorizedRule.go similarity index 78% rename from enshi_back/ABAC/globalRules/authorizedRule.go rename to enshi_back/ABAC/globalRules/AuthorizedRule.go index 9d48ab0..63fe327 100644 --- a/enshi_back/ABAC/globalRules/authorizedRule.go +++ b/enshi_back/ABAC/globalRules/AuthorizedRule.go @@ -9,10 +9,10 @@ import ( "github.com/gin-gonic/gin" ) -func AuthorizedRule(c *gin.Context) (bool, error) { +func AuthorizedRule(c *gin.Context) (bool, []error) { cookies := c.Request.CookiesNamed("auth_cookie") if len(cookies) == 0 { - return false, fmt.Errorf("no cookies provided") + return false, []error{fmt.Errorf("no cookies provided")} } tokenFromCookies := cookies[0].Value @@ -20,7 +20,7 @@ func AuthorizedRule(c *gin.Context) (bool, error) { if err != nil { c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()}) c.Abort() - return false, err + return false, []error{err} } else { c.Set(global.ContextUserId, cookieClimes["id"]) c.Set(global.ContextTokenData, cookieClimes) diff --git a/enshi_back/ABAC/globalRules/IsAdminRule.go b/enshi_back/ABAC/globalRules/IsAdminRule.go new file mode 100644 index 0000000..d1596e5 --- /dev/null +++ b/enshi_back/ABAC/globalRules/IsAdminRule.go @@ -0,0 +1,33 @@ +package globalrules + +import ( + "context" + db_repo "enshi/db/go_queries" + "enshi/db_connection" + "enshi/middleware/getters" + "fmt" + + "github.com/gin-gonic/gin" +) + +func IsAdminRule(c *gin.Context) (bool, []error) { + contextUserId, err := getters.GetUserIdFromContext(c) + + if err != nil { + return false, []error{err} + } + + user, err := + db_repo.New(db_connection.Dbx). + GetUserById(context.Background(), contextUserId) + + if err != nil || user.UserID == 0 { + return false, []error{err} + } + + if !user.IsAdmin { + return false, []error{fmt.Errorf("not admin")} + } + + return true, nil +} diff --git a/enshi_back/ABAC/globalRules/IsOwnerOfTheBlogRule.go b/enshi_back/ABAC/globalRules/IsOwnerOfTheBlogRule.go new file mode 100644 index 0000000..e67ca75 --- /dev/null +++ b/enshi_back/ABAC/globalRules/IsOwnerOfTheBlogRule.go @@ -0,0 +1,39 @@ +package globalrules + +import ( + "context" + db_repo "enshi/db/go_queries" + "enshi/db_connection" + "enshi/middleware/getters" + "fmt" + + "github.com/gin-gonic/gin" +) + +func IsOwnerOfTheBlogRule(c *gin.Context) (bool, []error) { + blogId, err := getters.GetInt64Param(c, "blog-id") + + if err != nil { + return false, []error{err} + } + + contextUserId, err := getters.GetUserIdFromContext(c) + + if err != nil { + return false, []error{err} + } + + blog, err := + db_repo.New(db_connection.Dbx). + GetBlogByBlogId(context.Background(), blogId) + + if err != nil { + return false, []error{err} + } + + if blog.UserID != contextUserId { + return false, []error{fmt.Errorf("now owner of the blog")} + } + + return true, nil +} diff --git a/enshi_back/ABAC/globalRules/IsOwnerOfThePostRule.go b/enshi_back/ABAC/globalRules/IsOwnerOfThePostRule.go new file mode 100644 index 0000000..af25324 --- /dev/null +++ b/enshi_back/ABAC/globalRules/IsOwnerOfThePostRule.go @@ -0,0 +1,39 @@ +package globalrules + +import ( + "context" + db_repo "enshi/db/go_queries" + "enshi/db_connection" + "enshi/middleware/getters" + "fmt" + + "github.com/gin-gonic/gin" +) + +func IsOwnerOfThePostRule(c *gin.Context) (bool, []error) { + postId, err := getters.GetInt64Param(c, "post-id") + + if err != nil { + return false, []error{err} + } + + contextUserId, err := getters.GetUserIdFromContext(c) + + if err != nil { + return false, []error{err} + } + + post, err := + db_repo.New(db_connection.Dbx). + GetPostsByPostId(context.Background(), postId) + + if err != nil { + return false, []error{err} + } + + if post.UserID != contextUserId { + return false, []error{fmt.Errorf("now owner of the post")} + } + + return true, nil +} diff --git a/enshi_back/ABAC/postsPolicies/postPolicy.go b/enshi_back/ABAC/postsPolicies/postPolicy.go index 7d16b81..d4bb86d 100644 --- a/enshi_back/ABAC/postsPolicies/postPolicy.go +++ b/enshi_back/ABAC/postsPolicies/postPolicy.go @@ -2,6 +2,7 @@ package postspolicies import ( postRules "enshi/ABAC/postsPolicies/postRules" + "enshi/ABAC/rules" "github.com/gin-gonic/gin" ) @@ -13,36 +14,25 @@ const ( GET_POST = "get_post" ) -func checkRule( - c *gin.Context, - ruleChecker func(*gin.Context) (bool, error), -) (bool, error) { - IsAllowed, err := ruleChecker(c) - if err != nil { - return false, err - } - - return IsAllowed, nil -} - -func PostsPolicies(c *gin.Context) (bool, error) { +func PostsPolicies(c *gin.Context) (bool, []error) { target, exists := c.Get("target") if !exists { return false, nil } + // Permit if one permit switch target { case DELETE_POST: - return checkRule(c, postRules.DeleteRule) + return rules.CheckRule(c, postRules.DeleteRule) case UPDATE_POST: - return checkRule(c, postRules.PostUpdateRule) + return rules.CheckRule(c, postRules.PostUpdateRule) case GET_POST: - return checkRule(c, postRules.PostReadRule) + return rules.CheckRule(c, postRules.PostReadRule) case CREATE_POST: - return checkRule(c, postRules.PostCreateRule) + return rules.CheckRule(c, postRules.PostCreateRule) } diff --git a/enshi_back/ABAC/postsPolicies/postRules/createRule.go b/enshi_back/ABAC/postsPolicies/postRules/createRule.go index 81026fd..1745c19 100644 --- a/enshi_back/ABAC/postsPolicies/postRules/createRule.go +++ b/enshi_back/ABAC/postsPolicies/postRules/createRule.go @@ -1,10 +1,23 @@ package postRules import ( + globalrules "enshi/ABAC/globalRules" + "enshi/ABAC/rules" + "github.com/gin-gonic/gin" ) // Only owner of the post can change it -func PostCreateRule(c *gin.Context) (bool, error) { - return true, nil +func PostCreateRule(c *gin.Context) (bool, []error) { + rulesToCheck := []rules.RuleFunction{ + globalrules.AuthorizedRule, + } + + isAllowed, errors := rules.CheckRules( + c, + rulesToCheck, + rules.ALL_RULES_MUST_BE_COMPLETED, + ) + + return isAllowed, errors } diff --git a/enshi_back/ABAC/postsPolicies/postRules/deleteRule.go b/enshi_back/ABAC/postsPolicies/postRules/deleteRule.go index 0bfa92b..fb3aecc 100644 --- a/enshi_back/ABAC/postsPolicies/postRules/deleteRule.go +++ b/enshi_back/ABAC/postsPolicies/postRules/deleteRule.go @@ -2,30 +2,26 @@ package postRules import ( globalrules "enshi/ABAC/globalRules" - "enshi/middleware/checkRole" + "enshi/ABAC/rules" "github.com/gin-gonic/gin" ) +const RULES_NUMBER_TO_COMPLETE = 2 + // Only owner or admin can delete post -func DeleteRule(c *gin.Context) (bool, error) { - // Sender should be authorized - isAuthorized, err := globalrules.AuthorizedRule(c) - if err != nil { - return false, err - } else if !isAuthorized { - return false, nil +func DeleteRule(c *gin.Context) (bool, []error) { + rulesToCheck := []rules.RuleFunction{ + globalrules.AuthorizedRule, + globalrules.IsOwnerOfThePostRule, + globalrules.IsAdminRule, } - isOwner, err := checkRole.IsOwnerOfThePost(c) - if err != nil { - return false, err - } + isAllowed, errors := rules.CheckRules( + c, + rulesToCheck, + RULES_NUMBER_TO_COMPLETE, + ) - isAdmin, err := checkRole.IsAdmin(c) - if err != nil { - return false, err - } - - return isAdmin || isOwner, err + return isAllowed, errors } diff --git a/enshi_back/ABAC/postsPolicies/postRules/readRule.go b/enshi_back/ABAC/postsPolicies/postRules/readRule.go index df83bc5..922b45e 100644 --- a/enshi_back/ABAC/postsPolicies/postRules/readRule.go +++ b/enshi_back/ABAC/postsPolicies/postRules/readRule.go @@ -5,6 +5,6 @@ import ( ) // Only owner of the post can change it -func PostReadRule(c *gin.Context) (bool, error) { +func PostReadRule(c *gin.Context) (bool, []error) { return true, nil } diff --git a/enshi_back/ABAC/postsPolicies/postRules/updateRule.go b/enshi_back/ABAC/postsPolicies/postRules/updateRule.go index 959fd55..eabb5b9 100644 --- a/enshi_back/ABAC/postsPolicies/postRules/updateRule.go +++ b/enshi_back/ABAC/postsPolicies/postRules/updateRule.go @@ -2,25 +2,23 @@ package postRules import ( globalrules "enshi/ABAC/globalRules" - "enshi/middleware/checkRole" + "enshi/ABAC/rules" "github.com/gin-gonic/gin" ) // Only owner of the post can change it -func PostUpdateRule(c *gin.Context) (bool, error) { - // Sender should be authorized - isAuthorized, err := globalrules.AuthorizedRule(c) - if err != nil { - return false, err - } else if !isAuthorized { - return false, nil +func PostUpdateRule(c *gin.Context) (bool, []error) { + rulesToCheck := []rules.RuleFunction{ + globalrules.AuthorizedRule, + globalrules.IsOwnerOfThePostRule, } - isOwner, err := checkRole.IsOwnerOfThePost(c) - if err != nil { - return false, err - } + isAllowed, errors := rules.CheckRules( + c, + rulesToCheck, + rules.ALL_RULES_MUST_BE_COMPLETED, + ) - return isOwner, nil + return isAllowed, errors } diff --git a/enshi_back/ABAC/rules/CheckRule.go b/enshi_back/ABAC/rules/CheckRule.go new file mode 100644 index 0000000..6147efc --- /dev/null +++ b/enshi_back/ABAC/rules/CheckRule.go @@ -0,0 +1,72 @@ +package rules + +import ( + "fmt" + "strconv" + + "github.com/gin-gonic/gin" +) + +type RuleFunction func(*gin.Context) (bool, []error) + +const ( + ALL_RULES_MUST_BE_COMPLETED = iota +) + +func CheckRule( + c *gin.Context, + ruleChecker RuleFunction, +) (bool, []error) { + IsAllowed, err := ruleChecker(c) + if err != nil { + return false, err + } + + return IsAllowed, nil +} + +func CheckRules( + c *gin.Context, + rules []RuleFunction, + completedRulesCount int, +) (bool, []error) { + var allowancesIndexes []int + var errors []error + + if len(rules) < completedRulesCount { + return false, []error{fmt.Errorf("there is less rules, that should be completed")} + } + + for i, rule := range rules { + if isAllowed, err := CheckRule(c, rule); err != nil { + errors = append( + errors, + err..., + ) + } else if !isAllowed { + errors = append( + errors, + fmt.Errorf("rule "+ + strconv.Itoa(i)+ + " was rejected"), + ) + } else { + allowancesIndexes = append(allowancesIndexes, i) + } + } + + switch completedRulesCount { + case ALL_RULES_MUST_BE_COMPLETED: + if len(allowancesIndexes) == len(rules) { + return true, nil + } else { + return false, errors + } + default: + if len(allowancesIndexes) >= completedRulesCount { + return true, nil + } else { + return false, errors + } + } +} diff --git a/enshi_back/db/go_queries/blogs_queries.sql.go b/enshi_back/db/go_queries/blogs_queries.sql.go index ecf6c80..fc040cf 100644 --- a/enshi_back/db/go_queries/blogs_queries.sql.go +++ b/enshi_back/db/go_queries/blogs_queries.sql.go @@ -56,6 +56,26 @@ func (q *Queries) DeleteBlogByBlogId(ctx context.Context, blogID int64) error { return err } +const getBlogByBlogId = `-- name: GetBlogByBlogId :one +SELECT blog_id, user_id, title, description, category_id, created_at +FROM public.blogs +WHERE blog_id = $1 +` + +func (q *Queries) GetBlogByBlogId(ctx context.Context, blogID int64) (Blog, error) { + row := q.db.QueryRow(ctx, getBlogByBlogId, blogID) + var i Blog + err := row.Scan( + &i.BlogID, + &i.UserID, + &i.Title, + &i.Description, + &i.CategoryID, + &i.CreatedAt, + ) + return i, err +} + const getBlogsByUserId = `-- name: GetBlogsByUserId :many SELECT blog_id, user_id, title, description, category_id, created_at FROM public.blogs diff --git a/enshi_back/db/queries/blogs_queries.sql b/enshi_back/db/queries/blogs_queries.sql index e47614b..cb5c575 100644 --- a/enshi_back/db/queries/blogs_queries.sql +++ b/enshi_back/db/queries/blogs_queries.sql @@ -15,6 +15,11 @@ SELECT * FROM public.blogs WHERE user_id = $1; +-- name: GetBlogByBlogId :one +SELECT * +FROM public.blogs +WHERE blog_id = $1; + -- name: DeleteBlogByBlogId :exec DELETE FROM public.blogs WHERE blog_id=$1; \ No newline at end of file diff --git a/enshi_back/main.go b/enshi_back/main.go index 85fe413..3b8374b 100644 --- a/enshi_back/main.go +++ b/enshi_back/main.go @@ -6,7 +6,7 @@ import ( "enshi/db_connection" "enshi/env" "enshi/global" - utils "enshi/utils" + "enshi/routes" "fmt" "github.com/gin-gonic/gin" @@ -27,7 +27,7 @@ func main() { defer db_connection.Dbx_connection.Close(context.Background()) router := gin.Default() - if err := utils.SetupRotes(router); err != nil { + if err := routes.SetupRotes(router); err != nil { fmt.Println(err.Error()) return } diff --git a/enshi_back/middleware/postsMiddleware.go b/enshi_back/middleware/postsMiddleware.go index fc2ee21..9f3c911 100644 --- a/enshi_back/middleware/postsMiddleware.go +++ b/enshi_back/middleware/postsMiddleware.go @@ -4,6 +4,7 @@ import ( postspolicies "enshi/ABAC/postsPolicies" rest_api_stuff "enshi/REST_API_stuff" "fmt" + "net/http" "github.com/gin-gonic/gin" ) @@ -22,10 +23,15 @@ func PostsMiddleware() gin.HandlerFunc { c.Set("target", postspolicies.GET_POST) } - isAllowed, err := postspolicies.PostsPolicies(c) + isAllowed, errors := postspolicies.PostsPolicies(c) - if err != nil { - rest_api_stuff.InternalErrorAnswer(c, err) + var errorsMap = map[int]string{} + for i, error := range errors { + errorsMap[i] = error.Error() + } + + if errors != nil { + c.IndentedJSON(http.StatusUnauthorized, errorsMap) c.Abort() return } diff --git a/enshi_back/routes/postsRoutes/updatePost.go b/enshi_back/routes/postsRoutes/updatePost.go index 8568897..bb26330 100644 --- a/enshi_back/routes/postsRoutes/updatePost.go +++ b/enshi_back/routes/postsRoutes/updatePost.go @@ -18,20 +18,14 @@ func UpdatePost(c *gin.Context) { return } - _, err := getters.GetUserIdFromContext(c) + postId, err := getters.GetInt64Param(c, "post-id") if err != nil { rest_api_stuff.InternalErrorAnswer(c, err) return } - // if isOwner, _ := checkRole.IsOwnerOfThePost( - // userId, - // UpdatedPostParams.PostID, - // ); !isOwner { - // rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you are now allowed to change this")) - // return - // } + UpdatedPostParams.PostID = postId _, err = db_repo.New( db_connection.Dbx, diff --git a/enshi_back/utils/routesSetup.go b/enshi_back/routes/routesSetup.go similarity index 98% rename from enshi_back/utils/routesSetup.go rename to enshi_back/routes/routesSetup.go index 9ef6d4f..f2c9f4c 100644 --- a/enshi_back/utils/routesSetup.go +++ b/enshi_back/routes/routesSetup.go @@ -1,4 +1,4 @@ -package utils +package routes import ( "enshi/middleware" @@ -37,10 +37,6 @@ func SetupRotes(g *gin.Engine) error { authRoutes.RegisterUser, ) - // Auth group routes - authGroup := g.Group("/") - authGroup.Use(middleware.AuthMiddleware()) - postsGroup := g.Group("/") postsGroup.Use(middleware.PostsMiddleware()) @@ -61,6 +57,9 @@ func SetupRotes(g *gin.Engine) error { postsRoutes.DeletePost, ) + // Auth group routes + authGroup := g.Group("/") + authGroup.Use(middleware.AuthMiddleware()) authGroup.PUT( "user-profiles", userProfileRoutes.UpdateUserProfile,