package serverconfig import ( "fmt" "bitbucket.org/nemt/nemt-portal-api/application/applicationservice" "bitbucket.org/nemt/nemt-portal-api/application/viewmodel" "bitbucket.org/nemt/nemt-portal-api/infra/auth" "bitbucket.org/nemt/nemt-portal-api/infra/config" "bitbucket.org/nemt/nemt-portal-api/infra/logger" "bitbucket.org/nemt/nemt-portal-api/server/router/routeutils" "github.com/casbin/casbin" "github.com/labstack/echo" "github.com/labstack/echo/middleware" "strings" ) type ( // Config defines the config for CasbinAuth middleware. Config struct { // Skipper defines a function to skip middleware. Skipper middleware.Skipper // Enforcer CasbinAuth main rule. // Required. Enforcer *casbin.Enforcer Application *config.Config Svc *applicationservice.Service Logger *logger.Logger } ) var ( // DefaultConfig is the default CasbinAuth middleware config. DefaultConfig = Config{ Skipper: middleware.DefaultSkipper, } ) // MiddlewareWithConfig returns a CasbinAuth middleware with config. // See `Middleware()`. func MiddlewareWithConfig(cfg *config.Config, svc *applicationservice.Service, log *logger.Logger) echo.MiddlewareFunc { config := &DefaultConfig config.Enforcer = casbin.NewEnforcer("authorization_model.conf", "authorization_policy.csv") config.Svc = svc config.Logger = log config.Application = cfg return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if config.Skipper(c) || config.CheckPermission(c) { return next(c) } return routeutils.HandleHTMLError(c, echo.ErrForbidden) } } } func setAuthorizationMiddleware(e *echo.Echo, log *logger.Logger, cfg *config.Config, svc *applicationservice.Service) { e.Use(MiddlewareWithConfig(cfg, svc, log)) } // CheckPermission checks the user/method/path combination from the request. // Returns true (permission granted) or false (permission forbidden) func (a *Config) CheckPermission(c echo.Context) bool { user, err := auth.GetUserDetail(c, a.Application) if err != nil { a.Logger.Warningf("Cannot get user details. %v\n", err) user = viewmodel.User{} } method := c.Request().Method path := c.Request().URL.Path objectsRole, objectsOrganization, objectsOrganizationType, object := a.policyObjectAttributes(c, user) currentUsersOrganization := viewmodel.Organization{} if len(user.Organizations) > 0 { currentUsersOrganization = user.Organizations[0] } currentUsersRole := viewmodel.Profile{} if len(user.Profiles) > 0 { currentUsersRole = user.Profiles[0] } currentUsersOrganizationType := "" if len(user.Profiles) > 0 { currentUsersOrganizationType = user.Profiles[0].Organization.Type.Key } orgRelation := organizationsRelation(currentUsersOrganization, objectsOrganization) objRelation := a.objectRelation(object, user) // parameters to Enforce must match the request section of the authorization_model.conf return a.Enforcer.Enforce(currentUsersRole.Key, objectsRole.Key, objectsOrganizationType, currentUsersOrganizationType, orgRelation, objRelation, path, method) } // policyObjectAttributes returns all information about the object being accessed for the policy // in case object exists and returns users information if it is a new object func (a *Config) policyObjectAttributes(c echo.Context, userDetails viewmodel.User) (viewmodel.Profile, viewmodel.Organization, string, interface{}) { var object interface{} const userIDParamName = "user_uuid" existingUser := strings.Contains(c.Request().URL.Path, "/users/") && c.Param(userIDParamName) != "" newUser := strings.Contains(c.Request().URL.Path, "/users/") && c.Param(userIDParamName) == "" const organizationIDParamName = "org_uuid" existingOrganization := strings.Contains(c.Request().URL.Path, "/organization") && c.Param(organizationIDParamName) != "" newOrganization := strings.Contains(c.Request().URL.Path, "/organization") && c.Param(organizationIDParamName) == "" fmt.Println("**********") fmt.Printf("url %v\n", c.Param(userIDParamName)) fmt.Printf("user %v\n", userDetails.ID) fmt.Printf("existing %v\n", existingUser) fmt.Printf("new %v\n", newUser) fmt.Println("**********") switch { case existingUser: object, _ = a.Svc.Users.GetByUUID(c.Param(userIDParamName), "") case newUser && len(userDetails.Organizations) > 0: object = userDetails case existingOrganization: object, _ = a.Svc.Organization.GetByUUID(c.Param(organizationIDParamName), userDetails) case newOrganization: object = viewmodel.Organization{} } objectsRole := viewmodel.Profile{} switch obj := object.(type) { case viewmodel.User: if len(obj.Profiles) > 0 { objectsRole = obj.Profiles[0] } } objectsOrganization := viewmodel.Organization{} switch obj := object.(type) { case viewmodel.User: if len(obj.Profiles) > 0 { objectsOrganization = obj.Profiles[0].Organization } case viewmodel.Organization: objectsOrganization = obj } objectsOrganizationType := objectsOrganization.Type.Key return objectsRole, objectsOrganization, objectsOrganizationType, object } func organizationsRelation(requestOrganization, currentUsersOrganization viewmodel.Organization) string { if requestOrganization.UUID == currentUsersOrganization.UUID { return "[equal]" } for _, childOrg := range currentUsersOrganization.ChildOrgs { if childOrg.UUID == requestOrganization.UUID { return "[equal-or-child]" } } for _, childOrg := range requestOrganization.ChildOrgs { if childOrg.UUID == currentUsersOrganization.UUID { return "[parent]" } } return "[unrelated]" } // organizationGoverningObject returns the role that is the owner of the object that is being accessed // in case object exists and returns users role if it is a new object func (a *Config) objectRelation(object interface{}, currentUser viewmodel.User) string { switch obj := object.(type) { case viewmodel.User: if obj.ID == currentUser.ID { return "[self]" } } return "[other]" }