From 28ee6babd39b1d31af0bdf8c7f8b588ae751fb84 Mon Sep 17 00:00:00 2001 From: Iman Mirazimi Date: Sat, 24 Feb 2024 20:36:13 +0330 Subject: [PATCH] feat(niki): add admin authorization service. --- .gitignore | 1 - Makefile | 6 +- delivery/http_server/admin/admin/handler.go | 20 ++-- delivery/http_server/admin/admin/route.go | 4 +- .../http_server/admin/kind_box/handler.go | 20 ++-- .../http_server/admin/kind_box_req/handler.go | 4 + .../http_server/admin/kind_box_req/route.go | 7 +- .../middleware/admin_authorization.go | 36 +++++++ delivery/http_server/server.go | 6 +- internal/initial/authorization.go | 14 +++ main.go | 6 +- pkg/err_msg/message.go | 1 + pkg/slice/admin_permission.go | 15 +++ repository/mysql/admin/authorization.go | 98 +++++++++++++++++++ repository/mysql/admin/{admin.go => db.go} | 0 service/admin/authorization/service.go | 37 +++++++ service/benefactor/kind_box_req/add.go | 2 +- 17 files changed, 246 insertions(+), 31 deletions(-) create mode 100644 delivery/http_server/middleware/admin_authorization.go create mode 100644 internal/initial/authorization.go create mode 100644 pkg/slice/admin_permission.go create mode 100644 repository/mysql/admin/authorization.go rename repository/mysql/admin/{admin.go => db.go} (100%) create mode 100644 service/admin/authorization/service.go diff --git a/.gitignore b/.gitignore index 6d09df6..c841ccc 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,3 @@ bin *.env logs/ -mise.log \ No newline at end of file diff --git a/Makefile b/Makefile index c1de5cb..4fa0af0 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,4 @@ format: @golangci-lint run --fix build: - go build main.go - -run-dev: - sudo docker compose up - + go build main.go \ No newline at end of file diff --git a/delivery/http_server/admin/admin/handler.go b/delivery/http_server/admin/admin/handler.go index 7db1cc5..0736bdc 100644 --- a/delivery/http_server/admin/admin/handler.go +++ b/delivery/http_server/admin/admin/handler.go @@ -2,24 +2,28 @@ package adminhandler import ( adminservice "git.gocasts.ir/ebhomengo/niki/service/admin/admin" + adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization" adminauthservice "git.gocasts.ir/ebhomengo/niki/service/auth" adminvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/admin" ) type Handler struct { - authConfig adminauthservice.Config - authSvc adminauthservice.Service - adminSvc adminservice.Service - adminVld adminvalidator.Validator + authConfig adminauthservice.Config + authSvc adminauthservice.Service + adminSvc adminservice.Service + adminVld adminvalidator.Validator + adminAuthorizeSvc adminauthorizationservice.Service } func New(authConfig adminauthservice.Config, authSvc adminauthservice.Service, adminSvc adminservice.Service, adminVld adminvalidator.Validator, + adminAuthorizeSvc adminauthorizationservice.Service, ) Handler { return Handler{ - authConfig: authConfig, - authSvc: authSvc, - adminSvc: adminSvc, - adminVld: adminVld, + authConfig: authConfig, + authSvc: authSvc, + adminSvc: adminSvc, + adminVld: adminVld, + adminAuthorizeSvc: adminAuthorizeSvc, } } diff --git a/delivery/http_server/admin/admin/route.go b/delivery/http_server/admin/admin/route.go index d15776d..544be17 100644 --- a/delivery/http_server/admin/admin/route.go +++ b/delivery/http_server/admin/admin/route.go @@ -2,6 +2,7 @@ package adminhandler import ( "git.gocasts.ir/ebhomengo/niki/delivery/http_server/middleware" + "git.gocasts.ir/ebhomengo/niki/entity" "github.com/labstack/echo/v4" ) @@ -10,9 +11,8 @@ func (h Handler) SetRoutes(e *echo.Echo) { //nolint:gocritic //r.POST("/", h.Add).Name = "admin-addkindboxreq" - r.POST("/register", h.Register) + r.POST("/register", h.Register, middleware.Auth(h.authSvc, h.authConfig), middleware.AdminAuthorization(h.adminAuthorizeSvc, entity.AdminAdminRegisterPermission)) r.POST("/login-by-phone", h.LoginByPhoneNumber) - r.GET("/test", h.LoginByPhoneNumber, middleware.Auth(h.authSvc, h.authConfig)) //nolint:gocritic //r.PATCH("/:id", h.Update).Name = "admin-updatekindboxreq" } diff --git a/delivery/http_server/admin/kind_box/handler.go b/delivery/http_server/admin/kind_box/handler.go index df616d4..cb9ae76 100644 --- a/delivery/http_server/admin/kind_box/handler.go +++ b/delivery/http_server/admin/kind_box/handler.go @@ -1,25 +1,29 @@ package adminkindboxhandler import ( + adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization" adminkindboxservice "git.gocasts.ir/ebhomengo/niki/service/admin/kind_box" authservice "git.gocasts.ir/ebhomengo/niki/service/auth" adminkindboxvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/kind_box" ) type Handler struct { - authConfig authservice.Config - authSvc authservice.Service - adminKindBoxSvc adminkindboxservice.Service - adminKindBoxVld adminkindboxvalidator.Validator + authConfig authservice.Config + authSvc authservice.Service + adminKindBoxSvc adminkindboxservice.Service + adminKindBoxVld adminkindboxvalidator.Validator + adminAuthorizeSvc adminauthorizationservice.Service } func New(authConfig authservice.Config, authSvc authservice.Service, adminKindBoxSvc adminkindboxservice.Service, adminKindBoxVld adminkindboxvalidator.Validator, + adminAuthorizeSvc adminauthorizationservice.Service, ) Handler { return Handler{ - authConfig: authConfig, - authSvc: authSvc, - adminKindBoxSvc: adminKindBoxSvc, - adminKindBoxVld: adminKindBoxVld, + authConfig: authConfig, + authSvc: authSvc, + adminKindBoxSvc: adminKindBoxSvc, + adminKindBoxVld: adminKindBoxVld, + adminAuthorizeSvc: adminAuthorizeSvc, } } diff --git a/delivery/http_server/admin/kind_box_req/handler.go b/delivery/http_server/admin/kind_box_req/handler.go index cc99b6c..7e65063 100644 --- a/delivery/http_server/admin/kind_box_req/handler.go +++ b/delivery/http_server/admin/kind_box_req/handler.go @@ -1,6 +1,7 @@ package adminkindboxreqhandler import ( + adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization" adminkindboxreqservice "git.gocasts.ir/ebhomengo/niki/service/admin/kind_box_req" authservice "git.gocasts.ir/ebhomengo/niki/service/auth" adminkindboxreqvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/kind_box_req" @@ -11,15 +12,18 @@ type Handler struct { authSvc authservice.Service adminKindBoxReqSvc adminkindboxreqservice.Service adminKindBoxReqVld adminkindboxreqvalidator.Validator + adminAuthorizeSvc adminauthorizationservice.Service } func New(authConfig authservice.Config, authSvc authservice.Service, adminKindBoxReqSvc adminkindboxreqservice.Service, adminKindBoxReqVld adminkindboxreqvalidator.Validator, + adminAuthorizeSvc adminauthorizationservice.Service, ) Handler { return Handler{ authConfig: authConfig, authSvc: authSvc, adminKindBoxReqSvc: adminKindBoxReqSvc, adminKindBoxReqVld: adminKindBoxReqVld, + adminAuthorizeSvc: adminAuthorizeSvc, } } diff --git a/delivery/http_server/admin/kind_box_req/route.go b/delivery/http_server/admin/kind_box_req/route.go index 0787eae..2269bc8 100644 --- a/delivery/http_server/admin/kind_box_req/route.go +++ b/delivery/http_server/admin/kind_box_req/route.go @@ -2,6 +2,7 @@ package adminkindboxreqhandler import ( "git.gocasts.ir/ebhomengo/niki/delivery/http_server/middleware" + "git.gocasts.ir/ebhomengo/niki/entity" echo "github.com/labstack/echo/v4" ) @@ -9,9 +10,9 @@ func (h Handler) SetRoutes(e *echo.Echo) { r := e.Group("/admin/kindboxreqs") // todo - add acl - r.PATCH("/accept-kind-box-req/:id", h.Accept) - r.PATCH("/reject-kind-box-req/:id", h.Reject) + r.PATCH("/accept-kind-box-req/:id", h.Accept, middleware.Auth(h.authSvc, h.authConfig), middleware.AdminAuthorization(h.adminAuthorizeSvc, entity.AdminKindBoxReqAcceptPermission)) + r.PATCH("/reject-kind-box-req/:id", h.Reject, middleware.Auth(h.authSvc, h.authConfig), middleware.AdminAuthorization(h.adminAuthorizeSvc, entity.AdminKindBoxReqRejectPermission)) r.PATCH("/deliver-kind-box-req/:id", h.Deliver) r.PATCH("/assign-sender-agent/:id", h.AssignSenderAgent) - r.GET("/", h.GetAll, middleware.Auth(h.authSvc, h.authConfig)) + r.GET("/", h.GetAll, middleware.Auth(h.authSvc, h.authConfig), middleware.AdminAuthorization(h.adminAuthorizeSvc, entity.AdminKindBoxReqGetAllPermission)) } diff --git a/delivery/http_server/middleware/admin_authorization.go b/delivery/http_server/middleware/admin_authorization.go new file mode 100644 index 0000000..cee306d --- /dev/null +++ b/delivery/http_server/middleware/admin_authorization.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "net/http" + + "git.gocasts.ir/ebhomengo/niki/entity" + "git.gocasts.ir/ebhomengo/niki/pkg/claim" + errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" + adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization" + "github.com/labstack/echo/v4" +) + +func AdminAuthorization(service adminauthorizationservice.Service, + permissions ...entity.AdminPermission, +) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + claims := claim.GetClaimsFromEchoContext(c) + + isAllowed, err := service.CheckAccess(claims.UserID, entity.MapToAdminRole(claims.Role), permissions...) + if err != nil { + return c.JSON(http.StatusInternalServerError, echo.Map{ + "message": errmsg.ErrorMsgSomethingWentWrong, + }) + } + + if !isAllowed { + return c.JSON(http.StatusForbidden, echo.Map{ + "message": errmsg.ErrorMsgUserNotAllowed, + }) + } + + return next(c) + } + } +} diff --git a/delivery/http_server/server.go b/delivery/http_server/server.go index e7d79fb..bb1f422 100644 --- a/delivery/http_server/server.go +++ b/delivery/http_server/server.go @@ -10,6 +10,7 @@ import ( benefactorhandler "git.gocasts.ir/ebhomengo/niki/delivery/http_server/benefactor/benefactor" benefactorkindboxreqhandler "git.gocasts.ir/ebhomengo/niki/delivery/http_server/benefactor/kind_box_req" adminservice "git.gocasts.ir/ebhomengo/niki/service/admin/admin" + adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization" adminkindboxreqservice "git.gocasts.ir/ebhomengo/niki/service/admin/kind_box_req" authservice "git.gocasts.ir/ebhomengo/niki/service/auth" benefactoraddressservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/address" @@ -48,6 +49,7 @@ func New( adminAuthSvc authservice.Service, adminKinBoxReqSvc adminkindboxreqservice.Service, adminKinBoxReqVld adminkindboxreqvalidator.Validator, + adminAuthorizeSvc adminauthorizationservice.Service, ) Server { return Server{ Router: echo.New(), @@ -55,8 +57,8 @@ func New( benefactorHandler: benefactorhandler.New(cfg.Auth, benefactorAuthSvc, benefactorSvc, benefactorVld), benefactorKindBoxReqHandler: benefactorkindboxreqhandler.New(cfg.Auth, benefactorAuthSvc, benefactorKindBoxReqSvc, benefactorKindBoxReqVld), benefactorAddressHandler: benefactoraddresshandler.New(cfg.Auth, benefactorAuthSvc, benefactorAddressSvc, benefactorAddressVld), - adminHandler: adminhandler.New(cfg.AdminAuth, adminAuthSvc, adminSvc, adminVld), - adminKindBoxReqHandler: adminkindboxreqhandler.New(cfg.Auth, adminAuthSvc, adminKinBoxReqSvc, adminKinBoxReqVld), + adminHandler: adminhandler.New(cfg.AdminAuth, adminAuthSvc, adminSvc, adminVld, adminAuthorizeSvc), + adminKindBoxReqHandler: adminkindboxreqhandler.New(cfg.Auth, adminAuthSvc, adminKinBoxReqSvc, adminKinBoxReqVld, adminAuthorizeSvc), } } diff --git a/internal/initial/authorization.go b/internal/initial/authorization.go new file mode 100644 index 0000000..ba9a899 --- /dev/null +++ b/internal/initial/authorization.go @@ -0,0 +1,14 @@ +package initial + +import ( + "git.gocasts.ir/ebhomengo/niki/repository/mysql" + adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization" +) + +type AdminAuthorization struct { + AdminAuthorizationSvc adminauthorizationservice.Service +} + +func InitAdminAuthorizationService(db *mysql.DB) adminauthorizationservice.Service { + return adminauthorizationservice.New(InitAdminMysql(db)) +} diff --git a/main.go b/main.go index 6989ed1..8481cea 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ type Dependencies struct { initial.Databases initial.Validators initial.Services + initial.AdminAuthorization } func parseFlags() bool { @@ -64,6 +65,9 @@ func initDependencies(cfg config.Config, redisAdapter redis.Adapter, db *mysql.D AdminKindBoxReqSvc: initial.InitAdminKindBoxReqService(db), AdminSvc: initial.InitAdminService(cfg, db), }, + initial.AdminAuthorization{ + AdminAuthorizationSvc: initial.InitAdminAuthorizationService(db), + }, } } @@ -73,7 +77,7 @@ func initAndRunServer(cfg config.Config, dependencies *Dependencies) { dependencies.BenefactorKindBoxReqSvc, dependencies.BenefactorKindBoxReqVld, dependencies.BenefactorAddressSvc, dependencies.BenefactorAddressVld, dependencies.AdminSvc, dependencies.AdminVld, dependencies.AdminAuthSvc, - dependencies.AdminKindBoxReqSvc, dependencies.AdminKindBoxReqVld) + dependencies.AdminKindBoxReqSvc, dependencies.AdminKindBoxReqVld, dependencies.AdminAuthorizationSvc) server.Serve() } diff --git a/pkg/err_msg/message.go b/pkg/err_msg/message.go index be587b0..63bb951 100644 --- a/pkg/err_msg/message.go +++ b/pkg/err_msg/message.go @@ -1,6 +1,7 @@ package errmsg const ( + ErrorMsgAdminNotAllowed = "admin is not allowed" ErrorMsgNotFound = "record not found" ErrorMsgSomethingWentWrong = "something went wrong" ErrorMsgInvalidInput = "invalid input" diff --git a/pkg/slice/admin_permission.go b/pkg/slice/admin_permission.go new file mode 100644 index 0000000..aac3d86 --- /dev/null +++ b/pkg/slice/admin_permission.go @@ -0,0 +1,15 @@ +package slice + +import ( + "git.gocasts.ir/ebhomengo/niki/entity" +) + +func DoesExist(list []entity.AdminPermission, value entity.AdminPermission) bool { + for _, item := range list { + if item == value { + return true + } + } + + return false +} diff --git a/repository/mysql/admin/authorization.go b/repository/mysql/admin/authorization.go new file mode 100644 index 0000000..560784f --- /dev/null +++ b/repository/mysql/admin/authorization.go @@ -0,0 +1,98 @@ +package mysqladmin + +import ( + "time" + + "git.gocasts.ir/ebhomengo/niki/entity" + errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" + "git.gocasts.ir/ebhomengo/niki/pkg/slice" + "git.gocasts.ir/ebhomengo/niki/repository/mysql" +) + +func (d *DB) GetAdminPermissions(adminID uint, role entity.AdminRole) ([]entity.AdminPermission, error) { + const op = "mysqladmin.GetAdminPermissions" + + // get adminRoleACL + adminRoleACL := make([]entity.AdminAccessControl, 0) + + adminRoleRows, err := d.conn.Conn().Query(`select * from admin_access_controls where actor_type = ? and actor_id = ?`, + entity.AdminRoleActorType, role) + if err != nil { + return nil, richerror.New(op).WithErr(err). + WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected) + } + + defer adminRoleRows.Close() + + for adminRoleRows.Next() { + acl, err := scanAccessControl(adminRoleRows) + if err != nil { + return nil, richerror.New(op).WithErr(err). + WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected) + } + + adminRoleACL = append(adminRoleACL, acl) + } + + if err := adminRoleRows.Err(); err != nil { + return nil, richerror.New(op).WithErr(err). + WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected) + } + + // get adminACL + adminACL := make([]entity.AdminAccessControl, 0) + + adminRows, err := d.conn.Conn().Query(`select * from admin_access_controls where actor_type = ? and actor_id = ?`, + entity.AdminAdminActorType, adminID) + if err != nil { + return nil, richerror.New(op).WithErr(err). + WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected) + } + + defer adminRows.Close() + + for adminRows.Next() { + acl, err := scanAccessControl(adminRows) + if err != nil { + return nil, richerror.New(op).WithErr(err). + WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected) + } + + adminACL = append(adminACL, acl) + } + + if err := adminRows.Err(); err != nil { + return nil, richerror.New(op).WithErr(err). + WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected) + } + + // merge ACLs by permission + adminPermissions := make([]entity.AdminPermission, 0) + for _, r := range adminRoleACL { + if !slice.DoesExist(adminPermissions, r.Permission) { + adminPermissions = append(adminPermissions, r.Permission) + } + } + for _, a := range adminACL { + if !slice.DoesExist(adminPermissions, a.Permission) { + adminPermissions = append(adminPermissions, a.Permission) + } + } + if len(adminPermissions) == 0 { + return nil, nil + } + + return adminPermissions, nil +} + +func scanAccessControl(scanner mysql.Scanner) (entity.AdminAccessControl, error) { + var ( + createdAt time.Time + acl entity.AdminAccessControl + ) + + err := scanner.Scan(&acl.ID, &acl.ActorID, &acl.ActorType, &acl.Permission, &createdAt) + + return acl, err +} diff --git a/repository/mysql/admin/admin.go b/repository/mysql/admin/db.go similarity index 100% rename from repository/mysql/admin/admin.go rename to repository/mysql/admin/db.go diff --git a/service/admin/authorization/service.go b/service/admin/authorization/service.go new file mode 100644 index 0000000..f695e55 --- /dev/null +++ b/service/admin/authorization/service.go @@ -0,0 +1,37 @@ +package adminauthorizationservice + +import ( + "git.gocasts.ir/ebhomengo/niki/entity" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" +) + +type Repository interface { + GetAdminPermissions(adminID uint, role entity.AdminRole) ([]entity.AdminPermission, error) +} + +type Service struct { + repo Repository +} + +func New(repo Repository) Service { + return Service{repo: repo} +} + +func (s Service) CheckAccess(adminID uint, role entity.AdminRole, permissions ...entity.AdminPermission) (bool, error) { + const op = "adminauthorizationservice.CheckAccess" + + AdminPermissions, err := s.repo.GetAdminPermissions(adminID, role) + if err != nil { + return false, richerror.New(op).WithErr(err) + } + + for _, p := range permissions { + for _, ap := range AdminPermissions { + if p == ap { + return true, nil + } + } + } + + return false, nil +} diff --git a/service/benefactor/kind_box_req/add.go b/service/benefactor/kind_box_req/add.go index 0baab21..38f4a15 100644 --- a/service/benefactor/kind_box_req/add.go +++ b/service/benefactor/kind_box_req/add.go @@ -2,11 +2,11 @@ package benefactorkindboxreqservice import ( "context" + "time" "git.gocasts.ir/ebhomengo/niki/entity" param "git.gocasts.ir/ebhomengo/niki/param/benefactor/kind_box_req" richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" - "time" ) func (s Service) Add(ctx context.Context, req param.KindBoxReqAddRequest) (param.KindBoxReqAddResponse, error) {