diff --git a/enshi_back/ABAC/GlobalRules/AuthorizedRule.go b/enshi_back/ABAC/GlobalRules/AuthorizedRule.go new file mode 100644 index 0000000..63fe327 --- /dev/null +++ b/enshi_back/ABAC/GlobalRules/AuthorizedRule.go @@ -0,0 +1,30 @@ +package globalrules + +import ( + "enshi/auth" + "enshi/global" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" +) + +func AuthorizedRule(c *gin.Context) (bool, []error) { + cookies := c.Request.CookiesNamed("auth_cookie") + if len(cookies) == 0 { + return false, []error{fmt.Errorf("no cookies provided")} + } + + tokenFromCookies := cookies[0].Value + cookieClimes, err := auth.ValidateToken(tokenFromCookies) + if err != nil { + c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()}) + c.Abort() + return false, []error{err} + } else { + c.Set(global.ContextUserId, cookieClimes["id"]) + c.Set(global.ContextTokenData, cookieClimes) + } + + return true, nil +} 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 new file mode 100644 index 0000000..0d5415f --- /dev/null +++ b/enshi_back/ABAC/PostsPolicies/postPolicy.go @@ -0,0 +1,40 @@ +package postspolicies + +import ( + "enshi/ABAC/PostsPolicies/postRules" + "enshi/ABAC/rules" + + "github.com/gin-gonic/gin" +) + +const ( + DELETE_POST = "delete_post" + UPDATE_POST = "update_post" + CREATE_POST = "create_post" + GET_POST = "get_post" +) + +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 rules.CheckRule(c, postRules.DeleteRule) + + case UPDATE_POST: + return rules.CheckRule(c, postRules.PostUpdateRule) + + case GET_POST: + return rules.CheckRule(c, postRules.PostReadRule) + + case CREATE_POST: + return rules.CheckRule(c, postRules.PostCreateRule) + + } + + return false, nil +} diff --git a/enshi_back/ABAC/PostsPolicies/postRules/createRule.go b/enshi_back/ABAC/PostsPolicies/postRules/createRule.go new file mode 100644 index 0000000..b998d5c --- /dev/null +++ b/enshi_back/ABAC/PostsPolicies/postRules/createRule.go @@ -0,0 +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) { + 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 new file mode 100644 index 0000000..1e71cb6 --- /dev/null +++ b/enshi_back/ABAC/PostsPolicies/postRules/deleteRule.go @@ -0,0 +1,27 @@ +package postRules + +import ( + globalrules "enshi/ABAC/GlobalRules" + "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) { + rulesToCheck := []rules.RuleFunction{ + globalrules.AuthorizedRule, + globalrules.IsOwnerOfThePostRule, + globalrules.IsAdminRule, + } + + isAllowed, errors := rules.CheckRules( + c, + rulesToCheck, + RULES_NUMBER_TO_COMPLETE, + ) + + return isAllowed, errors +} diff --git a/enshi_back/ABAC/PostsPolicies/postRules/readRule.go b/enshi_back/ABAC/PostsPolicies/postRules/readRule.go new file mode 100644 index 0000000..922b45e --- /dev/null +++ b/enshi_back/ABAC/PostsPolicies/postRules/readRule.go @@ -0,0 +1,10 @@ +package postRules + +import ( + "github.com/gin-gonic/gin" +) + +// Only owner of the post can change it +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 new file mode 100644 index 0000000..44cd7b6 --- /dev/null +++ b/enshi_back/ABAC/PostsPolicies/postRules/updateRule.go @@ -0,0 +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 PostUpdateRule(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/ProfilePolicies/ProfilePolicies.go b/enshi_back/ABAC/ProfilePolicies/ProfilePolicies.go new file mode 100644 index 0000000..cee74e1 --- /dev/null +++ b/enshi_back/ABAC/ProfilePolicies/ProfilePolicies.go @@ -0,0 +1,31 @@ +package profilepolicies + +import ( + profilesrules "enshi/ABAC/ProfilePolicies/ProfilesRules" + "enshi/ABAC/rules" + + "github.com/gin-gonic/gin" +) + +const ( + RESET_PROFILE = "reset_profile" + UPDATE_PROFILE = "update_profile" + CREATE_PROFILE = "create_profile" + GET_PROFILE = "get_profile" +) + +func ProfilePolicies(c *gin.Context) (bool, []error) { + target, exists := c.Get("target") + if !exists { + return false, nil + } + + // Permit if one permit + switch target { + case UPDATE_PROFILE: + return rules.CheckRule(c, profilesrules.UpdateProfileRule) + + } + + return false, nil +} diff --git a/enshi_back/ABAC/ProfilePolicies/ProfilesRules/UpdateRule.go b/enshi_back/ABAC/ProfilePolicies/ProfilesRules/UpdateRule.go new file mode 100644 index 0000000..f90f857 --- /dev/null +++ b/enshi_back/ABAC/ProfilePolicies/ProfilesRules/UpdateRule.go @@ -0,0 +1,22 @@ +package profilesrules + +import ( + globalrules "enshi/ABAC/GlobalRules" + "enshi/ABAC/rules" + + "github.com/gin-gonic/gin" +) + +func UpdateProfileRule(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/blogsPolicies/blogPolicies.go b/enshi_back/ABAC/blogsPolicies/blogPolicies.go new file mode 100644 index 0000000..e9dcfff --- /dev/null +++ b/enshi_back/ABAC/blogsPolicies/blogPolicies.go @@ -0,0 +1,40 @@ +package blogspolicies + +import ( + blogrules "enshi/ABAC/blogsPolicies/blogRules" + "enshi/ABAC/rules" + + "github.com/gin-gonic/gin" +) + +const ( + DELETE_BLOG = "delete_blog" + UPDATE_BLOG = "update_blog" + CREATE_BLOG = "create_blog" + GET_BLOG = "get_blog" +) + +func BlogPolicies(c *gin.Context) (bool, []error) { + target, exists := c.Get("target") + if !exists { + return false, nil + } + + // Permit if one permit + switch target { + case DELETE_BLOG: + return rules.CheckRule(c, blogrules.BlogDeleteRule) + + case UPDATE_BLOG: + return rules.CheckRule(c, blogrules.BlogUpdateRule) + + case GET_BLOG: + return rules.CheckRule(c, blogrules.BlogReadRule) + + case CREATE_BLOG: + return rules.CheckRule(c, blogrules.BlogCreateRule) + + } + + return false, nil +} diff --git a/enshi_back/ABAC/blogsPolicies/blogRules/createRule.go b/enshi_back/ABAC/blogsPolicies/blogRules/createRule.go new file mode 100644 index 0000000..c973353 --- /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/blogsPolicies/blogRules/deleteRule.go b/enshi_back/ABAC/blogsPolicies/blogRules/deleteRule.go new file mode 100644 index 0000000..5c4f024 --- /dev/null +++ b/enshi_back/ABAC/blogsPolicies/blogRules/deleteRule.go @@ -0,0 +1,24 @@ +package blogrules + +import ( + globalrules "enshi/ABAC/GlobalRules" + "enshi/ABAC/rules" + + "github.com/gin-gonic/gin" +) + +func BlogDeleteRule(c *gin.Context) (bool, []error) { + rulesToCheck := []rules.RuleFunction{ + globalrules.AuthorizedRule, + globalrules.IsOwnerOfTheBlogRule, + globalrules.IsAdminRule, + } + + isAllowed, errors := rules.CheckRules( + c, + rulesToCheck, + 2, + ) + + return isAllowed, errors +} diff --git a/enshi_back/ABAC/blogsPolicies/blogRules/readRule.go b/enshi_back/ABAC/blogsPolicies/blogRules/readRule.go new file mode 100644 index 0000000..6658687 --- /dev/null +++ b/enshi_back/ABAC/blogsPolicies/blogRules/readRule.go @@ -0,0 +1,19 @@ +package blogrules + +import ( + "enshi/ABAC/rules" + + "github.com/gin-gonic/gin" +) + +func BlogReadRule(c *gin.Context) (bool, []error) { + rulesToCheck := []rules.RuleFunction{} + + isAllowed, errors := rules.CheckRules( + c, + rulesToCheck, + rules.ALL_RULES_MUST_BE_COMPLETED, + ) + + return isAllowed, errors +} diff --git a/enshi_back/ABAC/blogsPolicies/blogRules/updateRule.go b/enshi_back/ABAC/blogsPolicies/blogRules/updateRule.go new file mode 100644 index 0000000..cb6ab72 --- /dev/null +++ b/enshi_back/ABAC/blogsPolicies/blogRules/updateRule.go @@ -0,0 +1,23 @@ +package blogrules + +import ( + globalrules "enshi/ABAC/GlobalRules" + "enshi/ABAC/rules" + + "github.com/gin-gonic/gin" +) + +func BlogUpdateRule(c *gin.Context) (bool, []error) { + rulesToCheck := []rules.RuleFunction{ + globalrules.AuthorizedRule, + globalrules.IsOwnerOfTheBlogRule, + } + + isAllowed, errors := rules.CheckRules( + c, + rulesToCheck, + rules.ALL_RULES_MUST_BE_COMPLETED, + ) + + 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..3029ce9 100644 --- a/enshi_back/db/go_queries/blogs_queries.sql.go +++ b/enshi_back/db/go_queries/blogs_queries.sql.go @@ -21,7 +21,7 @@ RETURNING blog_id, user_id, title, description, category_id, created_at type CreateBlogByUserIdParams struct { BlogID int64 `json:"blog_id"` UserID int64 `json:"user_id"` - Title pgtype.Text `json:"title"` + Title pgtype.Text `json:"title" validate:"required"` Description pgtype.Text `json:"description"` CategoryID pgtype.Int4 `json:"category_id"` } @@ -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 @@ -97,7 +117,7 @@ RETURNING blog_id, user_id, title, description, category_id, created_at ` type UpdateBlogInfoByBlogIdParams struct { - Title pgtype.Text `json:"title"` + Title pgtype.Text `json:"title" validate:"required"` Description pgtype.Text `json:"description"` CategoryID pgtype.Int4 `json:"category_id"` BlogID int64 `json:"blog_id"` diff --git a/enshi_back/db/go_queries/models.go b/enshi_back/db/go_queries/models.go index 7d227a9..6438133 100644 --- a/enshi_back/db/go_queries/models.go +++ b/enshi_back/db/go_queries/models.go @@ -11,7 +11,7 @@ import ( type Blog struct { BlogID int64 `json:"blog_id"` UserID int64 `json:"user_id"` - Title pgtype.Text `json:"title"` + Title pgtype.Text `json:"title" validate:"required"` Description pgtype.Text `json:"description"` CategoryID pgtype.Int4 `json:"category_id"` CreatedAt pgtype.Timestamp `json:"created_at"` 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/db/sqlc.yml b/enshi_back/db/sqlc.yml index cdfcea3..1eb5d5f 100644 --- a/enshi_back/db/sqlc.yml +++ b/enshi_back/db/sqlc.yml @@ -18,6 +18,9 @@ sql: - column: users.email go_struct_tag: validate:"required,email" + - column: blogs.title + go_struct_tag: validate:"required" + - db_type: "uuid" go_type: import: 'github.com/google/uuid' 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/ProfileMiddleware.go b/enshi_back/middleware/ProfileMiddleware.go new file mode 100644 index 0000000..494b09c --- /dev/null +++ b/enshi_back/middleware/ProfileMiddleware.go @@ -0,0 +1,40 @@ +package middleware + +import ( + profilepolicies "enshi/ABAC/ProfilePolicies" + rest_api_stuff "enshi/REST_API_stuff" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" +) + +func ProfileMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + switch c.Request.Method { + case "PUT": + c.Set("target", profilepolicies.UPDATE_PROFILE) + } + + isAllowed, errors := profilepolicies.ProfilePolicies(c) + + 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 + } + + if !isAllowed { + rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you have no permission")) + c.Abort() + return + } + + c.Next() + } +} diff --git a/enshi_back/middleware/adminMiddleware.go b/enshi_back/middleware/adminMiddleware.go index 3d35800..0ddf300 100644 --- a/enshi_back/middleware/adminMiddleware.go +++ b/enshi_back/middleware/adminMiddleware.go @@ -3,7 +3,6 @@ package middleware import ( rest_api_stuff "enshi/REST_API_stuff" "enshi/middleware/checkRole" - "enshi/middleware/getters" "fmt" "github.com/gin-gonic/gin" @@ -12,14 +11,7 @@ import ( func AdminMiddleware() gin.HandlerFunc { return func(c *gin.Context) { - userId, err := getters.GetUserIdFromContext(c) - - if err != nil || userId == 0 { - rest_api_stuff.BadRequestAnswer(c, err) - c.Abort() - } - - isAdmin, err := checkRole.IsAdmin(userId) + isAdmin, err := checkRole.IsAdmin(c) if err != nil { rest_api_stuff.BadRequestAnswer(c, err) diff --git a/enshi_back/middleware/blogsMiddleware.go b/enshi_back/middleware/blogsMiddleware.go new file mode 100644 index 0000000..bed7ea1 --- /dev/null +++ b/enshi_back/middleware/blogsMiddleware.go @@ -0,0 +1,47 @@ +package middleware + +import ( + blogspolicies "enshi/ABAC/blogsPolicies" + rest_api_stuff "enshi/REST_API_stuff" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" +) + +func BlogsMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + + switch c.Request.Method { + case "DELETE": + c.Set("target", blogspolicies.DELETE_BLOG) + case "PUT": + c.Set("target", blogspolicies.UPDATE_BLOG) + case "POST": + c.Set("target", blogspolicies.CREATE_BLOG) + case "GET": + c.Set("target", blogspolicies.GET_BLOG) + } + + isAllowed, errors := blogspolicies.BlogPolicies(c) + + 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 + } + + if !isAllowed { + rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you have no permission")) + c.Abort() + return + } + + c.Next() + } +} diff --git a/enshi_back/middleware/checkRole/isAdmin.go b/enshi_back/middleware/checkRole/isAdmin.go index 93a356e..4e16a59 100644 --- a/enshi_back/middleware/checkRole/isAdmin.go +++ b/enshi_back/middleware/checkRole/isAdmin.go @@ -4,9 +4,18 @@ import ( "context" db_repo "enshi/db/go_queries" "enshi/db_connection" + "enshi/middleware/getters" + + "github.com/gin-gonic/gin" ) -func IsAdmin(userId int64) (bool, error) { +func IsAdmin(c *gin.Context) (bool, error) { + userId, err := getters.GetUserIdFromContext(c) + + if err != nil { + return false, err + } + user, err := db_repo.New(db_connection.Dbx). GetUserById(context.Background(), userId) diff --git a/enshi_back/middleware/checkRole/isOwner.go b/enshi_back/middleware/checkRole/isOwnerOfThePost.go similarity index 51% rename from enshi_back/middleware/checkRole/isOwner.go rename to enshi_back/middleware/checkRole/isOwnerOfThePost.go index 5f046e9..58cc5b5 100644 --- a/enshi_back/middleware/checkRole/isOwner.go +++ b/enshi_back/middleware/checkRole/isOwnerOfThePost.go @@ -4,9 +4,24 @@ import ( "context" db_repo "enshi/db/go_queries" "enshi/db_connection" + "enshi/middleware/getters" + + "github.com/gin-gonic/gin" ) -func IsOwnerOfThePost(userId int64, postId int64) (bool, error) { +func IsOwnerOfThePost(c *gin.Context) (bool, error) { + postId, err := getters.GetInt64Param(c, "post-id") + + if err != nil { + return false, err + } + + userId, err := getters.GetUserIdFromContext(c) + + if err != nil { + return false, err + } + post, err := db_repo.New(db_connection.Dbx). GetPostsByPostId(context.Background(), postId) diff --git a/enshi_back/middleware/getters/getIntParam.go b/enshi_back/middleware/getters/getIntParam.go new file mode 100644 index 0000000..f5ddd57 --- /dev/null +++ b/enshi_back/middleware/getters/getIntParam.go @@ -0,0 +1,17 @@ +package getters + +import ( + "strconv" + + "github.com/gin-gonic/gin" +) + +func GetInt64Param(c *gin.Context, paramName string) (int64, error) { + int64ParamValue, err := strconv.ParseInt(c.Param(paramName), 10, 64) + + if err != nil { + return -1, err + } + + return int64ParamValue, nil +} diff --git a/enshi_back/middleware/postsMiddleware.go b/enshi_back/middleware/postsMiddleware.go new file mode 100644 index 0000000..2dc90fa --- /dev/null +++ b/enshi_back/middleware/postsMiddleware.go @@ -0,0 +1,47 @@ +package middleware + +import ( + postspolicies "enshi/ABAC/PostsPolicies" + rest_api_stuff "enshi/REST_API_stuff" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" +) + +func PostsMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + + switch c.Request.Method { + case "DELETE": + c.Set("target", postspolicies.DELETE_POST) + case "PUT": + c.Set("target", postspolicies.UPDATE_POST) + case "POST": + c.Set("target", postspolicies.CREATE_POST) + case "GET": + c.Set("target", postspolicies.GET_POST) + } + + isAllowed, errors := postspolicies.PostsPolicies(c) + + 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 + } + + if !isAllowed { + rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you have no permission")) + c.Abort() + return + } + + c.Next() + } +} diff --git a/enshi_back/routes/blogRoutes/createBlog.go b/enshi_back/routes/blogRoutes/createBlog.go new file mode 100644 index 0000000..ecb1e2f --- /dev/null +++ b/enshi_back/routes/blogRoutes/createBlog.go @@ -0,0 +1,48 @@ +package blogRoutes + +import ( + "context" + rest_api_stuff "enshi/REST_API_stuff" + db_repo "enshi/db/go_queries" + "enshi/db_connection" + "enshi/middleware/getters" + "enshi/utils" + + "github.com/gin-gonic/gin" +) + +func CreateBlog(c *gin.Context) { + blogParams, err := utils.GetContextPayload[db_repo.CreateBlogByUserIdParams](c) + if err != nil { + rest_api_stuff.BadRequestAnswer(c, err) + return + } + + userId, err := getters.GetUserIdFromContext(c) + if err != nil { + rest_api_stuff.BadRequestAnswer(c, err) + return + } + + blogId, err := utils.GetUUIDv7AsInt64() + if err != nil { + rest_api_stuff.InternalErrorAnswer(c, err) + return + } + + blogParams.UserID = userId + blogParams.BlogID = blogId + + _, err = db_repo. + New(db_connection.Dbx). + CreateBlogByUserId( + context.Background(), + blogParams, + ) + if err != nil { + rest_api_stuff.InternalErrorAnswer(c, err) + return + } + + rest_api_stuff.OkAnswer(c, "blog has been created") +} diff --git a/enshi_back/routes/changeUserProfile.go b/enshi_back/routes/changeUserProfile.go deleted file mode 100644 index f4fd3e5..0000000 --- a/enshi_back/routes/changeUserProfile.go +++ /dev/null @@ -1,17 +0,0 @@ -package routes - -import ( - rest_api_stuff "enshi/REST_API_stuff" - db_repo "enshi/db/go_queries" - - "github.com/gin-gonic/gin" -) - -func ChangeUserProfile(c *gin.Context) { - var userProfileParams db_repo.UpdateProfileByUserIdParams - - if err := c.BindJSON(&userProfileParams); err != nil { - rest_api_stuff.BadRequestAnswer(c, err) - } - -} diff --git a/enshi_back/routes/postsRoutes/deletePost.go b/enshi_back/routes/postsRoutes/deletePost.go index 52c6e1b..b0e3388 100644 --- a/enshi_back/routes/postsRoutes/deletePost.go +++ b/enshi_back/routes/postsRoutes/deletePost.go @@ -7,16 +7,15 @@ import ( "enshi/db_connection" "enshi/middleware/getters" "fmt" + "strconv" "github.com/gin-gonic/gin" ) func DeletePost(c *gin.Context) { - var deletePostId struct { - PostId int64 `json:"post_id"` - } + postId, err := strconv.ParseInt(c.Param("post-id"), 10, 64) - if err := c.BindJSON(&deletePostId); err != nil { + if err != nil { rest_api_stuff.BadRequestAnswer(c, err) return } @@ -28,7 +27,7 @@ func DeletePost(c *gin.Context) { } query := db_repo.New(db_connection.Dbx) - post, err := query.GetPostsByPostId(context.Background(), deletePostId.PostId) + post, err := query.GetPostsByPostId(context.Background(), postId) if err != nil { rest_api_stuff.InternalErrorAnswer(c, err) return @@ -41,7 +40,7 @@ func DeletePost(c *gin.Context) { // TODO: Add block of code, so admin could delete anything - err = query.DeletePostByPostId(context.Background(), deletePostId.PostId) + err = query.DeletePostByPostId(context.Background(), postId) if err != nil { rest_api_stuff.InternalErrorAnswer(c, err) return diff --git a/enshi_back/routes/postsRoutes/getPost.go b/enshi_back/routes/postsRoutes/getPost.go index 2f9e8b2..f05cc2c 100644 --- a/enshi_back/routes/postsRoutes/getPost.go +++ b/enshi_back/routes/postsRoutes/getPost.go @@ -5,24 +5,23 @@ import ( rest_api_stuff "enshi/REST_API_stuff" db_repo "enshi/db/go_queries" "enshi/db_connection" + "enshi/middleware/getters" "net/http" "github.com/gin-gonic/gin" ) func GetPost(c *gin.Context) { - var postParams struct { - PostId int64 `json:"post_id"` - } + postId, err := getters.GetInt64Param(c, "post-id") - if err := c.BindJSON(&postParams); err != nil { + if err != nil { rest_api_stuff.BadRequestAnswer(c, err) return } postData, err := db_repo.New(db_connection.Dbx). - GetPostsByPostId(context.Background(), postParams.PostId) + GetPostsByPostId(context.Background(), postId) if err != nil { rest_api_stuff.InternalErrorAnswer(c, err) diff --git a/enshi_back/routes/postsRoutes/updatePost.go b/enshi_back/routes/postsRoutes/updatePost.go index d1cfd45..bb26330 100644 --- a/enshi_back/routes/postsRoutes/updatePost.go +++ b/enshi_back/routes/postsRoutes/updatePost.go @@ -5,9 +5,7 @@ import ( rest_api_stuff "enshi/REST_API_stuff" db_repo "enshi/db/go_queries" "enshi/db_connection" - "enshi/middleware/checkRole" "enshi/middleware/getters" - "fmt" "github.com/gin-gonic/gin" ) @@ -20,20 +18,14 @@ func UpdatePost(c *gin.Context) { return } - userId, 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 53% rename from enshi_back/utils/routesSetup.go rename to enshi_back/routes/routesSetup.go index 832273e..6662770 100644 --- a/enshi_back/utils/routesSetup.go +++ b/enshi_back/routes/routesSetup.go @@ -1,9 +1,9 @@ -package utils +package routes import ( "enshi/middleware" - "enshi/routes" "enshi/routes/authRoutes" + "enshi/routes/blogRoutes" "enshi/routes/postsRoutes" "enshi/routes/userProfileRoutes" "net/http" @@ -29,21 +29,55 @@ func SetupRotes(g *gin.Engine) error { freeGroup.GET("getCookie", testCookie) - freeGroup.POST("login", authRoutes.Login) - freeGroup.POST("registerUser", authRoutes.RegisterUser) - freeGroup.GET("getPost", postsRoutes.GetPost) + freeGroup.POST( + "login", + authRoutes.Login, + ) + freeGroup.POST( + "users", + authRoutes.RegisterUser, + ) + + postsGroup := g.Group("/") + postsGroup.Use(middleware.PostsMiddleware()) + + postsGroup.GET( + "posts/:post-id", + postsRoutes.GetPost, + ) + postsGroup.PUT( + "posts/:post-id", + postsRoutes.UpdatePost, + ) + postsGroup.POST( + "posts", + postsRoutes.CreatePost, + ) + postsGroup.DELETE( + "posts/:post-id", + postsRoutes.DeletePost, + ) + + blogGroup := g.Group("/") + blogGroup.Use(middleware.BlogsMiddleware()) + + blogGroup.POST( + "blogs", + blogRoutes.CreateBlog, + ) + + profilesGroup := g.Group("/") + profilesGroup.Use(middleware.ProfileMiddleware()) + + profilesGroup.PUT( + "profiles", + userProfileRoutes.UpdateUserProfile, + ) // Auth group routes authGroup := g.Group("/") authGroup.Use(middleware.AuthMiddleware()) - authGroup.POST("updatePost", postsRoutes.UpdatePost) - authGroup.POST("createPost", postsRoutes.CreatePost) - authGroup.POST("changeUserProfile", routes.ChangeUserProfile) - authGroup.POST("updateProfile", userProfileRoutes.UpdateUserProfile) - - authGroup.DELETE("deletePost", postsRoutes.DeletePost) - // Admin group routes adminGroup := authGroup.Group("/admin/") adminGroup.Use(middleware.AdminMiddleware()) diff --git a/enshi_back/routes/userProfileRoutes/updateUserProfile.go b/enshi_back/routes/userProfileRoutes/updateUserProfile.go index fe792e9..22f04e7 100644 --- a/enshi_back/routes/userProfileRoutes/updateUserProfile.go +++ b/enshi_back/routes/userProfileRoutes/updateUserProfile.go @@ -6,14 +6,16 @@ import ( db_repo "enshi/db/go_queries" "enshi/db_connection" "enshi/middleware/getters" + "enshi/utils" "github.com/gin-gonic/gin" ) func UpdateUserProfile(c *gin.Context) { - var newProfile db_repo.UpdateProfileByUserIdParams + newProfile, err := + utils.GetContextPayload[db_repo.UpdateProfileByUserIdParams](c) - if err := c.BindJSON(&newProfile); err != nil { + if err != nil { rest_api_stuff.BadRequestAnswer(c, err) return } diff --git a/enshi_back/utils/GetContextPayload.go b/enshi_back/utils/GetContextPayload.go new file mode 100644 index 0000000..934207c --- /dev/null +++ b/enshi_back/utils/GetContextPayload.go @@ -0,0 +1,21 @@ +package utils + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +func GetContextPayload[T any](c *gin.Context) (T, error) { + var params T + + if err := c.BindJSON(¶ms); err != nil { + return params, err + } + + validate := validator.New(validator.WithRequiredStructEnabled()) + if err := validate.Struct(params); err != nil { + return params, err + } + + return params, nil +} diff --git a/enshi_back/utils/uuidGenerator.go b/enshi_back/utils/uuidGenerator.go new file mode 100644 index 0000000..d3f17b9 --- /dev/null +++ b/enshi_back/utils/uuidGenerator.go @@ -0,0 +1,18 @@ +package utils + +import ( + "encoding/binary" + + "github.com/google/uuid" +) + +func GetUUIDv7AsInt64() (int64, error) { + uuid, err := uuid.NewV7() + if err != nil { + return -1, err + } + + return -int64( + binary.BigEndian.Uint64(uuid[8:]), + ), nil +}