diff --git a/authorization_model.conf b/authorization_model.conf new file mode 100644 index 0000000..1353c1e --- /dev/null +++ b/authorization_model.conf @@ -0,0 +1,11 @@ +[request_definition] +r = role, objectsRole, orgRelation, objectsRelation, obj, act + +[policy_definition] +p = role, objectsRole, orgRelation, objectsRelation, obj, act + +[policy_effect] +e = some(where (p.eft == allow)) && !some(where (p.eft == deny)) + +[matchers] +m = keyMatch(r.role, p.role) && keyMatch(r.objectsRole, p.objectsRole) && keyMatch(r.objectsRelation, p.objectsRelation) && keyMatch(r.orgRelation, p.orgRelation) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*") \ No newline at end of file diff --git a/authorization_policy.csv b/authorization_policy.csv new file mode 100644 index 0000000..7c80d6e --- /dev/null +++ b/authorization_policy.csv @@ -0,0 +1,27 @@ +p, *, *, *, *, /v1/authenticate/portal, POST +p, *, *, *, *, /health/, GET +p, *, *, *, [self], /v1/users/*/, GET +p, *, *, *, [self], /v1/users/portal/*, DELETE +p, *, *, *, [self], /v1/users/portal/*, POST +p, *, *, *, [self], /v1/users/portal/*, GET +p, *AD, *, *, *, /v1/users/, GET +p, BDCAD, BCBSIAD, *, [other], /v1/users/*/, GET +p, BDCAD, BCBSIAD, *, [other], /v1/users/portal/*, DELETE +p, BDCAD, BCBSIAD, *, [other], /v1/users/portal/*, POST +p, BDCAD, BCBSIAD, *, [other], /v1/users/portal/*, GET +p, BDCAD, PLANAD, *, [other], /v1/users/*/, GET +p, BDCAD, PLANAD, *, [other], /v1/users/portal/*, DELETE +p, BDCAD, PLANAD, *, [other], /v1/users/portal/*, POST +p, BDCAD, PLANAD, *, [other], /v1/users/portal/*, GET +p, BCBSIAD, SP, *, [other], /v1/users/*/, GET +p, BCBSIAD, SP, *, [other], /v1/users/portal/*, DELETE +p, BCBSIAD, SP, *, [other], /v1/users/portal/*, POST +p, BCBSIAD, SP, *, [other], /v1/users/portal/*, GET +p, BCBSIAD, US, *, [other], /v1/users/*/, GET +p, BCBSIAD, US, *, [other], /v1/users/portal/*, DELETE +p, BCBSIAD, US, *, [other], /v1/users/portal/*, POST +p, BCBSIAD, US, *, [other], /v1/users/portal/*, GET +p, PLANAD, US, [equal], [other], /v1/users/*/, GET +p, PLANAD, US, [equal], [other], /v1/users/portal/*, DELETE +p, PLANAD, US, [equal], [other], /v1/users/portal/*, POST +p, PLANAD, US, [equal], [other], /v1/users/portal/*, GET \ No newline at end of file diff --git a/glide.yaml b/glide.yaml index 09aa359..ace60b2 100644 --- a/glide.yaml +++ b/glide.yaml @@ -42,3 +42,5 @@ import: - package: github.com/ttacon/builder - package: github.com/casbin/casbin version: ~1.5.0 +- package: github.com/Knetic/govaluate + version: 9aa49832a739dcd78a5542ff189fb82c3e423116 diff --git a/server/server.go b/server/server.go index bb3202a..703acf0 100644 --- a/server/server.go +++ b/server/server.go @@ -52,16 +52,16 @@ func (s *Server) Run() error { s.srv.Debug = s.cfg.App.Debug - err := serverconfig.SetMiddlewares(s.srv, s.cfg, s.log, s.svc) - if err != nil { - return errors.Wrap(err) - } - entityMapper := entitymapping.New() notificationService := notificationservice.New(s.svc, entityMapper, s.cfg, s.cache) appService := applicationservice.New(s.svc, entityMapper, notificationService, s.cfg) tncService := tncservice.New(s.svc, entityMapper, s.cfg, notificationService) + err := serverconfig.SetMiddlewares(s.srv, s.cfg, s.log, s.svc, appService) + if err != nil { + return errors.Wrap(err) + } + router.Register(s.srv, s.cfg, appService, tncService, notificationService) err = s.srv.Start(fmt.Sprintf(":%d", s.cfg.HTTP.Port)) diff --git a/server/serverconfig/authorization.go b/server/serverconfig/authorization.go index ee59064..96e9e68 100644 --- a/server/serverconfig/authorization.go +++ b/server/serverconfig/authorization.go @@ -1,6 +1,8 @@ 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" @@ -44,6 +46,8 @@ func MiddlewareWithConfig(cfg *config.Config, svc *applicationservice.Service, l config := &DefaultConfig + config.Enforcer = casbin.NewEnforcer("authorization_model.conf", "authorization_policy.csv") + config.Svc = svc config.Logger = log config.Application = cfg @@ -68,26 +72,100 @@ func setAuthorizationMiddleware(e *echo.Echo, log *logger.Logger, cfg *config.Co 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 ", err) + a.Logger.Warningf("Cannot get user details. %v\n", err) + user = viewmodel.User{} } method := c.Request().Method path := c.Request().URL.Path - //objectOrganization := a.organizationGoverningObject(c, user) + objectOrganization := a.organizationGoverningObject(c, user) + objectRole := a.roleGoverningObject(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] + } + orgRelation := organizationsRelation(currentUsersOrganization, objectOrganization) + objRelation := a.objectRelation(c, user) + + // parameters to Enforce must match the request section of the authorization model + return a.Enforcer.Enforce(currentUsersRole.Key, objectRole.Key, orgRelation, objRelation, path, method) - return a.Enforcer.Enforce(user, path, method) } +// organizationGoverningObject returns the organization 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) organizationGoverningObject(c echo.Context, userDetails viewmodel.User) (result viewmodel.Organization) { - existingUser := strings.Contains(c.Request().URL.Path, "/users") && len(c.ParamValues()) > 0 - newUser := strings.Contains(c.Request().URL.Path, "/users") && len(c.ParamValues()) <= 0 + fmt.Println("***************") + fmt.Println(c.ParamValues()) + fmt.Println("***************") + existingUser := strings.Contains(c.Request().URL.Path, "/users") && len(c.ParamValues()) > 1 + newUser := strings.Contains(c.Request().URL.Path, "/users") && len(c.ParamValues()) <= 1 switch { case existingUser: - user, _ := a.Svc.Users.GetByUUID(c.ParamValues()[0], "") + user, _ := a.Svc.Users.GetByUUID(c.ParamValues()[1], "") result = user.Organizations[0] - case newUser: + case newUser && len(userDetails.Organizations) > 0: result = userDetails.Organizations[0] } return } + +// 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) roleGoverningObject(c echo.Context, userDetails viewmodel.User) (result viewmodel.Profile) { + + existingUser := strings.Contains(c.Request().URL.Path, "/users") && len(c.ParamValues()) > 1 + newUser := strings.Contains(c.Request().URL.Path, "/users") && len(c.ParamValues()) <= 1 + + switch { + case existingUser: + user, _ := a.Svc.Users.GetByUUID(c.ParamValues()[1], "") + result = user.Profiles[0] + case newUser && len(userDetails.Organizations) > 0: + result = userDetails.Profiles[0] + } + return +} + +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(c echo.Context, userDetails viewmodel.User) string { + + existingUser := strings.Contains(c.Request().URL.Path, "/users") && len(c.ParamValues()) > 1 + + switch { + case existingUser: + user, _ := a.Svc.Users.GetByUUID(c.ParamValues()[1], "") + if user.ID == userDetails.ID { + return "[self]" + } + } + return "[other]" +} diff --git a/server/serverconfig/serverconfig.go b/server/serverconfig/serverconfig.go index 0d3be75..6b34b93 100644 --- a/server/serverconfig/serverconfig.go +++ b/server/serverconfig/serverconfig.go @@ -1,6 +1,7 @@ package serverconfig import ( + "bitbucket.org/nemt/nemt-portal-api/application/applicationservice" "bitbucket.org/nemt/nemt-portal-api/domain/service" "bitbucket.org/nemt/nemt-portal-api/infra/config" "bitbucket.org/nemt/nemt-portal-api/infra/errors" @@ -9,7 +10,7 @@ import ( ) // SetMiddlewares attaches middlewares to server -func SetMiddlewares(server *echo.Echo, cfg *config.Config, log *logger.Logger, svc *service.Service) error { +func SetMiddlewares(server *echo.Echo, cfg *config.Config, log *logger.Logger, svc *service.Service, appsvc *applicationservice.Service) error { setRecoverMiddleware(server) setGZIPMiddleware(server) setRequestIDMiddleware(server) @@ -17,7 +18,7 @@ func SetMiddlewares(server *echo.Echo, cfg *config.Config, log *logger.Logger, s setCORSMiddleware(server, cfg) setBodyLimitMiddleware(server) setRateLimitMiddleware(server) - //setAuthorizationMiddleware(server, log, svc) + setAuthorizationMiddleware(server, log, cfg, appsvc) err := setJWTMiddleware(server, cfg) if err != nil {