From ec668f689a94d0e92084f7cbff8b9d8bd4367620 Mon Sep 17 00:00:00 2001 From: Kien Nguyen-Tuan Date: Mon, 22 Jun 2020 16:27:06 +0700 Subject: [PATCH] Implement multiple users with policies (#100) * Update getting-started * [WIP] Add RBAC proposal * [WIP] Add RBAC proposal * Update Config struct - Add JWT Configuration to prepare rework authentication & authorization. - Remove GlobalConfig. * Init a new Auth module * Update getting-started * Update Config struct - Add JWT Configuration to prepare rework authentication & authorization. - Remove GlobalConfig. * Init a new Auth module * Nit:Remove return * WIP Init user logic * Add bcrypt functions * Init a new Authentication mechanism Using jwt-auth lib [1], there are two authentication options now: - Cookie. - Bearer tokens. [1] https://github.com/adam-hanna/jwt-auth * Update rbac proposal * Update go.mod * Use ntk148v/jwt-auth module * Revert disable CSRF * Move signout to restricted route * Grant role with valid permissions when creating user * Fix wrong HTTP status code in error response HTTP status code always be 200 OK even if there is an error response due to missing the override header logic. * Rename methods * Don't create Etcd user * Use ntk148v/jwt-middleware jwt-auth returns an access_token, refresh_token & csrf string. It seems be too much in our case, just keep it simple as much as possible. jwt-middleware allows to set the custom content in request context. It would be useful if there is other middlewares suppose to use this data. * Create admin user * Update getting-started * [WIP] Add RBAC proposal * Update Config struct - Add JWT Configuration to prepare rework authentication & authorization. - Remove GlobalConfig. * Init a new Auth module * Nit:Remove return * Update getting-started * WIP Init user logic * Add bcrypt functions * Init a new Authentication mechanism Using jwt-auth lib [1], there are two authentication options now: - Cookie. - Bearer tokens. [1] https://github.com/adam-hanna/jwt-auth * Update rbac proposal * Update go.mod * Use ntk148v/jwt-auth module * Revert disable CSRF * Move signout to restricted route * Grant role with valid permissions when creating user * Fix wrong HTTP status code in error response HTTP status code always be 200 OK even if there is an error response due to missing the override header logic. * Rename methods * Don't create Etcd user * Use ntk148v/jwt-middleware jwt-auth returns an access_token, refresh_token & csrf string. It seems be too much in our case, just keep it simple as much as possible. jwt-middleware allows to set the custom content in request context. It would be useful if there is other middlewares suppose to use this data. * Create admin user * Update vendor * Add Casbin policy engine for authorization Use an authorization library Casbin [1] [1] https://github.com/casbin/casbin * Create users API is restricted * If this is an existing user, 400 is more suitable than 401 * Create users and token submodules * Trim the key(s) - Without trim, the key path may include double sequential slash '/', for example: 'test//users'. - The etcd namespace should end with slash '/'. Here is the workaround, without it, the key path will be ``. * Include only the username * Add Authorizer middleware Authorizer is a middleware checks whether the user is allowed to perform the request. It uses Casbin Enforcer as the Policy Engine. * Add LICENSE header * Update method name * Force reload the policies before enforcing * Add RemoveUser handler * Add policy handlers - AddPolicy & RemovePolicy - TODO - ListPolicies * Update routing * Remove etcd-adapter v1.1.0 * Update proposal * Add listUsers API * Package-scope, not public * Use request's body to add/remove policies Using body instead of form is more flexible, allow user to add/remove more than one policy at once. * Update docs * Update vendor * Remove unknown characters * Update docs * Allow user to configure bcrypt cost * Remove redundant middlewares * Return status code 401 Follow: https://i.stack.imgur.com/ppsbq.jpg * Update api/token.go Co-authored-by: vtdat * Raise JWT TTL to 60m * Allow everyone to view clouds * Remove user in etcd * Remove redundant block * Revert trim path Co-authored-by: vtdat --- api/api.go | 128 +++- api/auth.go | 65 -- api/author_model.conf | 11 + api/policy.go | 135 ++++ api/token.go | 85 ++ api/users.go | 229 ++++++ cmd/faythe/main.go | 18 +- config/config.go | 129 ++-- docs/README.md | 7 +- docs/authn-authz.md | 146 ++++ docs/proposal/authn-authz.md | 89 +++ examples/faythe.yml | 43 +- examples/keys/faythe.rsa | 27 + examples/keys/faythe.rsa.pub | 0 go.mod | 7 +- go.sum | 12 +- middleware/middleware.go | 81 +- pkg/cluster/cluster.go | 2 +- pkg/common/utils.go | 16 + pkg/model/const.go | 7 + pkg/model/policy.go | 22 + pkg/model/user.go | 35 + vendor/github.com/Knetic/govaluate/.gitignore | 28 + .../github.com/Knetic/govaluate/.travis.yml | 10 + .../github.com/Knetic/govaluate/CONTRIBUTORS | 15 + .../Knetic/govaluate/EvaluableExpression.go | 276 +++++++ .../govaluate/EvaluableExpression_sql.go | 167 ++++ .../Knetic/govaluate/ExpressionToken.go | 9 + vendor/github.com/Knetic/govaluate/LICENSE | 21 + vendor/github.com/Knetic/govaluate/MANUAL.md | 176 +++++ .../Knetic/govaluate/OperatorSymbol.go | 309 ++++++++ vendor/github.com/Knetic/govaluate/README.md | 233 ++++++ .../github.com/Knetic/govaluate/TokenKind.go | 75 ++ .../Knetic/govaluate/evaluationStage.go | 516 +++++++++++++ .../Knetic/govaluate/expressionFunctions.go | 8 + .../govaluate/expressionOutputStream.go | 46 ++ .../github.com/Knetic/govaluate/lexerState.go | 373 +++++++++ .../Knetic/govaluate/lexerStream.go | 39 + .../github.com/Knetic/govaluate/parameters.go | 32 + vendor/github.com/Knetic/govaluate/parsing.go | 526 +++++++++++++ .../Knetic/govaluate/sanitizedParameters.go | 43 ++ .../Knetic/govaluate/stagePlanner.go | 724 ++++++++++++++++++ vendor/github.com/Knetic/govaluate/test.sh | 33 + .../Knetic/govaluate/tokenStream.go | 36 + vendor/github.com/casbin/casbin/v2/.gitignore | 30 + .../casbin/casbin/v2/.releaserc.json | 13 + .../github.com/casbin/casbin/v2/.travis.yml | 14 + .../casbin/casbin/v2/CONTRIBUTING.md | 37 + vendor/github.com/casbin/casbin/v2/LICENSE | 201 +++++ vendor/github.com/casbin/casbin/v2/README.md | 278 +++++++ .../casbin/casbin/v2/casbin-logo.png | Bin 0 -> 34267 bytes .../casbin/casbin/v2/config/config.go | 274 +++++++ .../casbin/v2/effect/default_effector.go | 80 ++ .../casbin/casbin/v2/effect/effector.go | 31 + .../github.com/casbin/casbin/v2/enforcer.go | 617 +++++++++++++++ .../casbin/casbin/v2/enforcer_cached.go | 95 +++ .../casbin/casbin/v2/enforcer_interface.go | 130 ++++ .../casbin/casbin/v2/enforcer_synced.go | 370 +++++++++ .../casbin/casbin/v2/errors/rbac_errors.go | 24 + vendor/github.com/casbin/casbin/v2/go.mod | 5 + vendor/github.com/casbin/casbin/v2/go.sum | 2 + .../casbin/casbin/v2/internal_api.go | 194 +++++ .../casbin/casbin/v2/log/default_logger.go | 42 + .../casbin/casbin/v2/log/log_util.go | 37 + .../github.com/casbin/casbin/v2/log/logger.go | 30 + .../casbin/casbin/v2/management_api.go | 298 +++++++ .../casbin/casbin/v2/model/assertion.go | 87 +++ .../casbin/casbin/v2/model/function.go | 43 ++ .../casbin/casbin/v2/model/model.go | 175 +++++ .../casbin/casbin/v2/model/policy.go | 218 ++++++ .../casbin/casbin/v2/persist/adapter.go | 55 ++ .../casbin/v2/persist/adapter_filtered.go | 29 + .../casbin/casbin/v2/persist/batch_adapter.go | 26 + .../casbin/v2/persist/file-adapter/adapter.go | 134 ++++ .../persist/file-adapter/adapter_filtered.go | 138 ++++ .../v2/persist/file-adapter/adapter_mock.go | 110 +++ .../casbin/casbin/v2/persist/watcher.go | 29 + .../casbin/casbin/v2/persist/watcher_ex.go | 34 + .../rbac/default-role-manager/role_manager.go | 317 ++++++++ .../casbin/casbin/v2/rbac/role_manager.go | 38 + .../github.com/casbin/casbin/v2/rbac_api.go | 252 ++++++ .../casbin/casbin/v2/rbac_api_synced.go | 122 +++ .../casbin/casbin/v2/rbac_api_with_domains.go | 44 ++ .../casbin/v2/rbac_api_with_domains_synced.go | 52 ++ .../casbin/v2/util/builtin_operators.go | 271 +++++++ .../github.com/casbin/casbin/v2/util/util.go | 173 +++++ .../dgrijalva/jwt-go/request/doc.go | 7 + .../dgrijalva/jwt-go/request/extractor.go | 81 ++ .../dgrijalva/jwt-go/request/oauth2.go | 28 + .../dgrijalva/jwt-go/request/request.go | 68 ++ vendor/github.com/gorilla/mux/README.md | 91 ++- vendor/github.com/gorilla/mux/context.go | 18 - vendor/github.com/gorilla/mux/go.mod | 2 + vendor/github.com/gorilla/mux/middleware.go | 25 +- vendor/github.com/gorilla/mux/mux.go | 28 +- vendor/github.com/gorilla/mux/regexp.go | 65 +- vendor/github.com/gorilla/mux/route.go | 38 +- vendor/github.com/gorilla/mux/test_helpers.go | 2 +- .../ntk148v/etcd-adapter/.gitignore | 17 + .../ntk148v/etcd-adapter/.travis.yml | 20 + .../github.com/ntk148v/etcd-adapter/LICENSE | 201 +++++ .../github.com/ntk148v/etcd-adapter/README.md | 32 + .../ntk148v/etcd-adapter/adapter.go | 373 +++++++++ .../ntk148v/etcd-adapter/docker-compose.yml | 16 + vendor/github.com/ntk148v/etcd-adapter/go.mod | 8 + vendor/github.com/ntk148v/etcd-adapter/go.sum | 184 +++++ .../ntk148v/jwt-middleware/.gitignore | 19 + .../github.com/ntk148v/jwt-middleware/LICENSE | 201 +++++ .../ntk148v/jwt-middleware/README.md | 38 + .../ntk148v/jwt-middleware/errors.go | 33 + .../github.com/ntk148v/jwt-middleware/go.mod | 8 + .../github.com/ntk148v/jwt-middleware/go.sum | 4 + .../ntk148v/jwt-middleware/middleware.go | 45 ++ .../ntk148v/jwt-middleware/options.go | 31 + .../ntk148v/jwt-middleware/store.go | 25 + .../ntk148v/jwt-middleware/token.go | 279 +++++++ vendor/golang.org/x/crypto/AUTHORS | 3 + vendor/golang.org/x/crypto/CONTRIBUTORS | 3 + vendor/golang.org/x/crypto/LICENSE | 27 + vendor/golang.org/x/crypto/PATENTS | 22 + vendor/golang.org/x/crypto/bcrypt/base64.go | 35 + vendor/golang.org/x/crypto/bcrypt/bcrypt.go | 295 +++++++ vendor/golang.org/x/crypto/blowfish/block.go | 159 ++++ vendor/golang.org/x/crypto/blowfish/cipher.go | 99 +++ vendor/golang.org/x/crypto/blowfish/const.go | 199 +++++ vendor/modules.txt | 24 +- 126 files changed, 12681 insertions(+), 310 deletions(-) delete mode 100644 api/auth.go create mode 100644 api/author_model.conf create mode 100644 api/policy.go create mode 100644 api/token.go create mode 100644 api/users.go create mode 100644 docs/authn-authz.md create mode 100644 docs/proposal/authn-authz.md create mode 100644 examples/keys/faythe.rsa create mode 100644 examples/keys/faythe.rsa.pub create mode 100644 pkg/model/policy.go create mode 100644 pkg/model/user.go create mode 100644 vendor/github.com/Knetic/govaluate/.gitignore create mode 100644 vendor/github.com/Knetic/govaluate/.travis.yml create mode 100644 vendor/github.com/Knetic/govaluate/CONTRIBUTORS create mode 100644 vendor/github.com/Knetic/govaluate/EvaluableExpression.go create mode 100644 vendor/github.com/Knetic/govaluate/EvaluableExpression_sql.go create mode 100644 vendor/github.com/Knetic/govaluate/ExpressionToken.go create mode 100644 vendor/github.com/Knetic/govaluate/LICENSE create mode 100644 vendor/github.com/Knetic/govaluate/MANUAL.md create mode 100644 vendor/github.com/Knetic/govaluate/OperatorSymbol.go create mode 100644 vendor/github.com/Knetic/govaluate/README.md create mode 100644 vendor/github.com/Knetic/govaluate/TokenKind.go create mode 100644 vendor/github.com/Knetic/govaluate/evaluationStage.go create mode 100644 vendor/github.com/Knetic/govaluate/expressionFunctions.go create mode 100644 vendor/github.com/Knetic/govaluate/expressionOutputStream.go create mode 100644 vendor/github.com/Knetic/govaluate/lexerState.go create mode 100644 vendor/github.com/Knetic/govaluate/lexerStream.go create mode 100644 vendor/github.com/Knetic/govaluate/parameters.go create mode 100644 vendor/github.com/Knetic/govaluate/parsing.go create mode 100644 vendor/github.com/Knetic/govaluate/sanitizedParameters.go create mode 100644 vendor/github.com/Knetic/govaluate/stagePlanner.go create mode 100644 vendor/github.com/Knetic/govaluate/test.sh create mode 100644 vendor/github.com/Knetic/govaluate/tokenStream.go create mode 100644 vendor/github.com/casbin/casbin/v2/.gitignore create mode 100644 vendor/github.com/casbin/casbin/v2/.releaserc.json create mode 100644 vendor/github.com/casbin/casbin/v2/.travis.yml create mode 100644 vendor/github.com/casbin/casbin/v2/CONTRIBUTING.md create mode 100644 vendor/github.com/casbin/casbin/v2/LICENSE create mode 100644 vendor/github.com/casbin/casbin/v2/README.md create mode 100644 vendor/github.com/casbin/casbin/v2/casbin-logo.png create mode 100644 vendor/github.com/casbin/casbin/v2/config/config.go create mode 100644 vendor/github.com/casbin/casbin/v2/effect/default_effector.go create mode 100644 vendor/github.com/casbin/casbin/v2/effect/effector.go create mode 100644 vendor/github.com/casbin/casbin/v2/enforcer.go create mode 100644 vendor/github.com/casbin/casbin/v2/enforcer_cached.go create mode 100644 vendor/github.com/casbin/casbin/v2/enforcer_interface.go create mode 100644 vendor/github.com/casbin/casbin/v2/enforcer_synced.go create mode 100644 vendor/github.com/casbin/casbin/v2/errors/rbac_errors.go create mode 100644 vendor/github.com/casbin/casbin/v2/go.mod create mode 100644 vendor/github.com/casbin/casbin/v2/go.sum create mode 100644 vendor/github.com/casbin/casbin/v2/internal_api.go create mode 100644 vendor/github.com/casbin/casbin/v2/log/default_logger.go create mode 100644 vendor/github.com/casbin/casbin/v2/log/log_util.go create mode 100644 vendor/github.com/casbin/casbin/v2/log/logger.go create mode 100644 vendor/github.com/casbin/casbin/v2/management_api.go create mode 100644 vendor/github.com/casbin/casbin/v2/model/assertion.go create mode 100644 vendor/github.com/casbin/casbin/v2/model/function.go create mode 100644 vendor/github.com/casbin/casbin/v2/model/model.go create mode 100644 vendor/github.com/casbin/casbin/v2/model/policy.go create mode 100644 vendor/github.com/casbin/casbin/v2/persist/adapter.go create mode 100644 vendor/github.com/casbin/casbin/v2/persist/adapter_filtered.go create mode 100644 vendor/github.com/casbin/casbin/v2/persist/batch_adapter.go create mode 100644 vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter.go create mode 100644 vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter_filtered.go create mode 100644 vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter_mock.go create mode 100644 vendor/github.com/casbin/casbin/v2/persist/watcher.go create mode 100644 vendor/github.com/casbin/casbin/v2/persist/watcher_ex.go create mode 100644 vendor/github.com/casbin/casbin/v2/rbac/default-role-manager/role_manager.go create mode 100644 vendor/github.com/casbin/casbin/v2/rbac/role_manager.go create mode 100644 vendor/github.com/casbin/casbin/v2/rbac_api.go create mode 100644 vendor/github.com/casbin/casbin/v2/rbac_api_synced.go create mode 100644 vendor/github.com/casbin/casbin/v2/rbac_api_with_domains.go create mode 100644 vendor/github.com/casbin/casbin/v2/rbac_api_with_domains_synced.go create mode 100644 vendor/github.com/casbin/casbin/v2/util/builtin_operators.go create mode 100644 vendor/github.com/casbin/casbin/v2/util/util.go create mode 100644 vendor/github.com/dgrijalva/jwt-go/request/doc.go create mode 100644 vendor/github.com/dgrijalva/jwt-go/request/extractor.go create mode 100644 vendor/github.com/dgrijalva/jwt-go/request/oauth2.go create mode 100644 vendor/github.com/dgrijalva/jwt-go/request/request.go delete mode 100644 vendor/github.com/gorilla/mux/context.go create mode 100644 vendor/github.com/ntk148v/etcd-adapter/.gitignore create mode 100644 vendor/github.com/ntk148v/etcd-adapter/.travis.yml create mode 100644 vendor/github.com/ntk148v/etcd-adapter/LICENSE create mode 100644 vendor/github.com/ntk148v/etcd-adapter/README.md create mode 100644 vendor/github.com/ntk148v/etcd-adapter/adapter.go create mode 100644 vendor/github.com/ntk148v/etcd-adapter/docker-compose.yml create mode 100644 vendor/github.com/ntk148v/etcd-adapter/go.mod create mode 100644 vendor/github.com/ntk148v/etcd-adapter/go.sum create mode 100644 vendor/github.com/ntk148v/jwt-middleware/.gitignore create mode 100644 vendor/github.com/ntk148v/jwt-middleware/LICENSE create mode 100644 vendor/github.com/ntk148v/jwt-middleware/README.md create mode 100644 vendor/github.com/ntk148v/jwt-middleware/errors.go create mode 100644 vendor/github.com/ntk148v/jwt-middleware/go.mod create mode 100644 vendor/github.com/ntk148v/jwt-middleware/go.sum create mode 100644 vendor/github.com/ntk148v/jwt-middleware/middleware.go create mode 100644 vendor/github.com/ntk148v/jwt-middleware/options.go create mode 100644 vendor/github.com/ntk148v/jwt-middleware/store.go create mode 100644 vendor/github.com/ntk148v/jwt-middleware/token.go create mode 100644 vendor/golang.org/x/crypto/AUTHORS create mode 100644 vendor/golang.org/x/crypto/CONTRIBUTORS create mode 100644 vendor/golang.org/x/crypto/LICENSE create mode 100644 vendor/golang.org/x/crypto/PATENTS create mode 100644 vendor/golang.org/x/crypto/bcrypt/base64.go create mode 100644 vendor/golang.org/x/crypto/bcrypt/bcrypt.go create mode 100644 vendor/golang.org/x/crypto/blowfish/block.go create mode 100644 vendor/golang.org/x/crypto/blowfish/cipher.go create mode 100644 vendor/golang.org/x/crypto/blowfish/const.go diff --git a/api/api.go b/api/api.go index 136d6a25..7c5c254e 100644 --- a/api/api.go +++ b/api/api.go @@ -20,85 +20,136 @@ import ( "net/http/pprof" "time" + "github.com/casbin/casbin/v2" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/gorilla/mux" + "github.com/jinzhu/copier" + etcdadapter "github.com/ntk148v/etcd-adapter" + "github.com/ntk148v/jwt-middleware" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus/promhttp" + etcdv3 "go.etcd.io/etcd/clientv3" "github.com/vCloud-DFTBA/faythe/config" + "github.com/vCloud-DFTBA/faythe/middleware" + "github.com/vCloud-DFTBA/faythe/pkg/cluster" "github.com/vCloud-DFTBA/faythe/pkg/common" + "github.com/vCloud-DFTBA/faythe/pkg/model" ) // API provides registration of handlers for API routes type API struct { - logger log.Logger - uptime time.Time - etcdcli *common.Etcd + logger log.Logger + uptime time.Time + etcdcli *common.Etcd + jwtToken *jwt.Token + policyEngine *casbin.Enforcer } // New returns a new API. -func New(l log.Logger, e *common.Etcd) *API { +func New(l log.Logger, e *common.Etcd) (*API, error) { + jwtCfg := config.Get().JWTConfig + token, err := jwt.NewToken(jwt.Options{ + SigningMethod: jwtCfg.SigningMethod, + PrivateKeyLocation: jwtCfg.PrivateKeyLocation, + PublicKeyLocation: jwtCfg.PublicKeyLocation, + IsBearerToken: jwtCfg.IsBearerToken, + UserProperty: jwtCfg.UserProperty, + TTL: jwtCfg.TTL, + }, nil) + + if err != nil { + // Exit immediately + return nil, errors.Wrapf(err, "Error initializing the JWT instance") + } if l == nil { l = log.NewNopLogger() } - return &API{ - logger: l, - uptime: time.Now(), - etcdcli: e, + // Init Policy engine + var etcdCfg etcdv3.Config + copier.Copy(&etcdCfg, config.Get().EtcdConfig) + adapter := etcdadapter.NewAdapter(etcdCfg, cluster.ClusterID, model.DefaultPoliciesPrefix) + // NOTE(kiennt): Hardcode here, don't allow users to configure + // the Casbin model themselves. Cannot handle at this time. + policyEngine, err := casbin.NewEnforcer("api/author_model.conf", adapter) + + a := &API{ + logger: l, + uptime: time.Now(), + etcdcli: e, + jwtToken: token, + policyEngine: policyEngine, + } + // Create an admin user & grant permissions + if err = a.createAdminUser(); err != nil { + return nil, errors.Wrap(err, "Error creating admin user") } + return a, nil } -// RegisterPublicRouter registers the Authentication API which has no -// Authentication middleware -func (a *API) RegisterPublicRouter(r *mux.Router) { - r.HandleFunc("/login", a.getToken).Methods("OPTIONS", "GET") - +// Register registers the API handlers under their correct routes +// in the given router. +func (a *API) Register(r *mux.Router) { + mw := middleware.New(log.With(a.logger, "component", "transport middleware")) + // General middleware + r.Use(mw.Instrument, mw.Logging, mw.RestrictDomain, mw.HandleCors) + // Unrestricted subrouter + pubr := r.PathPrefix("/public").Subrouter() + pubr.HandleFunc("/tokens", a.issueToken).Methods("OPTIONS", "POST") // Prometheus golang metrics - r.Handle("/metrics", promhttp.Handler()).Methods("GET") + pubr.Handle("/metrics", promhttp.Handler()).Methods("GET") - r.HandleFunc("/", a.index).Methods("GET") - r.HandleFunc("/status", a.status).Methods("GET") + pubr.HandleFunc("/", a.index).Methods("GET") + pubr.HandleFunc("/status", a.status).Methods("GET") // Profiling endpoints - cfg := config.Get().GlobalConfig + cfg := config.Get() if cfg.EnableProfiling { - r.HandleFunc("/debug/pprof/", pprof.Index) - r.Handle("/debug/pprof/{profile}", http.DefaultServeMux) + pubr.HandleFunc("/debug/pprof/", pprof.Index) + pubr.Handle("/debug/pprof/{profile}", http.DefaultServeMux) } -} -// Register registers the API handlers under their correct routes -// in the given router. -func (a *API) Register(r *mux.Router) { + // Restricted subrouter + resr := r.PathPrefix("/").Subrouter() + resr.Use(jwt.Authenticator(a.jwtToken), middleware.Authorizer(a.policyEngine)) + + // User endpoints + resr.HandleFunc("/users", a.addUser).Methods("OPTIONS", "POST") + resr.HandleFunc("/users/{user:[a-z 0-9]+}", a.removeUser).Methods("OPTIONS", "DELETE") + resr.HandleFunc("/users", a.listUsers).Methods("OPTIONS", "GET") + + // Policy endpoints + resr.HandleFunc("/policies/{user:[a-z 0-9]+}", a.addPolicies).Methods("OPTIONS", "POST") + resr.HandleFunc("/policies/{user:[a-z 0-9]+}", a.removePolicies).Methods("OPTIONS", "DELETE") // Cloud endpoints - r.HandleFunc("/clouds/{provider}", a.registerCloud).Methods("OPTIONS", "POST") - r.HandleFunc("/clouds", a.listClouds).Methods("OPTIONS", "GET") - r.HandleFunc("/clouds/{id:[a-z 0-9]+}", a.unregisterCloud).Methods("OPTIONS", "DELETE") - r.HandleFunc("/clouds/{id:[a-z 0-9]+}", a.updateCloud).Methods("OPTIONS", "PUT") + resr.HandleFunc("/clouds/{provider}", a.registerCloud).Methods("OPTIONS", "POST") + resr.HandleFunc("/clouds", a.listClouds).Methods("OPTIONS", "GET") + resr.HandleFunc("/clouds/{id:[a-z 0-9]+}", a.unregisterCloud).Methods("OPTIONS", "DELETE") + resr.HandleFunc("/clouds/{id:[a-z 0-9]+}", a.updateCloud).Methods("OPTIONS", "PUT") // Scaler endpoints - r.HandleFunc("/scalers/{provider_id:[a-z 0-9]+}", a.createScaler).Methods("OPTIONS", "POST") - r.HandleFunc("/scalers/{provider_id:[a-z 0-9]+}", a.listScalers).Methods("OPTIONS", "GET") - r.HandleFunc("/scalers/{provider_id:[a-z 0-9]+}/{id:[a-z 0-9]+}", + resr.HandleFunc("/scalers/{provider_id:[a-z 0-9]+}", a.createScaler).Methods("OPTIONS", "POST") + resr.HandleFunc("/scalers/{provider_id:[a-z 0-9]+}", a.listScalers).Methods("OPTIONS", "GET") + resr.HandleFunc("/scalers/{provider_id:[a-z 0-9]+}/{id:[a-z 0-9]+}", a.deleteScaler).Methods("OPTIONS", "DELETE") - r.HandleFunc("/scalers/{provider_id:[a-z 0-9]+}/{id:[a-z 0-9]+}", + resr.HandleFunc("/scalers/{provider_id:[a-z 0-9]+}/{id:[a-z 0-9]+}", a.updateScaler).Methods("OPTIONS", "PUT") // Name Resolver endpoints - r.HandleFunc("/nresolvers", a.listNResolvers).Methods("OPTIONS", "GET") + resr.HandleFunc("/nresolvers", a.listNResolvers).Methods("OPTIONS", "GET") // Healer endpoints - r.HandleFunc("/healers/{provider_id:[a-z 0-9]+}", a.createHealer).Methods("OPTIONS", "POST") - r.HandleFunc("/healers/{provider_id:[a-z 0-9]+}", a.listHealers).Methods("OPTIONS", "GET") - r.HandleFunc("/healers/{provider_id:[a-z 0-9]+}/{id:[a-z 0-9]+}", + resr.HandleFunc("/healers/{provider_id:[a-z 0-9]+}", a.createHealer).Methods("OPTIONS", "POST") + resr.HandleFunc("/healers/{provider_id:[a-z 0-9]+}", a.listHealers).Methods("OPTIONS", "GET") + resr.HandleFunc("/healers/{provider_id:[a-z 0-9]+}/{id:[a-z 0-9]+}", a.deleteHealer).Methods("OPTIONS", "DELETE") // Silences endpoints - r.HandleFunc("/silences/{provider_id:[a-z 0-9]+}", a.createSilence).Methods("OPTIONS", "POST") - r.HandleFunc("/silences/{provider_id:[a-z 0-9]+}", a.listSilences).Methods("OPTIONS", "GET") - r.HandleFunc("/silences/{provider_id:[a-z 0-9]+}/{id:[a-z 0-9]+}", a.expireSilence).Methods("OPTIONS", "DELETE") + resr.HandleFunc("/silences/{provider_id:[a-z 0-9]+}", a.createSilence).Methods("OPTIONS", "POST") + resr.HandleFunc("/silences/{provider_id:[a-z 0-9]+}", a.listSilences).Methods("OPTIONS", "GET") + resr.HandleFunc("/silences/{provider_id:[a-z 0-9]+}/{id:[a-z 0-9]+}", a.expireSilence).Methods("OPTIONS", "DELETE") } func (a *API) receive(req *http.Request, v interface{}) error { @@ -124,7 +175,6 @@ func (a *API) respondError(w http.ResponseWriter, e apiError) { if err != nil { level.Error(a.logger).Log("msg", "Error marshalling JSON", "err", err) http.Error(w, err.Error(), http.StatusInternalServerError) - return } else { if _, err := w.Write(b); err != nil { level.Error(a.logger).Log("msg", "Failed to write data to connection", "err", err) diff --git a/api/auth.go b/api/auth.go deleted file mode 100644 index c7cc325c..00000000 --- a/api/auth.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2020 Dat Vu Tuan -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package api - -import ( - "fmt" - "net/http" - "time" - - "github.com/dgrijalva/jwt-go" - - "github.com/vCloud-DFTBA/faythe/config" -) - -const TokenExpirationTime = 60 - -func (a *API) getToken(rw http.ResponseWriter, req *http.Request) { - user, pass, _ := req.BasicAuth() - creds := config.Get().GlobalConfig.BasicAuthentication - - if creds.Password != pass || creds.Username != user { - a.respondError(rw, apiError{ - code: http.StatusUnauthorized, - err: fmt.Errorf("invalid user or password"), - }) - return - } - - expTime := time.Now().Add(TokenExpirationTime * time.Minute) - - claims := jwt.StandardClaims{ExpiresAt: expTime.Unix()} - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - tokenString, err := token.SignedString([]byte(creds.SecretKey)) - - if err != nil { - a.respondError(rw, apiError{ - code: http.StatusInternalServerError, - err: err, - }) - return - } - - http.SetCookie(rw, &http.Cookie{ - Name: "api-token", - Value: tokenString, - Path: "/", - Expires: expTime, - HttpOnly: true, - }) - - a.respondSuccess(rw, http.StatusOK, "") - return -} diff --git a/api/author_model.conf b/api/author_model.conf new file mode 100644 index 00000000..4f86ba8f --- /dev/null +++ b/api/author_model.conf @@ -0,0 +1,11 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) \ No newline at end of file diff --git a/api/policy.go b/api/policy.go new file mode 100644 index 00000000..37fae7e5 --- /dev/null +++ b/api/policy.go @@ -0,0 +1,135 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "crypto" + "net/http" + + "github.com/gorilla/mux" + "github.com/pkg/errors" + + "github.com/vCloud-DFTBA/faythe/pkg/common" + "github.com/vCloud-DFTBA/faythe/pkg/model" +) + +// addPolicies allows to add more than one policys at once. +func (a *API) addPolicies(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + user := vars["user"] + // Check an user is existing. + path := common.Path(model.DefaultUsersPrefix, common.Hash(user, crypto.MD5)) + resp, err := a.etcdcli.DoGet(path) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + if len(resp.Kvs) == 0 { + a.respondError(w, apiError{ + code: http.StatusBadRequest, + err: errors.New("Unknown user"), + }) + return + } + + var ( + pols model.Polices + rules [][]string + ) + if err := a.receive(req, &pols); err != nil { + a.respondError(w, apiError{ + code: http.StatusBadRequest, + err: err, + }) + return + } + for _, p := range pols { + rules = append(rules, []string{user, p.Path, p.Method}) + } + // Add new policy to Etcd + if _, err := a.policyEngine.AddPolicies(rules); err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + if err := a.policyEngine.SavePolicy(); err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + a.respondSuccess(w, http.StatusOK, nil) + return +} + +// removePolicies allows to remove more than one policys at once. +func (a *API) removePolicies(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + user := vars["user"] + // Check an user is existing. + path := common.Path(model.DefaultUsersPrefix, common.Hash(user, crypto.MD5)) + resp, err := a.etcdcli.DoGet(path) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + if len(resp.Kvs) == 0 { + a.respondError(w, apiError{ + code: http.StatusBadRequest, + err: errors.New("Unknown user"), + }) + return + } + var ( + pols model.Polices + rules [][]string + ) + if err := a.receive(req, &pols); err != nil { + a.respondError(w, apiError{ + code: http.StatusBadRequest, + err: err, + }) + return + } + for _, p := range pols { + rules = append(rules, []string{user, p.Path, p.Method}) + } + // Remove policy from Etcd + if _, err := a.policyEngine.RemovePolicies(rules); err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + if err := a.policyEngine.SavePolicy(); err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + a.respondSuccess(w, http.StatusOK, nil) + return +} diff --git a/api/token.go b/api/token.go new file mode 100644 index 00000000..31837801 --- /dev/null +++ b/api/token.go @@ -0,0 +1,85 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "crypto" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" + + "github.com/vCloud-DFTBA/faythe/pkg/common" + "github.com/vCloud-DFTBA/faythe/pkg/model" +) + +const ( + bearerFormat string = "Bearer %s" +) + +// issueToken by username password. +func (a *API) issueToken(w http.ResponseWriter, req *http.Request) { + username, password, _ := req.BasicAuth() + // Check user in Etcd + path := common.Path(model.DefaultUsersPrefix, common.Hash(username, crypto.MD5)) + resp, err := a.etcdcli.DoGet(path) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + if len(resp.Kvs) == 0 { + a.respondError(w, apiError{ + code: http.StatusUnauthorized, + err: errors.New("Invalid credentials"), + }) + return + } + // Get only the first item + user := model.User{} + _ = json.Unmarshal(resp.Kvs[0].Value, &user) + // Validate password + if ok := common.CheckPasswordAgainstHash(password, user.Password); ok { + data := make(map[string]interface{}) + data["name"] = username + + tokenString, err := a.jwtToken.GenerateToken(data) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: errors.Wrap(err, "Something went wrong"), + }) + return + } + w.Header().Set("Content-Type", "application/json") + w.Header().Add("Authorization", fmt.Sprintf(bearerFormat, tokenString)) + a.respondSuccess(w, http.StatusOK, nil) + return + } + a.respondError(w, apiError{ + code: http.StatusUnauthorized, + err: errors.New("Invalid credentials"), + }) +} + +// revokeToken revoke a token +func (a *API) revokeToken(w http.ResponseWriter, req *http.Request) { + // TODO(kiennt): + // we don't use any store now so unable to + // revoke token at this time. +} diff --git a/api/users.go b/api/users.go new file mode 100644 index 00000000..bacc01b1 --- /dev/null +++ b/api/users.go @@ -0,0 +1,229 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "crypto" + "encoding/json" + "net/http" + "sync" + + "github.com/gorilla/mux" + cmap "github.com/orcaman/concurrent-map" + "github.com/pkg/errors" + etcdv3 "go.etcd.io/etcd/clientv3" + + "github.com/vCloud-DFTBA/faythe/config" + "github.com/vCloud-DFTBA/faythe/pkg/common" + "github.com/vCloud-DFTBA/faythe/pkg/model" +) + +func (a *API) createAdminUser() error { + // Check to see if the user is already taken + authCfg := config.Get().AdminAuthentication + path := common.Path(model.DefaultUsersPrefix, common.Hash(authCfg.Username, crypto.MD5)) + resp, err := a.etcdcli.DoGet(path) + if err != nil { + return err + } + var user model.User + if len(resp.Kvs) > 0 { + // Get only the first item + _ = json.Unmarshal(resp.Kvs[0].Value, &user) + if ok := common.CheckPasswordAgainstHash(authCfg.Password, user.Password); !ok { + return errors.New("An user is existing but the given password is wrong") + } + } else { + // Do not store the plain text password, encrypt it! + hashed, err := common.GenerateBcryptHash(authCfg.Password, config.Get().PasswordHashingCost) + if err != nil { + return err + } + user.Username = authCfg.Username + user.Password = hashed + _ = user.Validate() + r, _ := json.Marshal(&user) + _, err = a.etcdcli.DoPut(path, string(r)) + if err != nil { + return err + } + } + // Add admin permissions + if ok, err := a.policyEngine.AddPolicy(authCfg.Username, "/*", "(GET)|(POST)|(DELETE)|(PUT)"); !ok || err != nil { + return err + } + return nil +} + +// addUser creates a new user +func (a *API) addUser(w http.ResponseWriter, req *http.Request) { + // NOTE(kiennt): Who can signup (create new user)? + if err := req.ParseForm(); err != nil { + a.respondError(w, apiError{ + code: http.StatusBadRequest, + err: err, + }) + return + } + username := req.Form.Get("username") + password := req.Form.Get("password") + if username == "" || password == "" { + a.respondError(w, apiError{ + code: http.StatusBadRequest, + err: errors.New("Incorrect sign up form"), + }) + return + } + + // Check to see if the user is already taken + path := common.Path(model.DefaultUsersPrefix, common.Hash(username, crypto.MD5)) + resp, err := a.etcdcli.DoGet(path) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + if len(resp.Kvs) != 0 { + a.respondError(w, apiError{ + code: http.StatusBadRequest, + err: errors.New("The username is already taken"), + }) + return + } + // Do not store the plain text password, encrypt it! + hashed, err := common.GenerateBcryptHash(password, config.Get().PasswordHashingCost) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: errors.Wrap(err, "Something went wrong"), + }) + return + } + + user := &model.User{ + Username: username, + Password: hashed, + } + _ = user.Validate() + r, _ := json.Marshal(&user) + _, err = a.etcdcli.DoPut(path, string(r)) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: errors.Wrap(err, "Unable to put a key-value pair into etcd"), + }) + return + } + // Add user permission to view clouds + if ok, err := a.policyEngine.AddPolicy(username, "/clouds", "GET"); !ok || err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: errors.Wrap(err, "Unable to add view cloud permission"), + }) + return + } + a.respondSuccess(w, http.StatusOK, nil) + return +} + +// removeUser deletes an user +func (a *API) removeUser(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + username := vars["user"] + // Check an user is existing. + path := common.Path(model.DefaultUsersPrefix, common.Hash(username, crypto.MD5)) + resp, err := a.etcdcli.DoGet(path) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + if len(resp.Kvs) == 0 { + a.respondError(w, apiError{ + code: http.StatusBadRequest, + err: errors.New("Unknown user"), + }) + return + } + _, err = a.etcdcli.DoDelete(path, etcdv3.WithPrefix()) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + // Get & remove user permissions + rules := a.policyEngine.GetFilteredPolicy(0, username) + if ok, err := a.policyEngine.RemovePolicies(rules); !ok || err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: errors.Wrap(err, "Unable to remove user associated permissions"), + }) + return + } + a.respondSuccess(w, http.StatusOK, nil) + return +} + +// listUsers returns a list of current Faythe users with associated policies. +func (a *API) listUsers(w http.ResponseWriter, req *http.Request) { + var ( + path string + users cmap.ConcurrentMap + wg sync.WaitGroup + ) + // Force reload policy to get the newest + _ = a.policyEngine.LoadPolicy() + if username := req.FormValue("name"); username != "" { + path = common.Path(model.DefaultUsersPrefix, common.Hash(username, crypto.MD5)) + } else { + path = common.Path(model.DefaultUsersPrefix) + } + resp, err := a.etcdcli.DoGet(path, etcdv3.WithPrefix(), + etcdv3.WithSort(etcdv3.SortByKey, etcdv3.SortAscend)) + if err != nil { + a.respondError(w, apiError{ + code: http.StatusInternalServerError, + err: err, + }) + return + } + users = cmap.New() + for _, ev := range resp.Kvs { + wg.Add(1) + go func(evv []byte) { + defer wg.Done() + var ( + u model.User + p [][]string + ) + _ = json.Unmarshal(evv, &u) + p = a.policyEngine.GetFilteredPolicy(0, u.Username) + for i, v := range p { + // The first element is username, so just remove it. + p[i] = v[1:] + } + users.Set(u.Username, p) + }(ev.Value) + } + wg.Wait() + a.respondSuccess(w, http.StatusOK, users) + return +} diff --git a/cmd/faythe/main.go b/cmd/faythe/main.go index 3e928fdc..fca4a7f6 100644 --- a/cmd/faythe/main.go +++ b/cmd/faythe/main.go @@ -38,13 +38,11 @@ import ( "github.com/vCloud-DFTBA/faythe/api" "github.com/vCloud-DFTBA/faythe/config" - "github.com/vCloud-DFTBA/faythe/middleware" "github.com/vCloud-DFTBA/faythe/pkg/autohealer" "github.com/vCloud-DFTBA/faythe/pkg/autoscaler" "github.com/vCloud-DFTBA/faythe/pkg/cloud/store/openstack" "github.com/vCloud-DFTBA/faythe/pkg/cluster" "github.com/vCloud-DFTBA/faythe/pkg/common" - ) func main() { @@ -98,7 +96,6 @@ func main() { etcdcfg = etcdv3.Config{} etcdcli = &common.Etcd{} router = mux.NewRouter() - fmw = &middleware.Middleware{} fapi = &api.API{} fas = &autoscaler.Manager{} cls = &cluster.Cluster{} @@ -131,16 +128,13 @@ func main() { reloadc := make(chan struct{}) go cls.Run(reloadc) - fmw = middleware.New(log.With(logger, "component", "transport middleware")) - - fapi = api.New(log.With(logger, "component", "api"), etcdcli) + fapi, err = api.New(log.With(logger, "component", "api"), etcdcli) + if err != nil { + level.Error(logger).Log("msg", errors.Wrap(err, "Error initializing API")) + os.Exit(2) + } router.StrictSlash(true) - router.Use(fmw.Instrument, fmw.Logging, fmw.RestrictDomain, fmw.HandleCors) - publicRouter := router.PathPrefix("/public").Subrouter() - fapi.RegisterPublicRouter(publicRouter) - homeRouter := router.PathPrefix("/").Subrouter() - homeRouter.Use(fmw.Authenticate) - fapi.Register(homeRouter) + fapi.Register(router) // Init autoscaler manager fas = autoscaler.NewManager(log.With(logger, "component", "autoscaler manager"), etcdcli, cls) diff --git a/config/config.go b/config/config.go index 47b70481..5a6f6721 100644 --- a/config/config.go +++ b/config/config.go @@ -19,17 +19,37 @@ import ( "fmt" "time" + "golang.org/x/crypto/bcrypt" "google.golang.org/grpc" "gopkg.in/yaml.v2" ) // Config is the top-level configuration for Faythe's config file. type Config struct { - GlobalConfig GlobalConfig `yaml:"global"` - EtcdConfig EtcdConfig `yaml:"etcd"` - MailConfig MailConfig `yaml:"mail,omitempty"` + EtcdConfig EtcdConfig `yaml:"etcd"` + JWTConfig JWTConfig `yaml:"jwt"` + MailConfig MailConfig `yaml:"mail,omitempty"` + // RemoteHostPattern can define an optional regexp pattern to be matched: + // + // - {name} matches anything until the next dot. + // + // - {name:pattern} matches the given regexp pattern. + RemoteHostPattern string `yaml:"remote_host_pattern,omitempty"` + // PasswordHashingCost is the cost to hash the user password. + // Check bcrypt for details: https://godoc.org/golang.org/x/crypto/bcrypt#pkg-constants + PasswordHashingCost int `yaml:"password_hashing_cost"` + // EnableProfiling enables profiling via web interface host:port/debug/pprof/ + EnableProfiling bool `yaml:"enable_profiling"` + AdminAuthentication AdminAuthentication `yaml:"admin_authentication"` +} + +// AdminAuthentication represents the `root/admin` user authentication +type AdminAuthentication struct { + Username string `yaml:"username"` + Password string `yaml:"password"` } +// MailConfig stores configs to setup a SNMP client. type MailConfig struct { Host string `yaml:"host"` Protocol string `yaml:"protocol"` @@ -38,26 +58,31 @@ type MailConfig struct { Password string `yaml:"password"` } -// GlobalConfig configures values that are used to config Faythe HTTP server -type GlobalConfig struct { - // RemoteHostPattern can define an optional regexp pattern to be matched: - // - // - {name} matches anything until the next dot. - // - // - {name:pattern} matches the given regexp pattern. - RemoteHostPattern string `yaml:"remote_host_pattern,omitempty"` - // BasicAuthentication - HTTP Basic authentication. - BasicAuthentication BasicAuthentication `yaml:"basic_auth,omitempty"` - // EnableProfiling enables profiling via web interface host:port/debug/pprof/ - EnableProfiling bool `yaml:"enable_profiling"` -} - -// BasicAuthentication - HTTP Basic authentication. -type BasicAuthentication struct { - // Usename, Password to implement HTTP basic authentication - Username string `yaml:"username"` - Password string `yaml:"password"` - SecretKey string `yaml:"secret_key"` +// JWTConfig is a struct for specifying JWT configuration options +// A clone of Options struct: https://github.com/adam-hanna/jwt-auth/blob/develop/jwt/auth.go#L31 +type JWTConfig struct { + // SigningMethodString is a string to define the signing method + // Valid signing methods: + // - "RS256","RS384","RS512" (RSA signing method) + // - "ES256","ES384","ES512" (ECDSA signing method) + // More details here: https://github.com/dgrijalva/jwt-go#signing-methods-and-key-types + SigningMethod string `yaml:"signing_method"` + // PrivateKeyLocation is the private key path + // $ openssl genrsa -out faythe.rsa 2048 + PrivateKeyLocation string `yaml:"private_key_location"` + // PublicKeyLocation is the public key path + // $ openssl rsa -in faythe.rsa -pubout > faythe.rsa.pub + PublicKeyLocation string `yaml:"public_key_location"` + // TTL - Token time to live + TTL time.Duration `yaml:"ttl"` + IsBearerToken bool `yaml:"is_bearer_token"` + // Header is a name of the custom request header. + // If IsBearerToken is set, the header name will be `Authorization` + // with value format `Bearer `. + Header string `yaml:"header"` + // The name of the property in the request where the user information + // from the JWT will be stored. + UserProperty string `yaml:"user_property"` } // EtcdConfig stores Etcd related configurations. @@ -115,27 +140,25 @@ type EtcdConfig struct { } const ( - etcdDefaultDialTimeout = 5 * time.Second - etcdDefaultKeepAliveTime = 5 * time.Second - etcdDefaultKeepAliveTimeOut = 6 * time.Second + etcdDefaultDialTimeout = 5 * time.Second + etcdDefaultKeepAliveTime = 5 * time.Second + etcdDefaultKeepAliveTimeOut = 6 * time.Second + jwtDefaultSigningMethod = "RS256" + jwtDefaultTTL = 60 * time.Minute + jwtDefaultIsBearerToken = true + jwtDefaultUserProperty = "user" + jwtDefaultPrivateKeyLocation = "/etc/faythe/keys/faythe.rsa" + jwtDefaultPublicKeyLocation = "/etc/faythe/keys/faythe.rsa.pub" ) var ( // DefaultConfig is the default top-level configuration. DefaultConfig = Config{ - GlobalConfig: DefaultGlobalConfig, - EtcdConfig: DefaultEtcdConfig, - } - - // DefaultGlobalConfig is the default global configuration. - DefaultGlobalConfig = GlobalConfig{ + EtcdConfig: DefaultEtcdConfig, + JWTConfig: DefaultJWTConfig, RemoteHostPattern: ".*", - BasicAuthentication: BasicAuthentication{ - Username: "admin", - Password: "admin@123", - SecretKey: "YourSecretKey", - }, EnableProfiling: false, + PasswordHashingCost: bcrypt.DefaultCost, } // DefaultEtcdConfig is the default Etcd configuration. @@ -146,6 +169,16 @@ var ( DialKeepAliveTimeout: etcdDefaultKeepAliveTimeOut, DialOptions: []grpc.DialOption{grpc.WithBlock()}, } + + // DefaultJWTConfig is the default JWT configuration. + DefaultJWTConfig = JWTConfig{ + SigningMethod: jwtDefaultSigningMethod, + TTL: jwtDefaultTTL, + IsBearerToken: jwtDefaultIsBearerToken, + UserProperty: jwtDefaultUserProperty, + PrivateKeyLocation: jwtDefaultPrivateKeyLocation, + PublicKeyLocation: jwtDefaultPublicKeyLocation, + } ) // UnmarshalYAML implements the yaml.Unmarshaler interface @@ -171,12 +204,12 @@ func (c *Config) String() string { } // UnmarshalYAML implements the yaml.Unmarshaler interface -func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - *c = DefaultGlobalConfig +func (c *EtcdConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + *c = DefaultEtcdConfig // We want to set c to the defaults and then overwrite it with the input. // To make unmarshal fill the plain data struct rather than calling UnmarshalYAML // again, we have to hide it using a type indirection. - type plain GlobalConfig + type plain EtcdConfig if err := unmarshal((*plain)(c)); err != nil { return err } @@ -184,12 +217,8 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { } // UnmarshalYAML implements the yaml.Unmarshaler interface -func (c *EtcdConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - *c = DefaultEtcdConfig - // We want to set c to the defaults and then overwrite it with the input. - // To make unmarshal fill the plain data struct rather than calling UnmarshalYAML - // again, we have to hide it using a type indirection. - type plain EtcdConfig +func (c *MailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain MailConfig if err := unmarshal((*plain)(c)); err != nil { return err } @@ -197,8 +226,12 @@ func (c *EtcdConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { } // UnmarshalYAML implements the yaml.Unmarshaler interface -func (c *MailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - type plain MailConfig +func (c *JWTConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + *c = DefaultJWTConfig + // We want to set c to the defaults and then overwrite it with the input. + // To make unmarshal fill the plain data struct rather than calling UnmarshalYAML + // again, we have to hide it using a type indirection. + type plain JWTConfig if err := unmarshal((*plain)(c)); err != nil { return err } diff --git a/docs/README.md b/docs/README.md index 513da4f3..60a683e4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,7 @@ 1. [Getting started](./getting-started.md) 2. [Clustering](./clustering.md) -3. [Autoscaling module](./autoscaling.md) -4. [Autohealing module](autohealing.md) -5. [Monitoring](./monitoring.md) +3. [Authentication-Authorization](./authn-authz.md) +4. [Autoscaling module](./autoscaling.md) +5. [Autohealing module](autohealing.md) +6. [Monitoring](./monitoring.md) diff --git a/docs/authn-authz.md b/docs/authn-authz.md new file mode 100644 index 00000000..b2c31cef --- /dev/null +++ b/docs/authn-authz.md @@ -0,0 +1,146 @@ +# Authentication and Authorization + +- [Authentication and Authorization](#authentication-and-authorization) + - [1. Design](#1-design) + - [1.1. Authentication](#11-authentication) + - [1.2. Authorization](#12-authorization) + - [2. APIs](#2-apis) + - [2.1. Get token](#21-get-token) + - [2.2. Create user](#22-create-user) + - [2.3. Delete user](#23-delete-user) + - [2.4. List users with policies](#24-list-users-with-policies) + - [2.5. Add policies](#25-add-policies) + - [2.6. Remove policies](#26-remove-policies) + +## 1. Design + +Check the [proposal](./proposal/authn-authz.md). + +### 1.1. Authentication + +- Get Token flow: + + - User provide an username and password with HTTP Basic Authentication to issue a token. + - In Faythe side, the Basic Authentication passwords are stored as hashed as the [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) algorithm. It is your responsibility to pick the number of rounds that matches your security standards. More rounds make brute-froce more complicated at the cost of more CPU power and more time to authenticate the requests. + - Password is checked against the hashed password stored in Etcd. + - If a match is found, a token is created and sent back to the client in the response header with a key of "Authorization" (by default). Here is the response header format: `Authorization: Bearer `. + +- Restricted API request flow: + + - Client adds the Authorization token to the request header with the key "Authorization". + - The token is checked and parsed to get the user data stored in. + - If the token is valid, the user data will be passed in the request token context. The next middleware(s)/handler(s) can use it later. + - If this is invalid/expired token, return an error to client. + +### 1.2. Authorization + +- By using [Casbin](https://casbin.org/en/), we create an authorization mechanism. +- Flow: + - After pass the Authenticator middleware, the request jumps in Authorizor middleware. + - Faythe retrieves the user data from the request context and get the username. + - Using Casbin Enforcer, Faythe checks whether the authenticated user is allowed to perform the request. + - Return a message to client if they don't have permission. + - If OK, move to the handler. + +## 2. APIs + +### 2.1. Get token + +**PATH**: `/public/tokens` + +**METHOD**: `POST` + +With Basic Auth. + +If you configure the option `jwt.is_bearer_token` as `True`, the response format will be like this: + +``` +Authorization: Bearer +``` + +### 2.2. Create user + +**PATH**: `/users` + +**METHOD**: `POST` + +| Parameter | In | Type | Required | Default | Description | +| --------- | ----- | ------ | -------- | ------- | --------------- | +| username | query | string | true | | User's name | +| password | query | string | true | | User's password | + +### 2.3. Delete user + +**PATH**: `/users/{user}` + +**METHOD**: `DELETE` + +| Parameter | In | Type | Required | Default | Description | +| --------- | ---- | ------ | -------- | ------- | ----------- | +| user | path | string | true | | User's name | + +### 2.4. List users with policies + +**PATH**: `/users` + +**METHOD**: `GET` + +| Parameter | In | Type | Required | Default | Description | +| --------- | ----- | ------ | -------- | ------- | ----------- | +| name | query | string | true | | User's name | + +### 2.5. Add policies + +**PATH**: `/policies` +**METHOD**: `POST` + +| Parameter | In | Type | Required | Default | Description | +| ------------- | ---- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| user | path | string | true | | User's name | +| policies | body | object | true | | A list of Policy instance | +| policy | body | object | true | | A policy instance | +| policy.method | body | string | true | | Allowed RESTful methods, can be a regex pattern. For example: `(GET)|(POST)`... Please check [Casbin docs](https://casbin.org/docs/en/supported-models) for details | +| policy.path | body | string | true | | Allowed URL path, can be a regex pattern. For example: `/res/*`, `/res/:id/`... Please check [Casbin docs](https://casbin.org/docs/en/supported-models) for details | + +For example, the request body: + +```json +[ + { + "path": "/clouds/*", + "method": "GET" + }, + { + "path": "/scalers/*", + "method": "(GET)|(POST)" + } +] +``` + +### 2.6. Remove policies + +**PATH**: `/policies` +**METHOD**: `DELETE` + +| Parameter | In | Type | Required | Default | Description | +| ------------- | ---- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| user | path | string | true | | User's name | +| policies | body | object | true | | A list of Policy instance | +| policy | body | object | true | | A policy instance | +| policy.method | body | string | true | | Allowed RESTful methods, can be a regex pattern. For example: `(GET)|(POST)`... Please check [Casbin docs](https://casbin.org/docs/en/supported-models) for details | +| policy.path | body | string | true | | Allowed URL path, can be a regex pattern. For example: `/res/*`, `/res/:id/`... Please check [Casbin docs](https://casbin.org/docs/en/supported-models) for details | + +For example, the request body: + +```json +[ + { + "path": "/clouds/*", + "method": "GET" + }, + { + "path": "/scalers/*", + "method": "(GET)|(POST)" + } +] +``` diff --git a/docs/proposal/authn-authz.md b/docs/proposal/authn-authz.md new file mode 100644 index 00000000..5070d263 --- /dev/null +++ b/docs/proposal/authn-authz.md @@ -0,0 +1,89 @@ +# Faythe Implement Multiple Users Authentication & Authorization + +- [Faythe Implement Multiple Users Authentication & Authorization](#faythe-implement-multiple-users-authentication--authorization) + - [1. Summary](#1-summary) + - [2. Design](#2-design) + - [2.1. Multiple users](#21-multiple-users) + - [2.2. Authentication](#22-authentication) + - [2.3. Authorization](#23-authorization) + +## 1. Summary + +Issues: + +- https://github.com/vCloud-DFTBA/faythe/issues/94 +- https://github.com/vCloud-DFTBA/faythe/issues/90 + +By now, Faythe is a single-user system - admin. Admin user can do everything, assume that two users both use Faythe. Each one can be able to delete the scaler which created by another. + +We should implement the mutiple users authentication & authorization mechanism. + +## 2. Design + +### 2.1. Multiple users + +- Faythe already uses [JWT](https://jwt.io), just need to extend the current logic to handle multiple users. +- Create a new user key path `/users`. +- An User object contains: + - User name. + - Hashed password. + - Id. + +```go +type User struct { + Username string `json:"username"` + Password string `json:"password"` + ID string `json:"id,omitempty"` +} +``` + +- Faythe has its own root/admin user by configuring the config file. The admin user is allowed to perform any requests without any restriction. + +### 2.2. Authentication + +- Issue Token flow: + + - User provide an username and password with HTTP Basic Authentication to issue a token. + - In Faythe side, the Basic Authentication passwords are stored as hashed as the [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) algorithm. It is your responsibility to pick the number of rounds that matches your security standards. More rounds make brute-froce more complicated at the cost of more CPU power and more time to authenticate the requests. + - Password is checked against the hashed password stored in Etcd. + - If a match is found, a token is created and sent back to the client in the response header with a key of "Authorization" (by default). Here is the response header format: `Authorization: Bearer `. + +- Restricted API request: + - Client adds the Authorization token to the request header with the key "Authorization". + - The token is checked and parsed to get the user data stored in. + - If the token is valid, the user data will be passed in the request token context. The next middleware(s)/handler(s) can use it later. + - If this is invalid/expired token, return an error to client. + +### 2.3. Authorization + +- By using [Casbin](https://casbin.org/en/), we create an authorization mechanism. +- The Casbin model - RESTful (key match): + +```ini +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) +``` + +- Admin user has a policy which granted at the beginning. + +```csv +p, , /*, (GET)|(POST)|(PUT)|(DELETE) +``` + +- As same as other components, Faythe stores the policies in Etcd using [etcd adapter](https://github.com/ntk148v/etcd-adapter). By default, Casbin stores everything in a local file, it is suitable in cluster case. +- Faythe checks whether the authenticated user is allowed to perform the request in Authorizer middleware. +- Flow: + - After pass the Authenticator middleware, the request jumps in Authorizor middleware. + - Faythe retrieves the user data from the request context and get the username. + - Using Casbin Enforcer, Faythe checks whether the authenticated user is allowed to perform the request. + - Return a message to client if they don't have permission. + - If OK, move to the handler. diff --git a/examples/faythe.yml b/examples/faythe.yml index aaa47a43..9ba48b5e 100644 --- a/examples/faythe.yml +++ b/examples/faythe.yml @@ -1,14 +1,31 @@ -# Global configuration -global: - # Example: - # "www.example.com" - # "([a-z]+).domain.com" - # The regex has to follow Golang regex convention - # remote_host_pattern: "192.168.(128|129).*" - # basic_auth: - # username: "admin" - # password: "notverysecurepassword" - -# Etcd configuration +# Example: +# "www.example.com" +# "([a-z]+).domain.com" +# The regex has to follow Golang regex convention +remote_host_pattern: "192.168.(128|129).*" +# Enable the Go process profiler +enable_profiling: true +admin_authentication: + username: admin + password: adminpassword +# Etcd configurations etcd: - endpoints: ["etcd:2379"] + endpoints: ["192.168.1.2:2379", "192.168.1.3:2379", "192.168.1.4:2379"] + username: etcduser + password: etcdpassword + auto_sync_interval: 30s + dial_timeout: 2s + dial_keep_alive_time: 2s + dial_keep_alive_timeout: 6s +# JWT configurations +jwt: + signing_method: RS256 + is_bearer_token: true + private_key_location: /etc/faythe/keys/faythe.rsa + public_key_location: /etc/faythe/keys/faythe.rsa.pub +# Mail configurations +mail: + host: smtp.gmail.com + port: 465 + username: emailuser + password: emailpassword diff --git a/examples/keys/faythe.rsa b/examples/keys/faythe.rsa new file mode 100644 index 00000000..8b456978 --- /dev/null +++ b/examples/keys/faythe.rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAoa5Eu/bQekqTU7IZCoaWa4T9sl1QOeCIYL8cNNh+TJMsOL55 +m7gD0LfkGMHvBr7WA4fTvfnRgtjx092EFZzJuMz64gTcMcf7Gc128GAYxAsTySdu +4kedVrEV6JtzYlo+CRCW4mMqGPP9+ZbQp2IEFUDj8bgiBdEjFi2kdEgMw2NVxCJJ +4gQkRt75KR6GHdMULz53OI72UUZtpw8skn8nUcCGR4Q38bVUziCLq07GVXKmQKm3 +cGIsvp7wp5xb3JK89FxnztR+BsL3E0YVuAm+YGK/uNBNKVoMDrNSjzu2kjMH2O9V +lW7PUPoZ3o/znbVXUHMegryyl4LYtrJYEAINBwIDAQABAoIBAQCff/kk6Vc/3YQ8 +BC25+Y45IejZZj736e8AamicJZtrVaFBlX8IxN9Wr6MhXWi94dPlhcC44NP0Xqsr +FJPyckt6uRUjliHYJm4sRUr51gN4m2j4f6NfLcElPevcHkcVFuuuK6R+T8++8C10 +ZXkQN5zJjsuQsdM2o9wWTA65/D5DHZEntPdUyxaz19h7v17JA9FfztKFUSm3kfd3 +8hOqOXOe/P7fk+HXME9H/QiQbK7XkTFnt4Nn0Qp4r8FVvcskOe6K+gx/XYVns+Ja +UqZW5wr/mY1FYUjPkIc79pVFkf3SsXAJH+6KEmqWlvwpOWfuIHlZxaOt6Iwf2Tsp +A6RnLOppAoGBANVlKkQKU8kjO3eD851A1dLQDN6cLsyX+xInxnzwg0qlVAFJh9kG +fgFh+FVAjSgjbOy4u3DarmmW4pbLOi74R1QPeQBZjl2HX/tHJOgZFYsIuJKaIIiG +DFUtuHGaR3LIjXlvVl+mK7pt5PDMbegkUgn3TaFuksNeaouv/MScRHu1AoGBAMH1 +7ZfUeNcoeaPpxbAyJWBee/PAmm+v4TNMuL2Fls5DRzuE/UWb2GiPxb44t8b8d/ie +oKSw91sCyrJSAL3wk7N5OHFMzoc0bCBGvxuxkQRV4nx5d1TIpSzADXzotshUH+/M +8qy2tArad9/ONzdYLFVFOcmktEJNQXwRucWBkfNLAoGAPX3SaRrku/AYIb93Qtg9 +BWuIWdl9rt0Yr0mg673Ox4LOqzRKHiK9yp41ljNsuaVDrQ8qjEGnwquFDdjwIjrP +KwzuXEhyOsdYxCrjGrKnv5G6xhv9xfmCskTjgnDnp87c3aJUrGlQ7n7eWvk9rohQ +z6hgyWT4i624XBpRZ0J0hYUCgYEAsbZx1g4wFfsY0K1tmw2nH1ASw/hxx+lfLlCa +Z9ORpDUSfB9Sq8eqFcu9Q5fWErTZ+2rkB2G5kaLSM2ZdFarquaNUqsLT7B+E/mGz +0lz0YQOL+76TLpSUepcRBKR4bL5fcKjYpJTWC6e189KO1t+r5wkBdJa7LpB4coQP +MT6RilcCgYBAihO0DChhn+S4oSegSxCmoHTHCDG6/dJxaenhNa8XkBLkqsvK5RHu +2hxL7GkvyuvZA0Gnkqu3zHi3gxu5SVC1ED4JHtWaV1Zq3zu8fMHNw2g1XX4OpDIq +rPBlakuZdnoskAz55s9zWPL/LQdlT4x3iqyf3J2N7+Fagz4uj8khhQ== +-----END RSA PRIVATE KEY----- diff --git a/examples/keys/faythe.rsa.pub b/examples/keys/faythe.rsa.pub new file mode 100644 index 00000000..e69de29b diff --git a/go.mod b/go.mod index 1b87f32d..afaabb26 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.12 require ( github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect github.com/avast/retry-go v2.4.1+incompatible + github.com/casbin/casbin/v2 v2.6.10 github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect - github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dustin/go-humanize v1.0.0 // indirect github.com/fsnotify/fsnotify v1.4.7 github.com/go-kit/kit v0.9.0 @@ -17,13 +17,15 @@ require ( github.com/google/go-cmp v0.3.1 // indirect github.com/google/uuid v1.1.1 // indirect github.com/gophercloud/gophercloud v0.10.0 - github.com/gorilla/mux v1.7.3 + github.com/gorilla/mux v1.7.4 github.com/gorilla/websocket v1.4.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.11.3 // indirect github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 github.com/json-iterator/go v1.1.8 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/ntk148v/etcd-adapter v1.1.1 + github.com/ntk148v/jwt-middleware v0.0.0-20200609034444-e0f55c7eda1b github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 github.com/pkg/errors v0.8.1 github.com/prometheus/client_golang v1.2.1 @@ -32,6 +34,7 @@ require ( github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect go.etcd.io/etcd v0.0.0-20200401174654-e694b7bb0875 go.uber.org/zap v1.12.0 // indirect + golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6 // indirect google.golang.org/grpc v1.24.0 diff --git a/go.sum b/go.sum index 7abd0a8f..4ac3c4cd 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -17,6 +19,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/casbin/casbin/v2 v2.6.10 h1:Thp6H74ykPjCwr3cxpG4iWrPxV9Uege0gTmWgnYKdT8= +github.com/casbin/casbin/v2 v2.6.10/go.mod h1:XXtYGrs/0zlOsJMeRteEdVi/FsB0ph7KgNfjoCoJUD8= github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -81,8 +85,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gophercloud/gophercloud v0.10.0 h1:Et+UGxoD72pK6K+46uOwyVxbtXJ6KBkWAegXBmqlf6c= github.com/gophercloud/gophercloud v0.10.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss= -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -134,6 +138,10 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/ntk148v/etcd-adapter v1.1.1 h1:yb0gsG3rCN3sieHEqquwYjJgEZrkamtyStL+84K5+oY= +github.com/ntk148v/etcd-adapter v1.1.1/go.mod h1:RdyvZUyhLmZOgBNzttmmZHSs6OuFs/83S7Ye/wplAtc= +github.com/ntk148v/jwt-middleware v0.0.0-20200609034444-e0f55c7eda1b h1:AN3k2oI8wtzh7lIPleGOVRsfM7pfC0K6McR0h3O+gso= +github.com/ntk148v/jwt-middleware v0.0.0-20200609034444-e0f55c7eda1b/go.mod h1:zOOxFb60+9XOZP95ya/AWcOaFWi4mEg0lv/jSzG52Bw= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw= diff --git a/middleware/middleware.go b/middleware/middleware.go index 9d619e5d..55081189 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -15,12 +15,13 @@ package middleware import ( + "fmt" "net/http" "regexp" "strings" "time" - "github.com/dgrijalva/jwt-go" + "github.com/casbin/casbin/v2" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" @@ -33,7 +34,6 @@ import ( // Middleware represents middleware handlers. type Middleware struct { logger log.Logger - auth config.BasicAuthentication regexp *regexp.Regexp } @@ -43,13 +43,11 @@ func New(l log.Logger) *Middleware { l = log.NewNopLogger() } - cfg := config.Get().GlobalConfig - a := cfg.BasicAuthentication + cfg := config.Get() r, _ := regexp.Compile(cfg.RemoteHostPattern) return &Middleware{ logger: l, - auth: a, regexp: r, } } @@ -72,6 +70,30 @@ func (h instrumentHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { h.handler.ServeHTTP(w, req) } +// Authorizer is a middleware checks whether the user is allowed to perform +// the request. +func Authorizer(e *casbin.Enforcer) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, req *http.Request) { + data := req.Context().Value("user").(map[string]interface{}) + // Force re-load the policy + _ = e.LoadPolicy() + res, err := e.Enforce(data["name"], req.URL.Path, req.Method) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + if !res { + w.WriteHeader(http.StatusForbidden) + fmt.Fprint(w, "you don't have permission to perform this action") + return + } + next.ServeHTTP(w, req) + } + return http.HandlerFunc(fn) + } +} + // Instrument is a middleware that wraps the provided http.Handler // to observe the request result. func (m *Middleware) Instrument(next http.Handler) http.Handler { @@ -92,55 +114,6 @@ func (m *Middleware) Logging(next http.Handler) http.Handler { }) } -// Authenticate verifies authentication provided in the request's Authorization -// header if the request uses JSON web tokens. -func (m *Middleware) Authenticate(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.Method != "OPTIONS" { - c, err := req.Cookie("api-token") - if err != nil { - if err == http.ErrNoCookie { - level.Error(m.logger).Log("msg", "Unauthorized request") - http.Error(w, "Login Required!", http.StatusUnauthorized) - return - } - - level.Error(m.logger).Log("msg", "Error while getting request cookie", - "err", err.Error()) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - tokenString := c.Value - claims := &jwt.StandardClaims{} - token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (i interface{}, err error) { - return []byte(m.auth.SecretKey), nil - }) - - if err != nil { - if err == jwt.ErrSignatureInvalid { - level.Error(m.logger).Log("msg", "Unauthorized request") - http.Error(w, "Login Required!", http.StatusUnauthorized) - return - } - - level.Error(m.logger).Log("msg", "Error while verifying token", - "err", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if !token.Valid { - level.Error(m.logger).Log("msg", "Unauthorized request") - http.Error(w, "Login Required!", http.StatusUnauthorized) - return - } - } - - next.ServeHTTP(w, req) - }) -} - // RestrictDomain checks whether request's remote address was matched // a defined host pattern or not. func (m *Middleware) RestrictDomain(next http.Handler) http.Handler { diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 14911f99..bdae63e2 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -94,7 +94,7 @@ func New(cid, bindAddr string, l log.Logger, e *common.Etcd) (*Cluster, error) { ClusterID = common.RandToken() level.Info(c.logger).Log("msg", "A new cluster is starting...") } else { - ClusterID = cid + ClusterID = strings.Trim(cid, "/") level.Info(c.logger).Log("msg", "A node is joining to existing cluster...") } level.Info(c.logger).Log("msg", "Use the cluster id to join", "id", cid) diff --git a/pkg/common/utils.go b/pkg/common/utils.go index e3b66223..79a1b8a1 100644 --- a/pkg/common/utils.go +++ b/pkg/common/utils.go @@ -36,6 +36,7 @@ import ( "github.com/pkg/errors" prommodel "github.com/prometheus/common/model" + "golang.org/x/crypto/bcrypt" ) // BasicAuthTransport is an http.RoundTripper that authenticates all requests @@ -74,6 +75,18 @@ func (t *BasicAuthTransport) transport() http.RoundTripper { return t.Transport } +// GenerateBcryptHash return the hash password +func GenerateBcryptHash(password string, cost int) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost) + return string(bytes), err +} + +// CheckPasswordAgainstHash compares the password with the hashed password +func CheckPasswordAgainstHash(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} + // HashFNV generates a new 64-bit number from a given string // using 64-bit FNV-1a hash function. func HashFNV(s string) string { @@ -109,6 +122,9 @@ func RandToken() string { // Path returns a etcd key path. func Path(keys ...string) string { + for index, key := range keys { + keys[index] = key + } return strings.Join(append([]string{}, keys...), "/") } diff --git a/pkg/model/const.go b/pkg/model/const.go index f366625a..e8e04177 100644 --- a/pkg/model/const.go +++ b/pkg/model/const.go @@ -34,6 +34,13 @@ const ( // DefaultSilencePrefix is default etcd prefix for Silences DefaultSilencePrefix = "/silences" DefaultSilenceValidationInterval = "30s" + + // DefaultUserPrefix is default etcd prefix for Users + DefaultUsersPrefix = "/users" + + // DefaultPoliciesPrefix is default etcd prefix for policies + // This prefix is a bit different than others, it doesn't start with a slash '/' + DefaultPoliciesPrefix = "policies" ) const DefaultMaxNumberOfInstances int = 3 diff --git a/pkg/model/policy.go b/pkg/model/policy.go new file mode 100644 index 00000000..38060aae --- /dev/null +++ b/pkg/model/policy.go @@ -0,0 +1,22 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +type Policy struct { + Path string `json:"path"` + Method string `json:"method"` +} + +type Polices []Policy diff --git a/pkg/model/user.go b/pkg/model/user.go new file mode 100644 index 00000000..d11aedc0 --- /dev/null +++ b/pkg/model/user.go @@ -0,0 +1,35 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "crypto" + + "github.com/vCloud-DFTBA/faythe/pkg/common" +) + +// User represents an Faythe user +type User struct { + Username string `json:"username"` + Password string `json:"password"` + ID string `json:"id,omitempty"` +} + +// Validate returns nil if all fields of the User have valid values. +// Nothing to check, just generate to UUID. +func (u *User) Validate() error { + u.ID = common.Hash(u.Username, crypto.MD5) + return nil +} diff --git a/vendor/github.com/Knetic/govaluate/.gitignore b/vendor/github.com/Knetic/govaluate/.gitignore new file mode 100644 index 00000000..da210fb3 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/.gitignore @@ -0,0 +1,28 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +coverage.out + +manual_test.go +*.out +*.err diff --git a/vendor/github.com/Knetic/govaluate/.travis.yml b/vendor/github.com/Knetic/govaluate/.travis.yml new file mode 100644 index 00000000..35ae404a --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/.travis.yml @@ -0,0 +1,10 @@ +language: go + +script: ./test.sh + +go: + - 1.2 + - 1.3 + - 1.4 + - 1.5 + - 1.6 diff --git a/vendor/github.com/Knetic/govaluate/CONTRIBUTORS b/vendor/github.com/Knetic/govaluate/CONTRIBUTORS new file mode 100644 index 00000000..c1a7fe42 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/CONTRIBUTORS @@ -0,0 +1,15 @@ +This library was authored by George Lester, and contains contributions from: + +vjeantet (regex support) +iasci (ternary operator) +oxtoacart (parameter structures, deferred parameter retrieval) +wmiller848 (bitwise operators) +prashantv (optimization of bools) +dpaolella (exposure of variables used in an expression) +benpaxton (fix for missing type checks during literal elide process) +abrander (panic-finding testing tool, float32 conversions) +xfennec (fix for dates being parsed in the current Location) +bgaifullin (lifting restriction on complex/struct types) +gautambt (hexadecimal literals) +felixonmars (fix multiple typos in test names) +sambonfire (automatic type conversion for accessor function calls) \ No newline at end of file diff --git a/vendor/github.com/Knetic/govaluate/EvaluableExpression.go b/vendor/github.com/Knetic/govaluate/EvaluableExpression.go new file mode 100644 index 00000000..a5fe50d4 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/EvaluableExpression.go @@ -0,0 +1,276 @@ +package govaluate + +import ( + "errors" + "fmt" +) + +const isoDateFormat string = "2006-01-02T15:04:05.999999999Z0700" +const shortCircuitHolder int = -1 + +var DUMMY_PARAMETERS = MapParameters(map[string]interface{}{}) + +/* + EvaluableExpression represents a set of ExpressionTokens which, taken together, + are an expression that can be evaluated down into a single value. +*/ +type EvaluableExpression struct { + + /* + Represents the query format used to output dates. Typically only used when creating SQL or Mongo queries from an expression. + Defaults to the complete ISO8601 format, including nanoseconds. + */ + QueryDateFormat string + + /* + Whether or not to safely check types when evaluating. + If true, this library will return error messages when invalid types are used. + If false, the library will panic when operators encounter types they can't use. + + This is exclusively for users who need to squeeze every ounce of speed out of the library as they can, + and you should only set this to false if you know exactly what you're doing. + */ + ChecksTypes bool + + tokens []ExpressionToken + evaluationStages *evaluationStage + inputExpression string +} + +/* + Parses a new EvaluableExpression from the given [expression] string. + Returns an error if the given expression has invalid syntax. +*/ +func NewEvaluableExpression(expression string) (*EvaluableExpression, error) { + + functions := make(map[string]ExpressionFunction) + return NewEvaluableExpressionWithFunctions(expression, functions) +} + +/* + Similar to [NewEvaluableExpression], except that instead of a string, an already-tokenized expression is given. + This is useful in cases where you may be generating an expression automatically, or using some other parser (e.g., to parse from a query language) +*/ +func NewEvaluableExpressionFromTokens(tokens []ExpressionToken) (*EvaluableExpression, error) { + + var ret *EvaluableExpression + var err error + + ret = new(EvaluableExpression) + ret.QueryDateFormat = isoDateFormat + + err = checkBalance(tokens) + if err != nil { + return nil, err + } + + err = checkExpressionSyntax(tokens) + if err != nil { + return nil, err + } + + ret.tokens, err = optimizeTokens(tokens) + if err != nil { + return nil, err + } + + ret.evaluationStages, err = planStages(ret.tokens) + if err != nil { + return nil, err + } + + ret.ChecksTypes = true + return ret, nil +} + +/* + Similar to [NewEvaluableExpression], except enables the use of user-defined functions. + Functions passed into this will be available to the expression. +*/ +func NewEvaluableExpressionWithFunctions(expression string, functions map[string]ExpressionFunction) (*EvaluableExpression, error) { + + var ret *EvaluableExpression + var err error + + ret = new(EvaluableExpression) + ret.QueryDateFormat = isoDateFormat + ret.inputExpression = expression + + ret.tokens, err = parseTokens(expression, functions) + if err != nil { + return nil, err + } + + err = checkBalance(ret.tokens) + if err != nil { + return nil, err + } + + err = checkExpressionSyntax(ret.tokens) + if err != nil { + return nil, err + } + + ret.tokens, err = optimizeTokens(ret.tokens) + if err != nil { + return nil, err + } + + ret.evaluationStages, err = planStages(ret.tokens) + if err != nil { + return nil, err + } + + ret.ChecksTypes = true + return ret, nil +} + +/* + Same as `Eval`, but automatically wraps a map of parameters into a `govalute.Parameters` structure. +*/ +func (this EvaluableExpression) Evaluate(parameters map[string]interface{}) (interface{}, error) { + + if parameters == nil { + return this.Eval(nil) + } + + return this.Eval(MapParameters(parameters)) +} + +/* + Runs the entire expression using the given [parameters]. + e.g., If the expression contains a reference to the variable "foo", it will be taken from `parameters.Get("foo")`. + + This function returns errors if the combination of expression and parameters cannot be run, + such as if a variable in the expression is not present in [parameters]. + + In all non-error circumstances, this returns the single value result of the expression and parameters given. + e.g., if the expression is "1 + 1", this will return 2.0. + e.g., if the expression is "foo + 1" and parameters contains "foo" = 2, this will return 3.0 +*/ +func (this EvaluableExpression) Eval(parameters Parameters) (interface{}, error) { + + if this.evaluationStages == nil { + return nil, nil + } + + if parameters != nil { + parameters = &sanitizedParameters{parameters} + } else { + parameters = DUMMY_PARAMETERS + } + + return this.evaluateStage(this.evaluationStages, parameters) +} + +func (this EvaluableExpression) evaluateStage(stage *evaluationStage, parameters Parameters) (interface{}, error) { + + var left, right interface{} + var err error + + if stage.leftStage != nil { + left, err = this.evaluateStage(stage.leftStage, parameters) + if err != nil { + return nil, err + } + } + + if stage.isShortCircuitable() { + switch stage.symbol { + case AND: + if left == false { + return false, nil + } + case OR: + if left == true { + return true, nil + } + case COALESCE: + if left != nil { + return left, nil + } + + case TERNARY_TRUE: + if left == false { + right = shortCircuitHolder + } + case TERNARY_FALSE: + if left != nil { + right = shortCircuitHolder + } + } + } + + if right != shortCircuitHolder && stage.rightStage != nil { + right, err = this.evaluateStage(stage.rightStage, parameters) + if err != nil { + return nil, err + } + } + + if this.ChecksTypes { + if stage.typeCheck == nil { + + err = typeCheck(stage.leftTypeCheck, left, stage.symbol, stage.typeErrorFormat) + if err != nil { + return nil, err + } + + err = typeCheck(stage.rightTypeCheck, right, stage.symbol, stage.typeErrorFormat) + if err != nil { + return nil, err + } + } else { + // special case where the type check needs to know both sides to determine if the operator can handle it + if !stage.typeCheck(left, right) { + errorMsg := fmt.Sprintf(stage.typeErrorFormat, left, stage.symbol.String()) + return nil, errors.New(errorMsg) + } + } + } + + return stage.operator(left, right, parameters) +} + +func typeCheck(check stageTypeCheck, value interface{}, symbol OperatorSymbol, format string) error { + + if check == nil { + return nil + } + + if check(value) { + return nil + } + + errorMsg := fmt.Sprintf(format, value, symbol.String()) + return errors.New(errorMsg) +} + +/* + Returns an array representing the ExpressionTokens that make up this expression. +*/ +func (this EvaluableExpression) Tokens() []ExpressionToken { + + return this.tokens +} + +/* + Returns the original expression used to create this EvaluableExpression. +*/ +func (this EvaluableExpression) String() string { + + return this.inputExpression +} + +/* + Returns an array representing the variables contained in this EvaluableExpression. +*/ +func (this EvaluableExpression) Vars() []string { + var varlist []string + for _, val := range this.Tokens() { + if val.Kind == VARIABLE { + varlist = append(varlist, val.Value.(string)) + } + } + return varlist +} diff --git a/vendor/github.com/Knetic/govaluate/EvaluableExpression_sql.go b/vendor/github.com/Knetic/govaluate/EvaluableExpression_sql.go new file mode 100644 index 00000000..7e0ad1c8 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/EvaluableExpression_sql.go @@ -0,0 +1,167 @@ +package govaluate + +import ( + "errors" + "fmt" + "regexp" + "time" +) + +/* + Returns a string representing this expression as if it were written in SQL. + This function assumes that all parameters exist within the same table, and that the table essentially represents + a serialized object of some sort (e.g., hibernate). + If your data model is more normalized, you may need to consider iterating through each actual token given by `Tokens()` + to create your query. + + Boolean values are considered to be "1" for true, "0" for false. + + Times are formatted according to this.QueryDateFormat. +*/ +func (this EvaluableExpression) ToSQLQuery() (string, error) { + + var stream *tokenStream + var transactions *expressionOutputStream + var transaction string + var err error + + stream = newTokenStream(this.tokens) + transactions = new(expressionOutputStream) + + for stream.hasNext() { + + transaction, err = this.findNextSQLString(stream, transactions) + if err != nil { + return "", err + } + + transactions.add(transaction) + } + + return transactions.createString(" "), nil +} + +func (this EvaluableExpression) findNextSQLString(stream *tokenStream, transactions *expressionOutputStream) (string, error) { + + var token ExpressionToken + var ret string + + token = stream.next() + + switch token.Kind { + + case STRING: + ret = fmt.Sprintf("'%v'", token.Value) + case PATTERN: + ret = fmt.Sprintf("'%s'", token.Value.(*regexp.Regexp).String()) + case TIME: + ret = fmt.Sprintf("'%s'", token.Value.(time.Time).Format(this.QueryDateFormat)) + + case LOGICALOP: + switch logicalSymbols[token.Value.(string)] { + + case AND: + ret = "AND" + case OR: + ret = "OR" + } + + case BOOLEAN: + if token.Value.(bool) { + ret = "1" + } else { + ret = "0" + } + + case VARIABLE: + ret = fmt.Sprintf("[%s]", token.Value.(string)) + + case NUMERIC: + ret = fmt.Sprintf("%g", token.Value.(float64)) + + case COMPARATOR: + switch comparatorSymbols[token.Value.(string)] { + + case EQ: + ret = "=" + case NEQ: + ret = "<>" + case REQ: + ret = "RLIKE" + case NREQ: + ret = "NOT RLIKE" + default: + ret = fmt.Sprintf("%s", token.Value.(string)) + } + + case TERNARY: + + switch ternarySymbols[token.Value.(string)] { + + case COALESCE: + + left := transactions.rollback() + right, err := this.findNextSQLString(stream, transactions) + if err != nil { + return "", err + } + + ret = fmt.Sprintf("COALESCE(%v, %v)", left, right) + case TERNARY_TRUE: + fallthrough + case TERNARY_FALSE: + return "", errors.New("Ternary operators are unsupported in SQL output") + } + case PREFIX: + switch prefixSymbols[token.Value.(string)] { + + case INVERT: + ret = fmt.Sprintf("NOT") + default: + + right, err := this.findNextSQLString(stream, transactions) + if err != nil { + return "", err + } + + ret = fmt.Sprintf("%s%s", token.Value.(string), right) + } + case MODIFIER: + + switch modifierSymbols[token.Value.(string)] { + + case EXPONENT: + + left := transactions.rollback() + right, err := this.findNextSQLString(stream, transactions) + if err != nil { + return "", err + } + + ret = fmt.Sprintf("POW(%s, %s)", left, right) + case MODULUS: + + left := transactions.rollback() + right, err := this.findNextSQLString(stream, transactions) + if err != nil { + return "", err + } + + ret = fmt.Sprintf("MOD(%s, %s)", left, right) + default: + ret = fmt.Sprintf("%s", token.Value.(string)) + } + case CLAUSE: + ret = "(" + case CLAUSE_CLOSE: + ret = ")" + case SEPARATOR: + ret = "," + + default: + errorMsg := fmt.Sprintf("Unrecognized query token '%s' of kind '%s'", token.Value, token.Kind) + return "", errors.New(errorMsg) + } + + return ret, nil +} diff --git a/vendor/github.com/Knetic/govaluate/ExpressionToken.go b/vendor/github.com/Knetic/govaluate/ExpressionToken.go new file mode 100644 index 00000000..f849f381 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/ExpressionToken.go @@ -0,0 +1,9 @@ +package govaluate + +/* + Represents a single parsed token. +*/ +type ExpressionToken struct { + Kind TokenKind + Value interface{} +} diff --git a/vendor/github.com/Knetic/govaluate/LICENSE b/vendor/github.com/Knetic/govaluate/LICENSE new file mode 100644 index 00000000..24b9b459 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2016 George Lester + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/Knetic/govaluate/MANUAL.md b/vendor/github.com/Knetic/govaluate/MANUAL.md new file mode 100644 index 00000000..e0658285 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/MANUAL.md @@ -0,0 +1,176 @@ +govaluate +==== + +This library contains quite a lot of functionality, this document is meant to be formal documentation on the operators and features of it. +Some of this documentation may duplicate what's in README.md, but should never conflict. + +# Types + +This library only officially deals with four types; `float64`, `bool`, `string`, and arrays. + +All numeric literals, with or without a radix, will be converted to `float64` for evaluation. For instance; in practice, there is no difference between the literals "1.0" and "1", they both end up as `float64`. This matters to users because if you intend to return numeric values from your expressions, then the returned value will be `float64`, not any other numeric type. + +Any string _literal_ (not parameter) which is interpretable as a date will be converted to a `float64` representation of that date's unix time. Any `time.Time` parameters will not be operable with these date literals; such parameters will need to use the `time.Time.Unix()` method to get a numeric representation. + +Arrays are untyped, and can be mixed-type. Internally they're all just `interface{}`. Only two operators can interact with arrays, `IN` and `,`. All other operators will refuse to operate on arrays. + +# Operators + +## Modifiers + +### Addition, concatenation `+` + +If either left or right sides of the `+` operator are a `string`, then this operator will perform string concatenation and return that result. If neither are string, then both must be numeric, and this will return a numeric result. + +Any other case is invalid. + +### Arithmetic `-` `*` `/` `**` `%` + +`**` refers to "take to the power of". For instance, `3 ** 4` == 81. + +* _Left side_: numeric +* _Right side_: numeric +* _Returns_: numeric + +### Bitwise shifts, masks `>>` `<<` `|` `&` `^` + +All of these operators convert their `float64` left and right sides to `int64`, perform their operation, and then convert back. +Given how this library assumes numeric are represented (as `float64`), it is unlikely that this behavior will change, even though it may cause havoc with extremely large or small numbers. + +* _Left side_: numeric +* _Right side_: numeric +* _Returns_: numeric + +### Negation `-` + +Prefix only. This can never have a left-hand value. + +* _Right side_: numeric +* _Returns_: numeric + +### Inversion `!` + +Prefix only. This can never have a left-hand value. + +* _Right side_: bool +* _Returns_: bool + +### Bitwise NOT `~` + +Prefix only. This can never have a left-hand value. + +* _Right side_: numeric +* _Returns_: numeric + +## Logical Operators + +For all logical operators, this library will short-circuit the operation if the left-hand side is sufficient to determine what to do. For instance, `true || expensiveOperation()` will not actually call `expensiveOperation()`, since it knows the left-hand side is `true`. + +### Logical AND/OR `&&` `||` + +* _Left side_: bool +* _Right side_: bool +* _Returns_: bool + +### Ternary true `?` + +Checks if the left side is `true`. If so, returns the right side. If the left side is `false`, returns `nil`. +In practice, this is commonly used with the other ternary operator. + +* _Left side_: bool +* _Right side_: Any type. +* _Returns_: Right side or `nil` + +### Ternary false `:` + +Checks if the left side is `nil`. If so, returns the right side. If the left side is non-nil, returns the left side. +In practice, this is commonly used with the other ternary operator. + +* _Left side_: Any type. +* _Right side_: Any type. +* _Returns_: Right side or `nil` + +### Null coalescence `??` + +Similar to the C# operator. If the left value is non-nil, it returns that. If not, then the right-value is returned. + +* _Left side_: Any type. +* _Right side_: Any type. +* _Returns_: No specific type - whichever is passed to it. + +## Comparators + +### Numeric/lexicographic comparators `>` `<` `>=` `<=` + +If both sides are numeric, this returns the usual greater/lesser behavior that would be expected. +If both sides are string, this returns the lexicographic comparison of the strings. This uses Go's standard lexicographic compare. + +* _Accepts_: Left and right side must either be both string, or both numeric. +* _Returns_: bool + +### Regex comparators `=~` `!~` + +These use go's standard `regexp` flavor of regex. The left side is expected to be the candidate string, the right side is the pattern. `=~` returns whether or not the candidate string matches the regex pattern given on the right. `!~` is the inverted version of the same logic. + +* _Left side_: string +* _Right side_: string +* _Returns_: bool + +## Arrays + +### Separator `,` + +The separator, always paired with parenthesis, creates arrays. It must always have both a left and right-hand value, so for instance `(, 0)` and `(0,)` are invalid uses of it. + +Again, this should always be used with parenthesis; like `(1, 2, 3, 4)`. + +### Membership `IN` + +The only operator with a text name, this operator checks the right-hand side array to see if it contains a value that is equal to the left-side value. +Equality is determined by the use of the `==` operator, and this library doesn't check types between the values. Any two values, when cast to `interface{}`, and can still be checked for equality with `==` will act as expected. + +Note that you can use a parameter for the array, but it must be an `[]interface{}`. + +* _Left side_: Any type. +* _Right side_: array +* _Returns_: bool + +# Parameters + +Parameters must be passed in every time the expression is evaluated. Parameters can be of any type, but will not cause errors unless actually used in an erroneous way. There is no difference in behavior for any of the above operators for parameters - they are type checked when used. + +All `int` and `float` values of any width will be converted to `float64` before use. + +At no point is the parameter structure, or any value thereof, modified by this library. + +## Alternates to maps + +The default form of parameters as a map may not serve your use case. You may have parameters in some other structure, you may want to change the no-parameter-found behavior, or maybe even just have some debugging print statements invoked when a parameter is accessed. + +To do this, define a type that implements the `govaluate.Parameters` interface. When you want to evaluate, instead call `EvaluableExpression.Eval` and pass your parameter structure. + +# Functions + +During expression parsing (_not_ evaluation), a map of functions can be given to `govaluate.NewEvaluableExpressionWithFunctions` (the lengthiest and finest of function names). The resultant expression will be able to invoke those functions during evaluation. Once parsed, an expression cannot have functions added or removed - a new expression will need to be created if you want to change the functions, or behavior of said functions. + +Functions always take the form `()`, including parens. Functions can have an empty list of parameters, like `()`, but still must have parens. + +If the expression contains something that looks like it ought to be a function (such as `foo()`), but no such function was given to it, it will error on parsing. + +Functions must be of type `map[string]govaluate.ExpressionFunction`. `ExpressionFunction`, for brevity, has the following signature: + +`func(args ...interface{}) (interface{}, error)` + +Where `args` is whatever is passed to the function when called. If a non-nil error is returned from a function during evaluation, the evaluation stops and ultimately returns that error to the caller of `Evaluate()` or `Eval()`. + +## Built-in functions + +There aren't any builtin functions. The author is opposed to maintaining a standard library of functions to be used. + +Every use case of this library is different, and even in simple use cases (such as parameters, see above) different users need different behavior, naming, or even functionality. The author prefers that users make their own decisions about what functions they need, and how they operate. + +# Equality + +The `==` and `!=` operators involve a moderately complex workflow. They use [`reflect.DeepEqual`](https://golang.org/pkg/reflect/#DeepEqual). This is for complicated reasons, but there are some types in Go that cannot be compared with the native `==` operator. Arrays, in particular, cannot be compared - Go will panic if you try. One might assume this could be handled with the type checking system in `govaluate`, but unfortunately without reflection there is no way to know if a variable is a slice/array. Worse, structs can be incomparable if they _contain incomparable types_. + +It's all very complicated. Fortunately, Go includes the `reflect.DeepEqual` function to handle all the edge cases. Currently, `govaluate` uses that for all equality/inequality. diff --git a/vendor/github.com/Knetic/govaluate/OperatorSymbol.go b/vendor/github.com/Knetic/govaluate/OperatorSymbol.go new file mode 100644 index 00000000..4b810658 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/OperatorSymbol.go @@ -0,0 +1,309 @@ +package govaluate + +/* + Represents the valid symbols for operators. + +*/ +type OperatorSymbol int + +const ( + VALUE OperatorSymbol = iota + LITERAL + NOOP + EQ + NEQ + GT + LT + GTE + LTE + REQ + NREQ + IN + + AND + OR + + PLUS + MINUS + BITWISE_AND + BITWISE_OR + BITWISE_XOR + BITWISE_LSHIFT + BITWISE_RSHIFT + MULTIPLY + DIVIDE + MODULUS + EXPONENT + + NEGATE + INVERT + BITWISE_NOT + + TERNARY_TRUE + TERNARY_FALSE + COALESCE + + FUNCTIONAL + ACCESS + SEPARATE +) + +type operatorPrecedence int + +const ( + noopPrecedence operatorPrecedence = iota + valuePrecedence + functionalPrecedence + prefixPrecedence + exponentialPrecedence + additivePrecedence + bitwisePrecedence + bitwiseShiftPrecedence + multiplicativePrecedence + comparatorPrecedence + ternaryPrecedence + logicalAndPrecedence + logicalOrPrecedence + separatePrecedence +) + +func findOperatorPrecedenceForSymbol(symbol OperatorSymbol) operatorPrecedence { + + switch symbol { + case NOOP: + return noopPrecedence + case VALUE: + return valuePrecedence + case EQ: + fallthrough + case NEQ: + fallthrough + case GT: + fallthrough + case LT: + fallthrough + case GTE: + fallthrough + case LTE: + fallthrough + case REQ: + fallthrough + case NREQ: + fallthrough + case IN: + return comparatorPrecedence + case AND: + return logicalAndPrecedence + case OR: + return logicalOrPrecedence + case BITWISE_AND: + fallthrough + case BITWISE_OR: + fallthrough + case BITWISE_XOR: + return bitwisePrecedence + case BITWISE_LSHIFT: + fallthrough + case BITWISE_RSHIFT: + return bitwiseShiftPrecedence + case PLUS: + fallthrough + case MINUS: + return additivePrecedence + case MULTIPLY: + fallthrough + case DIVIDE: + fallthrough + case MODULUS: + return multiplicativePrecedence + case EXPONENT: + return exponentialPrecedence + case BITWISE_NOT: + fallthrough + case NEGATE: + fallthrough + case INVERT: + return prefixPrecedence + case COALESCE: + fallthrough + case TERNARY_TRUE: + fallthrough + case TERNARY_FALSE: + return ternaryPrecedence + case ACCESS: + fallthrough + case FUNCTIONAL: + return functionalPrecedence + case SEPARATE: + return separatePrecedence + } + + return valuePrecedence +} + +/* + Map of all valid comparators, and their string equivalents. + Used during parsing of expressions to determine if a symbol is, in fact, a comparator. + Also used during evaluation to determine exactly which comparator is being used. +*/ +var comparatorSymbols = map[string]OperatorSymbol{ + "==": EQ, + "!=": NEQ, + ">": GT, + ">=": GTE, + "<": LT, + "<=": LTE, + "=~": REQ, + "!~": NREQ, + "in": IN, +} + +var logicalSymbols = map[string]OperatorSymbol{ + "&&": AND, + "||": OR, +} + +var bitwiseSymbols = map[string]OperatorSymbol{ + "^": BITWISE_XOR, + "&": BITWISE_AND, + "|": BITWISE_OR, +} + +var bitwiseShiftSymbols = map[string]OperatorSymbol{ + ">>": BITWISE_RSHIFT, + "<<": BITWISE_LSHIFT, +} + +var additiveSymbols = map[string]OperatorSymbol{ + "+": PLUS, + "-": MINUS, +} + +var multiplicativeSymbols = map[string]OperatorSymbol{ + "*": MULTIPLY, + "/": DIVIDE, + "%": MODULUS, +} + +var exponentialSymbolsS = map[string]OperatorSymbol{ + "**": EXPONENT, +} + +var prefixSymbols = map[string]OperatorSymbol{ + "-": NEGATE, + "!": INVERT, + "~": BITWISE_NOT, +} + +var ternarySymbols = map[string]OperatorSymbol{ + "?": TERNARY_TRUE, + ":": TERNARY_FALSE, + "??": COALESCE, +} + +// this is defined separately from additiveSymbols et al because it's needed for parsing, not stage planning. +var modifierSymbols = map[string]OperatorSymbol{ + "+": PLUS, + "-": MINUS, + "*": MULTIPLY, + "/": DIVIDE, + "%": MODULUS, + "**": EXPONENT, + "&": BITWISE_AND, + "|": BITWISE_OR, + "^": BITWISE_XOR, + ">>": BITWISE_RSHIFT, + "<<": BITWISE_LSHIFT, +} + +var separatorSymbols = map[string]OperatorSymbol{ + ",": SEPARATE, +} + +/* + Returns true if this operator is contained by the given array of candidate symbols. + False otherwise. +*/ +func (this OperatorSymbol) IsModifierType(candidate []OperatorSymbol) bool { + + for _, symbolType := range candidate { + if this == symbolType { + return true + } + } + + return false +} + +/* + Generally used when formatting type check errors. + We could store the stringified symbol somewhere else and not require a duplicated codeblock to translate + OperatorSymbol to string, but that would require more memory, and another field somewhere. + Adding operators is rare enough that we just stringify it here instead. +*/ +func (this OperatorSymbol) String() string { + + switch this { + case NOOP: + return "NOOP" + case VALUE: + return "VALUE" + case EQ: + return "=" + case NEQ: + return "!=" + case GT: + return ">" + case LT: + return "<" + case GTE: + return ">=" + case LTE: + return "<=" + case REQ: + return "=~" + case NREQ: + return "!~" + case AND: + return "&&" + case OR: + return "||" + case IN: + return "in" + case BITWISE_AND: + return "&" + case BITWISE_OR: + return "|" + case BITWISE_XOR: + return "^" + case BITWISE_LSHIFT: + return "<<" + case BITWISE_RSHIFT: + return ">>" + case PLUS: + return "+" + case MINUS: + return "-" + case MULTIPLY: + return "*" + case DIVIDE: + return "/" + case MODULUS: + return "%" + case EXPONENT: + return "**" + case NEGATE: + return "-" + case INVERT: + return "!" + case BITWISE_NOT: + return "~" + case TERNARY_TRUE: + return "?" + case TERNARY_FALSE: + return ":" + case COALESCE: + return "??" + } + return "" +} diff --git a/vendor/github.com/Knetic/govaluate/README.md b/vendor/github.com/Knetic/govaluate/README.md new file mode 100644 index 00000000..2e5716d4 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/README.md @@ -0,0 +1,233 @@ +govaluate +==== + +[![Build Status](https://travis-ci.org/Knetic/govaluate.svg?branch=master)](https://travis-ci.org/Knetic/govaluate) +[![Godoc](https://img.shields.io/badge/godoc-reference-5272B4.svg)](https://godoc.org/github.com/Knetic/govaluate) +[![Go Report Card](https://goreportcard.com/badge/github.com/Knetic/govaluate)](https://goreportcard.com/report/github.com/Knetic/govaluate) +[![Gocover](https://gocover.io/_badge/github.com/Knetic/govaluate)](https://gocover.io/github.com/Knetic/govaluate) + +Provides support for evaluating arbitrary C-like artithmetic/string expressions. + +Why can't you just write these expressions in code? +-- + +Sometimes, you can't know ahead-of-time what an expression will look like, or you want those expressions to be configurable. +Perhaps you've got a set of data running through your application, and you want to allow your users to specify some validations to run on it before committing it to a database. Or maybe you've written a monitoring framework which is capable of gathering a bunch of metrics, then evaluating a few expressions to see if any metrics should be alerted upon, but the conditions for alerting are different for each monitor. + +A lot of people wind up writing their own half-baked style of evaluation language that fits their needs, but isn't complete. Or they wind up baking the expression into the actual executable, even if they know it's subject to change. These strategies may work, but they take time to implement, time for users to learn, and induce technical debt as requirements change. This library is meant to cover all the normal C-like expressions, so that you don't have to reinvent one of the oldest wheels on a computer. + +How do I use it? +-- + +You create a new EvaluableExpression, then call "Evaluate" on it. + +```go + expression, err := govaluate.NewEvaluableExpression("10 > 0"); + result, err := expression.Evaluate(nil); + // result is now set to "true", the bool value. +``` + +Cool, but how about with parameters? + +```go + expression, err := govaluate.NewEvaluableExpression("foo > 0"); + + parameters := make(map[string]interface{}, 8) + parameters["foo"] = -1; + + result, err := expression.Evaluate(parameters); + // result is now set to "false", the bool value. +``` + +That's cool, but we can almost certainly have done all that in code. What about a complex use case that involves some math? + +```go + expression, err := govaluate.NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90"); + + parameters := make(map[string]interface{}, 8) + parameters["requests_made"] = 100; + parameters["requests_succeeded"] = 80; + + result, err := expression.Evaluate(parameters); + // result is now set to "false", the bool value. +``` + +Or maybe you want to check the status of an alive check ("smoketest") page, which will be a string? + +```go + expression, err := govaluate.NewEvaluableExpression("http_response_body == 'service is ok'"); + + parameters := make(map[string]interface{}, 8) + parameters["http_response_body"] = "service is ok"; + + result, err := expression.Evaluate(parameters); + // result is now set to "true", the bool value. +``` + +These examples have all returned boolean values, but it's equally possible to return numeric ones. + +```go + expression, err := govaluate.NewEvaluableExpression("(mem_used / total_mem) * 100"); + + parameters := make(map[string]interface{}, 8) + parameters["total_mem"] = 1024; + parameters["mem_used"] = 512; + + result, err := expression.Evaluate(parameters); + // result is now set to "50.0", the float64 value. +``` + +You can also do date parsing, though the formats are somewhat limited. Stick to RF3339, ISO8061, unix date, or ruby date formats. If you're having trouble getting a date string to parse, check the list of formats actually used: [parsing.go:248](https://github.com/Knetic/govaluate/blob/0580e9b47a69125afa0e4ebd1cf93c49eb5a43ec/parsing.go#L258). + +```go + expression, err := govaluate.NewEvaluableExpression("'2014-01-02' > '2014-01-01 23:59:59'"); + result, err := expression.Evaluate(nil); + + // result is now set to true +``` + +Expressions are parsed once, and can be re-used multiple times. Parsing is the compute-intensive phase of the process, so if you intend to use the same expression with different parameters, just parse it once. Like so; + +```go + expression, err := govaluate.NewEvaluableExpression("response_time <= 100"); + parameters := make(map[string]interface{}, 8) + + for { + parameters["response_time"] = pingSomething(); + result, err := expression.Evaluate(parameters) + } +``` + +The normal C-standard order of operators is respected. When writing an expression, be sure that you either order the operators correctly, or use parenthesis to clarify which portions of an expression should be run first. + +Escaping characters +-- + +Sometimes you'll have parameters that have spaces, slashes, pluses, ampersands or some other character +that this library interprets as something special. For example, the following expression will not +act as one might expect: + + "response-time < 100" + +As written, the library will parse it as "[response] minus [time] is less than 100". In reality, +"response-time" is meant to be one variable that just happens to have a dash in it. + +There are two ways to work around this. First, you can escape the entire parameter name: + + "[response-time] < 100" + +Or you can use backslashes to escape only the minus sign. + + "response\\-time < 100" + +Backslashes can be used anywhere in an expression to escape the very next character. Square bracketed parameter names can be used instead of plain parameter names at any time. + +Functions +-- + +You may have cases where you want to call a function on a parameter during execution of the expression. Perhaps you want to aggregate some set of data, but don't know the exact aggregation you want to use until you're writing the expression itself. Or maybe you have a mathematical operation you want to perform, for which there is no operator; like `log` or `tan` or `sqrt`. For cases like this, you can provide a map of functions to `NewEvaluableExpressionWithFunctions`, which will then be able to use them during execution. For instance; + +```go + functions := map[string]govaluate.ExpressionFunction { + "strlen": func(args ...interface{}) (interface{}, error) { + length := len(args[0].(string)) + return (float64)(length), nil + }, + } + + expString := "strlen('someReallyLongInputString') <= 16" + expression, _ := govaluate.NewEvaluableExpressionWithFunctions(expString, functions) + + result, _ := expression.Evaluate(nil) + // result is now "false", the boolean value +``` + +Functions can accept any number of arguments, correctly handles nested functions, and arguments can be of any type (even if none of this library's operators support evaluation of that type). For instance, each of these usages of functions in an expression are valid (assuming that the appropriate functions and parameters are given): + +```go +"sqrt(x1 ** y1, x2 ** y2)" +"max(someValue, abs(anotherValue), 10 * lastValue)" +``` + +Functions cannot be passed as parameters, they must be known at the time when the expression is parsed, and are unchangeable after parsing. + +Accessors +-- + +If you have structs in your parameters, you can access their fields and methods in the usual way. For instance, given a struct that has a method "Echo", present in the parameters as `foo`, the following is valid: + + "foo.Echo('hello world')" + +Fields are accessed in a similar way. Assuming `foo` has a field called "Length": + + "foo.Length > 9000" + +Accessors can be nested to any depth, like the following + + "foo.Bar.Baz.SomeFunction()" + +However it is not _currently_ supported to access values in `map`s. So the following will not work + + "foo.SomeMap['key']" + +This may be convenient, but note that using accessors involves a _lot_ of reflection. This makes the expression about four times slower than just using a parameter (consult the benchmarks for more precise measurements on your system). +If at all reasonable, the author recommends extracting the values you care about into a parameter map beforehand, or defining a struct that implements the `Parameters` interface, and which grabs fields as required. If there are functions you want to use, it's better to pass them as expression functions (see the above section). These approaches use no reflection, and are designed to be fast and clean. + +What operators and types does this support? +-- + +* Modifiers: `+` `-` `/` `*` `&` `|` `^` `**` `%` `>>` `<<` +* Comparators: `>` `>=` `<` `<=` `==` `!=` `=~` `!~` +* Logical ops: `||` `&&` +* Numeric constants, as 64-bit floating point (`12345.678`) +* String constants (single quotes: `'foobar'`) +* Date constants (single quotes, using any permutation of RFC3339, ISO8601, ruby date, or unix date; date parsing is automatically tried with any string constant) +* Boolean constants: `true` `false` +* Parenthesis to control order of evaluation `(` `)` +* Arrays (anything separated by `,` within parenthesis: `(1, 2, 'foo')`) +* Prefixes: `!` `-` `~` +* Ternary conditional: `?` `:` +* Null coalescence: `??` + +See [MANUAL.md](https://github.com/Knetic/govaluate/blob/master/MANUAL.md) for exacting details on what types each operator supports. + +Types +-- + +Some operators don't make sense when used with some types. For instance, what does it mean to get the modulo of a string? What happens if you check to see if two numbers are logically AND'ed together? + +Everyone has a different intuition about the answers to these questions. To prevent confusion, this library will _refuse to operate_ upon types for which there is not an unambiguous meaning for the operation. See [MANUAL.md](https://github.com/Knetic/govaluate/blob/master/MANUAL.md) for details about what operators are valid for which types. + +Benchmarks +-- + +If you're concerned about the overhead of this library, a good range of benchmarks are built into this repo. You can run them with `go test -bench=.`. The library is built with an eye towards being quick, but has not been aggressively profiled and optimized. For most applications, though, it is completely fine. + +For a very rough idea of performance, here are the results output from a benchmark run on a 3rd-gen Macbook Pro (Linux Mint 17.1). + +``` +BenchmarkSingleParse-12 1000000 1382 ns/op +BenchmarkSimpleParse-12 200000 10771 ns/op +BenchmarkFullParse-12 30000 49383 ns/op +BenchmarkEvaluationSingle-12 50000000 30.1 ns/op +BenchmarkEvaluationNumericLiteral-12 10000000 119 ns/op +BenchmarkEvaluationLiteralModifiers-12 10000000 236 ns/op +BenchmarkEvaluationParameters-12 5000000 260 ns/op +BenchmarkEvaluationParametersModifiers-12 3000000 547 ns/op +BenchmarkComplexExpression-12 2000000 963 ns/op +BenchmarkRegexExpression-12 100000 20357 ns/op +BenchmarkConstantRegexExpression-12 1000000 1392 ns/op +ok +``` + +API Breaks +-- + +While this library has very few cases which will ever result in an API break, it can (and [has](https://github.com/Knetic/govaluate/releases/tag/v2.0.0)) happened. If you are using this in production, vendor the commit you've tested against, or use gopkg.in to redirect your import (e.g., `import "gopkg.in/Knetic/govaluate.v2"`). Master branch (while infrequent) _may_ at some point contain API breaking changes, and the author will have no way to communicate these to downstreams, other than creating a new major release. + +Releases will explicitly state when an API break happens, and if they do not specify an API break it should be safe to upgrade. + +License +-- + +This project is licensed under the MIT general use license. You're free to integrate, fork, and play with this code as you feel fit without consulting the author, as long as you provide proper credit to the author in your works. diff --git a/vendor/github.com/Knetic/govaluate/TokenKind.go b/vendor/github.com/Knetic/govaluate/TokenKind.go new file mode 100644 index 00000000..7c9516d2 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/TokenKind.go @@ -0,0 +1,75 @@ +package govaluate + +/* + Represents all valid types of tokens that a token can be. +*/ +type TokenKind int + +const ( + UNKNOWN TokenKind = iota + + PREFIX + NUMERIC + BOOLEAN + STRING + PATTERN + TIME + VARIABLE + FUNCTION + SEPARATOR + ACCESSOR + + COMPARATOR + LOGICALOP + MODIFIER + + CLAUSE + CLAUSE_CLOSE + + TERNARY +) + +/* + GetTokenKindString returns a string that describes the given TokenKind. + e.g., when passed the NUMERIC TokenKind, this returns the string "NUMERIC". +*/ +func (kind TokenKind) String() string { + + switch kind { + + case PREFIX: + return "PREFIX" + case NUMERIC: + return "NUMERIC" + case BOOLEAN: + return "BOOLEAN" + case STRING: + return "STRING" + case PATTERN: + return "PATTERN" + case TIME: + return "TIME" + case VARIABLE: + return "VARIABLE" + case FUNCTION: + return "FUNCTION" + case SEPARATOR: + return "SEPARATOR" + case COMPARATOR: + return "COMPARATOR" + case LOGICALOP: + return "LOGICALOP" + case MODIFIER: + return "MODIFIER" + case CLAUSE: + return "CLAUSE" + case CLAUSE_CLOSE: + return "CLAUSE_CLOSE" + case TERNARY: + return "TERNARY" + case ACCESSOR: + return "ACCESSOR" + } + + return "UNKNOWN" +} diff --git a/vendor/github.com/Knetic/govaluate/evaluationStage.go b/vendor/github.com/Knetic/govaluate/evaluationStage.go new file mode 100644 index 00000000..11ea5872 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/evaluationStage.go @@ -0,0 +1,516 @@ +package govaluate + +import ( + "errors" + "fmt" + "math" + "reflect" + "regexp" + "strings" +) + +const ( + logicalErrorFormat string = "Value '%v' cannot be used with the logical operator '%v', it is not a bool" + modifierErrorFormat string = "Value '%v' cannot be used with the modifier '%v', it is not a number" + comparatorErrorFormat string = "Value '%v' cannot be used with the comparator '%v', it is not a number" + ternaryErrorFormat string = "Value '%v' cannot be used with the ternary operator '%v', it is not a bool" + prefixErrorFormat string = "Value '%v' cannot be used with the prefix '%v'" +) + +type evaluationOperator func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) +type stageTypeCheck func(value interface{}) bool +type stageCombinedTypeCheck func(left interface{}, right interface{}) bool + +type evaluationStage struct { + symbol OperatorSymbol + + leftStage, rightStage *evaluationStage + + // the operation that will be used to evaluate this stage (such as adding [left] to [right] and return the result) + operator evaluationOperator + + // ensures that both left and right values are appropriate for this stage. Returns an error if they aren't operable. + leftTypeCheck stageTypeCheck + rightTypeCheck stageTypeCheck + + // if specified, will override whatever is used in "leftTypeCheck" and "rightTypeCheck". + // primarily used for specific operators that don't care which side a given type is on, but still requires one side to be of a given type + // (like string concat) + typeCheck stageCombinedTypeCheck + + // regardless of which type check is used, this string format will be used as the error message for type errors + typeErrorFormat string +} + +var ( + _true = interface{}(true) + _false = interface{}(false) +) + +func (this *evaluationStage) swapWith(other *evaluationStage) { + + temp := *other + other.setToNonStage(*this) + this.setToNonStage(temp) +} + +func (this *evaluationStage) setToNonStage(other evaluationStage) { + + this.symbol = other.symbol + this.operator = other.operator + this.leftTypeCheck = other.leftTypeCheck + this.rightTypeCheck = other.rightTypeCheck + this.typeCheck = other.typeCheck + this.typeErrorFormat = other.typeErrorFormat +} + +func (this *evaluationStage) isShortCircuitable() bool { + + switch this.symbol { + case AND: + fallthrough + case OR: + fallthrough + case TERNARY_TRUE: + fallthrough + case TERNARY_FALSE: + fallthrough + case COALESCE: + return true + } + + return false +} + +func noopStageRight(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return right, nil +} + +func addStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + + // string concat if either are strings + if isString(left) || isString(right) { + return fmt.Sprintf("%v%v", left, right), nil + } + + return left.(float64) + right.(float64), nil +} +func subtractStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return left.(float64) - right.(float64), nil +} +func multiplyStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return left.(float64) * right.(float64), nil +} +func divideStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return left.(float64) / right.(float64), nil +} +func exponentStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return math.Pow(left.(float64), right.(float64)), nil +} +func modulusStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return math.Mod(left.(float64), right.(float64)), nil +} +func gteStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + if isString(left) && isString(right) { + return boolIface(left.(string) >= right.(string)), nil + } + return boolIface(left.(float64) >= right.(float64)), nil +} +func gtStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + if isString(left) && isString(right) { + return boolIface(left.(string) > right.(string)), nil + } + return boolIface(left.(float64) > right.(float64)), nil +} +func lteStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + if isString(left) && isString(right) { + return boolIface(left.(string) <= right.(string)), nil + } + return boolIface(left.(float64) <= right.(float64)), nil +} +func ltStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + if isString(left) && isString(right) { + return boolIface(left.(string) < right.(string)), nil + } + return boolIface(left.(float64) < right.(float64)), nil +} +func equalStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return boolIface(reflect.DeepEqual(left, right)), nil +} +func notEqualStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return boolIface(!reflect.DeepEqual(left, right)), nil +} +func andStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return boolIface(left.(bool) && right.(bool)), nil +} +func orStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return boolIface(left.(bool) || right.(bool)), nil +} +func negateStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return -right.(float64), nil +} +func invertStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return boolIface(!right.(bool)), nil +} +func bitwiseNotStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return float64(^int64(right.(float64))), nil +} +func ternaryIfStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + if left.(bool) { + return right, nil + } + return nil, nil +} +func ternaryElseStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + if left != nil { + return left, nil + } + return right, nil +} + +func regexStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + + var pattern *regexp.Regexp + var err error + + switch right.(type) { + case string: + pattern, err = regexp.Compile(right.(string)) + if err != nil { + return nil, errors.New(fmt.Sprintf("Unable to compile regexp pattern '%v': %v", right, err)) + } + case *regexp.Regexp: + pattern = right.(*regexp.Regexp) + } + + return pattern.Match([]byte(left.(string))), nil +} + +func notRegexStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + + ret, err := regexStage(left, right, parameters) + if err != nil { + return nil, err + } + + return !(ret.(bool)), nil +} + +func bitwiseOrStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return float64(int64(left.(float64)) | int64(right.(float64))), nil +} +func bitwiseAndStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return float64(int64(left.(float64)) & int64(right.(float64))), nil +} +func bitwiseXORStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return float64(int64(left.(float64)) ^ int64(right.(float64))), nil +} +func leftShiftStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return float64(uint64(left.(float64)) << uint64(right.(float64))), nil +} +func rightShiftStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return float64(uint64(left.(float64)) >> uint64(right.(float64))), nil +} + +func makeParameterStage(parameterName string) evaluationOperator { + + return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + value, err := parameters.Get(parameterName) + if err != nil { + return nil, err + } + + return value, nil + } +} + +func makeLiteralStage(literal interface{}) evaluationOperator { + return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + return literal, nil + } +} + +func makeFunctionStage(function ExpressionFunction) evaluationOperator { + + return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + + if right == nil { + return function() + } + + switch right.(type) { + case []interface{}: + return function(right.([]interface{})...) + default: + return function(right) + } + } +} + +func typeConvertParam(p reflect.Value, t reflect.Type) (ret reflect.Value, err error) { + defer func() { + if r := recover(); r != nil { + errorMsg := fmt.Sprintf("Argument type conversion failed: failed to convert '%s' to '%s'", p.Kind().String(), t.Kind().String()) + err = errors.New(errorMsg) + ret = p + } + }() + + return p.Convert(t), nil +} + +func typeConvertParams(method reflect.Value, params []reflect.Value) ([]reflect.Value, error) { + + methodType := method.Type() + numIn := methodType.NumIn() + numParams := len(params) + + if numIn != numParams { + if numIn > numParams { + return nil, fmt.Errorf("Too few arguments to parameter call: got %d arguments, expected %d", len(params), numIn) + } + return nil, fmt.Errorf("Too many arguments to parameter call: got %d arguments, expected %d", len(params), numIn) + } + + for i := 0; i < numIn; i++ { + t := methodType.In(i) + p := params[i] + pt := p.Type() + + if t.Kind() != pt.Kind() { + np, err := typeConvertParam(p, t) + if err != nil { + return nil, err + } + params[i] = np + } + } + + return params, nil +} + +func makeAccessorStage(pair []string) evaluationOperator { + + reconstructed := strings.Join(pair, ".") + + return func(left interface{}, right interface{}, parameters Parameters) (ret interface{}, err error) { + + var params []reflect.Value + + value, err := parameters.Get(pair[0]) + if err != nil { + return nil, err + } + + // while this library generally tries to handle panic-inducing cases on its own, + // accessors are a sticky case which have a lot of possible ways to fail. + // therefore every call to an accessor sets up a defer that tries to recover from panics, converting them to errors. + defer func() { + if r := recover(); r != nil { + errorMsg := fmt.Sprintf("Failed to access '%s': %v", reconstructed, r.(string)) + err = errors.New(errorMsg) + ret = nil + } + }() + + for i := 1; i < len(pair); i++ { + + coreValue := reflect.ValueOf(value) + + var corePtrVal reflect.Value + + // if this is a pointer, resolve it. + if coreValue.Kind() == reflect.Ptr { + corePtrVal = coreValue + coreValue = coreValue.Elem() + } + + if coreValue.Kind() != reflect.Struct { + return nil, errors.New("Unable to access '" + pair[i] + "', '" + pair[i-1] + "' is not a struct") + } + + field := coreValue.FieldByName(pair[i]) + if field != (reflect.Value{}) { + value = field.Interface() + continue + } + + method := coreValue.MethodByName(pair[i]) + if method == (reflect.Value{}) { + if corePtrVal.IsValid() { + method = corePtrVal.MethodByName(pair[i]) + } + if method == (reflect.Value{}) { + return nil, errors.New("No method or field '" + pair[i] + "' present on parameter '" + pair[i-1] + "'") + } + } + + switch right.(type) { + case []interface{}: + + givenParams := right.([]interface{}) + params = make([]reflect.Value, len(givenParams)) + for idx, _ := range givenParams { + params[idx] = reflect.ValueOf(givenParams[idx]) + } + + default: + + if right == nil { + params = []reflect.Value{} + break + } + + params = []reflect.Value{reflect.ValueOf(right.(interface{}))} + } + + params, err = typeConvertParams(method, params) + + if err != nil { + return nil, errors.New("Method call failed - '" + pair[0] + "." + pair[1] + "': " + err.Error()) + } + + returned := method.Call(params) + retLength := len(returned) + + if retLength == 0 { + return nil, errors.New("Method call '" + pair[i-1] + "." + pair[i] + "' did not return any values.") + } + + if retLength == 1 { + + value = returned[0].Interface() + continue + } + + if retLength == 2 { + + errIface := returned[1].Interface() + err, validType := errIface.(error) + + if validType && errIface != nil { + return returned[0].Interface(), err + } + + value = returned[0].Interface() + continue + } + + return nil, errors.New("Method call '" + pair[0] + "." + pair[1] + "' did not return either one value, or a value and an error. Cannot interpret meaning.") + } + + value = castToFloat64(value) + return value, nil + } +} + +func separatorStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + + var ret []interface{} + + switch left.(type) { + case []interface{}: + ret = append(left.([]interface{}), right) + default: + ret = []interface{}{left, right} + } + + return ret, nil +} + +func inStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + + for _, value := range right.([]interface{}) { + if left == value { + return true, nil + } + } + return false, nil +} + +// + +func isString(value interface{}) bool { + + switch value.(type) { + case string: + return true + } + return false +} + +func isRegexOrString(value interface{}) bool { + + switch value.(type) { + case string: + return true + case *regexp.Regexp: + return true + } + return false +} + +func isBool(value interface{}) bool { + switch value.(type) { + case bool: + return true + } + return false +} + +func isFloat64(value interface{}) bool { + switch value.(type) { + case float64: + return true + } + return false +} + +/* + Addition usually means between numbers, but can also mean string concat. + String concat needs one (or both) of the sides to be a string. +*/ +func additionTypeCheck(left interface{}, right interface{}) bool { + + if isFloat64(left) && isFloat64(right) { + return true + } + if !isString(left) && !isString(right) { + return false + } + return true +} + +/* + Comparison can either be between numbers, or lexicographic between two strings, + but never between the two. +*/ +func comparatorTypeCheck(left interface{}, right interface{}) bool { + + if isFloat64(left) && isFloat64(right) { + return true + } + if isString(left) && isString(right) { + return true + } + return false +} + +func isArray(value interface{}) bool { + switch value.(type) { + case []interface{}: + return true + } + return false +} + +/* + Converting a boolean to an interface{} requires an allocation. + We can use interned bools to avoid this cost. +*/ +func boolIface(b bool) interface{} { + if b { + return _true + } + return _false +} diff --git a/vendor/github.com/Knetic/govaluate/expressionFunctions.go b/vendor/github.com/Knetic/govaluate/expressionFunctions.go new file mode 100644 index 00000000..ac6592b3 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/expressionFunctions.go @@ -0,0 +1,8 @@ +package govaluate + +/* + Represents a function that can be called from within an expression. + This method must return an error if, for any reason, it is unable to produce exactly one unambiguous result. + An error returned will halt execution of the expression. +*/ +type ExpressionFunction func(arguments ...interface{}) (interface{}, error) diff --git a/vendor/github.com/Knetic/govaluate/expressionOutputStream.go b/vendor/github.com/Knetic/govaluate/expressionOutputStream.go new file mode 100644 index 00000000..88a84163 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/expressionOutputStream.go @@ -0,0 +1,46 @@ +package govaluate + +import ( + "bytes" +) + +/* + Holds a series of "transactions" which represent each token as it is output by an outputter (such as ToSQLQuery()). + Some outputs (such as SQL) require a function call or non-c-like syntax to represent an expression. + To accomplish this, this struct keeps track of each translated token as it is output, and can return and rollback those transactions. +*/ +type expressionOutputStream struct { + transactions []string +} + +func (this *expressionOutputStream) add(transaction string) { + this.transactions = append(this.transactions, transaction) +} + +func (this *expressionOutputStream) rollback() string { + + index := len(this.transactions) - 1 + ret := this.transactions[index] + + this.transactions = this.transactions[:index] + return ret +} + +func (this *expressionOutputStream) createString(delimiter string) string { + + var retBuffer bytes.Buffer + var transaction string + + penultimate := len(this.transactions) - 1 + + for i := 0; i < penultimate; i++ { + + transaction = this.transactions[i] + + retBuffer.WriteString(transaction) + retBuffer.WriteString(delimiter) + } + retBuffer.WriteString(this.transactions[penultimate]) + + return retBuffer.String() +} diff --git a/vendor/github.com/Knetic/govaluate/lexerState.go b/vendor/github.com/Knetic/govaluate/lexerState.go new file mode 100644 index 00000000..6726e909 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/lexerState.go @@ -0,0 +1,373 @@ +package govaluate + +import ( + "errors" + "fmt" +) + +type lexerState struct { + isEOF bool + isNullable bool + kind TokenKind + validNextKinds []TokenKind +} + +// lexer states. +// Constant for all purposes except compiler. +var validLexerStates = []lexerState{ + + lexerState{ + kind: UNKNOWN, + isEOF: false, + isNullable: true, + validNextKinds: []TokenKind{ + + PREFIX, + NUMERIC, + BOOLEAN, + VARIABLE, + PATTERN, + FUNCTION, + ACCESSOR, + STRING, + TIME, + CLAUSE, + }, + }, + + lexerState{ + + kind: CLAUSE, + isEOF: false, + isNullable: true, + validNextKinds: []TokenKind{ + + PREFIX, + NUMERIC, + BOOLEAN, + VARIABLE, + PATTERN, + FUNCTION, + ACCESSOR, + STRING, + TIME, + CLAUSE, + CLAUSE_CLOSE, + }, + }, + + lexerState{ + + kind: CLAUSE_CLOSE, + isEOF: true, + isNullable: true, + validNextKinds: []TokenKind{ + + COMPARATOR, + MODIFIER, + NUMERIC, + BOOLEAN, + VARIABLE, + STRING, + PATTERN, + TIME, + CLAUSE, + CLAUSE_CLOSE, + LOGICALOP, + TERNARY, + SEPARATOR, + }, + }, + + lexerState{ + + kind: NUMERIC, + isEOF: true, + isNullable: false, + validNextKinds: []TokenKind{ + + MODIFIER, + COMPARATOR, + LOGICALOP, + CLAUSE_CLOSE, + TERNARY, + SEPARATOR, + }, + }, + lexerState{ + + kind: BOOLEAN, + isEOF: true, + isNullable: false, + validNextKinds: []TokenKind{ + + MODIFIER, + COMPARATOR, + LOGICALOP, + CLAUSE_CLOSE, + TERNARY, + SEPARATOR, + }, + }, + lexerState{ + + kind: STRING, + isEOF: true, + isNullable: false, + validNextKinds: []TokenKind{ + + MODIFIER, + COMPARATOR, + LOGICALOP, + CLAUSE_CLOSE, + TERNARY, + SEPARATOR, + }, + }, + lexerState{ + + kind: TIME, + isEOF: true, + isNullable: false, + validNextKinds: []TokenKind{ + + MODIFIER, + COMPARATOR, + LOGICALOP, + CLAUSE_CLOSE, + SEPARATOR, + }, + }, + lexerState{ + + kind: PATTERN, + isEOF: true, + isNullable: false, + validNextKinds: []TokenKind{ + + MODIFIER, + COMPARATOR, + LOGICALOP, + CLAUSE_CLOSE, + SEPARATOR, + }, + }, + lexerState{ + + kind: VARIABLE, + isEOF: true, + isNullable: false, + validNextKinds: []TokenKind{ + + MODIFIER, + COMPARATOR, + LOGICALOP, + CLAUSE_CLOSE, + TERNARY, + SEPARATOR, + }, + }, + lexerState{ + + kind: MODIFIER, + isEOF: false, + isNullable: false, + validNextKinds: []TokenKind{ + + PREFIX, + NUMERIC, + VARIABLE, + FUNCTION, + ACCESSOR, + STRING, + BOOLEAN, + CLAUSE, + CLAUSE_CLOSE, + }, + }, + lexerState{ + + kind: COMPARATOR, + isEOF: false, + isNullable: false, + validNextKinds: []TokenKind{ + + PREFIX, + NUMERIC, + BOOLEAN, + VARIABLE, + FUNCTION, + ACCESSOR, + STRING, + TIME, + CLAUSE, + CLAUSE_CLOSE, + PATTERN, + }, + }, + lexerState{ + + kind: LOGICALOP, + isEOF: false, + isNullable: false, + validNextKinds: []TokenKind{ + + PREFIX, + NUMERIC, + BOOLEAN, + VARIABLE, + FUNCTION, + ACCESSOR, + STRING, + TIME, + CLAUSE, + CLAUSE_CLOSE, + }, + }, + lexerState{ + + kind: PREFIX, + isEOF: false, + isNullable: false, + validNextKinds: []TokenKind{ + + NUMERIC, + BOOLEAN, + VARIABLE, + FUNCTION, + ACCESSOR, + CLAUSE, + CLAUSE_CLOSE, + }, + }, + + lexerState{ + + kind: TERNARY, + isEOF: false, + isNullable: false, + validNextKinds: []TokenKind{ + + PREFIX, + NUMERIC, + BOOLEAN, + STRING, + TIME, + VARIABLE, + FUNCTION, + ACCESSOR, + CLAUSE, + SEPARATOR, + }, + }, + lexerState{ + + kind: FUNCTION, + isEOF: false, + isNullable: false, + validNextKinds: []TokenKind{ + CLAUSE, + }, + }, + lexerState{ + + kind: ACCESSOR, + isEOF: true, + isNullable: false, + validNextKinds: []TokenKind{ + CLAUSE, + MODIFIER, + COMPARATOR, + LOGICALOP, + CLAUSE_CLOSE, + TERNARY, + SEPARATOR, + }, + }, + lexerState{ + + kind: SEPARATOR, + isEOF: false, + isNullable: true, + validNextKinds: []TokenKind{ + + PREFIX, + NUMERIC, + BOOLEAN, + STRING, + TIME, + VARIABLE, + FUNCTION, + ACCESSOR, + CLAUSE, + }, + }, +} + +func (this lexerState) canTransitionTo(kind TokenKind) bool { + + for _, validKind := range this.validNextKinds { + + if validKind == kind { + return true + } + } + + return false +} + +func checkExpressionSyntax(tokens []ExpressionToken) error { + + var state lexerState + var lastToken ExpressionToken + var err error + + state = validLexerStates[0] + + for _, token := range tokens { + + if !state.canTransitionTo(token.Kind) { + + // call out a specific error for tokens looking like they want to be functions. + if lastToken.Kind == VARIABLE && token.Kind == CLAUSE { + return errors.New("Undefined function " + lastToken.Value.(string)) + } + + firstStateName := fmt.Sprintf("%s [%v]", state.kind.String(), lastToken.Value) + nextStateName := fmt.Sprintf("%s [%v]", token.Kind.String(), token.Value) + + return errors.New("Cannot transition token types from " + firstStateName + " to " + nextStateName) + } + + state, err = getLexerStateForToken(token.Kind) + if err != nil { + return err + } + + if !state.isNullable && token.Value == nil { + + errorMsg := fmt.Sprintf("Token kind '%v' cannot have a nil value", token.Kind.String()) + return errors.New(errorMsg) + } + + lastToken = token + } + + if !state.isEOF { + return errors.New("Unexpected end of expression") + } + return nil +} + +func getLexerStateForToken(kind TokenKind) (lexerState, error) { + + for _, possibleState := range validLexerStates { + + if possibleState.kind == kind { + return possibleState, nil + } + } + + errorMsg := fmt.Sprintf("No lexer state found for token kind '%v'\n", kind.String()) + return validLexerStates[0], errors.New(errorMsg) +} diff --git a/vendor/github.com/Knetic/govaluate/lexerStream.go b/vendor/github.com/Knetic/govaluate/lexerStream.go new file mode 100644 index 00000000..b72e6bdb --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/lexerStream.go @@ -0,0 +1,39 @@ +package govaluate + +type lexerStream struct { + source []rune + position int + length int +} + +func newLexerStream(source string) *lexerStream { + + var ret *lexerStream + var runes []rune + + for _, character := range source { + runes = append(runes, character) + } + + ret = new(lexerStream) + ret.source = runes + ret.length = len(runes) + return ret +} + +func (this *lexerStream) readCharacter() rune { + + var character rune + + character = this.source[this.position] + this.position += 1 + return character +} + +func (this *lexerStream) rewind(amount int) { + this.position -= amount +} + +func (this lexerStream) canRead() bool { + return this.position < this.length +} diff --git a/vendor/github.com/Knetic/govaluate/parameters.go b/vendor/github.com/Knetic/govaluate/parameters.go new file mode 100644 index 00000000..6c5b9ecb --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/parameters.go @@ -0,0 +1,32 @@ +package govaluate + +import ( + "errors" +) + +/* + Parameters is a collection of named parameters that can be used by an EvaluableExpression to retrieve parameters + when an expression tries to use them. +*/ +type Parameters interface { + + /* + Get gets the parameter of the given name, or an error if the parameter is unavailable. + Failure to find the given parameter should be indicated by returning an error. + */ + Get(name string) (interface{}, error) +} + +type MapParameters map[string]interface{} + +func (p MapParameters) Get(name string) (interface{}, error) { + + value, found := p[name] + + if !found { + errorMessage := "No parameter '" + name + "' found." + return nil, errors.New(errorMessage) + } + + return value, nil +} diff --git a/vendor/github.com/Knetic/govaluate/parsing.go b/vendor/github.com/Knetic/govaluate/parsing.go new file mode 100644 index 00000000..40c7ed2c --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/parsing.go @@ -0,0 +1,526 @@ +package govaluate + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strconv" + "strings" + "time" + "unicode" +) + +func parseTokens(expression string, functions map[string]ExpressionFunction) ([]ExpressionToken, error) { + + var ret []ExpressionToken + var token ExpressionToken + var stream *lexerStream + var state lexerState + var err error + var found bool + + stream = newLexerStream(expression) + state = validLexerStates[0] + + for stream.canRead() { + + token, err, found = readToken(stream, state, functions) + + if err != nil { + return ret, err + } + + if !found { + break + } + + state, err = getLexerStateForToken(token.Kind) + if err != nil { + return ret, err + } + + // append this valid token + ret = append(ret, token) + } + + err = checkBalance(ret) + if err != nil { + return nil, err + } + + return ret, nil +} + +func readToken(stream *lexerStream, state lexerState, functions map[string]ExpressionFunction) (ExpressionToken, error, bool) { + + var function ExpressionFunction + var ret ExpressionToken + var tokenValue interface{} + var tokenTime time.Time + var tokenString string + var kind TokenKind + var character rune + var found bool + var completed bool + var err error + + // numeric is 0-9, or . or 0x followed by digits + // string starts with ' + // variable is alphanumeric, always starts with a letter + // bracket always means variable + // symbols are anything non-alphanumeric + // all others read into a buffer until they reach the end of the stream + for stream.canRead() { + + character = stream.readCharacter() + + if unicode.IsSpace(character) { + continue + } + + kind = UNKNOWN + + // numeric constant + if isNumeric(character) { + + if stream.canRead() && character == '0' { + character = stream.readCharacter() + + if stream.canRead() && character == 'x' { + tokenString, _ = readUntilFalse(stream, false, true, true, isHexDigit) + tokenValueInt, err := strconv.ParseUint(tokenString, 16, 64) + + if err != nil { + errorMsg := fmt.Sprintf("Unable to parse hex value '%v' to uint64\n", tokenString) + return ExpressionToken{}, errors.New(errorMsg), false + } + + kind = NUMERIC + tokenValue = float64(tokenValueInt) + break + } else { + stream.rewind(1) + } + } + + tokenString = readTokenUntilFalse(stream, isNumeric) + tokenValue, err = strconv.ParseFloat(tokenString, 64) + + if err != nil { + errorMsg := fmt.Sprintf("Unable to parse numeric value '%v' to float64\n", tokenString) + return ExpressionToken{}, errors.New(errorMsg), false + } + kind = NUMERIC + break + } + + // comma, separator + if character == ',' { + + tokenValue = "," + kind = SEPARATOR + break + } + + // escaped variable + if character == '[' { + + tokenValue, completed = readUntilFalse(stream, true, false, true, isNotClosingBracket) + kind = VARIABLE + + if !completed { + return ExpressionToken{}, errors.New("Unclosed parameter bracket"), false + } + + // above method normally rewinds us to the closing bracket, which we want to skip. + stream.rewind(-1) + break + } + + // regular variable - or function? + if unicode.IsLetter(character) { + + tokenString = readTokenUntilFalse(stream, isVariableName) + + tokenValue = tokenString + kind = VARIABLE + + // boolean? + if tokenValue == "true" { + + kind = BOOLEAN + tokenValue = true + } else { + + if tokenValue == "false" { + + kind = BOOLEAN + tokenValue = false + } + } + + // textual operator? + if tokenValue == "in" || tokenValue == "IN" { + + // force lower case for consistency + tokenValue = "in" + kind = COMPARATOR + } + + // function? + function, found = functions[tokenString] + if found { + kind = FUNCTION + tokenValue = function + } + + // accessor? + accessorIndex := strings.Index(tokenString, ".") + if accessorIndex > 0 { + + // check that it doesn't end with a hanging period + if tokenString[len(tokenString)-1] == '.' { + errorMsg := fmt.Sprintf("Hanging accessor on token '%s'", tokenString) + return ExpressionToken{}, errors.New(errorMsg), false + } + + kind = ACCESSOR + splits := strings.Split(tokenString, ".") + tokenValue = splits + + // check that none of them are unexported + for i := 1; i < len(splits); i++ { + + firstCharacter := getFirstRune(splits[i]) + + if unicode.ToUpper(firstCharacter) != firstCharacter { + errorMsg := fmt.Sprintf("Unable to access unexported field '%s' in token '%s'", splits[i], tokenString) + return ExpressionToken{}, errors.New(errorMsg), false + } + } + } + break + } + + if !isNotQuote(character) { + tokenValue, completed = readUntilFalse(stream, true, false, true, isNotQuote) + + if !completed { + return ExpressionToken{}, errors.New("Unclosed string literal"), false + } + + // advance the stream one position, since reading until false assumes the terminator is a real token + stream.rewind(-1) + + // check to see if this can be parsed as a time. + tokenTime, found = tryParseTime(tokenValue.(string)) + if found { + kind = TIME + tokenValue = tokenTime + } else { + kind = STRING + } + break + } + + if character == '(' { + tokenValue = character + kind = CLAUSE + break + } + + if character == ')' { + tokenValue = character + kind = CLAUSE_CLOSE + break + } + + // must be a known symbol + tokenString = readTokenUntilFalse(stream, isNotAlphanumeric) + tokenValue = tokenString + + // quick hack for the case where "-" can mean "prefixed negation" or "minus", which are used + // very differently. + if state.canTransitionTo(PREFIX) { + _, found = prefixSymbols[tokenString] + if found { + + kind = PREFIX + break + } + } + _, found = modifierSymbols[tokenString] + if found { + + kind = MODIFIER + break + } + + _, found = logicalSymbols[tokenString] + if found { + + kind = LOGICALOP + break + } + + _, found = comparatorSymbols[tokenString] + if found { + + kind = COMPARATOR + break + } + + _, found = ternarySymbols[tokenString] + if found { + + kind = TERNARY + break + } + + errorMessage := fmt.Sprintf("Invalid token: '%s'", tokenString) + return ret, errors.New(errorMessage), false + } + + ret.Kind = kind + ret.Value = tokenValue + + return ret, nil, (kind != UNKNOWN) +} + +func readTokenUntilFalse(stream *lexerStream, condition func(rune) bool) string { + + var ret string + + stream.rewind(1) + ret, _ = readUntilFalse(stream, false, true, true, condition) + return ret +} + +/* + Returns the string that was read until the given [condition] was false, or whitespace was broken. + Returns false if the stream ended before whitespace was broken or condition was met. +*/ +func readUntilFalse(stream *lexerStream, includeWhitespace bool, breakWhitespace bool, allowEscaping bool, condition func(rune) bool) (string, bool) { + + var tokenBuffer bytes.Buffer + var character rune + var conditioned bool + + conditioned = false + + for stream.canRead() { + + character = stream.readCharacter() + + // Use backslashes to escape anything + if allowEscaping && character == '\\' { + + character = stream.readCharacter() + tokenBuffer.WriteString(string(character)) + continue + } + + if unicode.IsSpace(character) { + + if breakWhitespace && tokenBuffer.Len() > 0 { + conditioned = true + break + } + if !includeWhitespace { + continue + } + } + + if condition(character) { + tokenBuffer.WriteString(string(character)) + } else { + conditioned = true + stream.rewind(1) + break + } + } + + return tokenBuffer.String(), conditioned +} + +/* + Checks to see if any optimizations can be performed on the given [tokens], which form a complete, valid expression. + The returns slice will represent the optimized (or unmodified) list of tokens to use. +*/ +func optimizeTokens(tokens []ExpressionToken) ([]ExpressionToken, error) { + + var token ExpressionToken + var symbol OperatorSymbol + var err error + var index int + + for index, token = range tokens { + + // if we find a regex operator, and the right-hand value is a constant, precompile and replace with a pattern. + if token.Kind != COMPARATOR { + continue + } + + symbol = comparatorSymbols[token.Value.(string)] + if symbol != REQ && symbol != NREQ { + continue + } + + index++ + token = tokens[index] + if token.Kind == STRING { + + token.Kind = PATTERN + token.Value, err = regexp.Compile(token.Value.(string)) + + if err != nil { + return tokens, err + } + + tokens[index] = token + } + } + return tokens, nil +} + +/* + Checks the balance of tokens which have multiple parts, such as parenthesis. +*/ +func checkBalance(tokens []ExpressionToken) error { + + var stream *tokenStream + var token ExpressionToken + var parens int + + stream = newTokenStream(tokens) + + for stream.hasNext() { + + token = stream.next() + if token.Kind == CLAUSE { + parens++ + continue + } + if token.Kind == CLAUSE_CLOSE { + parens-- + continue + } + } + + if parens != 0 { + return errors.New("Unbalanced parenthesis") + } + return nil +} + +func isDigit(character rune) bool { + return unicode.IsDigit(character) +} + +func isHexDigit(character rune) bool { + + character = unicode.ToLower(character) + + return unicode.IsDigit(character) || + character == 'a' || + character == 'b' || + character == 'c' || + character == 'd' || + character == 'e' || + character == 'f' +} + +func isNumeric(character rune) bool { + + return unicode.IsDigit(character) || character == '.' +} + +func isNotQuote(character rune) bool { + + return character != '\'' && character != '"' +} + +func isNotAlphanumeric(character rune) bool { + + return !(unicode.IsDigit(character) || + unicode.IsLetter(character) || + character == '(' || + character == ')' || + character == '[' || + character == ']' || // starting to feel like there needs to be an `isOperation` func (#59) + !isNotQuote(character)) +} + +func isVariableName(character rune) bool { + + return unicode.IsLetter(character) || + unicode.IsDigit(character) || + character == '_' || + character == '.' +} + +func isNotClosingBracket(character rune) bool { + + return character != ']' +} + +/* + Attempts to parse the [candidate] as a Time. + Tries a series of standardized date formats, returns the Time if one applies, + otherwise returns false through the second return. +*/ +func tryParseTime(candidate string) (time.Time, bool) { + + var ret time.Time + var found bool + + timeFormats := [...]string{ + time.ANSIC, + time.UnixDate, + time.RubyDate, + time.Kitchen, + time.RFC3339, + time.RFC3339Nano, + "2006-01-02", // RFC 3339 + "2006-01-02 15:04", // RFC 3339 with minutes + "2006-01-02 15:04:05", // RFC 3339 with seconds + "2006-01-02 15:04:05-07:00", // RFC 3339 with seconds and timezone + "2006-01-02T15Z0700", // ISO8601 with hour + "2006-01-02T15:04Z0700", // ISO8601 with minutes + "2006-01-02T15:04:05Z0700", // ISO8601 with seconds + "2006-01-02T15:04:05.999999999Z0700", // ISO8601 with nanoseconds + } + + for _, format := range timeFormats { + + ret, found = tryParseExactTime(candidate, format) + if found { + return ret, true + } + } + + return time.Now(), false +} + +func tryParseExactTime(candidate string, format string) (time.Time, bool) { + + var ret time.Time + var err error + + ret, err = time.ParseInLocation(format, candidate, time.Local) + if err != nil { + return time.Now(), false + } + + return ret, true +} + +func getFirstRune(candidate string) rune { + + for _, character := range candidate { + return character + } + + return 0 +} diff --git a/vendor/github.com/Knetic/govaluate/sanitizedParameters.go b/vendor/github.com/Knetic/govaluate/sanitizedParameters.go new file mode 100644 index 00000000..28bd795d --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/sanitizedParameters.go @@ -0,0 +1,43 @@ +package govaluate + +// sanitizedParameters is a wrapper for Parameters that does sanitization as +// parameters are accessed. +type sanitizedParameters struct { + orig Parameters +} + +func (p sanitizedParameters) Get(key string) (interface{}, error) { + value, err := p.orig.Get(key) + if err != nil { + return nil, err + } + + return castToFloat64(value), nil +} + +func castToFloat64(value interface{}) interface{} { + switch value.(type) { + case uint8: + return float64(value.(uint8)) + case uint16: + return float64(value.(uint16)) + case uint32: + return float64(value.(uint32)) + case uint64: + return float64(value.(uint64)) + case int8: + return float64(value.(int8)) + case int16: + return float64(value.(int16)) + case int32: + return float64(value.(int32)) + case int64: + return float64(value.(int64)) + case int: + return float64(value.(int)) + case float32: + return float64(value.(float32)) + } + + return value +} diff --git a/vendor/github.com/Knetic/govaluate/stagePlanner.go b/vendor/github.com/Knetic/govaluate/stagePlanner.go new file mode 100644 index 00000000..d71ed129 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/stagePlanner.go @@ -0,0 +1,724 @@ +package govaluate + +import ( + "errors" + "fmt" + "time" +) + +var stageSymbolMap = map[OperatorSymbol]evaluationOperator{ + EQ: equalStage, + NEQ: notEqualStage, + GT: gtStage, + LT: ltStage, + GTE: gteStage, + LTE: lteStage, + REQ: regexStage, + NREQ: notRegexStage, + AND: andStage, + OR: orStage, + IN: inStage, + BITWISE_OR: bitwiseOrStage, + BITWISE_AND: bitwiseAndStage, + BITWISE_XOR: bitwiseXORStage, + BITWISE_LSHIFT: leftShiftStage, + BITWISE_RSHIFT: rightShiftStage, + PLUS: addStage, + MINUS: subtractStage, + MULTIPLY: multiplyStage, + DIVIDE: divideStage, + MODULUS: modulusStage, + EXPONENT: exponentStage, + NEGATE: negateStage, + INVERT: invertStage, + BITWISE_NOT: bitwiseNotStage, + TERNARY_TRUE: ternaryIfStage, + TERNARY_FALSE: ternaryElseStage, + COALESCE: ternaryElseStage, + SEPARATE: separatorStage, +} + +/* + A "precedent" is a function which will recursively parse new evaluateionStages from a given stream of tokens. + It's called a `precedent` because it is expected to handle exactly what precedence of operator, + and defer to other `precedent`s for other operators. +*/ +type precedent func(stream *tokenStream) (*evaluationStage, error) + +/* + A convenience function for specifying the behavior of a `precedent`. + Most `precedent` functions can be described by the same function, just with different type checks, symbols, and error formats. + This struct is passed to `makePrecedentFromPlanner` to create a `precedent` function. +*/ +type precedencePlanner struct { + validSymbols map[string]OperatorSymbol + validKinds []TokenKind + + typeErrorFormat string + + next precedent + nextRight precedent +} + +var planPrefix precedent +var planExponential precedent +var planMultiplicative precedent +var planAdditive precedent +var planBitwise precedent +var planShift precedent +var planComparator precedent +var planLogicalAnd precedent +var planLogicalOr precedent +var planTernary precedent +var planSeparator precedent + +func init() { + + // all these stages can use the same code (in `planPrecedenceLevel`) to execute, + // they simply need different type checks, symbols, and recursive precedents. + // While not all precedent phases are listed here, most can be represented this way. + planPrefix = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: prefixSymbols, + validKinds: []TokenKind{PREFIX}, + typeErrorFormat: prefixErrorFormat, + nextRight: planFunction, + }) + planExponential = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: exponentialSymbolsS, + validKinds: []TokenKind{MODIFIER}, + typeErrorFormat: modifierErrorFormat, + next: planFunction, + }) + planMultiplicative = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: multiplicativeSymbols, + validKinds: []TokenKind{MODIFIER}, + typeErrorFormat: modifierErrorFormat, + next: planExponential, + }) + planAdditive = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: additiveSymbols, + validKinds: []TokenKind{MODIFIER}, + typeErrorFormat: modifierErrorFormat, + next: planMultiplicative, + }) + planShift = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: bitwiseShiftSymbols, + validKinds: []TokenKind{MODIFIER}, + typeErrorFormat: modifierErrorFormat, + next: planAdditive, + }) + planBitwise = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: bitwiseSymbols, + validKinds: []TokenKind{MODIFIER}, + typeErrorFormat: modifierErrorFormat, + next: planShift, + }) + planComparator = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: comparatorSymbols, + validKinds: []TokenKind{COMPARATOR}, + typeErrorFormat: comparatorErrorFormat, + next: planBitwise, + }) + planLogicalAnd = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: map[string]OperatorSymbol{"&&": AND}, + validKinds: []TokenKind{LOGICALOP}, + typeErrorFormat: logicalErrorFormat, + next: planComparator, + }) + planLogicalOr = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: map[string]OperatorSymbol{"||": OR}, + validKinds: []TokenKind{LOGICALOP}, + typeErrorFormat: logicalErrorFormat, + next: planLogicalAnd, + }) + planTernary = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: ternarySymbols, + validKinds: []TokenKind{TERNARY}, + typeErrorFormat: ternaryErrorFormat, + next: planLogicalOr, + }) + planSeparator = makePrecedentFromPlanner(&precedencePlanner{ + validSymbols: separatorSymbols, + validKinds: []TokenKind{SEPARATOR}, + next: planTernary, + }) +} + +/* + Given a planner, creates a function which will evaluate a specific precedence level of operators, + and link it to other `precedent`s which recurse to parse other precedence levels. +*/ +func makePrecedentFromPlanner(planner *precedencePlanner) precedent { + + var generated precedent + var nextRight precedent + + generated = func(stream *tokenStream) (*evaluationStage, error) { + return planPrecedenceLevel( + stream, + planner.typeErrorFormat, + planner.validSymbols, + planner.validKinds, + nextRight, + planner.next, + ) + } + + if planner.nextRight != nil { + nextRight = planner.nextRight + } else { + nextRight = generated + } + + return generated +} + +/* + Creates a `evaluationStageList` object which represents an execution plan (or tree) + which is used to completely evaluate a set of tokens at evaluation-time. + The three stages of evaluation can be thought of as parsing strings to tokens, then tokens to a stage list, then evaluation with parameters. +*/ +func planStages(tokens []ExpressionToken) (*evaluationStage, error) { + + stream := newTokenStream(tokens) + + stage, err := planTokens(stream) + if err != nil { + return nil, err + } + + // while we're now fully-planned, we now need to re-order same-precedence operators. + // this could probably be avoided with a different planning method + reorderStages(stage) + + stage = elideLiterals(stage) + return stage, nil +} + +func planTokens(stream *tokenStream) (*evaluationStage, error) { + + if !stream.hasNext() { + return nil, nil + } + + return planSeparator(stream) +} + +/* + The most usual method of parsing an evaluation stage for a given precedence. + Most stages use the same logic +*/ +func planPrecedenceLevel( + stream *tokenStream, + typeErrorFormat string, + validSymbols map[string]OperatorSymbol, + validKinds []TokenKind, + rightPrecedent precedent, + leftPrecedent precedent) (*evaluationStage, error) { + + var token ExpressionToken + var symbol OperatorSymbol + var leftStage, rightStage *evaluationStage + var checks typeChecks + var err error + var keyFound bool + + if leftPrecedent != nil { + + leftStage, err = leftPrecedent(stream) + if err != nil { + return nil, err + } + } + + for stream.hasNext() { + + token = stream.next() + + if len(validKinds) > 0 { + + keyFound = false + for _, kind := range validKinds { + if kind == token.Kind { + keyFound = true + break + } + } + + if !keyFound { + break + } + } + + if validSymbols != nil { + + if !isString(token.Value) { + break + } + + symbol, keyFound = validSymbols[token.Value.(string)] + if !keyFound { + break + } + } + + if rightPrecedent != nil { + rightStage, err = rightPrecedent(stream) + if err != nil { + return nil, err + } + } + + checks = findTypeChecks(symbol) + + return &evaluationStage{ + + symbol: symbol, + leftStage: leftStage, + rightStage: rightStage, + operator: stageSymbolMap[symbol], + + leftTypeCheck: checks.left, + rightTypeCheck: checks.right, + typeCheck: checks.combined, + typeErrorFormat: typeErrorFormat, + }, nil + } + + stream.rewind() + return leftStage, nil +} + +/* + A special case where functions need to be of higher precedence than values, and need a special wrapped execution stage operator. +*/ +func planFunction(stream *tokenStream) (*evaluationStage, error) { + + var token ExpressionToken + var rightStage *evaluationStage + var err error + + token = stream.next() + + if token.Kind != FUNCTION { + stream.rewind() + return planAccessor(stream) + } + + rightStage, err = planAccessor(stream) + if err != nil { + return nil, err + } + + return &evaluationStage{ + + symbol: FUNCTIONAL, + rightStage: rightStage, + operator: makeFunctionStage(token.Value.(ExpressionFunction)), + typeErrorFormat: "Unable to run function '%v': %v", + }, nil +} + +func planAccessor(stream *tokenStream) (*evaluationStage, error) { + + var token, otherToken ExpressionToken + var rightStage *evaluationStage + var err error + + if !stream.hasNext() { + return nil, nil + } + + token = stream.next() + + if token.Kind != ACCESSOR { + stream.rewind() + return planValue(stream) + } + + // check if this is meant to be a function or a field. + // fields have a clause next to them, functions do not. + // if it's a function, parse the arguments. Otherwise leave the right stage null. + if stream.hasNext() { + + otherToken = stream.next() + if otherToken.Kind == CLAUSE { + + stream.rewind() + + rightStage, err = planTokens(stream) + if err != nil { + return nil, err + } + } else { + stream.rewind() + } + } + + return &evaluationStage{ + + symbol: ACCESS, + rightStage: rightStage, + operator: makeAccessorStage(token.Value.([]string)), + typeErrorFormat: "Unable to access parameter field or method '%v': %v", + }, nil +} + +/* + A truly special precedence function, this handles all the "lowest-case" errata of the process, including literals, parmeters, + clauses, and prefixes. +*/ +func planValue(stream *tokenStream) (*evaluationStage, error) { + + var token ExpressionToken + var symbol OperatorSymbol + var ret *evaluationStage + var operator evaluationOperator + var err error + + if !stream.hasNext() { + return nil, nil + } + + token = stream.next() + + switch token.Kind { + + case CLAUSE: + + ret, err = planTokens(stream) + if err != nil { + return nil, err + } + + // advance past the CLAUSE_CLOSE token. We know that it's a CLAUSE_CLOSE, because at parse-time we check for unbalanced parens. + stream.next() + + // the stage we got represents all of the logic contained within the parens + // but for technical reasons, we need to wrap this stage in a "noop" stage which breaks long chains of precedence. + // see github #33. + ret = &evaluationStage{ + rightStage: ret, + operator: noopStageRight, + symbol: NOOP, + } + + return ret, nil + + case CLAUSE_CLOSE: + + // when functions have empty params, this will be hit. In this case, we don't have any evaluation stage to do, + // so we just return nil so that the stage planner continues on its way. + stream.rewind() + return nil, nil + + case VARIABLE: + operator = makeParameterStage(token.Value.(string)) + + case NUMERIC: + fallthrough + case STRING: + fallthrough + case PATTERN: + fallthrough + case BOOLEAN: + symbol = LITERAL + operator = makeLiteralStage(token.Value) + case TIME: + symbol = LITERAL + operator = makeLiteralStage(float64(token.Value.(time.Time).Unix())) + + case PREFIX: + stream.rewind() + return planPrefix(stream) + } + + if operator == nil { + errorMsg := fmt.Sprintf("Unable to plan token kind: '%s', value: '%v'", token.Kind.String(), token.Value) + return nil, errors.New(errorMsg) + } + + return &evaluationStage{ + symbol: symbol, + operator: operator, + }, nil +} + +/* + Convenience function to pass a triplet of typechecks between `findTypeChecks` and `planPrecedenceLevel`. + Each of these members may be nil, which indicates that type does not matter for that value. +*/ +type typeChecks struct { + left stageTypeCheck + right stageTypeCheck + combined stageCombinedTypeCheck +} + +/* + Maps a given [symbol] to a set of typechecks to be used during runtime. +*/ +func findTypeChecks(symbol OperatorSymbol) typeChecks { + + switch symbol { + case GT: + fallthrough + case LT: + fallthrough + case GTE: + fallthrough + case LTE: + return typeChecks{ + combined: comparatorTypeCheck, + } + case REQ: + fallthrough + case NREQ: + return typeChecks{ + left: isString, + right: isRegexOrString, + } + case AND: + fallthrough + case OR: + return typeChecks{ + left: isBool, + right: isBool, + } + case IN: + return typeChecks{ + right: isArray, + } + case BITWISE_LSHIFT: + fallthrough + case BITWISE_RSHIFT: + fallthrough + case BITWISE_OR: + fallthrough + case BITWISE_AND: + fallthrough + case BITWISE_XOR: + return typeChecks{ + left: isFloat64, + right: isFloat64, + } + case PLUS: + return typeChecks{ + combined: additionTypeCheck, + } + case MINUS: + fallthrough + case MULTIPLY: + fallthrough + case DIVIDE: + fallthrough + case MODULUS: + fallthrough + case EXPONENT: + return typeChecks{ + left: isFloat64, + right: isFloat64, + } + case NEGATE: + return typeChecks{ + right: isFloat64, + } + case INVERT: + return typeChecks{ + right: isBool, + } + case BITWISE_NOT: + return typeChecks{ + right: isFloat64, + } + case TERNARY_TRUE: + return typeChecks{ + left: isBool, + } + + // unchecked cases + case EQ: + fallthrough + case NEQ: + return typeChecks{} + case TERNARY_FALSE: + fallthrough + case COALESCE: + fallthrough + default: + return typeChecks{} + } +} + +/* + During stage planning, stages of equal precedence are parsed such that they'll be evaluated in reverse order. + For commutative operators like "+" or "-", it's no big deal. But for order-specific operators, it ruins the expected result. +*/ +func reorderStages(rootStage *evaluationStage) { + + // traverse every rightStage until we find multiples in a row of the same precedence. + var identicalPrecedences []*evaluationStage + var currentStage, nextStage *evaluationStage + var precedence, currentPrecedence operatorPrecedence + + nextStage = rootStage + precedence = findOperatorPrecedenceForSymbol(rootStage.symbol) + + for nextStage != nil { + + currentStage = nextStage + nextStage = currentStage.rightStage + + // left depth first, since this entire method only looks for precedences down the right side of the tree + if currentStage.leftStage != nil { + reorderStages(currentStage.leftStage) + } + + currentPrecedence = findOperatorPrecedenceForSymbol(currentStage.symbol) + + if currentPrecedence == precedence { + identicalPrecedences = append(identicalPrecedences, currentStage) + continue + } + + // precedence break. + // See how many in a row we had, and reorder if there's more than one. + if len(identicalPrecedences) > 1 { + mirrorStageSubtree(identicalPrecedences) + } + + identicalPrecedences = []*evaluationStage{currentStage} + precedence = currentPrecedence + } + + if len(identicalPrecedences) > 1 { + mirrorStageSubtree(identicalPrecedences) + } +} + +/* + Performs a "mirror" on a subtree of stages. + This mirror functionally inverts the order of execution for all members of the [stages] list. + That list is assumed to be a root-to-leaf (ordered) list of evaluation stages, where each is a right-hand stage of the last. +*/ +func mirrorStageSubtree(stages []*evaluationStage) { + + var rootStage, inverseStage, carryStage, frontStage *evaluationStage + + stagesLength := len(stages) + + // reverse all right/left + for _, frontStage = range stages { + + carryStage = frontStage.rightStage + frontStage.rightStage = frontStage.leftStage + frontStage.leftStage = carryStage + } + + // end left swaps with root right + rootStage = stages[0] + frontStage = stages[stagesLength-1] + + carryStage = frontStage.leftStage + frontStage.leftStage = rootStage.rightStage + rootStage.rightStage = carryStage + + // for all non-root non-end stages, right is swapped with inverse stage right in list + for i := 0; i < (stagesLength-2)/2+1; i++ { + + frontStage = stages[i+1] + inverseStage = stages[stagesLength-i-1] + + carryStage = frontStage.rightStage + frontStage.rightStage = inverseStage.rightStage + inverseStage.rightStage = carryStage + } + + // swap all other information with inverse stages + for i := 0; i < stagesLength/2; i++ { + + frontStage = stages[i] + inverseStage = stages[stagesLength-i-1] + frontStage.swapWith(inverseStage) + } +} + +/* + Recurses through all operators in the entire tree, eliding operators where both sides are literals. +*/ +func elideLiterals(root *evaluationStage) *evaluationStage { + + if root.leftStage != nil { + root.leftStage = elideLiterals(root.leftStage) + } + + if root.rightStage != nil { + root.rightStage = elideLiterals(root.rightStage) + } + + return elideStage(root) +} + +/* + Elides a specific stage, if possible. + Returns the unmodified [root] stage if it cannot or should not be elided. + Otherwise, returns a new stage representing the condensed value from the elided stages. +*/ +func elideStage(root *evaluationStage) *evaluationStage { + + var leftValue, rightValue, result interface{} + var err error + + // right side must be a non-nil value. Left side must be nil or a value. + if root.rightStage == nil || + root.rightStage.symbol != LITERAL || + root.leftStage == nil || + root.leftStage.symbol != LITERAL { + return root + } + + // don't elide some operators + switch root.symbol { + case SEPARATE: + fallthrough + case IN: + return root + } + + // both sides are values, get their actual values. + // errors should be near-impossible here. If we encounter them, just abort this optimization. + leftValue, err = root.leftStage.operator(nil, nil, nil) + if err != nil { + return root + } + + rightValue, err = root.rightStage.operator(nil, nil, nil) + if err != nil { + return root + } + + // typcheck, since the grammar checker is a bit loose with which operator symbols go together. + err = typeCheck(root.leftTypeCheck, leftValue, root.symbol, root.typeErrorFormat) + if err != nil { + return root + } + + err = typeCheck(root.rightTypeCheck, rightValue, root.symbol, root.typeErrorFormat) + if err != nil { + return root + } + + if root.typeCheck != nil && !root.typeCheck(leftValue, rightValue) { + return root + } + + // pre-calculate, and return a new stage representing the result. + result, err = root.operator(leftValue, rightValue, nil) + if err != nil { + return root + } + + return &evaluationStage{ + symbol: LITERAL, + operator: makeLiteralStage(result), + } +} diff --git a/vendor/github.com/Knetic/govaluate/test.sh b/vendor/github.com/Knetic/govaluate/test.sh new file mode 100644 index 00000000..11aa8b33 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/test.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Script that runs tests, code coverage, and benchmarks all at once. +# Builds a symlink in /tmp, mostly to avoid messing with GOPATH at the user's shell level. + +TEMPORARY_PATH="/tmp/govaluate_test" +SRC_PATH="${TEMPORARY_PATH}/src" +FULL_PATH="${TEMPORARY_PATH}/src/govaluate" + +# set up temporary directory +rm -rf "${FULL_PATH}" +mkdir -p "${SRC_PATH}" + +ln -s $(pwd) "${FULL_PATH}" +export GOPATH="${TEMPORARY_PATH}" + +pushd "${TEMPORARY_PATH}/src/govaluate" + +# run the actual tests. +export GOVALUATE_TORTURE_TEST="true" +go test -bench=. -benchmem #-coverprofile coverage.out +status=$? + +if [ "${status}" != 0 ]; +then + exit $status +fi + +# coverage +# disabled because travis go1.4 seems not to support it suddenly? +#go tool cover -func=coverage.out + +popd diff --git a/vendor/github.com/Knetic/govaluate/tokenStream.go b/vendor/github.com/Knetic/govaluate/tokenStream.go new file mode 100644 index 00000000..d0029209 --- /dev/null +++ b/vendor/github.com/Knetic/govaluate/tokenStream.go @@ -0,0 +1,36 @@ +package govaluate + +type tokenStream struct { + tokens []ExpressionToken + index int + tokenLength int +} + +func newTokenStream(tokens []ExpressionToken) *tokenStream { + + var ret *tokenStream + + ret = new(tokenStream) + ret.tokens = tokens + ret.tokenLength = len(tokens) + return ret +} + +func (this *tokenStream) rewind() { + this.index -= 1 +} + +func (this *tokenStream) next() ExpressionToken { + + var token ExpressionToken + + token = this.tokens[this.index] + + this.index += 1 + return token +} + +func (this tokenStream) hasNext() bool { + + return this.index < this.tokenLength +} diff --git a/vendor/github.com/casbin/casbin/v2/.gitignore b/vendor/github.com/casbin/casbin/v2/.gitignore new file mode 100644 index 00000000..da27805f --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/.gitignore @@ -0,0 +1,30 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.idea/ +*.iml + +# vendor files +vendor diff --git a/vendor/github.com/casbin/casbin/v2/.releaserc.json b/vendor/github.com/casbin/casbin/v2/.releaserc.json new file mode 100644 index 00000000..5a4595bb --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/.releaserc.json @@ -0,0 +1,13 @@ +{ + "debug": true, + "release": { + "branches": [ + "master" + ] + }, + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/github" + ] +} diff --git a/vendor/github.com/casbin/casbin/v2/.travis.yml b/vendor/github.com/casbin/casbin/v2/.travis.yml new file mode 100644 index 00000000..fe700453 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/.travis.yml @@ -0,0 +1,14 @@ +language: go + +sudo: false + +go: + - "1.11" + - "1.12" + - "1.13" + +before_install: + - go get github.com/mattn/goveralls + +script: + - $HOME/gopath/bin/goveralls -service=travis-ci diff --git a/vendor/github.com/casbin/casbin/v2/CONTRIBUTING.md b/vendor/github.com/casbin/casbin/v2/CONTRIBUTING.md new file mode 100644 index 00000000..8762972b --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# How to contribute + +The following is a set of guidelines for contributing to casbin and its libraries, which are hosted at [casbin organization at Github](https://github.com/casbin). + +This project adheres to the [Contributor Covenant 1.2.](https://www.contributor-covenant.org/version/1/2/0/code-of-conduct.html) By participating, you are expected to uphold this code. Please report unacceptable behavior to info@casbin.com. + +## Questions + +* We do our best to have un [up to date documentation](https://casbin.org/docs/en/overview) +* [Stack Overflow](https://stackoverflow.com) is the best place to start if you have a question. Please use the [casbin tag](https://stackoverflow.com/tags/casbin/info) we are actively monitoring. We encourage you to use Stack Overflow specially for Modeling Access Control Problems, in order to build a shared knowledge base. +* You can also join our [Gitter community](https://gitter.im/casbin/Lobby). + + +## Reporting issues + +Reporting issues are a great way to contribute to the project. We are perpetually grateful about a well-written, thorough bug report. + +Before raising a new issue, check our [issue list](https://github.com/casbin/casbin/issues) to determine if it already contains the problem that you are facing. + +A good bug report shouldn't leave others needing to chase you for more information. Please be as detailed as possible. The following questions might serve as a template for writing a detailed report: + +What were you trying to achieve? +What are the expected results? +What are the received results? +What are the steps to reproduce the issue? +In what environment did you encounter the issue? + +Feature requests can also be submitted as issues. + + +## Pull requests + +Good pull requests (e.g. patches, improvements, new features) are a fantastic help. They should remain focused in scope and avoid unrelated commits. + +Please ask first before embarking on any significant pull request (e.g. implementing new features, refactoring code etc.), otherwise you risk spending a lot of time working on something that the maintainers might not want to merge into the project. + +First add an issue to the project to discuss the improvement. Please adhere to the coding conventions used throughout the project. If in doubt, consult the [Effective Go style guide](https://golang.org/doc/effective_go.html). diff --git a/vendor/github.com/casbin/casbin/v2/LICENSE b/vendor/github.com/casbin/casbin/v2/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/casbin/casbin/v2/README.md b/vendor/github.com/casbin/casbin/v2/README.md new file mode 100644 index 00000000..6f4e6baa --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/README.md @@ -0,0 +1,278 @@ +Casbin +==== + +[![Go Report Card](https://goreportcard.com/badge/github.com/casbin/casbin)](https://goreportcard.com/report/github.com/casbin/casbin) +[![Build Status](https://travis-ci.org/casbin/casbin.svg?branch=master)](https://travis-ci.org/casbin/casbin) +[![Coverage Status](https://coveralls.io/repos/github/casbin/casbin/badge.svg?branch=master)](https://coveralls.io/github/casbin/casbin?branch=master) +[![Godoc](https://godoc.org/github.com/casbin/casbin?status.svg)](https://pkg.go.dev/github.com/casbin/casbin/v2) +[![Release](https://img.shields.io/github/release/casbin/casbin.svg)](https://github.com/casbin/casbin/releases/latest) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby) +[![Sourcegraph](https://sourcegraph.com/github.com/casbin/casbin/-/badge.svg)](https://sourcegraph.com/github.com/casbin/casbin?badge) + +**News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: http://casbin.org/editor/ + +![casbin Logo](casbin-logo.png) + +Casbin is a powerful and efficient open-source access control library for Golang projects. It provides support for enforcing authorization based on various [access control models](https://en.wikipedia.org/wiki/Computer_security_model). + +## All the languages supported by Casbin: + +[![golang](https://casbin.org/img/langs/golang.png)](https://github.com/casbin/casbin) | [![java](https://casbin.org/img/langs/java.png)](https://github.com/casbin/jcasbin) | [![nodejs](https://casbin.org/img/langs/nodejs.png)](https://github.com/casbin/node-casbin) | [![php](https://casbin.org/img/langs/php.png)](https://github.com/php-casbin/php-casbin) +----|----|----|---- +[Casbin](https://github.com/casbin/casbin) | [jCasbin](https://github.com/casbin/jcasbin) | [node-Casbin](https://github.com/casbin/node-casbin) | [PHP-Casbin](https://github.com/php-casbin/php-casbin) +production-ready | production-ready | production-ready | production-ready + +[![python](https://casbin.org/img/langs/python.png)](https://github.com/casbin/pycasbin) | [![dotnet](https://casbin.org/img/langs/dotnet.png)](https://github.com/casbin-net/Casbin.NET) | [![delphi](https://casbin.org/img/langs/delphi.png)](https://github.com/casbin4d/Casbin4D) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/casbin/casbin-rs) +----|----|----|---- +[PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin-net/Casbin.NET) | [Casbin4D](https://github.com/casbin4d/Casbin4D) | [Casbin-RS](https://github.com/casbin/casbin-rs) +production-ready | production-ready | experimental | production-ready + +## Table of contents + +- [Supported models](#supported-models) +- [How it works?](#how-it-works) +- [Features](#features) +- [Installation](#installation) +- [Documentation](#documentation) +- [Online editor](#online-editor) +- [Tutorials](#tutorials) +- [Get started](#get-started) +- [Policy management](#policy-management) +- [Policy persistence](#policy-persistence) +- [Policy consistence between multiple nodes](#policy-consistence-between-multiple-nodes) +- [Role manager](#role-manager) +- [Benchmarks](#benchmarks) +- [Examples](#examples) +- [Middlewares](#middlewares) +- [Our adopters](#our-adopters) + +## Supported models + +1. [**ACL (Access Control List)**](https://en.wikipedia.org/wiki/Access_control_list) +2. **ACL with [superuser](https://en.wikipedia.org/wiki/Superuser)** +3. **ACL without users**: especially useful for systems that don't have authentication or user log-ins. +3. **ACL without resources**: some scenarios may target for a type of resources instead of an individual resource by using permissions like ``write-article``, ``read-log``. It doesn't control the access to a specific article or log. +4. **[RBAC (Role-Based Access Control)](https://en.wikipedia.org/wiki/Role-based_access_control)** +5. **RBAC with resource roles**: both users and resources can have roles (or groups) at the same time. +6. **RBAC with domains/tenants**: users can have different role sets for different domains/tenants. +7. **[ABAC (Attribute-Based Access Control)](https://en.wikipedia.org/wiki/Attribute-Based_Access_Control)**: syntax sugar like ``resource.Owner`` can be used to get the attribute for a resource. +8. **[RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer)**: supports paths like ``/res/*``, ``/res/:id`` and HTTP methods like ``GET``, ``POST``, ``PUT``, ``DELETE``. +9. **Deny-override**: both allow and deny authorizations are supported, deny overrides the allow. +10. **Priority**: the policy rules can be prioritized like firewall rules. + +## How it works? + +In Casbin, an access control model is abstracted into a CONF file based on the **PERM metamodel (Policy, Effect, Request, Matchers)**. So switching or upgrading the authorization mechanism for a project is just as simple as modifying a configuration. You can customize your own access control model by combining the available models. For example, you can get RBAC roles and ABAC attributes together inside one model and share one set of policy rules. + +The most basic and simplest model in Casbin is ACL. ACL's model CONF is: + +```ini +# Request definition +[request_definition] +r = sub, obj, act + +# Policy definition +[policy_definition] +p = sub, obj, act + +# Policy effect +[policy_effect] +e = some(where (p.eft == allow)) + +# Matchers +[matchers] +m = r.sub == p.sub && r.obj == p.obj && r.act == p.act + +``` + +An example policy for ACL model is like: + +``` +p, alice, data1, read +p, bob, data2, write +``` + +It means: + +- alice can read data1 +- bob can write data2 + +We also support multi-line mode by appending '\\' in the end: + +```ini +# Matchers +[matchers] +m = r.sub == p.sub && r.obj == p.obj \ + && r.act == p.act +``` + +Further more, if you are using ABAC, you can try operator `in` like following in Casbin **golang** edition (jCasbin and Node-Casbin are not supported yet): + +```ini +# Matchers +[matchers] +m = r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3') +``` + +But you **SHOULD** make sure that the length of the array is **MORE** than **1**, otherwise there will cause it to panic. + +For more operators, you may take a look at [govaluate](https://github.com/Knetic/govaluate) + +## Features + +What Casbin does: + +1. enforce the policy in the classic ``{subject, object, action}`` form or a customized form as you defined, both allow and deny authorizations are supported. +2. handle the storage of the access control model and its policy. +3. manage the role-user mappings and role-role mappings (aka role hierarchy in RBAC). +4. support built-in superuser like ``root`` or ``administrator``. A superuser can do anything without explict permissions. +5. multiple built-in operators to support the rule matching. For example, ``keyMatch`` can map a resource key ``/foo/bar`` to the pattern ``/foo*``. + +What Casbin does NOT do: + +1. authentication (aka verify ``username`` and ``password`` when a user logs in) +2. manage the list of users or roles. I believe it's more convenient for the project itself to manage these entities. Users usually have their passwords, and Casbin is not designed as a password container. However, Casbin stores the user-role mapping for the RBAC scenario. + +## Installation + +``` +go get github.com/casbin/casbin +``` + +## Documentation + +https://casbin.org/docs/en/overview + +## Online editor + +You can also use the online editor (https://casbin.org/editor/) to write your Casbin model and policy in your web browser. It provides functionality such as ``syntax highlighting`` and ``code completion``, just like an IDE for a programming language. + +## Tutorials + +https://casbin.org/docs/en/tutorials + +## Get started + +1. New a Casbin enforcer with a model file and a policy file: + + ```go + e, _ := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv") + ``` + +Note: you can also initialize an enforcer with policy in DB instead of file, see [Policy-persistence](#policy-persistence) section for details. + +2. Add an enforcement hook into your code right before the access happens: + + ```go + sub := "alice" // the user that wants to access a resource. + obj := "data1" // the resource that is going to be accessed. + act := "read" // the operation that the user performs on the resource. + + if res := e.Enforce(sub, obj, act); res { + // permit alice to read data1 + } else { + // deny the request, show an error + } + ``` + +3. Besides the static policy file, Casbin also provides API for permission management at run-time. For example, You can get all the roles assigned to a user as below: + + ```go + roles, _ := e.GetImplicitRolesForUser(sub) + ``` + +See [Policy management APIs](#policy-management) for more usage. + +## Policy management + +Casbin provides two sets of APIs to manage permissions: + +- [Management API](https://casbin.org/docs/en/management-api): the primitive API that provides full support for Casbin policy management. +- [RBAC API](https://casbin.org/docs/en/rbac-api): a more friendly API for RBAC. This API is a subset of Management API. The RBAC users could use this API to simplify the code. + +We also provide a [web-based UI](https://casbin.org/docs/en/admin-portal) for model management and policy management: + +![model editor](https://hsluoyz.github.io/casbin/ui_model_editor.png) + +![policy editor](https://hsluoyz.github.io/casbin/ui_policy_editor.png) + +## Policy persistence + +https://casbin.org/docs/en/adapters + +## Policy consistence between multiple nodes + +https://casbin.org/docs/en/watchers + +## Role manager + +https://casbin.org/docs/en/role-managers + +## Benchmarks + +https://casbin.org/docs/en/benchmark + +## Examples + +Model | Model file | Policy file +----|------|---- +ACL | [basic_model.conf](https://github.com/casbin/casbin/blob/master/examples/basic_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv) +ACL with superuser | [basic_model_with_root.conf](https://github.com/casbin/casbin/blob/master/examples/basic_with_root_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv) +ACL without users | [basic_model_without_users.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_model.conf) | [basic_policy_without_users.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_policy.csv) +ACL without resources | [basic_model_without_resources.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_model.conf) | [basic_policy_without_resources.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_policy.csv) +RBAC | [rbac_model.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_model.conf) | [rbac_policy.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_policy.csv) +RBAC with resource roles | [rbac_model_with_resource_roles.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_policy.csv) +RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_model.conf) | [rbac_policy_with_domains.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_policy.csv) +ABAC | [abac_model.conf](https://github.com/casbin/casbin/blob/master/examples/abac_model.conf) | N/A +RESTful | [keymatch_model.conf](https://github.com/casbin/casbin/blob/master/examples/keymatch_model.conf) | [keymatch_policy.csv](https://github.com/casbin/casbin/blob/master/examples/keymatch_policy.csv) +Deny-override | [rbac_model_with_deny.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_policy.csv) +Priority | [priority_model.conf](https://github.com/casbin/casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/casbin/casbin/blob/master/examples/priority_policy.csv) + +## Middlewares + +Authz middlewares for web frameworks: https://casbin.org/docs/en/middlewares + +## Our adopters + +https://casbin.org/docs/en/adopters + +## How to Contribute + +Please read the [contributing guide](CONTRIBUTING.md). + +## Contributors + +This project exists thanks to all the people who contribute. + + +## Backers + +Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/casbin#backer)] + + + +## Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/casbin#sponsor)] + + + + + + + + + + + + +## License + +This project is licensed under the [Apache 2.0 license](LICENSE). + +## Contact + +If you have any issues or feature requests, please contact us. PR is welcomed. +- https://github.com/casbin/casbin/issues +- hsluoyz@gmail.com +- Tencent QQ group: [546057381](//shang.qq.com/wpa/qunwpa?idkey=8ac8b91fc97ace3d383d0035f7aa06f7d670fd8e8d4837347354a31c18fac885) diff --git a/vendor/github.com/casbin/casbin/v2/casbin-logo.png b/vendor/github.com/casbin/casbin/v2/casbin-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7e5d1ecf95b4f0a47e297dc1269b2faa309837d3 GIT binary patch literal 34267 zcmeFZ1yo$iwy@i{1cHSi!QE-xU4v_|;56>;?!h6!-3bl>0>Le~g`mM5f(Mt^*}~4* z=bZoMzvsU9#vLPT4Cu*PbIz*zs%lo%?54vM`fS%xVX3&fy|7|%=BOldIvXaM}1d%YX{Qbo&4!X z6y#uNZ)WRgW@AnC-LJlZjguoE3CZ_@9p}su>wFf-FH+AZtelupQICw6is}akOzTwfQ$iK0N-n1%vxUR`#Lne_3BE ztAAOvgQJ);7{MP%|D~maikmHnQ3>Q=<796L5_1MOh4f#w-oa4`^jGivge$Q6zjWki zX8dpbe1Gz9A%I-X{*Br9C%>EhHl}{>YF;ojZV`KszN3x3ij9pW|L?<9;m^Jx5)mPy zQZutQvT<>s=4Jf5_FrS>FGnCzeMb;KcuaH91KH?-tSZdR+$=zDCU#mNCpQrIgOlGi z|K>y1#>mXr?Vo(GaI-LTGco<>;|I;Z`vA{DBYj8xpYrmf?hk&947rVM?5*@2`OU2K zO+bvc)+W4+f8G2+^I>Fii`ZD&*n_JD;%DJy{8zVsu=;JZazD3raMZUp1U(n!2j5^Y zGc)35(&q#LSq+%!LB^cM^qkDBO!NjuOic8K`kYK40}zPA*uda7h_bRjn*Wn)Q5!?2 z?=$WX*G6F1tVSS^F^eIPo`aLgn4XK3m6hIrL!XNt1Y|bmVg~B7a~T``>G}ure{wBv zZw8K*`j$WFe6RiYB8)-ITpY|mV|sS5Lwa@=R%3AOjg9G2w z?B~`u{2mec4ZqI?5SYS0-2JOs50-zJnf-GI{v3ke{r_P8@5^v81zG=}Ce-h)e$)E* zP8@8E9bNS8K|&_rG4Wqa^WPo*qs8An^D=&4JS@%rU+4n|edqtI2RIBttn4f-Ec76D z@M6f$#>z~u&jA9DNoE697NEYdp^*{$pFRBF?EzMHZdR5*!_ogQJ@D7qH8jNF!34rVq^W+QqK3yT3gy8$?IaIte3(i`Xl4fKscTx^Ey9Dla&zuReS zOx!G7zdFtFA==qkJA>^1Z{i&|s_KKcxFCB6eq(zZE24+Rk;u`8NZ;1h(#%l*dmv?W zwl?}>!}zce{@Hs!`1t>DfEt19&HiuV$`7?P{1X$=|FSm!$C32sb`1a7TJ~#F{^J(p zKkhU(W>!`mp=FxIFd5cbFyJI`yodCuZ*Psd)oXTPf8G*5eR&QGoWYX;$)>~0Uy5T^^J{L z=s7u!nGJ!=AZGBa{j*ztP0H`btlv&~|IuOYKkl@jJB$A(%irJf8vnZw62G4b|M6h{ z=VAY!5AxqrM&KNV=JA*-);XgRkb#A0!RmXq^Y?t5GC0Ez;%8@LV`1ZE{QK>{TPT>hf-KcV&A^E>hwtei7EW+b{>|v` zPyVH)#$T3P;0(iGhCe*`!|?aH@ewTsq;cbQ=y6qwV^gl2B)uR7- z-wz&t74m@MXM|t4eumP6&M#aKD1Juxh3jW1J?Q+x^?>4MgkQLRhSG!1FI*2Oen$9( z>t`rE=={R{ z_=W3dC_U)>!u5dSXM|t4eumP6&M#aKD1Juxh3jW1J?Q+x^?>4MgkQLRhSG!1FI*2O zen$9(>t`rE=={R{_=W3dC_U)>!u5dSXM|t4eumP6&M#aKD1Juxh3jW1J?Q+x^?>4MgkQLRhSG!1 zFI*2Oen$8|;zIoEi+e!U;BV`30e>wI2O35=_$z&g3?-Fh0RVS$0Khi@0JyvdzitBn zPCx))M-KqtP67b1Y~pmg#Q^|5hv%X~Dz3A8=`KkGs?EIuCjE1D2gX|Nq<*N7#KeMv zCBFWw0Yo&2Ikr_E-~3;{vd6^iwh$_HL{kjS$vL$z_5&yhqmO&k9ke*@y!T0)F~V>@ z_o@d;Z=`N|IoGfpBqy_M-ngSFNeKtSqmgvo!R5xpd!Oz_*%pXn_gn0!-z_OwUkzS& z_Q5md0Bsg{pEBymFelG{U$?Ar!=R}8hJ|8fvAA4RlCpS+5@Z60{rG17Mwx0^e@Yag#)pvE9o9BT`Tp!wVHcO zX+&lU$OWwWK5v86!&N}+0^oQ$5_M*0|~%7@8Uqm4w59|)HO`3dqhjOP@bGwh(AavRbe z@&`yu_(aiAR1v6=lOz_C$0Gvwp7p)7G&Cfyp3*l~7Zt(#20g;kxtwZZWRitthccMwbHrXTP|?t6^7`C)lElKtN(OeKWXhYFZO=&H z*>xdkkCYV`>-Q&6XP*j);|Lgra&zbRmH*pLC+@Q?-zM-ao> zY7!m!AOa;uy>R@*50Uy&(7wABgcp*Qe&nz@OpY4~3G_JId-UqnE0koP>1;A|gthSA zm}fc^1kp9N%aHv81M(Sc7N7gFR&F+j)1|=|HUw)Ac3YyKPd15%IL(fqhu=YP8vw{BoQeg4eswDaknhK9zV zCj!SbMO?jI?~rUR)|U|Co-2Igt3{DyUNrg!dLVoioXkWQ5GgOADbJBu6p8AH*QR=v zvxX&k@RSSO%8HT_ZqMtpuW;TMorDFY4e+}|61I!pPf3^tofwsPZ{g**x5?hnYdO#Q z!#v6;E-Uk@DK6f&(w^eL2|g-7HF$+eLK|2qTmWRo@qLVkdb8jc-hSZtCAH1QH#)N8 zJrCT)qe^Jg-Q#o3EZ(HKp>~!Kc%~|h{i_l4SIEWB?AF3+Li<28neA#8xm$KVuua<{y1`J z^?K_sD@M%kIn(9hrSd--*Sug(9@KpEZUz{E2@VaZufoF@(1qz>G&|RsZjhE-mr~ot z{D76p9hwj}5LycC_hWea4(NxRwpa?Z%X$zjevNvDEf6=1wh|4WgpttYpoWEmgyoTv z+MZ@Xs6W1W`Vn~(xm>&HO&A8{0;kz{o{O4_itWX6i1N#qG9VE7`Z_4$Z8_+r6Y}fT z9>o_=)QpMSG*9sMk3Ghu-a>@c>{;)vGE(xNNHTml6b6iQrEY8(Yo_3u*;qu@)z*q? zX=x4cjqn()_rFee3NCd$;%0}o3eY6Q& z0*)j;=_Mu0p~yIxh^@2bySJ6asD~T>xCpoFuO#IN72Z$NiTUCF`Aw@ZsKurG!_sCq zRs5P&r&n%dWF*I%#6-ekGtIsklguGMID+Z*A{5h$@__VxUm04~@^-g1N_bvYZ2;t= zOV#}mKIi&L=&S}RBc*Xq5^r`!ABOq%PKTwn^+t+CIdQU#Gx(d5c%L!6UIqcUZC^tx z!wV_Sua!m0erSG|jI&Cc%$1EkcXZerIwU7W6`pKqZfepU3`;TfhRH^gemiaAGD1KM zgkwVxv1`^S$Nn1hm3EmM<$RaNb_u>=WI7y^Hsg@@Ktj1tu6d&%)q16$w!)|*5Md1u z>+Y7LH1RHCam;tI4!`=VsrBP!qp_9sCECU;@_xHTcO;J?2T4-cFvQw%&D!vzuBW=t za2b-HM$N!AhZDGfpj9hRc*bsdkqMrwyvU+{Ksi=IdYl|P6TktcWWTgQ>Fj*hc;Vv8 zdrU3$ckh!$>HJ5bgJOKfhC&zw;Gl9+GjmX;VkEGn}IAjdcwwNdGv`D955yY>7&IrIf>%V%ORH~N-*eqq(hk3w>ZA98e)-6%11~37 zj^_17<8JwCabbqRmqw>n%^Kq&j3+NJ=(rIa*6X8R4~G&u1GoU8CF z)F5g7Q)@hhLTF8$ST+6SL*B-oL1#SK%JCT#1T+HVg?p5zOzmp5r?zcj-Y||LCXo?C z_mvKf!O81=&Z8N}fm@7+&#nystio~^w;Z6+(vYM`9&Qr840}p84h}z9@z~btf;=%A z@9o#iz*lt3&GEoXuWysJ_4S6IhqKmd&d~%eJ1@$kPa*hl;W_!0`oA}BTQP5!gq&>n z4zfG(OPB@|0d3-H<8DhQcrou8PUkt$0MGbLNZ2Ik4%TITBfzd72bq7JO4NI03r^>K zqn-X&)b!zT@94KvP0jMQ5%oJF=WBW zq%PR2b~SFtX(P*InO@G#;N3dmoD4Bm1xL1?q*l|onSe`SEjUh~FqQ;s6J ziu-*$%)Ed?pMcIwDLW8#z2~=8-t)w{qfTxrVsQ#y6{B&vEW61PRSsk3WUNfr6@>3W zHf~@Yh>@S*c`A-h`O^P@(={XL)YEeWB}wqGX>+xS%M(G&L`UohMnI4S$#Zd&Qs9>Q zeDR)Rc5d$eo%ijPmY=bnhpm->Co&9tM^M-BSqAuJ1^NV;QK~Jm+~=pQVWN8ppkRY1u0_5%?%AvV?io zW`!`Uo0@@fGcmI%28*zP=jHorN-5t&h4oR7j(T4U@sGE-MU`8coj~K^6t>-_I;)Y3 zEeU~swbnB>Rm6_r@#EE+N?i}=nmp(+vE0O>=KVVIFnkvXOttwSMpo7(N`XrRjxUZF z<-7bf)?XSAB&l7XyINzv=x^kTfS%>zx9dR4ePTVT-M!9pV26hOz_GHpVVcL=e!V;4 zX=D)Bm?X^&9AVXvT9J3dO^F$#c5X0I3P1m3Wa0 zXCeU!g<~t`tTVVnb<9kvVxDh46*ky<;>NJf;s$TsitRKKr8`tUIns?R0fHO9Xm<8I z;q}Sek&rHdt|I?OtPn%xyX9I<&R<=8BU>-J85>TbcoGz_!btlmmo%2#`7;hy-51Ii z`9^w&{9bV3K(6@gmen7x65b~{a^D_becQE(QQAj>_c<{oy)&GI{uDOlhrvo*e#C3G zZdu=Zh2QA=SfcX`hA<_@Sn<7BeA$H2?zA7h4pzVC>T}8W{`ha?nsV)gL2qg-Sd4g) zdk+@y$N+O8PbF5HCp8v$7({vk(%*=Wqq)ZMl`q3;b~q0#Wud={mXkr7ATN^}kpXXN zhMkXi3AHfC3D?HOc_wZppp^9*iUgwUQYkYq8eZ{D+ufI-Zw}Nalv+0^W zrD@4NduKQ^U52w)#~QV|^8C=T(xr4}9pn41bGpuI?oB$6%ia9>Z6w694O#6q3NjSc z_&}(+o#Pl|VJ@7fG`!qI;Pfp0GBh3`bITIcH)>NKXd!i`(GxXIN_LDzzqDTgdFjB|`DNp2`h(Sj(@P$7s|bz11fLtoBCto_t7K<$R`CrtN|n z?D-fUsv&>bTt`;GPbjeHO;)BKtUToX))@tK?KJMIeuqJNr|CGhCw^0HPWY3wwdHS$ zl?$2mJD|b)Gbj8&R8wI$j})u)+yMub6n=aUG*je{SN@0-t4ZN_vUS05 zOE?0?$k!^RpnK)0;f0P)d0yvdt2O6~27b}UJ{+onU%gcP?9>kX; zCsN?k185x+FwHX+lf)9%kK!57g(eM@DoKb|;E=?-XEF#$6F#14GQFHYu}f7Y0EvOf z#l*zy=IW#^=N?<#iIhPkY^!c{B69WG4kzs-O1qv{0aX#V~5Q*(9y+{eeKVyw{a-#M`pM+EPa~H6p@FulBWuSLJ@I zueAGK640={XV_Wp`+_E!PQSCWLwVdPaK}XWev3p-VATt+`2w5&c=^$oV*mmMr6CV{ zFa>z7hsF%jzN@++Xl{R9R6TZ^c;}UwV)mNXdo!|@!xBZ!ER+dqpxQiVjB3@NJ-Vv+ zB-wVzFd^L$CpM0Tb!j<-aN`@tf*{;r|B*_|!GKJ|88HbP3q(;#DG5QeMaE_ja>^MD zRE#7mtJEnXZhBd1{X&)st=Jj;r#kqett&W)>5Mj1VAIF;W0;Hz%cE{`aut=R)i`N} z4XoCNu&+GMhpCG-Jm`I#Y#&m9!^NuAJJ;Qy6nxSC{ z@(rCv$MrC00ByyGxXHI_P^g)_(H|isNb>VaO5)V%f;Zh*I&i+gA?YO=h09mzh=0(#kAIvaPWk-?QfD_ zj^a63n@mhp*3xQvl`2_$gD|V}TuTWN8eO?^*LSxOe0r-koMX3c+!i+;$rxJei%;VB z;x;A|W_`N4ZIZEkHg}L!&_Xcfq-tDUsAw$NPkL7_v)o+9A!ygC8(O~l{xReHy@Y1H z&0{sw(5m2BS6(?;SvFxRtD9t#%=Oho&xo2~?pNU42W7kr1>tP1*@W_xu1~W^KXu{Z zWQAuChgxads8b-Sike!yB-v8B*pculnim7CTB+)x`IVg`(s0oY^!T}5o-D*WKCiRz zv9B_xR7{>ELgw3Uhe&B6p!m(RWHC*f93?w`NIDTZji#N z8$9b8&)t5tNpt2(HR9dD%;sVK+JeXZVBC3Y=BBPIg!U9Q4Rw3}y87L*y^UiMy1+>k{`@kHz~twAQ9eV}_Os1BT{PB`|B*CD9DJK=`JB zuhOtYN7Q4q8gH&BTMjvER_p^S^atp2T!K1sQxA!C;bpR`ZSjlCN0Vt4R2AP#Ycf(( zXTQr2%9lj)#xTf_q68YTOUQ#GeIG4A>e z0}L@XhxL3p^CNJz{hlt1ES)~}4a*C0qTDt~N;q&!n0jq@@>(r>+g*K!xJ%s-;H14xUthf;jMMmPWdOW4fxUrtqOmRWBhVPS;?mTH9 zb$M18LF882_jxSP9@ioTyc3_(aP1tkbpPso5tc*?F8LjbxqwhA^I zojjj#&+bx8j*FdwCmK#Gl)O7%vhU5lb=H@$kC*;|41I1iybj=8hfFGe!1cjBIHz_& zoPz7TsWOzr6wq{kN{LXwqHxRZ@e0oS)o5KiKE z^Ec}Ufw2O_Jv8GPn`KVgQ_uv^8OL4%n5cJ8Rm5#94t>yWS|N*sHEaoCbO2E`bi1s? zft??ze41OC0L~g*Qr~D626+c|lTA45cP0iS2?Meb=meVUf%ASeXguY+@C6iR4y<)+ zjvKG19UW6Npi8x`*$dO`X+{Ljkz5w+C~eAK4*A2Jyx+=+R?^k&O__ZAL?2elvF~$Q z`{y*du;l~&j3aU~q)aP~^hlg^s8{%PTHIr%3HWn$OrRZB0!xo{Gu-ZAC?YC0 zZ&1=k6eS3UHX?VORkIz)RkjIi3T0C))9oh?hLIA!zLmlizYCm3$$Cx=-W}l%C?vNN zG!1ap!gv(EJ)Q2u(}WUNz(Boeg&4?Buqv|jh^)NYY%vC;i_RC$7j?hUAh2vo-{c|K zqShGPj{=Ib0$E+(llEtmvomRzY~c<8ct4kE3?)2|Z`hcdGMIZBE14ie(>GhMW|&0L zGCCS7%G!5Q#+3J@%h8rM?BKejF{|J$`Y2?E%_}?Gux9DGP*D=g*;>Dy9bEqNAV&Kj zD?@RayxmIuuXC;~(fZgI=p>LNDQl4`&;mebr#&v|lXzznmLg{9v}$nco+KFue*XMIQ%h^q zN!4Qh+>2GV6MnxPj-rcK=OFaZID|&vOF-e2aDFq^9Xc$>u>@3a$4P1VX} zT>qgOg2@8=Az*1!avO0Qnaavbyve0`c(M*TkHybRCogPUNg*(NJN=5=)5K@+qZ*iq zRCxT5+i2SlNYR|YDe}iO5++K5N%gRz@6pu#sw-TO5R(;l~BKeM=-Cai!1%v2G`(8sfdEct3YrgQKGk{!n4+xy#ah@$!?9u2zndI$Ll zWSPj}<%VkVPsz%rPS5xx8fOfev7z(6kTs6Rz&mCQ#_&!jWYMg!y!{AD&0oGKDUhcm zRX}fs?-0u#Jf!CE$}o#OQBY1`)?Y;kxPscLjN@{)ei@Ausl<~1c!PhajYz}*>a$&c3WOUZ(Z83t}_`Gdjexth$yg-5lJtG#K)kG zx-F<}-nXsi(Z#~B-VuL*IoaJ6YVkNmz%>u1>VP+7iJWKlGy$-r&`$t1!a_F(c5?hA zP0yMV^P(9ULdXlc;h8$u;58rmhY)$dNgtua0pzZ4qMvTgj@vgZ?@Go}UmRDDv21w& z7WA(3OWUs-zL)b)Ke*uRRj{i}RSb*Xdn&p-Qs6@`@psvVl_|o!u&%uG$c-(8!h(tL zanG;-`PKWG_}#<2h%|jy8iPFxm8df=fu=&bX|i>wnH0|%o*OQ2qPeHnAF(3f*iej; z;{Jp%)A%i#h$(z_E~%LVnrx#^}+NvuW)lHFxGUwE63B_Ag8_**h+X+36 zH=@)SlwxkXeyxXX6+Xjr1DWJ0RK}lk4BbY0?*lX_Gx|T%o1Gf*$VRp6gBS`Y^QgzY z;>yK8tD%XePcgGfLQiR?GF_b1k>F7mv3gh`olpQP6ok#iN%Q9BF7ASQ&w4Ml9(%%H zAlZ%rru;Ecr=MX9-up8YK}Nyx6G+O61=JeTFYzxGww+uHO5KqhK0oU>IDo5$K)YUq z09N|326B;JTz7cu%t77ZMBI-Pjhtw->3VQwKu006#@)fcM}egLW_wfROYK~*D-Tr; z`xIp<{T}H;sBN$jD%3OtM*Kng=5_rOWMo~XFjJY z?z)YXs$0Ai1*fz4a`9PAI=XtUYF>#jYSji)!Pj17b&Re&R>wZ{OtTIOFXGywp4b)5 z3+~4ItYPThT=5`iDDWuH{)sRy$FHiU5)r8}%;L=pReKii=iVw`? z@=oEBmr$3H%1>4GdT3Z4xE?8QPM++J_=JVnyw9$P_rN{$S{fx*$2te7!Ju3EST^fI zGawH>5((S&tw0`|K7g>M6I|Iv;fK8OCS(G?uT}5&3hwBx6&qOH-XYfZwHa>&q8Ebk z%y4g9u#zh;WOcyT@QhhZ720DrKH$`o);D@wq+!RF8DMA{l9)sy^^S}B%E*2@|32~N zTCDNw*w|RxxtdShxqM^9Wqu+0WXZ<{f;WQ-ICj*`WQ_H@ACg&<(`<{QTt$Pr#72#G zE$d`v@zP0_l{E}j%(+9FJu!O4a4+@<_jf+0u0ht{ZZ*-B4ab&yp`;ez$;+BkK7U4F zDtQ>O%)+&=WOkJVX^9{k)l(25G&@lQ^fE{poI__;xO|io-@U%h>#-UFX3+^@DqkFad90ybqNWIK0}9Hj+Q(pw~GF^5DWn8dHxV(ZJbZz?3l zyxvN0n|{DYm3iLYbciGh^5&3m?k}aw&lH{S+B?$p2)&3##l?|=;~-Rsl4zEpWzDhF zTvs=^GQ^G=>>^&t8*N6h%^#mR@oL}6Z@k7If#`lz9Q6r(l7@;Ut3NOeg&J$N@dJsh zVAbPEw9uqc)3&_z)&k>+3CkpJ&cgItZvyRetvZf(`*tj#Zm~4So^3}{F}%}$o2qZv zJiAzmJ2s@je(UW!weM+N00=3tip0fXMZ&h$`Voy~G4YNQUFYPjH-nc(%3g@Z7vG4a zD4O+o4?_fxHMQF=4`BB}fS|*xFb7{<7^OA6r4nL_yDU$32Sw+Vc5#LaH#fnLr^N2T z(b=Aega(ee7QjU=DxXe;(&ae4J(p}x=-hA!JVM(Fx{_WKiMDsmONOwSje@h-Ax0^V zw<38J5j*Z(MC>h)xy*iZvANv#06FQDp|5Y%C$4D3#*|8Oz9mTPP_va3g?>Vvdd0+w zAtHL19}*I>t2Nycw4QG^_A-i&D>zrvC58@1UPS8+m-6n##CgTq5!RyKWI>rEHJ6?e zp9G;h(YM!_mX^=aU#YTKeOuh3%8bgV1b)Cn(2jxke4ftlTP|>&m5KMQd)Xo3a9OHravtUqNopZK*yl6nvcMs1z8yKOp+rE^O&JENev)GMyyu7FGk+J-fGx# zM(^y?@9vK&pFc@%Gr zOC@UW-Uq?)UD-&vQS$Z*fRncu(QUbS&}tdNhLr(u*l1~MiC_q__tsnQ#kS3Kz983| zM@-bbp*vS~+^E60Q2sgr;ReaU$75+y9DG233uIM+{Snv`wa|i5UqsDme?-g-KSZ0z z^JRCn+#FK^+7cYGT+E$hXk$2!;sTT@Eo{?60USOg!If3%h-2)I(b)2e zGe0Erb4O?WDH!1?)L?*!#H%g%2?ahc?XXOTPgtHYZ{GIMaEuKgNqr@e!b91pr=8Cwk=gk-M;kaxl$nWeZ7P6;8^pe5*^T@fgV%wg3 zr{)r(in@tQTGPb-1x+QZUW5tE_r%a@4ln*n^Y+)}K7KTV7bA;yRrrqrR|SLy1)`AtE+Zee~tj>j>YW$F#XxEMw#2)7B_ z9TvGc{8UM*gyKc@X|Gz%*RLFpa7LiLb(Y>`BIPA}F544S;#}w-x(U#v7(-kxTFQ^- zeSS5wfd!ejH4;MaD} zyqw*h(n%fuAecaZ5(P0I_oc$@WX8HQ6b!pr_F^DVc7jb10kkFE2kdC^*HPYQle3xgHD$^z(Kxk>%^un4>nJJ zGLJ9gX{_Wuw;p29Mb0oG0#cUEC;7Elmu{WRmYl+a^L}+`rHRnBxGt8+C)Z}RDv#tm zFkX1DB%i%tE|Yz7jU$DO!tzCwcOv460ad6^Vb=nwRPW?rzX;0lCqf_XkLt04=7S+7 zP!3TnzSnX&9W?SN@Zyv@ls-uWexEML`uV-x_0C+~P&dDA_P66!rS+;j6oDCXF{ z*9|yCk^@Slo-GsTBBaertSn+?3ac&+hG-OpR#o}px8v@k_}$_63nZXWW(`*6hC)Q> z!E2NH_Xayhr!i7R3KmlrMRZ&=^akhkZaAlsER(Q#RSxlMD}HW*C9*BWE4RXA{``Ke ze!C7%Q`kIa|30&*D@hm1Mai{eB)&bzdxQvWVBc26FR-YhXvE=${P>zqoa9AL+88bP z<0L`MWthh|(mep`nLy@md@R09Ei&HM-bku77Ai@;;=Fl*%oBy}>=$3(p!Xyb6vT8n zf?RZynVAB29s#?Y45d%L6uxLUsgJ?lUCbuog2mWXuv;TV9#DuHSTLVHEmXV+ARvtADHp0yFg?Z)a;h083Sr9hWs>r zcD2C1N7y&aixtZ1N6oq7%PIDX`fc*aD@n8;4|a9<)*dU0n=W#ah+G+6KAUIep`LLX$Vkla}n>mnU~R*o4Z>= z0oS#Q>ZP(z-hqDq|%13ai%8&%a|%*@e7A`DhZz?B_>d#v?X!2 zW)&L>>X648klmtsq5SObK>$VlxFfn3?^=gkP%f}rQ}4OnMDPU9pJC4BaT};ri5Fn-f`L&ZK*kXy*vqOciayA(N2iEdx(q{Ep4LJn zdh@Qx9z7S~h&ZjnbGC#f4TazGdrRO0*g3jO&Qxzkgc6S?X9$Vnr6%SVpmEb)yz@;+ z1tn|)?A$}4$0Ombka=A98S6Kn8RA~zCs)*rlxdMdZWH6ykIvFzWoh2sVg+m-64INB zzwX>Z2CbpI{h|w8c{Yh+j%{k9B`M-~Hukz<+#`fXjU>zzjk%@!YK^-*-HS^%(+MKk zC?(-_PC*gk)LDaJ@2ZMe|Dgm{IxX6G#f~>uLsGC@X;I)y?wL948bntPi{5EtYGig9 z#jK|T1Y`w$B96DcicaCfj7#%jMJ;fdLX&eoq3yEn&x$KQ9t4Cjx#Ml*Dr(^;BMCk~ zrO+5LD4Pt7ly26Ld#4dh|Ak>wNJaFcE7uFB(v534uM}F7^pMF|@~=kNiA5dJxM}q_ zbG)CAaYLTUcgHSlxy~t(1cho;9XBu{1?zkd|k!^|j#tTA?ZJ|7D>lrQyb zY{p6zeB|OPc~wFTd7$e}@AKv8iGwssSNz>p((~1-mdv#y`ECMdD4ww0(sa{0+ZIdBJm;RW$$u6ORQv^+?#Ls z7Vn+UKuv@EIJ83zqU6jZ?++&rsvNJC@szw#xpM&9KG=|aP3h3cv@eDuR8fFl>*EJk z;-~AP1^SrUI7taIr8vEVM{WZliCDJXb&8cOa$=Lv+zz4S=j~ksMI@&6qpjWpH31{N zkY!6IRGIDSLd3{66hV1Tj#&znm3t&TEvs1!th4j1V(tmjy9>>`2XN#R$D<$KauMnD zwkq#RN+;IG*_<>7@DFf2zKLPJp6=fhnO~bi)O00A<93%(z`;tyqw?%-nyz+ z+4pNL824zM>vz)@7QEW=-cJ(i2urBk#h}k`N0&)e@BIK(aK>[F1lucH={v$u@` zNr)%b)rwJ(wS)VG)t70dC`w}_$D9VAP25zRm^hw15L+5aaJm^`CgZdi7`}bTJpbk5 zl%RKq6qA?l-b03o*3Dt_{-}8-(Qa~&JTHzqPA+99$GkSF-HlOuK(}6?n-~N~63dtB z-DMsBDuKT-6o`I2!^4A}TFB5zRvVJn&zzV$ zWh}TP(2bP_q94Caoe-8l?DgCNA-1ntP>m|$$zoqw}lMSfR=?GCa|FK5Op}Nx|VEC3TOWO67i|f*; z1?M}@dW3I>aX5H7%ur-TrFg3=ulx5-#_KP4qwWoLE`=nG^w9)_NwiVq-4jwUw^G_p zv}^UBW5)|6y$#;b*lG!Q!Y$lLBN!6p=U3u^4;n4s|AHyE6Z2cJGbR$0#LH?mI(3kS#xuI9X!8({1R2=W4zob zbc5DF1komsO!O2Q8g5~$GxGWHEscp9!>qFHIbRQEBos^N83Nh8T#dX=pRV#3gS=JZ zQ7Wdl{?_`P>+nyP-X62az6)X2pHO~6ULQwaqQfwzGtKiv1p{)f`lCj(d0)^4pn#<^ zWj!E_>&XI#9$!Dx6&GmM0x{2m2#uWFrj=e8X?>; z1)@|5sSQT=Oc)UcXT(6b6j$OZ?R*2}{lO9~SB<@k#xw1yqx_Me3eV{LxwD8f>gm_J z(!6u+qogY0J0X0EGUnV>mMmqf8^>4v1I06!{nbaQ8Mg{u=F%Ql8N|12QUwnlss+ZYgMg+0q{<^gw67xD(E3gseHv5{qVq?yyPbb~3d(n^s&u-D~k zy?954!cK2uy-i2PpON78Xyp?O$!csBPtpr#cvWM34aOk$K{v6I*2+Apclh$Jl2Qwr zpUogEo0kURKRZDj*j3c1awn)6kZjX{Njqs`lDRpj@XmyKlatnbX}&^-fZa2!cxd|pTt{5SUAP(~Z+JqL5x3%hvsJlG zi_-VhOTW<`VD<|$^66M@b1R}a3*&|U)~476Fn*HdW^HVl*nzFQCCxc|VhYJEl%mBrhZ2WeZ$k7^nAN0bBoP(07<9)VT z4~`z(rF7qmZ1Wu?${fL#z~El&bUr7za1%uk@v(m8Gyk>V!(>D=gF9T}9x^Cbc-g$$ z+DJ#wz-%lT7tkTfR%_=A$l%9y@)Fgt)u!}tPw=HaKZ{+@funh?_2hHL{=B~T@XXrC zv6s8Rf^Iz~(HBbC%M^*n%O`ZyOKDi}OcXe6sluTg`LstY?80+XqG}|p$Q+(ObBCNIeb;`XvF8Vrz^h zv0p}T?vjiFS+4|0+pGpi*aR0PWCZ#KMo;rUmig6zU~(u)If<}bi^_E z#0==e?-;fB?s=i<+k(O}D~p_LFKLXMtmKKxf^lNzG8@cyQv(M(YKWb7AU$Y`M2bGd zG29RN4m=6d!UDoHRha5cwwk>orM<7`28BLU(E7fT7`sD<0hYcYZaj1GqlGgoji)ND zo8TjUjz53|aL%%Dd9J(i&iFNs5iR?1Dls=PpwyG zq7X0fr99y-Fm7r4te}M@iSsQENFweRQLug2$Q_T=wyzrKaabp)$V#j3Ri0xk1xzIr z3|SZ*H8@a5C&V4ab*kLZGwSSo&D!MZl9r+_DWWkdWnT)0tndI;BJ_SUME*cYbhEgT zF1LB}isyUc zzsa4CvY6~^ZmKMqr$JjHVINvm0Rf0RKveEE>s!jV68(gYOD`xT`bn^EjB(18y?fGz0~2+70?FErN{V{>72$)-*2;@*`>N)$$ms}k@C4xq zRn_x)a#a6Pi3eA5yhrSt8pad`E2uvE6`F?9?SUWbCzjqx$342WahP*Q`P{V3=u;v4 zCIZ!zOQ9v%v(x8EMG?cKj}rRhPcvpNfixz|4ZN#(FdyJ=&{fs$#TGQ!U71TTrqB^- znV7J*!cgT<;4fYLe4rz4xttTY_3P5M5S3Ws^G@cW$0Y>L{fbH@<1_>x(0jL3Z?O z++RRRwQIL!`0#;9eJG{0{= zJGDEaj-mmw4s*s*uPnH1I!vB7ve#vHK8cOxOsr7WLyve}mCS3xf3%`hUF;tpe9sdz zEzlU9xeK=@|HA!=>dt$GoHu~S+ncv9LulTdzY=n9QT5{F5V|YM5VoZb(1q1}baoP1 z^d_Qp0nTDofNGu>#{k+O%Q-;+L3^Hz)$d&urdd;H!H$b$q$M`&TV>xVg zeF-*77-iWr*4V{enQgU-3Yzx2XB2YRYnU=AEUgt&7tq(`CB{7cVVCESBdzGFi{4a6 zi$#?^gMgsKa@QEIL~TnbLc^_KYLPNlq*^in35E(fv#gKF=_EZ(Gv(6Ig>SM$e1m^YqoIj=Z)W>|X zjET*Kna3r;H$GW=qar~bieDcmo-OlmsWfa>1TT5N$Rbg1!M|yxi>A(Fe9<8bkvj+M zvM_QxM5;d^jl5$l3O)^TI@OAg*aY%xje7FA^J4 z^Bf_qFF+7j&7#aHF(_g@!SL24kwHe|$nK`7e;GI@zN~1HV#Gm zC`ho8)>GBoKdQi+L+(mdFw2J{G@=b6Lr@GoeM){(2X=5{up)j~@%>mEj3fo;7 zigw$>E8PdS>o^3W21lk;!b!#hD+u#qpk9~OgRAA-p<<_l6RuH?TZAu~T)un&fTQL@ zRd7$vGQV`)oNW}kCEUWT{TeBX&_W%@ceY3=qr5u;G@iKjnSF(|5KR$--kOGh(t{#U za~@ijsNQ&My7slxuvQw|CCf|RZGo8-i_T*@M}461QG8p(D2I9KfZa`y+7l)89LiEe z8DUCDft-6)>`a6fA1UDHF9ASGj@)Vs0r`6p?Kj~6ItksFQW)+jcTLHNkGhleD`;r0 zgu=WRAbgEt%W^9}e(dX$-C>87opwGWlr)7$<6gvLxi8f8Flna`=O9R1x0;6eQFxhEP;<%X{&q)Fj|mSW>l4|Be+*koiF)RKSB<&!ZFY{EU2TU zT)cDqfbzUogfYxeg?to)LU=;Z_mo_R8H-8)cl;aCS3NeT(a|ah4iYY(aV)NyGsrqk zdZRB$!N8EJZI239R-!bo7Ou{S92{6Re1YN$G$Fq1i7IZm$#-THG-RU-()J5ci1!{W zF~^HWrdCTNL?tzl2As&4?W;~)3{ZX7Wxl94O@J^b<^v=(8JI!CN`+VbMj4ocyGG6~ zmvP$r;DzbfSfyyoSw*2ufylpKM{Y0Qtt`LtMZ^T;h9`+FKw)8B+Z3YxB?j-vE2x!-kju}!l^-%qGm?h7ci8uDS%W^kWcHBFfS2LxjNI2L8QAh23)mhoJQZ>@ z)I4%C@fHsy?)v_|ZfM4qc;H3qQ#O9MwDrw2sGA66p>GNTh5-k;npEz!7JYE?sorL% zX;<&RM_zv{yZyE-In;*19mSL9*ol*E zv4GQkJ&7+}YZAdHABxrvc*34-nI(XV=EWbn>I)U?9W=@|`tGUrgzncvo(xk57I{K7 zcRah1>rbueYtch1N3o&t9kp@3qK}nNqc=QU8->^v1NNIlS!7k%%Np2JxsTB0S(v`y z-GTs(6wf7}iA;&-Z()P~PcH`Ii9akEiod4<+c!*HNLK2bTH6`34i6S(&b@Ad*zN&_ zh{QRo*KneBa^6^d)kkDXLtApN#i`e}nq3zpV)1L94ptao2HOk%QNQ^aq-NkmOL6rmLi#Pjmy79J7U?^5ZzLQvV;Wz%@pkTd@X2sBh6u4A zV(dAA^DuyPHp>(%!KIB;sjwp?W7etlF&^{^fJ>* zoq(zrlNh4;aE0`ntZ4Z&v24qh!(RB&XJWZ#JykXt$4H=i4iXt;Z-}oj2t}|W5tFu5 znlJzOX4Y=m5$=W%AwmYQ&#=#f>un@uzQNZ!>Zmn++G8n75!-C3KQAOlX@O5F8?V7l zc$Dw>_w%vhnb)W54sJKCR1BDi3SXNXd}>kQj`gT+Q3fhhE`mzLhyfNV4%-esPVoON zsF1y_s7%l`z)B~YXsO-wEb-*_RqZtgepaGS*F7r=!qNpBTq*u81-A``E|Rh=W=)+) zSxIrY6GDUtF}4{1fFT7D0gmPo0bFyso2IvTgel}nT>7LahkO&;J_$Gn4MM{4mh$;!K8gjXSv>YT; zl&)zcGg%xr48{l%BE){c095Rmil_J4Bhd3{fvk{-SqRwx^07V+o$iLPn~KNuF`jf+ zmou3I@j}2{GW4O|LX1?1;j}-62oWMgh~a}4PCkey:value to the configuration. +func (c *Config) AddConfig(section string, option string, value string) bool { + if section == "" { + section = DEFAULT_SECTION + } + + if _, ok := c.data[section]; !ok { + c.data[section] = make(map[string]string) + } + + _, ok := c.data[section][option] + c.data[section][option] = value + + return !ok +} + +func (c *Config) parse(fname string) (err error) { + c.Lock() + f, err := os.Open(fname) + if err != nil { + return err + } + defer c.Unlock() + defer f.Close() + + buf := bufio.NewReader(f) + return c.parseBuffer(buf) +} + +func (c *Config) parseBuffer(buf *bufio.Reader) error { + var section string + var lineNum int + var buffer bytes.Buffer + var canWrite bool + for { + if canWrite { + if err := c.write(section, lineNum, &buffer); err != nil { + return err + } else { + canWrite = false + } + } + lineNum++ + line, _, err := buf.ReadLine() + if err == io.EOF { + // force write when buffer is not flushed yet + if buffer.Len() > 0 { + if err := c.write(section, lineNum, &buffer); err != nil { + return err + } + } + break + } else if err != nil { + return err + } + + line = bytes.TrimSpace(line) + switch { + case bytes.Equal(line, []byte{}), bytes.HasPrefix(line, DEFAULT_COMMENT_SEM), + bytes.HasPrefix(line, DEFAULT_COMMENT): + canWrite = true + continue + case bytes.HasPrefix(line, []byte{'['}) && bytes.HasSuffix(line, []byte{']'}): + // force write when buffer is not flushed yet + if buffer.Len() > 0 { + if err := c.write(section, lineNum, &buffer); err != nil { + return err + } + canWrite = false + } + section = string(line[1 : len(line)-1]) + default: + var p []byte + if bytes.HasSuffix(line, DEFAULT_MULTI_LINE_SEPARATOR) { + p = bytes.TrimSpace(line[:len(line)-1]) + p = append(p, " "...) + } else { + p = line + canWrite = true + } + + end := len(p) + for i, value := range p { + if value == DEFAULT_COMMENT[0] || value == DEFAULT_COMMENT_SEM[0] { + end = i + break + } + } + if _, err := buffer.Write(p[:end]); err != nil { + return err + } + } + } + + return nil +} + +func (c *Config) write(section string, lineNum int, b *bytes.Buffer) error { + if b.Len() <= 0 { + return nil + } + + optionVal := bytes.SplitN(b.Bytes(), []byte{'='}, 2) + if len(optionVal) != 2 { + return fmt.Errorf("parse the content error : line %d , %s = ? ", lineNum, optionVal[0]) + } + option := bytes.TrimSpace(optionVal[0]) + value := bytes.TrimSpace(optionVal[1]) + c.AddConfig(section, string(option), string(value)) + + // flush buffer after adding + b.Reset() + + return nil +} + +// Bool lookups up the value using the provided key and converts the value to a bool +func (c *Config) Bool(key string) (bool, error) { + return strconv.ParseBool(c.get(key)) +} + +// Int lookups up the value using the provided key and converts the value to a int +func (c *Config) Int(key string) (int, error) { + return strconv.Atoi(c.get(key)) +} + +// Int64 lookups up the value using the provided key and converts the value to a int64 +func (c *Config) Int64(key string) (int64, error) { + return strconv.ParseInt(c.get(key), 10, 64) +} + +// Float64 lookups up the value using the provided key and converts the value to a float64 +func (c *Config) Float64(key string) (float64, error) { + return strconv.ParseFloat(c.get(key), 64) +} + +// String lookups up the value using the provided key and converts the value to a string +func (c *Config) String(key string) string { + return c.get(key) +} + +// Strings lookups up the value using the provided key and converts the value to an array of string +// by splitting the string by comma +func (c *Config) Strings(key string) []string { + v := c.get(key) + if v == "" { + return nil + } + return strings.Split(v, ",") +} + +// Set sets the value for the specific key in the Config +func (c *Config) Set(key string, value string) error { + c.Lock() + defer c.Unlock() + if len(key) == 0 { + return errors.New("key is empty") + } + + var ( + section string + option string + ) + + keys := strings.Split(strings.ToLower(key), "::") + if len(keys) >= 2 { + section = keys[0] + option = keys[1] + } else { + option = keys[0] + } + + c.AddConfig(section, option, value) + return nil +} + +// section.key or key +func (c *Config) get(key string) string { + var ( + section string + option string + ) + + keys := strings.Split(strings.ToLower(key), "::") + if len(keys) >= 2 { + section = keys[0] + option = keys[1] + } else { + section = DEFAULT_SECTION + option = keys[0] + } + + if value, ok := c.data[section][option]; ok { + return value + } + + return "" +} diff --git a/vendor/github.com/casbin/casbin/v2/effect/default_effector.go b/vendor/github.com/casbin/casbin/v2/effect/default_effector.go new file mode 100644 index 00000000..8fe7f16b --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/effect/default_effector.go @@ -0,0 +1,80 @@ +// Copyright 2018 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package effect + +import "errors" + +// DefaultEffector is default effector for Casbin. +type DefaultEffector struct { +} + +// NewDefaultEffector is the constructor for DefaultEffector. +func NewDefaultEffector() *DefaultEffector { + e := DefaultEffector{} + return &e +} + +// MergeEffects merges all matching results collected by the enforcer into a single decision. +func (e *DefaultEffector) MergeEffects(expr string, effects []Effect, results []float64) (bool, int, error) { + result := false + explainIndex := -1 + if expr == "some(where (p_eft == allow))" { + result = false + for i, eft := range effects { + if eft == Allow { + result = true + explainIndex = i + break + } + } + } else if expr == "!some(where (p_eft == deny))" { + result = true + for i, eft := range effects { + if eft == Deny { + result = false + explainIndex = i + break + } + } + } else if expr == "some(where (p_eft == allow)) && !some(where (p_eft == deny))" { + result = false + for i, eft := range effects { + if eft == Allow { + result = true + } else if eft == Deny { + result = false + explainIndex = i + break + } + } + } else if expr == "priority(p_eft) || deny" { + result = false + for i, eft := range effects { + if eft != Indeterminate { + if eft == Allow { + result = true + } else { + result = false + } + explainIndex = i + break + } + } + } else { + return false, -1, errors.New("unsupported effect") + } + + return result, explainIndex, nil +} diff --git a/vendor/github.com/casbin/casbin/v2/effect/effector.go b/vendor/github.com/casbin/casbin/v2/effect/effector.go new file mode 100644 index 00000000..d48ce236 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/effect/effector.go @@ -0,0 +1,31 @@ +// Copyright 2018 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package effect + +// Effect is the result for a policy rule. +type Effect int + +// Values for policy effect. +const ( + Allow Effect = iota + Indeterminate + Deny +) + +// Effector is the interface for Casbin effectors. +type Effector interface { + // MergeEffects merges all matching results collected by the enforcer into a single decision. + MergeEffects(expr string, effects []Effect, results []float64) (bool, int, error) +} diff --git a/vendor/github.com/casbin/casbin/v2/enforcer.go b/vendor/github.com/casbin/casbin/v2/enforcer.go new file mode 100644 index 00000000..d995ade9 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/enforcer.go @@ -0,0 +1,617 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +import ( + "errors" + "fmt" + "strings" + + "github.com/Knetic/govaluate" + "github.com/casbin/casbin/v2/effect" + "github.com/casbin/casbin/v2/log" + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" + fileadapter "github.com/casbin/casbin/v2/persist/file-adapter" + "github.com/casbin/casbin/v2/rbac" + defaultrolemanager "github.com/casbin/casbin/v2/rbac/default-role-manager" + "github.com/casbin/casbin/v2/util" +) + +// Enforcer is the main interface for authorization enforcement and policy management. +type Enforcer struct { + modelPath string + model model.Model + fm model.FunctionMap + eft effect.Effector + + adapter persist.Adapter + watcher persist.Watcher + rm rbac.RoleManager + + enabled bool + autoSave bool + autoBuildRoleLinks bool + autoNotifyWatcher bool +} + +// NewEnforcer creates an enforcer via file or DB. +// +// File: +// +// e := casbin.NewEnforcer("path/to/basic_model.conf", "path/to/basic_policy.csv") +// +// MySQL DB: +// +// a := mysqladapter.NewDBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") +// e := casbin.NewEnforcer("path/to/basic_model.conf", a) +// +func NewEnforcer(params ...interface{}) (*Enforcer, error) { + e := &Enforcer{} + + parsedParamLen := 0 + paramLen := len(params) + if paramLen >= 1 { + enableLog, ok := params[paramLen-1].(bool) + if ok { + e.EnableLog(enableLog) + + parsedParamLen++ + } + } + + if paramLen-parsedParamLen == 2 { + switch p0 := params[0].(type) { + case string: + switch p1 := params[1].(type) { + case string: + err := e.InitWithFile(p0, p1) + if err != nil { + return nil, err + } + default: + err := e.InitWithAdapter(p0, p1.(persist.Adapter)) + if err != nil { + return nil, err + } + } + default: + switch params[1].(type) { + case string: + return nil, errors.New("invalid parameters for enforcer") + default: + err := e.InitWithModelAndAdapter(p0.(model.Model), params[1].(persist.Adapter)) + if err != nil { + return nil, err + } + } + } + } else if paramLen-parsedParamLen == 1 { + switch p0 := params[0].(type) { + case string: + err := e.InitWithFile(p0, "") + if err != nil { + return nil, err + } + default: + err := e.InitWithModelAndAdapter(p0.(model.Model), nil) + if err != nil { + return nil, err + } + } + } else if paramLen-parsedParamLen == 0 { + return e, nil + } else { + return nil, errors.New("invalid parameters for enforcer") + } + + return e, nil +} + +// InitWithFile initializes an enforcer with a model file and a policy file. +func (e *Enforcer) InitWithFile(modelPath string, policyPath string) error { + a := fileadapter.NewAdapter(policyPath) + return e.InitWithAdapter(modelPath, a) +} + +// InitWithAdapter initializes an enforcer with a database adapter. +func (e *Enforcer) InitWithAdapter(modelPath string, adapter persist.Adapter) error { + m, err := model.NewModelFromFile(modelPath) + if err != nil { + return err + } + + err = e.InitWithModelAndAdapter(m, adapter) + if err != nil { + return err + } + + e.modelPath = modelPath + return nil +} + +// InitWithModelAndAdapter initializes an enforcer with a model and a database adapter. +func (e *Enforcer) InitWithModelAndAdapter(m model.Model, adapter persist.Adapter) error { + e.adapter = adapter + + e.model = m + e.model.PrintModel() + e.fm = model.LoadFunctionMap() + + e.initialize() + + // Do not initialize the full policy when using a filtered adapter + fa, ok := e.adapter.(persist.FilteredAdapter) + if e.adapter != nil && (!ok || ok && !fa.IsFiltered()) { + err := e.LoadPolicy() + if err != nil { + return err + } + } + + return nil +} + +func (e *Enforcer) initialize() { + e.rm = defaultrolemanager.NewRoleManager(10) + e.eft = effect.NewDefaultEffector() + e.watcher = nil + + e.enabled = true + e.autoSave = true + e.autoBuildRoleLinks = true + e.autoNotifyWatcher = true +} + +// LoadModel reloads the model from the model CONF file. +// Because the policy is attached to a model, so the policy is invalidated and needs to be reloaded by calling LoadPolicy(). +func (e *Enforcer) LoadModel() error { + var err error + e.model, err = model.NewModelFromFile(e.modelPath) + if err != nil { + return err + } + + e.model.PrintModel() + e.fm = model.LoadFunctionMap() + + e.initialize() + + return nil +} + +// GetModel gets the current model. +func (e *Enforcer) GetModel() model.Model { + return e.model +} + +// SetModel sets the current model. +func (e *Enforcer) SetModel(m model.Model) { + e.model = m + e.fm = model.LoadFunctionMap() + + e.initialize() +} + +// GetAdapter gets the current adapter. +func (e *Enforcer) GetAdapter() persist.Adapter { + return e.adapter +} + +// SetAdapter sets the current adapter. +func (e *Enforcer) SetAdapter(adapter persist.Adapter) { + e.adapter = adapter +} + +// SetWatcher sets the current watcher. +func (e *Enforcer) SetWatcher(watcher persist.Watcher) error { + e.watcher = watcher + return watcher.SetUpdateCallback(func(string) { e.LoadPolicy() }) +} + +// GetRoleManager gets the current role manager. +func (e *Enforcer) GetRoleManager() rbac.RoleManager { + return e.rm +} + +// SetRoleManager sets the current role manager. +func (e *Enforcer) SetRoleManager(rm rbac.RoleManager) { + e.rm = rm +} + +// SetEffector sets the current effector. +func (e *Enforcer) SetEffector(eft effect.Effector) { + e.eft = eft +} + +// ClearPolicy clears all policy. +func (e *Enforcer) ClearPolicy() { + e.model.ClearPolicy() +} + +// LoadPolicy reloads the policy from file/database. +func (e *Enforcer) LoadPolicy() error { + e.model.ClearPolicy() + if err := e.adapter.LoadPolicy(e.model); err != nil && err.Error() != "invalid file path, file path cannot be empty" { + return err + } + + e.model.PrintPolicy() + if e.autoBuildRoleLinks { + err := e.BuildRoleLinks() + if err != nil { + return err + } + } + return nil +} + +// LoadFilteredPolicy reloads a filtered policy from file/database. +func (e *Enforcer) LoadFilteredPolicy(filter interface{}) error { + e.model.ClearPolicy() + + var filteredAdapter persist.FilteredAdapter + + // Attempt to cast the Adapter as a FilteredAdapter + switch adapter := e.adapter.(type) { + case persist.FilteredAdapter: + filteredAdapter = adapter + default: + return errors.New("filtered policies are not supported by this adapter") + } + if err := filteredAdapter.LoadFilteredPolicy(e.model, filter); err != nil && err.Error() != "invalid file path, file path cannot be empty" { + return err + } + + e.model.PrintPolicy() + if e.autoBuildRoleLinks { + err := e.BuildRoleLinks() + if err != nil { + return err + } + } + return nil +} + +// IsFiltered returns true if the loaded policy has been filtered. +func (e *Enforcer) IsFiltered() bool { + filteredAdapter, ok := e.adapter.(persist.FilteredAdapter) + if !ok { + return false + } + return filteredAdapter.IsFiltered() +} + +// SavePolicy saves the current policy (usually after changed with Casbin API) back to file/database. +func (e *Enforcer) SavePolicy() error { + if e.IsFiltered() { + return errors.New("cannot save a filtered policy") + } + if err := e.adapter.SavePolicy(e.model); err != nil { + return err + } + if e.watcher != nil { + var err error + if watcher, ok := e.watcher.(persist.WatcherEx); ok { + err = watcher.UpdateForSavePolicy(e.model) + } else { + err = e.watcher.Update() + } + return err + } + return nil +} + +// EnableEnforce changes the enforcing state of Casbin, when Casbin is disabled, all access will be allowed by the Enforce() function. +func (e *Enforcer) EnableEnforce(enable bool) { + e.enabled = enable +} + +// EnableLog changes whether Casbin will log messages to the Logger. +func (e *Enforcer) EnableLog(enable bool) { + log.GetLogger().EnableLog(enable) +} + +// EnableAutoNotifyWatcher controls whether to save a policy rule automatically notify the Watcher when it is added or removed. +func (e *Enforcer) EnableAutoNotifyWatcher(enable bool) { + e.autoNotifyWatcher = enable +} + +// EnableAutoSave controls whether to save a policy rule automatically to the adapter when it is added or removed. +func (e *Enforcer) EnableAutoSave(autoSave bool) { + e.autoSave = autoSave +} + +// EnableAutoBuildRoleLinks controls whether to rebuild the role inheritance relations when a role is added or deleted. +func (e *Enforcer) EnableAutoBuildRoleLinks(autoBuildRoleLinks bool) { + e.autoBuildRoleLinks = autoBuildRoleLinks +} + +// BuildRoleLinks manually rebuild the role inheritance relations. +func (e *Enforcer) BuildRoleLinks() error { + err := e.rm.Clear() + if err != nil { + return err + } + + return e.model.BuildRoleLinks(e.rm) +} + +// BuildIncrementalRoleLinks provides incremental build the role inheritance relations. +func (e *Enforcer) BuildIncrementalRoleLinks(op model.PolicyOp, ptype string, rules [][]string) error { + return e.model.BuildIncrementalRoleLinks(e.rm, op, "g", ptype, rules) +} + +// enforce use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "". +func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interface{}) (ok bool, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic: %v", r) + } + }() + + if !e.enabled { + return true, nil + } + + functions := model.FunctionMap{} + for k, v := range e.fm { + functions[k] = v + } + if _, ok := e.model["g"]; ok { + for key, ast := range e.model["g"] { + rm := ast.RM + functions[key] = util.GenerateGFunction(rm) + } + } + var expString string + if matcher == "" { + expString = e.model["m"]["m"].Value + } else { + expString = util.RemoveComments(util.EscapeAssertion(matcher)) + } + + var expression *govaluate.EvaluableExpression + hasEval := util.HasEval(expString) + + if !hasEval { + expression, err = govaluate.NewEvaluableExpressionWithFunctions(expString, functions) + if err != nil { + return false, err + } + } + + rTokens := make(map[string]int, len(e.model["r"]["r"].Tokens)) + for i, token := range e.model["r"]["r"].Tokens { + rTokens[token] = i + } + pTokens := make(map[string]int, len(e.model["p"]["p"].Tokens)) + for i, token := range e.model["p"]["p"].Tokens { + pTokens[token] = i + } + + parameters := enforceParameters{ + rTokens: rTokens, + rVals: rvals, + + pTokens: pTokens, + } + + var policyEffects []effect.Effect + var matcherResults []float64 + + if policyLen := len(e.model["p"]["p"].Policy); policyLen != 0 { + policyEffects = make([]effect.Effect, policyLen) + matcherResults = make([]float64, policyLen) + if len(e.model["r"]["r"].Tokens) != len(rvals) { + return false, fmt.Errorf( + "invalid request size: expected %d, got %d, rvals: %v", + len(e.model["r"]["r"].Tokens), + len(rvals), + rvals) + } + for i, pvals := range e.model["p"]["p"].Policy { + // log.LogPrint("Policy Rule: ", pvals) + if len(e.model["p"]["p"].Tokens) != len(pvals) { + return false, fmt.Errorf( + "invalid policy size: expected %d, got %d, pvals: %v", + len(e.model["p"]["p"].Tokens), + len(pvals), + pvals) + } + + parameters.pVals = pvals + + if hasEval { + ruleNames := util.GetEvalValue(expString) + var expWithRule = expString + for _, ruleName := range ruleNames { + if j, ok := parameters.pTokens[ruleName]; ok { + rule := util.EscapeAssertion(pvals[j]) + expWithRule = util.ReplaceEval(expWithRule, rule) + } else { + return false, errors.New("please make sure rule exists in policy when using eval() in matcher") + } + + expression, err = govaluate.NewEvaluableExpressionWithFunctions(expWithRule, functions) + if err != nil { + return false, fmt.Errorf("p.sub_rule should satisfy the syntax of matcher: %s", err) + } + } + + } + + result, err := expression.Eval(parameters) + // log.LogPrint("Result: ", result) + + if err != nil { + return false, err + } + + switch result := result.(type) { + case bool: + if !result { + policyEffects[i] = effect.Indeterminate + continue + } + case float64: + if result == 0 { + policyEffects[i] = effect.Indeterminate + continue + } else { + matcherResults[i] = result + } + default: + return false, errors.New("matcher result should be bool, int or float") + } + + if j, ok := parameters.pTokens["p_eft"]; ok { + eft := parameters.pVals[j] + if eft == "allow" { + policyEffects[i] = effect.Allow + } else if eft == "deny" { + policyEffects[i] = effect.Deny + } else { + policyEffects[i] = effect.Indeterminate + } + } else { + policyEffects[i] = effect.Allow + } + + if e.model["e"]["e"].Value == "priority(p_eft) || deny" { + break + } + + } + } else { + policyEffects = make([]effect.Effect, 1) + matcherResults = make([]float64, 1) + + parameters.pVals = make([]string, len(parameters.pTokens)) + + result, err := expression.Eval(parameters) + // log.LogPrint("Result: ", result) + + if err != nil { + return false, err + } + + if result.(bool) { + policyEffects[0] = effect.Allow + } else { + policyEffects[0] = effect.Indeterminate + } + } + + // log.LogPrint("Rule Results: ", policyEffects) + + result, explainIndex, err := e.eft.MergeEffects(e.model["e"]["e"].Value, policyEffects, matcherResults) + if err != nil { + return false, err + } + + if explains != nil { + if explainIndex != -1 { + *explains = e.model["p"]["p"].Policy[explainIndex] + } + } + + // Log request. + if log.GetLogger().IsEnabled() { + var reqStr strings.Builder + reqStr.WriteString("Request: ") + for i, rval := range rvals { + if i != len(rvals)-1 { + reqStr.WriteString(fmt.Sprintf("%v, ", rval)) + } else { + reqStr.WriteString(fmt.Sprintf("%v", rval)) + } + } + reqStr.WriteString(fmt.Sprintf(" ---> %t\n", result)) + + if explains != nil { + reqStr.WriteString("Hit Policy: ") + for i, pval := range *explains { + if i != len(*explains)-1 { + reqStr.WriteString(fmt.Sprintf("%v, ", pval)) + } else { + reqStr.WriteString(fmt.Sprintf("%v \n", pval)) + } + } + + } + + log.LogPrint(reqStr.String()) + } + + return result, nil +} + +// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). +func (e *Enforcer) Enforce(rvals ...interface{}) (bool, error) { + return e.enforce("", nil, rvals...) +} + +// EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "". +func (e *Enforcer) EnforceWithMatcher(matcher string, rvals ...interface{}) (bool, error) { + return e.enforce(matcher, nil, rvals...) +} + +// EnforceEx explain enforcement by informing matched rules +func (e *Enforcer) EnforceEx(rvals ...interface{}) (bool, []string, error) { + explain := []string{} + result, err := e.enforce("", &explain, rvals...) + return result, explain, err +} + +// EnforceExWithMatcher use a custom matcher and explain enforcement by informing matched rules +func (e *Enforcer) EnforceExWithMatcher(matcher string, rvals ...interface{}) (bool, []string, error) { + explain := []string{} + result, err := e.enforce(matcher, &explain, rvals...) + return result, explain, err +} + +// assumes bounds have already been checked +type enforceParameters struct { + rTokens map[string]int + rVals []interface{} + + pTokens map[string]int + pVals []string +} + +// implements govaluate.Parameters +func (p enforceParameters) Get(name string) (interface{}, error) { + if name == "" { + return nil, nil + } + + switch name[0] { + case 'p': + i, ok := p.pTokens[name] + if !ok { + return nil, errors.New("No parameter '" + name + "' found.") + } + return p.pVals[i], nil + case 'r': + i, ok := p.rTokens[name] + if !ok { + return nil, errors.New("No parameter '" + name + "' found.") + } + return p.rVals[i], nil + default: + return nil, errors.New("No parameter '" + name + "' found.") + } +} diff --git a/vendor/github.com/casbin/casbin/v2/enforcer_cached.go b/vendor/github.com/casbin/casbin/v2/enforcer_cached.go new file mode 100644 index 00000000..d729d5cb --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/enforcer_cached.go @@ -0,0 +1,95 @@ +// Copyright 2018 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +import ( + "strings" + "sync" +) + +// CachedEnforcer wraps Enforcer and provides decision cache +type CachedEnforcer struct { + *Enforcer + m map[string]bool + enableCache bool + locker *sync.RWMutex +} + +// NewCachedEnforcer creates a cached enforcer via file or DB. +func NewCachedEnforcer(params ...interface{}) (*CachedEnforcer, error) { + e := &CachedEnforcer{} + var err error + e.Enforcer, err = NewEnforcer(params...) + if err != nil { + return nil, err + } + + e.enableCache = true + e.m = make(map[string]bool) + e.locker = new(sync.RWMutex) + return e, nil +} + +// EnableCache determines whether to enable cache on Enforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions. +func (e *CachedEnforcer) EnableCache(enableCache bool) { + e.enableCache = enableCache +} + +// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). +// if rvals is not string , ingore the cache +func (e *CachedEnforcer) Enforce(rvals ...interface{}) (bool, error) { + if !e.enableCache { + return e.Enforcer.Enforce(rvals...) + } + + var key strings.Builder + for _, rval := range rvals { + if val, ok := rval.(string); ok { + key.WriteString(val) + key.WriteString("$$") + } else { + return e.Enforcer.Enforce(rvals...) + } + } + + if res, ok := e.getCachedResult(key.String()); ok { + return res, nil + } + res, err := e.Enforcer.Enforce(rvals...) + if err != nil { + return false, err + } + + e.setCachedResult(key.String(), res) + return res, nil +} + +func (e *CachedEnforcer) getCachedResult(key string) (res bool, ok bool) { + e.locker.RLock() + defer e.locker.RUnlock() + res, ok = e.m[key] + return +} + +func (e *CachedEnforcer) setCachedResult(key string, res bool) { + e.locker.Lock() + defer e.locker.Unlock() + e.m[key] = res +} + +// InvalidateCache deletes all the existing cached decisions. +func (e *CachedEnforcer) InvalidateCache() { + e.m = make(map[string]bool) +} diff --git a/vendor/github.com/casbin/casbin/v2/enforcer_interface.go b/vendor/github.com/casbin/casbin/v2/enforcer_interface.go new file mode 100644 index 00000000..f414c1de --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/enforcer_interface.go @@ -0,0 +1,130 @@ +// Copyright 2019 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +import ( + "github.com/Knetic/govaluate" + "github.com/casbin/casbin/v2/effect" + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" + "github.com/casbin/casbin/v2/rbac" +) + +var _ IEnforcer = &Enforcer{} +var _ IEnforcer = &SyncedEnforcer{} +var _ IEnforcer = &CachedEnforcer{} + +// IEnforcer is the API interface of Enforcer +type IEnforcer interface { + /* Enforcer API */ + InitWithFile(modelPath string, policyPath string) error + InitWithAdapter(modelPath string, adapter persist.Adapter) error + InitWithModelAndAdapter(m model.Model, adapter persist.Adapter) error + initialize() + LoadModel() error + GetModel() model.Model + SetModel(m model.Model) + GetAdapter() persist.Adapter + SetAdapter(adapter persist.Adapter) + SetWatcher(watcher persist.Watcher) error + GetRoleManager() rbac.RoleManager + SetRoleManager(rm rbac.RoleManager) + SetEffector(eft effect.Effector) + ClearPolicy() + LoadPolicy() error + LoadFilteredPolicy(filter interface{}) error + IsFiltered() bool + SavePolicy() error + EnableEnforce(enable bool) + EnableLog(enable bool) + EnableAutoNotifyWatcher(enable bool) + EnableAutoSave(autoSave bool) + EnableAutoBuildRoleLinks(autoBuildRoleLinks bool) + BuildRoleLinks() error + enforce(matcher string, explains *[]string, rvals ...interface{}) (bool, error) + Enforce(rvals ...interface{}) (bool, error) + EnforceWithMatcher(matcher string, rvals ...interface{}) (bool, error) + EnforceEx(rvals ...interface{}) (bool, []string, error) + EnforceExWithMatcher(matcher string, rvals ...interface{}) (bool, []string, error) + + /* RBAC API */ + GetRolesForUser(name string, domain ...string) ([]string, error) + GetUsersForRole(name string, domain ...string) ([]string, error) + HasRoleForUser(name string, role string) (bool, error) + AddRoleForUser(user string, role string) (bool, error) + AddPermissionForUser(user string, permission ...string) (bool, error) + DeletePermissionForUser(user string, permission ...string) (bool, error) + DeletePermissionsForUser(user string) (bool, error) + GetPermissionsForUser(user string) [][]string + HasPermissionForUser(user string, permission ...string) bool + GetImplicitRolesForUser(name string, domain ...string) ([]string, error) + GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error) + GetImplicitUsersForPermission(permission ...string) ([]string, error) + DeleteRoleForUser(user string, role string) (bool, error) + DeleteRolesForUser(user string) (bool, error) + DeleteUser(user string) (bool, error) + DeleteRole(role string) (bool, error) + DeletePermission(permission ...string) (bool, error) + + /* RBAC API with domains*/ + GetUsersForRoleInDomain(name string, domain string) []string + GetRolesForUserInDomain(name string, domain string) []string + GetPermissionsForUserInDomain(user string, domain string) [][]string + AddRoleForUserInDomain(user string, role string, domain string) (bool, error) + DeleteRoleForUserInDomain(user string, role string, domain string) (bool, error) + + /* Management API */ + GetAllSubjects() []string + GetAllNamedSubjects(ptype string) []string + GetAllObjects() []string + GetAllNamedObjects(ptype string) []string + GetAllActions() []string + GetAllNamedActions(ptype string) []string + GetAllRoles() []string + GetAllNamedRoles(ptype string) []string + GetPolicy() [][]string + GetFilteredPolicy(fieldIndex int, fieldValues ...string) [][]string + GetNamedPolicy(ptype string) [][]string + GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string + GetGroupingPolicy() [][]string + GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) [][]string + GetNamedGroupingPolicy(ptype string) [][]string + GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string + HasPolicy(params ...interface{}) bool + HasNamedPolicy(ptype string, params ...interface{}) bool + AddPolicy(params ...interface{}) (bool, error) + AddPolicies(rules [][]string) (bool, error) + AddNamedPolicy(ptype string, params ...interface{}) (bool, error) + AddNamedPolicies(ptype string, rules [][]string) (bool, error) + RemovePolicy(params ...interface{}) (bool, error) + RemovePolicies(rules [][]string) (bool, error) + RemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error) + RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error) + RemoveNamedPolicies(ptype string, rules [][]string) (bool, error) + RemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) + HasGroupingPolicy(params ...interface{}) bool + HasNamedGroupingPolicy(ptype string, params ...interface{}) bool + AddGroupingPolicy(params ...interface{}) (bool, error) + AddGroupingPolicies(rules [][]string) (bool, error) + AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) + AddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) + RemoveGroupingPolicy(params ...interface{}) (bool, error) + RemoveGroupingPolicies(rules [][]string) (bool, error) + RemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error) + RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) + RemoveNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) + RemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) + AddFunction(name string, function govaluate.ExpressionFunction) +} diff --git a/vendor/github.com/casbin/casbin/v2/enforcer_synced.go b/vendor/github.com/casbin/casbin/v2/enforcer_synced.go new file mode 100644 index 00000000..e1470acb --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/enforcer_synced.go @@ -0,0 +1,370 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +import ( + "sync" + "time" + + "github.com/Knetic/govaluate" + "github.com/casbin/casbin/v2/log" + "github.com/casbin/casbin/v2/persist" +) + +// SyncedEnforcer wraps Enforcer and provides synchronized access +type SyncedEnforcer struct { + *Enforcer + m sync.RWMutex + stopAutoLoad chan struct{} + autoLoadRunning bool +} + +// NewSyncedEnforcer creates a synchronized enforcer via file or DB. +func NewSyncedEnforcer(params ...interface{}) (*SyncedEnforcer, error) { + e := &SyncedEnforcer{} + var err error + e.Enforcer, err = NewEnforcer(params...) + if err != nil { + return nil, err + } + + e.stopAutoLoad = make(chan struct{}, 1) + return e, nil +} + +// StartAutoLoadPolicy starts a go routine that will every specified duration call LoadPolicy +func (e *SyncedEnforcer) StartAutoLoadPolicy(d time.Duration) { + // Don't start another goroutine if there is already one running + if e.autoLoadRunning { + return + } + e.autoLoadRunning = true + ticker := time.NewTicker(d) + go func() { + defer func() { + ticker.Stop() + e.autoLoadRunning = false + }() + n := 1 + log.LogPrintf("Start automatically load policy") + for { + select { + case <-ticker.C: + // error intentionally ignored + _ = e.LoadPolicy() + // Uncomment this line to see when the policy is loaded. + // log.Print("Load policy for time: ", n) + n++ + case <-e.stopAutoLoad: + log.LogPrintf("Stop automatically load policy") + return + } + } + }() +} + +// StopAutoLoadPolicy causes the go routine to exit. +func (e *SyncedEnforcer) StopAutoLoadPolicy() { + if e.autoLoadRunning { + e.stopAutoLoad <- struct{}{} + } +} + +// SetWatcher sets the current watcher. +func (e *SyncedEnforcer) SetWatcher(watcher persist.Watcher) error { + e.watcher = watcher + return watcher.SetUpdateCallback(func(string) { e.LoadPolicy() }) +} + +// ClearPolicy clears all policy. +func (e *SyncedEnforcer) ClearPolicy() { + e.m.Lock() + defer e.m.Unlock() + e.Enforcer.ClearPolicy() +} + +// LoadPolicy reloads the policy from file/database. +func (e *SyncedEnforcer) LoadPolicy() error { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.LoadPolicy() +} + +// LoadFilteredPolicy reloads a filtered policy from file/database. +func (e *SyncedEnforcer) LoadFilteredPolicy(filter interface{}) error { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.LoadFilteredPolicy(filter) +} + +// SavePolicy saves the current policy (usually after changed with Casbin API) back to file/database. +func (e *SyncedEnforcer) SavePolicy() error { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.SavePolicy() +} + +// BuildRoleLinks manually rebuild the role inheritance relations. +func (e *SyncedEnforcer) BuildRoleLinks() error { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.BuildRoleLinks() +} + +// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). +func (e *SyncedEnforcer) Enforce(rvals ...interface{}) (bool, error) { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.Enforce(rvals...) +} + +// GetAllSubjects gets the list of subjects that show up in the current policy. +func (e *SyncedEnforcer) GetAllSubjects() []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetAllSubjects() +} + +// GetAllNamedSubjects gets the list of subjects that show up in the current named policy. +func (e *SyncedEnforcer) GetAllNamedSubjects(ptype string) []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetAllNamedSubjects(ptype) +} + +// GetAllObjects gets the list of objects that show up in the current policy. +func (e *SyncedEnforcer) GetAllObjects() []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetAllObjects() +} + +// GetAllNamedObjects gets the list of objects that show up in the current named policy. +func (e *SyncedEnforcer) GetAllNamedObjects(ptype string) []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetAllNamedObjects(ptype) +} + +// GetAllActions gets the list of actions that show up in the current policy. +func (e *SyncedEnforcer) GetAllActions() []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetAllActions() +} + +// GetAllNamedActions gets the list of actions that show up in the current named policy. +func (e *SyncedEnforcer) GetAllNamedActions(ptype string) []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetAllNamedActions(ptype) +} + +// GetAllRoles gets the list of roles that show up in the current policy. +func (e *SyncedEnforcer) GetAllRoles() []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetAllRoles() +} + +// GetAllNamedRoles gets the list of roles that show up in the current named policy. +func (e *SyncedEnforcer) GetAllNamedRoles(ptype string) []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetAllNamedRoles(ptype) +} + +// GetPolicy gets all the authorization rules in the policy. +func (e *SyncedEnforcer) GetPolicy() [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetPolicy() +} + +// GetFilteredPolicy gets all the authorization rules in the policy, field filters can be specified. +func (e *SyncedEnforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetFilteredPolicy(fieldIndex, fieldValues...) +} + +// GetNamedPolicy gets all the authorization rules in the named policy. +func (e *SyncedEnforcer) GetNamedPolicy(ptype string) [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetNamedPolicy(ptype) +} + +// GetFilteredNamedPolicy gets all the authorization rules in the named policy, field filters can be specified. +func (e *SyncedEnforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetFilteredNamedPolicy(ptype, fieldIndex, fieldValues...) +} + +// GetGroupingPolicy gets all the role inheritance rules in the policy. +func (e *SyncedEnforcer) GetGroupingPolicy() [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetGroupingPolicy() +} + +// GetFilteredGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified. +func (e *SyncedEnforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetFilteredGroupingPolicy(fieldIndex, fieldValues...) +} + +// GetNamedGroupingPolicy gets all the role inheritance rules in the policy. +func (e *SyncedEnforcer) GetNamedGroupingPolicy(ptype string) [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetNamedGroupingPolicy(ptype) +} + +// GetFilteredNamedGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified. +func (e *SyncedEnforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetFilteredNamedGroupingPolicy(ptype, fieldIndex, fieldValues...) +} + +// HasPolicy determines whether an authorization rule exists. +func (e *SyncedEnforcer) HasPolicy(params ...interface{}) bool { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.HasPolicy(params...) +} + +// HasNamedPolicy determines whether a named authorization rule exists. +func (e *SyncedEnforcer) HasNamedPolicy(ptype string, params ...interface{}) bool { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.HasNamedPolicy(ptype, params...) +} + +// AddPolicy adds an authorization rule to the current policy. +// If the rule already exists, the function returns false and the rule will not be added. +// Otherwise the function returns true by adding the new rule. +func (e *SyncedEnforcer) AddPolicy(params ...interface{}) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.AddPolicy(params...) +} + +// AddNamedPolicy adds an authorization rule to the current named policy. +// If the rule already exists, the function returns false and the rule will not be added. +// Otherwise the function returns true by adding the new rule. +func (e *SyncedEnforcer) AddNamedPolicy(ptype string, params ...interface{}) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.AddNamedPolicy(ptype, params...) +} + +// RemovePolicy removes an authorization rule from the current policy. +func (e *SyncedEnforcer) RemovePolicy(params ...interface{}) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.RemovePolicy(params...) +} + +// RemoveFilteredPolicy removes an authorization rule from the current policy, field filters can be specified. +func (e *SyncedEnforcer) RemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.RemoveFilteredPolicy(fieldIndex, fieldValues...) +} + +// RemoveNamedPolicy removes an authorization rule from the current named policy. +func (e *SyncedEnforcer) RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.RemoveNamedPolicy(ptype, params...) +} + +// RemoveFilteredNamedPolicy removes an authorization rule from the current named policy, field filters can be specified. +func (e *SyncedEnforcer) RemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.RemoveFilteredNamedPolicy(ptype, fieldIndex, fieldValues...) +} + +// HasGroupingPolicy determines whether a role inheritance rule exists. +func (e *SyncedEnforcer) HasGroupingPolicy(params ...interface{}) bool { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.HasGroupingPolicy(params...) +} + +// HasNamedGroupingPolicy determines whether a named role inheritance rule exists. +func (e *SyncedEnforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) bool { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.HasNamedGroupingPolicy(ptype, params...) +} + +// AddGroupingPolicy adds a role inheritance rule to the current policy. +// If the rule already exists, the function returns false and the rule will not be added. +// Otherwise the function returns true by adding the new rule. +func (e *SyncedEnforcer) AddGroupingPolicy(params ...interface{}) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.AddGroupingPolicy(params...) +} + +// AddNamedGroupingPolicy adds a named role inheritance rule to the current policy. +// If the rule already exists, the function returns false and the rule will not be added. +// Otherwise the function returns true by adding the new rule. +func (e *SyncedEnforcer) AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.AddNamedGroupingPolicy(ptype, params...) +} + +// RemoveGroupingPolicy removes a role inheritance rule from the current policy. +func (e *SyncedEnforcer) RemoveGroupingPolicy(params ...interface{}) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.RemoveGroupingPolicy(params...) +} + +// RemoveFilteredGroupingPolicy removes a role inheritance rule from the current policy, field filters can be specified. +func (e *SyncedEnforcer) RemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.RemoveFilteredGroupingPolicy(fieldIndex, fieldValues...) +} + +// RemoveNamedGroupingPolicy removes a role inheritance rule from the current named policy. +func (e *SyncedEnforcer) RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.RemoveNamedGroupingPolicy(ptype, params...) +} + +// RemoveFilteredNamedGroupingPolicy removes a role inheritance rule from the current named policy, field filters can be specified. +func (e *SyncedEnforcer) RemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.RemoveFilteredNamedGroupingPolicy(ptype, fieldIndex, fieldValues...) +} + +// AddFunction adds a customized function. +func (e *SyncedEnforcer) AddFunction(name string, function govaluate.ExpressionFunction) { + e.m.Lock() + defer e.m.Unlock() + e.Enforcer.AddFunction(name, function) +} diff --git a/vendor/github.com/casbin/casbin/v2/errors/rbac_errors.go b/vendor/github.com/casbin/casbin/v2/errors/rbac_errors.go new file mode 100644 index 00000000..68b1450c --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/errors/rbac_errors.go @@ -0,0 +1,24 @@ +// Copyright 2018 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import "errors" + +// Global errors for rbac defined here +var ( + ERR_NAME_NOT_FOUND = errors.New("error: name does not exist") + ERR_DOMAIN_PARAMETER = errors.New("error: domain should be 1 parameter") + ERR_NAMES12_NOT_FOUND = errors.New("error: name1 or name2 does not exist") +) diff --git a/vendor/github.com/casbin/casbin/v2/go.mod b/vendor/github.com/casbin/casbin/v2/go.mod new file mode 100644 index 00000000..c6c765ac --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/go.mod @@ -0,0 +1,5 @@ +module github.com/casbin/casbin/v2 + +require github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible + +go 1.13 diff --git a/vendor/github.com/casbin/casbin/v2/go.sum b/vendor/github.com/casbin/casbin/v2/go.sum new file mode 100644 index 00000000..d1c7c352 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/go.sum @@ -0,0 +1,2 @@ +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= diff --git a/vendor/github.com/casbin/casbin/v2/internal_api.go b/vendor/github.com/casbin/casbin/v2/internal_api.go new file mode 100644 index 00000000..455ebdff --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/internal_api.go @@ -0,0 +1,194 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +import ( + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" +) + +const ( + notImplemented = "not implemented" +) + +// addPolicy adds a rule to the current policy. +func (e *Enforcer) addPolicy(sec string, ptype string, rule []string) (bool, error) { + ruleAdded := e.model.AddPolicy(sec, ptype, rule) + if !ruleAdded { + return ruleAdded, nil + } + + if sec == "g" { + err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{rule}) + if err != nil { + return ruleAdded, err + } + } + + if e.adapter != nil && e.autoSave { + if err := e.adapter.AddPolicy(sec, ptype, rule); err != nil { + if err.Error() != notImplemented { + return ruleAdded, err + } + } + } + + if e.watcher != nil && e.autoNotifyWatcher { + var err error + if watcher, ok := e.watcher.(persist.WatcherEx); ok { + err = watcher.UpdateForAddPolicy(rule...) + } else { + err = e.watcher.Update() + } + return ruleAdded, err + } + + return ruleAdded, nil +} + +// addPolicies adds rules to the current policy. +func (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string) (bool, error) { + rulesAdded := e.model.AddPolicies(sec, ptype, rules) + if !rulesAdded { + return rulesAdded, nil + } + + if sec == "g" { + err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, rules) + if err != nil { + return rulesAdded, err + } + } + + if e.adapter != nil && e.autoSave { + if err := e.adapter.(persist.BatchAdapter).AddPolicies(sec, ptype, rules); err != nil { + if err.Error() != notImplemented { + return rulesAdded, err + } + } + } + + if e.watcher != nil && e.autoNotifyWatcher { + err := e.watcher.Update() + if err != nil { + return rulesAdded, err + } + } + + return rulesAdded, nil +} + +// removePolicy removes a rule from the current policy. +func (e *Enforcer) removePolicy(sec string, ptype string, rule []string) (bool, error) { + ruleRemoved := e.model.RemovePolicy(sec, ptype, rule) + if !ruleRemoved { + return ruleRemoved, nil + } + + if sec == "g" { + err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{rule}) + if err != nil { + return ruleRemoved, err + } + } + + if e.adapter != nil && e.autoSave { + if err := e.adapter.RemovePolicy(sec, ptype, rule); err != nil { + if err.Error() != notImplemented { + return ruleRemoved, err + } + } + } + + if e.watcher != nil && e.autoNotifyWatcher { + var err error + if watcher, ok := e.watcher.(persist.WatcherEx); ok { + err = watcher.UpdateForRemovePolicy(rule...) + } else { + err = e.watcher.Update() + } + return ruleRemoved, err + + } + + return ruleRemoved, nil +} + +// removePolicies removes rules from the current policy. +func (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string) (bool, error) { + rulesRemoved := e.model.RemovePolicies(sec, ptype, rules) + if !rulesRemoved { + return rulesRemoved, nil + } + + if sec == "g" { + err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, rules) + if err != nil { + return rulesRemoved, err + } + } + + if e.adapter != nil && e.autoSave { + if err := e.adapter.(persist.BatchAdapter).RemovePolicies(sec, ptype, rules); err != nil { + if err.Error() != notImplemented { + return rulesRemoved, err + } + } + } + + if e.watcher != nil && e.autoNotifyWatcher { + err := e.watcher.Update() + if err != nil { + return rulesRemoved, err + } + } + + return rulesRemoved, nil +} + +// removeFilteredPolicy removes rules based on field filters from the current policy. +func (e *Enforcer) removeFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error) { + ruleRemoved, effects := e.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...) + if !ruleRemoved { + return ruleRemoved, nil + } + + if sec == "g" { + err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, effects) + if err != nil { + return ruleRemoved, err + } + } + + if e.adapter != nil && e.autoSave { + if err := e.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil { + if err.Error() != notImplemented { + return ruleRemoved, err + } + } + } + + if e.watcher != nil && e.autoNotifyWatcher { + var err error + if watcher, ok := e.watcher.(persist.WatcherEx); ok { + err = watcher.UpdateForRemoveFilteredPolicy(fieldIndex, fieldValues...) + } else { + err = e.watcher.Update() + } + return ruleRemoved, err + } + + return ruleRemoved, nil +} diff --git a/vendor/github.com/casbin/casbin/v2/log/default_logger.go b/vendor/github.com/casbin/casbin/v2/log/default_logger.go new file mode 100644 index 00000000..74a04228 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/log/default_logger.go @@ -0,0 +1,42 @@ +// Copyright 2018 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import "log" + +// DefaultLogger is the implementation for a Logger using golang log. +type DefaultLogger struct { + enable bool +} + +func (l *DefaultLogger) EnableLog(enable bool) { + l.enable = enable +} + +func (l *DefaultLogger) IsEnabled() bool { + return l.enable +} + +func (l *DefaultLogger) Print(v ...interface{}) { + if l.enable { + log.Print(v...) + } +} + +func (l *DefaultLogger) Printf(format string, v ...interface{}) { + if l.enable { + log.Printf(format, v...) + } +} diff --git a/vendor/github.com/casbin/casbin/v2/log/log_util.go b/vendor/github.com/casbin/casbin/v2/log/log_util.go new file mode 100644 index 00000000..ef4b8165 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/log/log_util.go @@ -0,0 +1,37 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +var logger Logger = &DefaultLogger{} + +// SetLogger sets the current logger. +func SetLogger(l Logger) { + logger = l +} + +// GetLogger returns the current logger. +func GetLogger() Logger { + return logger +} + +// LogPrint prints the log. +func LogPrint(v ...interface{}) { + logger.Print(v...) +} + +// LogPrintf prints the log with the format. +func LogPrintf(format string, v ...interface{}) { + logger.Printf(format, v...) +} diff --git a/vendor/github.com/casbin/casbin/v2/log/logger.go b/vendor/github.com/casbin/casbin/v2/log/logger.go new file mode 100644 index 00000000..e3aadd1a --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/log/logger.go @@ -0,0 +1,30 @@ +// Copyright 2018 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +// Logger is the logging interface implementation. +type Logger interface { + //EnableLog controls whether print the message. + EnableLog(bool) + + //IsEnabled returns if logger is enabled. + IsEnabled() bool + + //Print formats using the default formats for its operands and logs the message. + Print(...interface{}) + + //Printf formats according to a format specifier and logs the message. + Printf(string, ...interface{}) +} diff --git a/vendor/github.com/casbin/casbin/v2/management_api.go b/vendor/github.com/casbin/casbin/v2/management_api.go new file mode 100644 index 00000000..103257c5 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/management_api.go @@ -0,0 +1,298 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +import "github.com/Knetic/govaluate" + +// GetAllSubjects gets the list of subjects that show up in the current policy. +func (e *Enforcer) GetAllSubjects() []string { + return e.model.GetValuesForFieldInPolicyAllTypes("p", 0) +} + +// GetAllNamedSubjects gets the list of subjects that show up in the current named policy. +func (e *Enforcer) GetAllNamedSubjects(ptype string) []string { + return e.model.GetValuesForFieldInPolicy("p", ptype, 0) +} + +// GetAllObjects gets the list of objects that show up in the current policy. +func (e *Enforcer) GetAllObjects() []string { + return e.model.GetValuesForFieldInPolicyAllTypes("p", 1) +} + +// GetAllNamedObjects gets the list of objects that show up in the current named policy. +func (e *Enforcer) GetAllNamedObjects(ptype string) []string { + return e.model.GetValuesForFieldInPolicy("p", ptype, 1) +} + +// GetAllActions gets the list of actions that show up in the current policy. +func (e *Enforcer) GetAllActions() []string { + return e.model.GetValuesForFieldInPolicyAllTypes("p", 2) +} + +// GetAllNamedActions gets the list of actions that show up in the current named policy. +func (e *Enforcer) GetAllNamedActions(ptype string) []string { + return e.model.GetValuesForFieldInPolicy("p", ptype, 2) +} + +// GetAllRoles gets the list of roles that show up in the current policy. +func (e *Enforcer) GetAllRoles() []string { + return e.model.GetValuesForFieldInPolicyAllTypes("g", 1) +} + +// GetAllNamedRoles gets the list of roles that show up in the current named policy. +func (e *Enforcer) GetAllNamedRoles(ptype string) []string { + return e.model.GetValuesForFieldInPolicy("g", ptype, 1) +} + +// GetPolicy gets all the authorization rules in the policy. +func (e *Enforcer) GetPolicy() [][]string { + return e.GetNamedPolicy("p") +} + +// GetFilteredPolicy gets all the authorization rules in the policy, field filters can be specified. +func (e *Enforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) [][]string { + return e.GetFilteredNamedPolicy("p", fieldIndex, fieldValues...) +} + +// GetNamedPolicy gets all the authorization rules in the named policy. +func (e *Enforcer) GetNamedPolicy(ptype string) [][]string { + return e.model.GetPolicy("p", ptype) +} + +// GetFilteredNamedPolicy gets all the authorization rules in the named policy, field filters can be specified. +func (e *Enforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string { + return e.model.GetFilteredPolicy("p", ptype, fieldIndex, fieldValues...) +} + +// GetGroupingPolicy gets all the role inheritance rules in the policy. +func (e *Enforcer) GetGroupingPolicy() [][]string { + return e.GetNamedGroupingPolicy("g") +} + +// GetFilteredGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified. +func (e *Enforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) [][]string { + return e.GetFilteredNamedGroupingPolicy("g", fieldIndex, fieldValues...) +} + +// GetNamedGroupingPolicy gets all the role inheritance rules in the policy. +func (e *Enforcer) GetNamedGroupingPolicy(ptype string) [][]string { + return e.model.GetPolicy("g", ptype) +} + +// GetFilteredNamedGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified. +func (e *Enforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string { + return e.model.GetFilteredPolicy("g", ptype, fieldIndex, fieldValues...) +} + +// HasPolicy determines whether an authorization rule exists. +func (e *Enforcer) HasPolicy(params ...interface{}) bool { + return e.HasNamedPolicy("p", params...) +} + +// HasNamedPolicy determines whether a named authorization rule exists. +func (e *Enforcer) HasNamedPolicy(ptype string, params ...interface{}) bool { + if strSlice, ok := params[0].([]string); len(params) == 1 && ok { + return e.model.HasPolicy("p", ptype, strSlice) + } + + policy := make([]string, 0) + for _, param := range params { + policy = append(policy, param.(string)) + } + + return e.model.HasPolicy("p", ptype, policy) +} + +// AddPolicy adds an authorization rule to the current policy. +// If the rule already exists, the function returns false and the rule will not be added. +// Otherwise the function returns true by adding the new rule. +func (e *Enforcer) AddPolicy(params ...interface{}) (bool, error) { + return e.AddNamedPolicy("p", params...) +} + +// AddPolicies adds authorization rules to the current policy. +// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added. +// Otherwise the function returns true for the corresponding rule by adding the new rule. +func (e *Enforcer) AddPolicies(rules [][]string) (bool, error) { + return e.AddNamedPolicies("p", rules) +} + +// AddNamedPolicy adds an authorization rule to the current named policy. +// If the rule already exists, the function returns false and the rule will not be added. +// Otherwise the function returns true by adding the new rule. +func (e *Enforcer) AddNamedPolicy(ptype string, params ...interface{}) (bool, error) { + if strSlice, ok := params[0].([]string); len(params) == 1 && ok { + return e.addPolicy("p", ptype, strSlice) + } + policy := make([]string, 0) + for _, param := range params { + policy = append(policy, param.(string)) + } + + return e.addPolicy("p", ptype, policy) +} + +// AddNamedPolicies adds authorization rules to the current named policy. +// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added. +// Otherwise the function returns true for the corresponding by adding the new rule. +func (e *Enforcer) AddNamedPolicies(ptype string, rules [][] string) (bool, error) { + return e.addPolicies("p", ptype, rules) +} + +// RemovePolicy removes an authorization rule from the current policy. +func (e *Enforcer) RemovePolicy(params ...interface{}) (bool, error) { + return e.RemoveNamedPolicy("p", params...) +} + +// RemovePolicies removes authorization rules from the current policy. +func (e *Enforcer) RemovePolicies(rules [][]string) (bool, error) { + return e.RemoveNamedPolicies("p", rules) +} + +// RemoveFilteredPolicy removes an authorization rule from the current policy, field filters can be specified. +func (e *Enforcer) RemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error) { + return e.RemoveFilteredNamedPolicy("p", fieldIndex, fieldValues...) +} + +// RemoveNamedPolicy removes an authorization rule from the current named policy. +func (e *Enforcer) RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error) { + if strSlice, ok := params[0].([]string); len(params) == 1 && ok { + return e.removePolicy("p", ptype, strSlice) + } + policy := make([]string, 0) + for _, param := range params { + policy = append(policy, param.(string)) + } + + return e.removePolicy("p", ptype, policy) +} + +// RemoveNamedPolicies removes authorization rules from the current named policy. +func (e *Enforcer) RemoveNamedPolicies(ptype string, rules [][] string) (bool, error) { + return e.removePolicies("p", ptype, rules) +} + +// RemoveFilteredNamedPolicy removes an authorization rule from the current named policy, field filters can be specified. +func (e *Enforcer) RemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) { + return e.removeFilteredPolicy("p", ptype, fieldIndex, fieldValues...) +} + +// HasGroupingPolicy determines whether a role inheritance rule exists. +func (e *Enforcer) HasGroupingPolicy(params ...interface{}) bool { + return e.HasNamedGroupingPolicy("g", params...) +} + +// HasNamedGroupingPolicy determines whether a named role inheritance rule exists. +func (e *Enforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) bool { + if strSlice, ok := params[0].([]string); len(params) == 1 && ok { + return e.model.HasPolicy("g", ptype, strSlice) + } + + policy := make([]string, 0) + for _, param := range params { + policy = append(policy, param.(string)) + } + + return e.model.HasPolicy("g", ptype, policy) +} + +// AddGroupingPolicy adds a role inheritance rule to the current policy. +// If the rule already exists, the function returns false and the rule will not be added. +// Otherwise the function returns true by adding the new rule. +func (e *Enforcer) AddGroupingPolicy(params ...interface{}) (bool, error) { + return e.AddNamedGroupingPolicy("g", params...) +} + +// AddGroupingPolicies adds role inheritance rulea to the current policy. +// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. +// Otherwise the function returns true for the corresponding policy rule by adding the new rule. +func (e *Enforcer) AddGroupingPolicies(rules [][]string) (bool, error) { + return e.AddNamedGroupingPolicies("g", rules) +} + +// AddNamedGroupingPolicy adds a named role inheritance rule to the current policy. +// If the rule already exists, the function returns false and the rule will not be added. +// Otherwise the function returns true by adding the new rule. +func (e *Enforcer) AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) { + var ruleAdded bool + var err error + if strSlice, ok := params[0].([]string); len(params) == 1 && ok { + ruleAdded, err = e.addPolicy("g", ptype, strSlice) + } else { + policy := make([]string, 0) + for _, param := range params { + policy = append(policy, param.(string)) + } + + ruleAdded, err = e.addPolicy("g", ptype, policy) + } + + return ruleAdded, err +} + +// AddNamedGroupingPolicies adds named role inheritance rules to the current policy. +// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. +// Otherwise the function returns true for the corresponding policy rule by adding the new rule. +func (e *Enforcer) AddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) { + return e.addPolicies("g", ptype, rules) +} + +// RemoveGroupingPolicy removes a role inheritance rule from the current policy. +func (e *Enforcer) RemoveGroupingPolicy(params ...interface{}) (bool, error) { + return e.RemoveNamedGroupingPolicy("g", params...) +} + +// RemoveGroupingPolicies removes role inheritance rulea from the current policy. +func (e *Enforcer) RemoveGroupingPolicies(rules [][]string) (bool, error) { + return e.RemoveNamedGroupingPolicies("g", rules) +} + +// RemoveFilteredGroupingPolicy removes a role inheritance rule from the current policy, field filters can be specified. +func (e *Enforcer) RemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error) { + return e.RemoveFilteredNamedGroupingPolicy("g", fieldIndex, fieldValues...) +} + +// RemoveNamedGroupingPolicy removes a role inheritance rule from the current named policy. +func (e *Enforcer) RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) { + var ruleRemoved bool + var err error + if strSlice, ok := params[0].([]string); len(params) == 1 && ok { + ruleRemoved, err = e.removePolicy("g", ptype, strSlice) + } else { + policy := make([]string, 0) + for _, param := range params { + policy = append(policy, param.(string)) + } + + ruleRemoved, err = e.removePolicy("g", ptype, policy) + } + + return ruleRemoved, err +} + +// RemoveNamedGroupingPolicies removes role inheritance rules from the current named policy. +func (e *Enforcer) RemoveNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) { + return e.removePolicies("g", ptype, rules) +} + +// RemoveFilteredNamedGroupingPolicy removes a role inheritance rule from the current named policy, field filters can be specified. +func (e *Enforcer) RemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) { + return e.removeFilteredPolicy("g", ptype, fieldIndex, fieldValues...) +} + +// AddFunction adds a customized function. +func (e *Enforcer) AddFunction(name string, function govaluate.ExpressionFunction) { + e.fm.AddFunction(name, function) +} diff --git a/vendor/github.com/casbin/casbin/v2/model/assertion.go b/vendor/github.com/casbin/casbin/v2/model/assertion.go new file mode 100644 index 00000000..eabc53a3 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/model/assertion.go @@ -0,0 +1,87 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "errors" + "strings" + + "github.com/casbin/casbin/v2/log" + "github.com/casbin/casbin/v2/rbac" +) + +// Assertion represents an expression in a section of the model. +// For example: r = sub, obj, act +type Assertion struct { + Key string + Value string + Tokens []string + Policy [][]string + RM rbac.RoleManager +} + +func (ast *Assertion) buildIncrementalRoleLinks(rm rbac.RoleManager, op PolicyOp, rules [][]string) error { + ast.RM = rm + count := strings.Count(ast.Value, "_") + if count < 2 { + return errors.New("the number of \"_\" in role definition should be at least 2") + } + + for _, rule := range rules { + if len(rule) < count { + return errors.New("grouping policy elements do not meet role definition") + } + if len(rule) > count { + rule = rule[:count] + } + switch op { + case PolicyAdd: + err := rm.AddLink(rule[0], rule[1], rule[2:]...) + if err != nil { + return err + } + case PolicyRemove: + err := rm.DeleteLink(rule[0], rule[1], rule[2:]...) + if err != nil { + return err + } + } + } + + return nil +} + +func (ast *Assertion) buildRoleLinks(rm rbac.RoleManager) error { + ast.RM = rm + count := strings.Count(ast.Value, "_") + if count < 2 { + return errors.New("the number of \"_\" in role definition should be at least 2") + } + for _, rule := range ast.Policy { + if len(rule) < count { + return errors.New("grouping policy elements do not meet role definition") + } + if len(rule) > count { + rule = rule[:count] + } + err := ast.RM.AddLink(rule[0], rule[1], rule[2:]...) + if err != nil { + return err + } + } + + log.LogPrint("Role links for: " + ast.Key) + return ast.RM.PrintRoles() +} diff --git a/vendor/github.com/casbin/casbin/v2/model/function.go b/vendor/github.com/casbin/casbin/v2/model/function.go new file mode 100644 index 00000000..f29ae372 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/model/function.go @@ -0,0 +1,43 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "github.com/Knetic/govaluate" + "github.com/casbin/casbin/v2/util" +) + +// FunctionMap represents the collection of Function. +type FunctionMap map[string]govaluate.ExpressionFunction + +// AddFunction adds an expression function. +func (fm FunctionMap) AddFunction(name string, function govaluate.ExpressionFunction) { + fm[name] = function +} + +// LoadFunctionMap loads an initial function map. +func LoadFunctionMap() FunctionMap { + fm := make(FunctionMap) + + fm.AddFunction("keyMatch", util.KeyMatchFunc) + fm.AddFunction("keyMatch2", util.KeyMatch2Func) + fm.AddFunction("keyMatch3", util.KeyMatch3Func) + fm.AddFunction("keyMatch4", util.KeyMatch4Func) + fm.AddFunction("regexMatch", util.RegexMatchFunc) + fm.AddFunction("ipMatch", util.IPMatchFunc) + fm.AddFunction("globMatch", util.GlobMatchFunc) + + return fm +} diff --git a/vendor/github.com/casbin/casbin/v2/model/model.go b/vendor/github.com/casbin/casbin/v2/model/model.go new file mode 100644 index 00000000..f2168ac8 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/model/model.go @@ -0,0 +1,175 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "fmt" + "strconv" + "strings" + + "github.com/casbin/casbin/v2/config" + "github.com/casbin/casbin/v2/log" + "github.com/casbin/casbin/v2/util" +) + +// Model represents the whole access control model. +type Model map[string]AssertionMap + +// AssertionMap is the collection of assertions, can be "r", "p", "g", "e", "m". +type AssertionMap map[string]*Assertion + +var sectionNameMap = map[string]string{ + "r": "request_definition", + "p": "policy_definition", + "g": "role_definition", + "e": "policy_effect", + "m": "matchers", +} + +// Minimal required sections for a model to be valid +var requiredSections = []string{"r", "p", "e", "m"} + +func loadAssertion(model Model, cfg config.ConfigInterface, sec string, key string) bool { + value := cfg.String(sectionNameMap[sec] + "::" + key) + return model.AddDef(sec, key, value) +} + +// AddDef adds an assertion to the model. +func (model Model) AddDef(sec string, key string, value string) bool { + if value == "" { + return false + } + + ast := Assertion{} + ast.Key = key + ast.Value = value + + if sec == "r" || sec == "p" { + ast.Tokens = strings.Split(ast.Value, ",") + for i := range ast.Tokens { + ast.Tokens[i] = key + "_" + strings.TrimSpace(ast.Tokens[i]) + } + } else { + ast.Value = util.RemoveComments(util.EscapeAssertion(ast.Value)) + } + + _, ok := model[sec] + if !ok { + model[sec] = make(AssertionMap) + } + + model[sec][key] = &ast + return true +} + +func getKeySuffix(i int) string { + if i == 1 { + return "" + } + + return strconv.Itoa(i) +} + +func loadSection(model Model, cfg config.ConfigInterface, sec string) { + i := 1 + for { + if !loadAssertion(model, cfg, sec, sec+getKeySuffix(i)) { + break + } else { + i++ + } + } +} + +// NewModel creates an empty model. +func NewModel() Model { + m := make(Model) + return m +} + +// NewModelFromFile creates a model from a .CONF file. +func NewModelFromFile(path string) (Model, error) { + m := NewModel() + + err := m.LoadModel(path) + if err != nil { + return nil, err + } + + return m, nil +} + +// NewModelFromString creates a model from a string which contains model text. +func NewModelFromString(text string) (Model, error) { + m := NewModel() + + err := m.LoadModelFromText(text) + if err != nil { + return nil, err + } + + return m, nil +} + +// LoadModel loads the model from model CONF file. +func (model Model) LoadModel(path string) error { + cfg, err := config.NewConfig(path) + if err != nil { + return err + } + + return model.loadModelFromConfig(cfg) +} + +// LoadModelFromText loads the model from the text. +func (model Model) LoadModelFromText(text string) error { + cfg, err := config.NewConfigFromText(text) + if err != nil { + return err + } + + return model.loadModelFromConfig(cfg) +} + +func (model Model) loadModelFromConfig(cfg config.ConfigInterface) error { + for s := range sectionNameMap { + loadSection(model, cfg, s) + } + ms := make([]string, 0) + for _, rs := range requiredSections { + if !model.hasSection(rs) { + ms = append(ms, sectionNameMap[rs]) + } + } + if len(ms) > 0 { + return fmt.Errorf("missing required sections: %s", strings.Join(ms, ",")) + } + return nil +} + +func (model Model) hasSection(sec string) bool { + section := model[sec] + return section != nil +} + +// PrintModel prints the model to the log. +func (model Model) PrintModel() { + log.LogPrint("Model:") + for k, v := range model { + for i, j := range v { + log.LogPrintf("%s.%s: %s", k, i, j.Value) + } + } +} diff --git a/vendor/github.com/casbin/casbin/v2/model/policy.go b/vendor/github.com/casbin/casbin/v2/model/policy.go new file mode 100644 index 00000000..09fb041a --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/model/policy.go @@ -0,0 +1,218 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "github.com/casbin/casbin/v2/log" + "github.com/casbin/casbin/v2/rbac" + "github.com/casbin/casbin/v2/util" +) + +type PolicyOp int + +const ( + PolicyAdd PolicyOp = iota + PolicyRemove +) + +// BuildIncrementalRoleLinks provides incremental build the role inheritance relations. +func (model Model) BuildIncrementalRoleLinks(rm rbac.RoleManager, op PolicyOp, sec string, ptype string, rules [][]string) error { + if sec == "g" { + return model[sec][ptype].buildIncrementalRoleLinks(rm, op, rules) + } + return nil +} + +// BuildRoleLinks initializes the roles in RBAC. +func (model Model) BuildRoleLinks(rm rbac.RoleManager) error { + for _, ast := range model["g"] { + err := ast.buildRoleLinks(rm) + if err != nil { + return err + } + } + + return nil +} + +// PrintPolicy prints the policy to log. +func (model Model) PrintPolicy() { + log.LogPrint("Policy:") + for key, ast := range model["p"] { + log.LogPrint(key, ": ", ast.Value, ": ", ast.Policy) + } + + for key, ast := range model["g"] { + log.LogPrint(key, ": ", ast.Value, ": ", ast.Policy) + } +} + +// ClearPolicy clears all current policy. +func (model Model) ClearPolicy() { + for _, ast := range model["p"] { + ast.Policy = nil + } + + for _, ast := range model["g"] { + ast.Policy = nil + } +} + +// GetPolicy gets all rules in a policy. +func (model Model) GetPolicy(sec string, ptype string) [][]string { + return model[sec][ptype].Policy +} + +// GetFilteredPolicy gets rules based on field filters from a policy. +func (model Model) GetFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) [][]string { + res := [][]string{} + + for _, rule := range model[sec][ptype].Policy { + matched := true + for i, fieldValue := range fieldValues { + if fieldValue != "" && rule[fieldIndex+i] != fieldValue { + matched = false + break + } + } + + if matched { + res = append(res, rule) + } + } + + return res +} + +// HasPolicy determines whether a model has the specified policy rule. +func (model Model) HasPolicy(sec string, ptype string, rule []string) bool { + for _, r := range model[sec][ptype].Policy { + if util.ArrayEquals(rule, r) { + return true + } + } + + return false +} + +// AddPolicy adds a policy rule to the model. +func (model Model) AddPolicy(sec string, ptype string, rule []string) bool { + if !model.HasPolicy(sec, ptype, rule) { + model[sec][ptype].Policy = append(model[sec][ptype].Policy, rule) + return true + } + return false +} + +// AddPolicies adds policy rules to the model. +func (model Model) AddPolicies(sec string, ptype string, rules [][]string) bool { + for i := 0; i < len(rules); i++ { + if model.HasPolicy(sec, ptype, rules[i]) { + return false + } + } + + for i := 0; i < len(rules); i++ { + model[sec][ptype].Policy = append(model[sec][ptype].Policy, rules[i]) + } + + return true +} + +// RemovePolicy removes a policy rule from the model. +func (model Model) RemovePolicy(sec string, ptype string, rule []string) bool { + for i, r := range model[sec][ptype].Policy { + if util.ArrayEquals(rule, r) { + model[sec][ptype].Policy = append(model[sec][ptype].Policy[:i], model[sec][ptype].Policy[i+1:]...) + return true + } + } + + return false +} + +// RemovePolicies removes policy rules from the model. +func (model Model) RemovePolicies(sec string, ptype string, rules [][]string) bool { +OUTER: + for j := 0; j < len(rules); j++ { + for _, r := range model[sec][ptype].Policy { + if util.ArrayEquals(rules[j], r) { + continue OUTER + } + } + return false + } + + for j := 0; j < len(rules); j++ { + for i, r := range model[sec][ptype].Policy { + if util.ArrayEquals(rules[j], r) { + model[sec][ptype].Policy = append(model[sec][ptype].Policy[:i], model[sec][ptype].Policy[i+1:]...) + } + } + } + return true +} + +// RemoveFilteredPolicy removes policy rules based on field filters from the model. +func (model Model) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, [][]string) { + var tmp [][]string + var effects [][]string + res := false + for _, rule := range model[sec][ptype].Policy { + matched := true + for i, fieldValue := range fieldValues { + if fieldValue != "" && rule[fieldIndex+i] != fieldValue { + matched = false + break + } + } + + if matched { + effects = append(effects, rule) + res = true + } else { + tmp = append(tmp, rule) + } + } + + model[sec][ptype].Policy = tmp + return res, effects +} + +// GetValuesForFieldInPolicy gets all values for a field for all rules in a policy, duplicated values are removed. +func (model Model) GetValuesForFieldInPolicy(sec string, ptype string, fieldIndex int) []string { + values := []string{} + + for _, rule := range model[sec][ptype].Policy { + values = append(values, rule[fieldIndex]) + } + + util.ArrayRemoveDuplicates(&values) + + return values +} + +// GetValuesForFieldInPolicyAllTypes gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed. +func (model Model) GetValuesForFieldInPolicyAllTypes(sec string, fieldIndex int) []string { + values := []string{} + + for ptype := range model[sec] { + values = append(values, model.GetValuesForFieldInPolicy(sec, ptype, fieldIndex)...) + } + + util.ArrayRemoveDuplicates(&values) + + return values +} diff --git a/vendor/github.com/casbin/casbin/v2/persist/adapter.go b/vendor/github.com/casbin/casbin/v2/persist/adapter.go new file mode 100644 index 00000000..0561c48c --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/persist/adapter.go @@ -0,0 +1,55 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package persist + +import ( + "strings" + + "github.com/casbin/casbin/v2/model" +) + +// LoadPolicyLine loads a text line as a policy rule to model. +func LoadPolicyLine(line string, model model.Model) { + if line == "" || strings.HasPrefix(line, "#") { + return + } + + tokens := strings.Split(line, ",") + for i := 0; i < len(tokens); i++ { + tokens[i] = strings.TrimSpace(tokens[i]) + } + + key := tokens[0] + sec := key[:1] + model[sec][key].Policy = append(model[sec][key].Policy, tokens[1:]) +} + +// Adapter is the interface for Casbin adapters. +type Adapter interface { + // LoadPolicy loads all policy rules from the storage. + LoadPolicy(model model.Model) error + // SavePolicy saves all policy rules to the storage. + SavePolicy(model model.Model) error + + // AddPolicy adds a policy rule to the storage. + // This is part of the Auto-Save feature. + AddPolicy(sec string, ptype string, rule []string) error + // RemovePolicy removes a policy rule from the storage. + // This is part of the Auto-Save feature. + RemovePolicy(sec string, ptype string, rule []string) error + // RemoveFilteredPolicy removes policy rules that match the filter from the storage. + // This is part of the Auto-Save feature. + RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error +} diff --git a/vendor/github.com/casbin/casbin/v2/persist/adapter_filtered.go b/vendor/github.com/casbin/casbin/v2/persist/adapter_filtered.go new file mode 100644 index 00000000..82c9a0e7 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/persist/adapter_filtered.go @@ -0,0 +1,29 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package persist + +import ( + "github.com/casbin/casbin/v2/model" +) + +// FilteredAdapter is the interface for Casbin adapters supporting filtered policies. +type FilteredAdapter interface { + Adapter + + // LoadFilteredPolicy loads only policy rules that match the filter. + LoadFilteredPolicy(model model.Model, filter interface{}) error + // IsFiltered returns true if the loaded policy has been filtered. + IsFiltered() bool +} diff --git a/vendor/github.com/casbin/casbin/v2/persist/batch_adapter.go b/vendor/github.com/casbin/casbin/v2/persist/batch_adapter.go new file mode 100644 index 00000000..56ec415f --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/persist/batch_adapter.go @@ -0,0 +1,26 @@ +// Copyright 2020 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package persist + +// BatchAdapter is the interface for Casbin adapters with multiple add and remove policy functions. +type BatchAdapter interface { + Adapter + // AddPolicies adds policy rules to the storage. + // This is part of the Auto-Save feature. + AddPolicies(sec string, ptype string, rules [][]string) error + // RemovePolicies removes policy rules from the storage. + // This is part of the Auto-Save feature. + RemovePolicies(sec string, ptype string, rules [][]string) error +} diff --git a/vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter.go b/vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter.go new file mode 100644 index 00000000..5b08d7a0 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter.go @@ -0,0 +1,134 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fileadapter + +import ( + "bufio" + "bytes" + "errors" + "os" + "strings" + + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" + "github.com/casbin/casbin/v2/util" +) + +// Adapter is the file adapter for Casbin. +// It can load policy from file or save policy to file. +type Adapter struct { + filePath string +} + +// NewAdapter is the constructor for Adapter. +func NewAdapter(filePath string) *Adapter { + return &Adapter{filePath: filePath} +} + +// LoadPolicy loads all policy rules from the storage. +func (a *Adapter) LoadPolicy(model model.Model) error { + if a.filePath == "" { + return errors.New("invalid file path, file path cannot be empty") + } + + return a.loadPolicyFile(model, persist.LoadPolicyLine) +} + +// SavePolicy saves all policy rules to the storage. +func (a *Adapter) SavePolicy(model model.Model) error { + if a.filePath == "" { + return errors.New("invalid file path, file path cannot be empty") + } + + var tmp bytes.Buffer + + for ptype, ast := range model["p"] { + for _, rule := range ast.Policy { + tmp.WriteString(ptype + ", ") + tmp.WriteString(util.ArrayToString(rule)) + tmp.WriteString("\n") + } + } + + for ptype, ast := range model["g"] { + for _, rule := range ast.Policy { + tmp.WriteString(ptype + ", ") + tmp.WriteString(util.ArrayToString(rule)) + tmp.WriteString("\n") + } + } + + return a.savePolicyFile(strings.TrimRight(tmp.String(), "\n")) +} + +func (a *Adapter) loadPolicyFile(model model.Model, handler func(string, model.Model)) error { + f, err := os.Open(a.filePath) + if err != nil { + return err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + handler(line, model) + } + return scanner.Err() +} + +func (a *Adapter) savePolicyFile(text string) error { + f, err := os.Create(a.filePath) + if err != nil { + return err + } + w := bufio.NewWriter(f) + + _, err = w.WriteString(text) + if err != nil { + return err + } + + err = w.Flush() + if err != nil { + return err + } + + return f.Close() +} + +// AddPolicy adds a policy rule to the storage. +func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error { + return errors.New("not implemented") +} + +// AddPolicies adds policy rules to the storage. +func (a *Adapter) AddPolicies(sec string, ptype string, rules [][]string) error { + return errors.New("not implemented") +} + +// RemovePolicy removes a policy rule from the storage. +func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error { + return errors.New("not implemented") +} + +// RemovePolicies removes policy rules from the storage. +func (a *Adapter) RemovePolicies(sec string, ptype string, rules [][]string) error { + return errors.New("not implemented") +} + +// RemoveFilteredPolicy removes policy rules that match the filter from the storage. +func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error { + return errors.New("not implemented") +} diff --git a/vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter_filtered.go b/vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter_filtered.go new file mode 100644 index 00000000..924ef2dd --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter_filtered.go @@ -0,0 +1,138 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fileadapter + +import ( + "bufio" + "errors" + "os" + "strings" + + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" +) + +// FilteredAdapter is the filtered file adapter for Casbin. It can load policy +// from file or save policy to file and supports loading of filtered policies. +type FilteredAdapter struct { + *Adapter + filtered bool +} + +// Filter defines the filtering rules for a FilteredAdapter's policy. Empty values +// are ignored, but all others must match the filter. +type Filter struct { + P []string + G []string +} + +// NewFilteredAdapter is the constructor for FilteredAdapter. +func NewFilteredAdapter(filePath string) *FilteredAdapter { + a := FilteredAdapter{} + a.filtered = true + a.Adapter = NewAdapter(filePath) + return &a +} + +// LoadPolicy loads all policy rules from the storage. +func (a *FilteredAdapter) LoadPolicy(model model.Model) error { + a.filtered = false + return a.Adapter.LoadPolicy(model) +} + +// LoadFilteredPolicy loads only policy rules that match the filter. +func (a *FilteredAdapter) LoadFilteredPolicy(model model.Model, filter interface{}) error { + if filter == nil { + return a.LoadPolicy(model) + } + if a.filePath == "" { + return errors.New("invalid file path, file path cannot be empty") + } + + filterValue, ok := filter.(*Filter) + if !ok { + return errors.New("invalid filter type") + } + err := a.loadFilteredPolicyFile(model, filterValue, persist.LoadPolicyLine) + if err == nil { + a.filtered = true + } + return err +} + +func (a *FilteredAdapter) loadFilteredPolicyFile(model model.Model, filter *Filter, handler func(string, model.Model)) error { + f, err := os.Open(a.filePath) + if err != nil { + return err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + if filterLine(line, filter) { + continue + } + + handler(line, model) + } + return scanner.Err() +} + +// IsFiltered returns true if the loaded policy has been filtered. +func (a *FilteredAdapter) IsFiltered() bool { + return a.filtered +} + +// SavePolicy saves all policy rules to the storage. +func (a *FilteredAdapter) SavePolicy(model model.Model) error { + if a.filtered { + return errors.New("cannot save a filtered policy") + } + return a.Adapter.SavePolicy(model) +} + +func filterLine(line string, filter *Filter) bool { + if filter == nil { + return false + } + p := strings.Split(line, ",") + if len(p) == 0 { + return true + } + var filterSlice []string + switch strings.TrimSpace(p[0]) { + case "p": + filterSlice = filter.P + case "g": + filterSlice = filter.G + } + return filterWords(p, filterSlice) +} + +func filterWords(line []string, filter []string) bool { + if len(line) < len(filter)+1 { + return true + } + var skipLine bool + for i, v := range filter { + if len(v) > 0 && strings.TrimSpace(v) != strings.TrimSpace(line[i+1]) { + skipLine = true + break + } + } + return skipLine +} diff --git a/vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter_mock.go b/vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter_mock.go new file mode 100644 index 00000000..b9c71d81 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/persist/file-adapter/adapter_mock.go @@ -0,0 +1,110 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fileadapter + +import ( + "bufio" + "errors" + "io" + "os" + "strings" + + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" +) + +// AdapterMock is the file adapter for Casbin. +// It can load policy from file or save policy to file. +type AdapterMock struct { + filePath string + errorValue string +} + +// NewAdapterMock is the constructor for AdapterMock. +func NewAdapterMock(filePath string) *AdapterMock { + a := AdapterMock{} + a.filePath = filePath + return &a +} + +// LoadPolicy loads all policy rules from the storage. +func (a *AdapterMock) LoadPolicy(model model.Model) error { + err := a.loadPolicyFile(model, persist.LoadPolicyLine) + return err +} + +// SavePolicy saves all policy rules to the storage. +func (a *AdapterMock) SavePolicy(model model.Model) error { + return nil +} + +func (a *AdapterMock) loadPolicyFile(model model.Model, handler func(string, model.Model)) error { + f, err := os.Open(a.filePath) + if err != nil { + return err + } + defer f.Close() + + buf := bufio.NewReader(f) + for { + line, err := buf.ReadString('\n') + line = strings.TrimSpace(line) + handler(line, model) + if err != nil { + if err == io.EOF { + return nil + } + } + } +} + +// SetMockErr sets string to be returned by of the mock during testing +func (a *AdapterMock) SetMockErr(errorToSet string) { + a.errorValue = errorToSet +} + +// GetMockErr returns a mock error or nil +func (a *AdapterMock) GetMockErr() error { + var returnError error + if a.errorValue != "" { + returnError = errors.New(a.errorValue) + } + return returnError +} + +// AddPolicy adds a policy rule to the storage. +func (a *AdapterMock) AddPolicy(sec string, ptype string, rule []string) error { + return a.GetMockErr() +} + +// AddPolicies removes policy rules from the storage. +func (a *AdapterMock) AddPolicies(sec string, ptype string, rules [][]string) error { + return a.GetMockErr() +} + +// RemovePolicy removes a policy rule from the storage. +func (a *AdapterMock) RemovePolicy(sec string, ptype string, rule []string) error { + return a.GetMockErr() +} + +// RemovePolicies removes policy rules from the storage. +func (a *AdapterMock) RemovePolicies(sec string, ptype string, rules [][]string) error { + return a.GetMockErr() +} + +// RemoveFilteredPolicy removes policy rules that match the filter from the storage. +func (a *AdapterMock) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error { + return a.GetMockErr() +} diff --git a/vendor/github.com/casbin/casbin/v2/persist/watcher.go b/vendor/github.com/casbin/casbin/v2/persist/watcher.go new file mode 100644 index 00000000..0d843606 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/persist/watcher.go @@ -0,0 +1,29 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package persist + +// Watcher is the interface for Casbin watchers. +type Watcher interface { + // SetUpdateCallback sets the callback function that the watcher will call + // when the policy in DB has been changed by other instances. + // A classic callback is Enforcer.LoadPolicy(). + SetUpdateCallback(func(string)) error + // Update calls the update callback of other instances to synchronize their policy. + // It is usually called after changing the policy in DB, like Enforcer.SavePolicy(), + // Enforcer.AddPolicy(), Enforcer.RemovePolicy(), etc. + Update() error + // Close stops and releases the watcher, the callback function will not be called any more. + Close() +} diff --git a/vendor/github.com/casbin/casbin/v2/persist/watcher_ex.go b/vendor/github.com/casbin/casbin/v2/persist/watcher_ex.go new file mode 100644 index 00000000..24ce953b --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/persist/watcher_ex.go @@ -0,0 +1,34 @@ +// Copyright 2020 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package persist + +import "github.com/casbin/casbin/v2/model" + +// WatcherEx is the strengthen for Casbin watchers. +type WatcherEx interface { + Watcher + // UpdateForAddPolicy calls the update callback of other instances to synchronize their policy. + // It is called after Enforcer.AddPolicy() + UpdateForAddPolicy(params ...string) error + // UPdateForRemovePolicy calls the update callback of other instances to synchronize their policy. + // It is called after Enforcer.RemovePolicy() + UpdateForRemovePolicy(params ...string) error + // UpdateForRemoveFilteredPolicy calls the update callback of other instances to synchronize their policy. + // It is called after Enforcer.RemoveFilteredNamedGroupingPolicy() + UpdateForRemoveFilteredPolicy(fieldIndex int, fieldValues ...string) error + // UpdateForSavePolicy calls the update callback of other instances to synchronize their policy. + // It is called after Enforcer.RemoveFilteredNamedGroupingPolicy() + UpdateForSavePolicy(model model.Model) error +} diff --git a/vendor/github.com/casbin/casbin/v2/rbac/default-role-manager/role_manager.go b/vendor/github.com/casbin/casbin/v2/rbac/default-role-manager/role_manager.go new file mode 100644 index 00000000..e203e903 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/rbac/default-role-manager/role_manager.go @@ -0,0 +1,317 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package defaultrolemanager + +import ( + "strings" + "sync" + + "github.com/casbin/casbin/v2/errors" + "github.com/casbin/casbin/v2/log" + "github.com/casbin/casbin/v2/rbac" +) + +type MatchingFunc func(arg1, arg2 string) bool + +// RoleManager provides a default implementation for the RoleManager interface +type RoleManager struct { + allRoles *sync.Map + maxHierarchyLevel int + hasPattern bool + matchingFunc MatchingFunc +} + +// NewRoleManager is the constructor for creating an instance of the +// default RoleManager implementation. +func NewRoleManager(maxHierarchyLevel int) rbac.RoleManager { + rm := RoleManager{} + rm.allRoles = &sync.Map{} + rm.maxHierarchyLevel = maxHierarchyLevel + rm.hasPattern = false + + return &rm +} + +// e.BuildRoleLinks must be called after AddMatchingFunc(). +// +// example: e.GetRoleManager().(*defaultrolemanager.RoleManager).AddMatchingFunc('matcher', util.KeyMatch) +func (rm *RoleManager) AddMatchingFunc(name string, fn MatchingFunc) { + rm.hasPattern = true + rm.matchingFunc = fn +} + +func (rm *RoleManager) hasRole(name string) bool { + var ok bool + if rm.hasPattern { + rm.allRoles.Range(func(key, value interface{}) bool { + if rm.matchingFunc(name, key.(string)) { + ok = true + } + return true + }) + } else { + _, ok = rm.allRoles.Load(name) + } + + return ok +} + +func (rm *RoleManager) createRole(name string) *Role { + role, _ := rm.allRoles.LoadOrStore(name, newRole(name)) + + if rm.hasPattern { + rm.allRoles.Range(func(key, value interface{}) bool { + if rm.matchingFunc(name, key.(string)) && name!=key.(string) { + // Add new role to matching role + role1, _ := rm.allRoles.LoadOrStore(key.(string), newRole(key.(string))) + role.(*Role).addRole(role1.(*Role)) + } + return true + }) + } + + return role.(*Role) +} + +// Clear clears all stored data and resets the role manager to the initial state. +func (rm *RoleManager) Clear() error { + rm.allRoles = &sync.Map{} + return nil +} + +// AddLink adds the inheritance link between role: name1 and role: name2. +// aka role: name1 inherits role: name2. +// domain is a prefix to the roles. +func (rm *RoleManager) AddLink(name1 string, name2 string, domain ...string) error { + if len(domain) == 1 { + name1 = domain[0] + "::" + name1 + name2 = domain[0] + "::" + name2 + } else if len(domain) > 1 { + return errors.ERR_DOMAIN_PARAMETER + } + + role1 := rm.createRole(name1) + role2 := rm.createRole(name2) + role1.addRole(role2) + return nil +} + +// DeleteLink deletes the inheritance link between role: name1 and role: name2. +// aka role: name1 does not inherit role: name2 any more. +// domain is a prefix to the roles. +func (rm *RoleManager) DeleteLink(name1 string, name2 string, domain ...string) error { + if len(domain) == 1 { + name1 = domain[0] + "::" + name1 + name2 = domain[0] + "::" + name2 + } else if len(domain) > 1 { + return errors.ERR_DOMAIN_PARAMETER + } + + if !rm.hasRole(name1) || !rm.hasRole(name2) { + return errors.ERR_NAMES12_NOT_FOUND + } + + role1 := rm.createRole(name1) + role2 := rm.createRole(name2) + role1.deleteRole(role2) + return nil +} + +// HasLink determines whether role: name1 inherits role: name2. +// domain is a prefix to the roles. +func (rm *RoleManager) HasLink(name1 string, name2 string, domain ...string) (bool, error) { + if len(domain) == 1 { + name1 = domain[0] + "::" + name1 + name2 = domain[0] + "::" + name2 + } else if len(domain) > 1 { + return false, errors.ERR_DOMAIN_PARAMETER + } + + if name1 == name2 { + return true, nil + } + + if !rm.hasRole(name1) || !rm.hasRole(name2) { + return false, nil + } + + role1 := rm.createRole(name1) + return role1.hasRole(name2, rm.maxHierarchyLevel), nil +} + +// GetRoles gets the roles that a subject inherits. +// domain is a prefix to the roles. +func (rm *RoleManager) GetRoles(name string, domain ...string) ([]string, error) { + if len(domain) == 1 { + name = domain[0] + "::" + name + } else if len(domain) > 1 { + return nil, errors.ERR_DOMAIN_PARAMETER + } + + if !rm.hasRole(name) { + return []string{}, nil + } + + roles := rm.createRole(name).getRoles() + if len(domain) == 1 { + for i := range roles { + roles[i] = roles[i][len(domain[0])+2:] + } + } + return roles, nil +} + +// GetUsers gets the users that inherits a subject. +// domain is an unreferenced parameter here, may be used in other implementations. +func (rm *RoleManager) GetUsers(name string, domain ...string) ([]string, error) { + if len(domain) == 1 { + name = domain[0] + "::" + name + } else if len(domain) > 1 { + return nil, errors.ERR_DOMAIN_PARAMETER + } + + if !rm.hasRole(name) { + return nil, errors.ERR_NAME_NOT_FOUND + } + + names := []string{} + rm.allRoles.Range(func(_, value interface{}) bool { + role := value.(*Role) + if role.hasDirectRole(name) { + names = append(names, role.name) + } + return true + }) + if len(domain) == 1 { + for i := range names { + names[i] = names[i][len(domain[0])+2:] + } + } + return names, nil +} + +// PrintRoles prints all the roles to log. +func (rm *RoleManager) PrintRoles() error { + if log.GetLogger().IsEnabled() { + var sb strings.Builder + rm.allRoles.Range(func(_, value interface{}) bool { + if text := value.(*Role).toString(); text != "" { + if sb.Len() == 0 { + sb.WriteString(text) + } else { + sb.WriteString(", ") + sb.WriteString(text) + } + } + return true + }) + log.LogPrint(sb.String()) + } + return nil +} + +// Role represents the data structure for a role in RBAC. +type Role struct { + name string + roles []*Role +} + +func newRole(name string) *Role { + r := Role{} + r.name = name + return &r +} + +func (r *Role) addRole(role *Role) { + for _, rr := range r.roles { + if rr.name == role.name { + return + } + } + + r.roles = append(r.roles, role) +} + +func (r *Role) deleteRole(role *Role) { + for i, rr := range r.roles { + if rr.name == role.name { + r.roles = append(r.roles[:i], r.roles[i+1:]...) + return + } + } +} + +func (r *Role) hasRole(name string, hierarchyLevel int) bool { + if r.name == name { + return true + } + + if hierarchyLevel <= 0 { + return false + } + + for _, role := range r.roles { + if role.hasRole(name, hierarchyLevel-1) { + return true + } + } + return false +} + +func (r *Role) hasDirectRole(name string) bool { + for _, role := range r.roles { + if role.name == name { + return true + } + } + + return false +} + +func (r *Role) toString() string { + if len(r.roles) == 0 { + return "" + } + + var sb strings.Builder + sb.WriteString(r.name) + sb.WriteString(" < ") + if len(r.roles) != 1 { + sb.WriteString("(") + } + + for i, role := range r.roles { + if i == 0 { + sb.WriteString(role.name) + } else { + sb.WriteString(", ") + sb.WriteString(role.name) + } + } + + if len(r.roles) != 1 { + sb.WriteString(")") + } + + return sb.String() +} + +func (r *Role) getRoles() []string { + names := []string{} + for _, role := range r.roles { + names = append(names, role.name) + } + return names +} diff --git a/vendor/github.com/casbin/casbin/v2/rbac/role_manager.go b/vendor/github.com/casbin/casbin/v2/rbac/role_manager.go new file mode 100644 index 00000000..b853bc93 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/rbac/role_manager.go @@ -0,0 +1,38 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rbac + +// RoleManager provides interface to define the operations for managing roles. +type RoleManager interface { + // Clear clears all stored data and resets the role manager to the initial state. + Clear() error + // AddLink adds the inheritance link between two roles. role: name1 and role: name2. + // domain is a prefix to the roles (can be used for other purposes). + AddLink(name1 string, name2 string, domain ...string) error + // DeleteLink deletes the inheritance link between two roles. role: name1 and role: name2. + // domain is a prefix to the roles (can be used for other purposes). + DeleteLink(name1 string, name2 string, domain ...string) error + // HasLink determines whether a link exists between two roles. role: name1 inherits role: name2. + // domain is a prefix to the roles (can be used for other purposes). + HasLink(name1 string, name2 string, domain ...string) (bool, error) + // GetRoles gets the roles that a user inherits. + // domain is a prefix to the roles (can be used for other purposes). + GetRoles(name string, domain ...string) ([]string, error) + // GetUsers gets the users that inherits a role. + // domain is a prefix to the users (can be used for other purposes). + GetUsers(name string, domain ...string) ([]string, error) + // PrintRoles prints all the roles to log. + PrintRoles() error +} diff --git a/vendor/github.com/casbin/casbin/v2/rbac_api.go b/vendor/github.com/casbin/casbin/v2/rbac_api.go new file mode 100644 index 00000000..c222dd23 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/rbac_api.go @@ -0,0 +1,252 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +import ( + "errors" + + "github.com/casbin/casbin/v2/util" +) + +// GetRolesForUser gets the roles that a user has. +func (e *Enforcer) GetRolesForUser(name string, domain ...string) ([]string, error) { + res, err := e.model["g"]["g"].RM.GetRoles(name, domain...) + return res, err +} + +// GetUsersForRole gets the users that has a role. +func (e *Enforcer) GetUsersForRole(name string, domain ...string) ([]string, error) { + res, err := e.model["g"]["g"].RM.GetUsers(name, domain...) + return res, err +} + +// HasRoleForUser determines whether a user has a role. +func (e *Enforcer) HasRoleForUser(name string, role string) (bool, error) { + roles, err := e.GetRolesForUser(name) + if err != nil { + return false, err + } + hasRole := false + for _, r := range roles { + if r == role { + hasRole = true + break + } + } + + return hasRole, nil +} + +// AddRoleForUser adds a role for a user. +// Returns false if the user already has the role (aka not affected). +func (e *Enforcer) AddRoleForUser(user string, role string) (bool, error) { + return e.AddGroupingPolicy(user, role) +} + +// AddRolesForUser adds roles for a user. +// Returns false if the user already has the roles (aka not affected). +func (e *Enforcer) AddRolesForUser(user string, roles []string) (bool, error) { + f := false + for _, r := range roles { + b, err := e.AddGroupingPolicy(user, r) + if err != nil { + return false, err + } + if b { + f = true + } + } + return f, nil +} + +// DeleteRoleForUser deletes a role for a user. +// Returns false if the user does not have the role (aka not affected). +func (e *Enforcer) DeleteRoleForUser(user string, role string) (bool, error) { + return e.RemoveGroupingPolicy(user, role) +} + +// DeleteRolesForUser deletes all roles for a user. +// Returns false if the user does not have any roles (aka not affected). +func (e *Enforcer) DeleteRolesForUser(user string) (bool, error) { + return e.RemoveFilteredGroupingPolicy(0, user) +} + +// DeleteUser deletes a user. +// Returns false if the user does not exist (aka not affected). +func (e *Enforcer) DeleteUser(user string) (bool, error) { + var err error + res1, err := e.RemoveFilteredGroupingPolicy(0, user) + if err != nil { + return res1, err + } + + res2, err := e.RemoveFilteredPolicy(0, user) + return res1 || res2, err +} + +// DeleteRole deletes a role. +// Returns false if the role does not exist (aka not affected). +func (e *Enforcer) DeleteRole(role string) (bool, error) { + var err error + res1, err := e.RemoveFilteredGroupingPolicy(1, role) + if err != nil { + return res1, err + } + + res2, err := e.RemoveFilteredPolicy(0, role) + return res1 || res2, err +} + +// DeletePermission deletes a permission. +// Returns false if the permission does not exist (aka not affected). +func (e *Enforcer) DeletePermission(permission ...string) (bool, error) { + return e.RemoveFilteredPolicy(1, permission...) +} + +// AddPermissionForUser adds a permission for a user or role. +// Returns false if the user or role already has the permission (aka not affected). +func (e *Enforcer) AddPermissionForUser(user string, permission ...string) (bool, error) { + return e.AddPolicy(util.JoinSlice(user, permission...)) +} + +// DeletePermissionForUser deletes a permission for a user or role. +// Returns false if the user or role does not have the permission (aka not affected). +func (e *Enforcer) DeletePermissionForUser(user string, permission ...string) (bool, error) { + return e.RemovePolicy(util.JoinSlice(user, permission...)) +} + +// DeletePermissionsForUser deletes permissions for a user or role. +// Returns false if the user or role does not have any permissions (aka not affected). +func (e *Enforcer) DeletePermissionsForUser(user string) (bool, error) { + return e.RemoveFilteredPolicy(0, user) +} + +// GetPermissionsForUser gets permissions for a user or role. +func (e *Enforcer) GetPermissionsForUser(user string) [][]string { + return e.GetFilteredPolicy(0, user) +} + +// HasPermissionForUser determines whether a user has a permission. +func (e *Enforcer) HasPermissionForUser(user string, permission ...string) bool { + return e.HasPolicy(util.JoinSlice(user, permission...)) +} + +// GetImplicitRolesForUser gets implicit roles that a user has. +// Compared to GetRolesForUser(), this function retrieves indirect roles besides direct roles. +// For example: +// g, alice, role:admin +// g, role:admin, role:user +// +// GetRolesForUser("alice") can only get: ["role:admin"]. +// But GetImplicitRolesForUser("alice") will get: ["role:admin", "role:user"]. +func (e *Enforcer) GetImplicitRolesForUser(name string, domain ...string) ([]string, error) { + res := []string{} + roleSet := make(map[string]bool) + roleSet[name] = true + + q := make([]string, 0) + q = append(q, name) + + for len(q) > 0 { + name := q[0] + q = q[1:] + + roles, err := e.rm.GetRoles(name, domain...) + if err != nil { + return nil, err + } + for _, r := range roles { + if _, ok := roleSet[r]; !ok { + res = append(res, r) + q = append(q, r) + roleSet[r] = true + } + } + } + + return res, nil +} + +// GetImplicitPermissionsForUser gets implicit permissions for a user or role. +// Compared to GetPermissionsForUser(), this function retrieves permissions for inherited roles. +// For example: +// p, admin, data1, read +// p, alice, data2, read +// g, alice, admin +// +// GetPermissionsForUser("alice") can only get: [["alice", "data2", "read"]]. +// But GetImplicitPermissionsForUser("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. +func (e *Enforcer) GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error) { + roles, err := e.GetImplicitRolesForUser(user, domain...) + if err != nil { + return nil, err + } + + roles = append([]string{user}, roles...) + + withDomain := false + if len(domain) == 1 { + withDomain = true + } else if len(domain) > 1 { + return nil, errors.New("domain should be 1 parameter") + } + + res := [][]string{} + permissions := [][]string{} + for _, role := range roles { + if withDomain { + permissions = e.GetPermissionsForUserInDomain(role, domain[0]) + } else { + permissions = e.GetPermissionsForUser(role) + } + res = append(res, permissions...) + } + + return res, nil +} + +// GetImplicitUsersForPermission gets implicit users for a permission. +// For example: +// p, admin, data1, read +// p, bob, data1, read +// g, alice, admin +// +// GetImplicitUsersForPermission("data1", "read") will get: ["alice", "bob"]. +// Note: only users will be returned, roles (2nd arg in "g") will be excluded. +func (e *Enforcer) GetImplicitUsersForPermission(permission ...string) ([]string, error) { + pSubjects := e.GetAllSubjects() + gInherit := e.model.GetValuesForFieldInPolicyAllTypes("g", 1) + gSubjects := e.model.GetValuesForFieldInPolicyAllTypes("g", 0) + + subjects := append(pSubjects, gSubjects...) + util.ArrayRemoveDuplicates(&subjects) + + res := []string{} + for _, user := range subjects { + req := util.JoinSliceAny(user, permission...) + allowed, err := e.Enforce(req...) + if err != nil { + return nil, err + } + + if allowed { + res = append(res, user) + } + } + + res = util.SetSubtract(res, gInherit) + + return res, nil +} diff --git a/vendor/github.com/casbin/casbin/v2/rbac_api_synced.go b/vendor/github.com/casbin/casbin/v2/rbac_api_synced.go new file mode 100644 index 00000000..5f793157 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/rbac_api_synced.go @@ -0,0 +1,122 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +// GetRolesForUser gets the roles that a user has. +func (e *SyncedEnforcer) GetRolesForUser(name string, domain ...string) ([]string, error) { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetRolesForUser(name, domain...) +} + +// GetUsersForRole gets the users that has a role. +func (e *SyncedEnforcer) GetUsersForRole(name string, domain ...string) ([]string, error) { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetUsersForRole(name, domain...) +} + +// HasRoleForUser determines whether a user has a role. +func (e *SyncedEnforcer) HasRoleForUser(name string, role string) (bool, error) { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.HasRoleForUser(name, role) +} + +// AddRoleForUser adds a role for a user. +// Returns false if the user already has the role (aka not affected). +func (e *SyncedEnforcer) AddRoleForUser(user string, role string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.AddRoleForUser(user, role) +} + +// DeleteRoleForUser deletes a role for a user. +// Returns false if the user does not have the role (aka not affected). +func (e *SyncedEnforcer) DeleteRoleForUser(user string, role string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.DeleteRoleForUser(user, role) +} + +// DeleteRolesForUser deletes all roles for a user. +// Returns false if the user does not have any roles (aka not affected). +func (e *SyncedEnforcer) DeleteRolesForUser(user string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.DeleteRolesForUser(user) +} + +// DeleteUser deletes a user. +// Returns false if the user does not exist (aka not affected). +func (e *SyncedEnforcer) DeleteUser(user string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.DeleteUser(user) +} + +// DeleteRole deletes a role. +// Returns false if the role does not exist (aka not affected). +func (e *SyncedEnforcer) DeleteRole(role string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.DeleteRole(role) +} + +// DeletePermission deletes a permission. +// Returns false if the permission does not exist (aka not affected). +func (e *SyncedEnforcer) DeletePermission(permission ...string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.DeletePermission(permission...) +} + +// AddPermissionForUser adds a permission for a user or role. +// Returns false if the user or role already has the permission (aka not affected). +func (e *SyncedEnforcer) AddPermissionForUser(user string, permission ...string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.AddPermissionForUser(user, permission...) +} + +// DeletePermissionForUser deletes a permission for a user or role. +// Returns false if the user or role does not have the permission (aka not affected). +func (e *SyncedEnforcer) DeletePermissionForUser(user string, permission ...string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.DeletePermissionForUser(user, permission...) +} + +// DeletePermissionsForUser deletes permissions for a user or role. +// Returns false if the user or role does not have any permissions (aka not affected). +func (e *SyncedEnforcer) DeletePermissionsForUser(user string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.DeletePermissionsForUser(user) +} + +// GetPermissionsForUser gets permissions for a user or role. +func (e *SyncedEnforcer) GetPermissionsForUser(user string) [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetPermissionsForUser(user) +} + +// HasPermissionForUser determines whether a user has a permission. +func (e *SyncedEnforcer) HasPermissionForUser(user string, permission ...string) bool { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.HasPermissionForUser(user, permission...) +} diff --git a/vendor/github.com/casbin/casbin/v2/rbac_api_with_domains.go b/vendor/github.com/casbin/casbin/v2/rbac_api_with_domains.go new file mode 100644 index 00000000..f1273cb1 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/rbac_api_with_domains.go @@ -0,0 +1,44 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +// GetUsersForRoleInDomain gets the users that has a role inside a domain. Add by Gordon +func (e *Enforcer) GetUsersForRoleInDomain(name string, domain string) []string { + res, _ := e.model["g"]["g"].RM.GetUsers(name, domain) + return res +} + +// GetRolesForUserInDomain gets the roles that a user has inside a domain. +func (e *Enforcer) GetRolesForUserInDomain(name string, domain string) []string { + res, _ := e.model["g"]["g"].RM.GetRoles(name, domain) + return res +} + +// GetPermissionsForUserInDomain gets permissions for a user or role inside a domain. +func (e *Enforcer) GetPermissionsForUserInDomain(user string, domain string) [][]string { + return e.GetFilteredPolicy(0, user, domain) +} + +// AddRoleForUserInDomain adds a role for a user inside a domain. +// Returns false if the user already has the role (aka not affected). +func (e *Enforcer) AddRoleForUserInDomain(user string, role string, domain string) (bool, error) { + return e.AddGroupingPolicy(user, role, domain) +} + +// DeleteRoleForUserInDomain deletes a role for a user inside a domain. +// Returns false if the user does not have the role (aka not affected). +func (e *Enforcer) DeleteRoleForUserInDomain(user string, role string, domain string) (bool, error) { + return e.RemoveGroupingPolicy(user, role, domain) +} diff --git a/vendor/github.com/casbin/casbin/v2/rbac_api_with_domains_synced.go b/vendor/github.com/casbin/casbin/v2/rbac_api_with_domains_synced.go new file mode 100644 index 00000000..90c67e94 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/rbac_api_with_domains_synced.go @@ -0,0 +1,52 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casbin + +// GetUsersForRoleInDomain gets the users that has a role inside a domain. Add by Gordon +func (e *SyncedEnforcer) GetUsersForRoleInDomain(name string, domain string) []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetUsersForRoleInDomain(name, domain) +} + +// GetRolesForUserInDomain gets the roles that a user has inside a domain. +func (e *SyncedEnforcer) GetRolesForUserInDomain(name string, domain string) []string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetRolesForUserInDomain(name, domain) +} + +// GetPermissionsForUserInDomain gets permissions for a user or role inside a domain. +func (e *SyncedEnforcer) GetPermissionsForUserInDomain(user string, domain string) [][]string { + e.m.RLock() + defer e.m.RUnlock() + return e.Enforcer.GetPermissionsForUserInDomain(user, domain) +} + +// AddRoleForUserInDomain adds a role for a user inside a domain. +// Returns false if the user already has the role (aka not affected). +func (e *SyncedEnforcer) AddRoleForUserInDomain(user string, role string, domain string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.AddRoleForUserInDomain(user, role, domain) +} + +// DeleteRoleForUserInDomain deletes a role for a user inside a domain. +// Returns false if the user does not have the role (aka not affected). +func (e *SyncedEnforcer) DeleteRoleForUserInDomain(user string, role string, domain string) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + return e.Enforcer.DeleteRoleForUserInDomain(user, role, domain) +} diff --git a/vendor/github.com/casbin/casbin/v2/util/builtin_operators.go b/vendor/github.com/casbin/casbin/v2/util/builtin_operators.go new file mode 100644 index 00000000..717c7e9c --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/util/builtin_operators.go @@ -0,0 +1,271 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "errors" + "fmt" + "net" + "path" + "regexp" + "strings" + + "github.com/Knetic/govaluate" + "github.com/casbin/casbin/v2/rbac" +) + +// validate the variadic parameter size and type as string +func validateVariadicArgs(expectedLen int, args ...interface{}) error { + if len(args) != expectedLen { + return fmt.Errorf("Expected %d arguments, but got %d", expectedLen, len(args)) + } + + for _, p := range args { + _, ok := p.(string) + if !ok { + return errors.New("Argument must be a string") + } + } + + return nil +} + +// KeyMatch determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. +// For example, "/foo/bar" matches "/foo/*" +func KeyMatch(key1 string, key2 string) bool { + i := strings.Index(key2, "*") + if i == -1 { + return key1 == key2 + } + + if len(key1) > i { + return key1[:i] == key2[:i] + } + return key1 == key2[:i] +} + +// KeyMatchFunc is the wrapper for KeyMatch. +func KeyMatchFunc(args ...interface{}) (interface{}, error) { + if err := validateVariadicArgs(2, args...); err != nil { + return false, fmt.Errorf("%s: %s", "keyMatch", err) + } + + name1 := args[0].(string) + name2 := args[1].(string) + + return bool(KeyMatch(name1, name2)), nil +} + +// KeyMatch2 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. +// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource" +func KeyMatch2(key1 string, key2 string) bool { + key2 = strings.Replace(key2, "/*", "/.*", -1) + + re := regexp.MustCompile(`:[^/]+`) + key2 = re.ReplaceAllString(key2, "$1[^/]+$2") + + return RegexMatch(key1, "^"+key2+"$") +} + +// KeyMatch2Func is the wrapper for KeyMatch2. +func KeyMatch2Func(args ...interface{}) (interface{}, error) { + if err := validateVariadicArgs(2, args...); err != nil { + return false, fmt.Errorf("%s: %s", "keyMatch2", err) + } + + name1 := args[0].(string) + name2 := args[1].(string) + + return bool(KeyMatch2(name1, name2)), nil +} + +// KeyMatch3 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. +// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}" +func KeyMatch3(key1 string, key2 string) bool { + key2 = strings.Replace(key2, "/*", "/.*", -1) + + re := regexp.MustCompile(`\{[^/]+\}`) + key2 = re.ReplaceAllString(key2, "$1[^/]+$2") + + return RegexMatch(key1, "^"+key2+"$") +} + +// KeyMatch3Func is the wrapper for KeyMatch3. +func KeyMatch3Func(args ...interface{}) (interface{}, error) { + if err := validateVariadicArgs(2, args...); err != nil { + return false, fmt.Errorf("%s: %s", "keyMatch3", err) + } + + name1 := args[0].(string) + name2 := args[1].(string) + + return bool(KeyMatch3(name1, name2)), nil +} + +// KeyMatch4 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. +// Besides what KeyMatch3 does, KeyMatch4 can also match repeated patterns: +// "/parent/123/child/123" matches "/parent/{id}/child/{id}" +// "/parent/123/child/456" does not match "/parent/{id}/child/{id}" +// But KeyMatch3 will match both. +func KeyMatch4(key1 string, key2 string) bool { + key2 = strings.Replace(key2, "/*", "/.*", -1) + + tokens := []string{} + + re := regexp.MustCompile(`\{([^/]+)\}`) + key2 = re.ReplaceAllStringFunc(key2, func(s string) string { + tokens = append(tokens, s[1:len(s)-1]) + return "([^/]+)" + }) + + re = regexp.MustCompile("^" + key2 + "$") + matches := re.FindStringSubmatch(key1) + if matches == nil { + return false + } + matches = matches[1:] + + if len(tokens) != len(matches) { + panic(errors.New("KeyMatch4: number of tokens is not equal to number of values")) + } + + values := map[string]string{} + + for key, token := range tokens { + if _, ok := values[token]; !ok { + values[token] = matches[key] + } + if values[token] != matches[key] { + return false + } + } + + return true +} + +// KeyMatch4Func is the wrapper for KeyMatch4. +func KeyMatch4Func(args ...interface{}) (interface{}, error) { + if err := validateVariadicArgs(2, args...); err != nil { + return false, fmt.Errorf("%s: %s", "keyMatch4", err) + } + + name1 := args[0].(string) + name2 := args[1].(string) + + return bool(KeyMatch4(name1, name2)), nil +} + +// RegexMatch determines whether key1 matches the pattern of key2 in regular expression. +func RegexMatch(key1 string, key2 string) bool { + res, err := regexp.MatchString(key2, key1) + if err != nil { + panic(err) + } + return res +} + +// RegexMatchFunc is the wrapper for RegexMatch. +func RegexMatchFunc(args ...interface{}) (interface{}, error) { + if err := validateVariadicArgs(2, args...); err != nil { + return false, fmt.Errorf("%s: %s", "regexMatch", err) + } + + name1 := args[0].(string) + name2 := args[1].(string) + + return bool(RegexMatch(name1, name2)), nil +} + +// IPMatch determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR pattern. +// For example, "192.168.2.123" matches "192.168.2.0/24" +func IPMatch(ip1 string, ip2 string) bool { + objIP1 := net.ParseIP(ip1) + if objIP1 == nil { + panic("invalid argument: ip1 in IPMatch() function is not an IP address.") + } + + _, cidr, err := net.ParseCIDR(ip2) + if err != nil { + objIP2 := net.ParseIP(ip2) + if objIP2 == nil { + panic("invalid argument: ip2 in IPMatch() function is neither an IP address nor a CIDR.") + } + + return objIP1.Equal(objIP2) + } + + return cidr.Contains(objIP1) +} + +// IPMatchFunc is the wrapper for IPMatch. +func IPMatchFunc(args ...interface{}) (interface{}, error) { + if err := validateVariadicArgs(2, args...); err != nil { + return false, fmt.Errorf("%s: %s", "ipMatch", err) + } + + ip1 := args[0].(string) + ip2 := args[1].(string) + + return bool(IPMatch(ip1, ip2)), nil +} + +// GlobMatch determines whether key1 matches the pattern of key2 using glob pattern +func GlobMatch(key1 string, key2 string) (bool, error) { + return path.Match(key2, key1) +} + +// GlobMatchFunc is the wrapper for GlobMatch. +func GlobMatchFunc(args ...interface{}) (interface{}, error) { + if err := validateVariadicArgs(2, args...); err != nil { + return false, fmt.Errorf("%s: %s", "globMatch", err) + } + + name1 := args[0].(string) + name2 := args[1].(string) + + return GlobMatch(name1, name2) +} + +// GenerateGFunction is the factory method of the g(_, _) function. +func GenerateGFunction(rm rbac.RoleManager) govaluate.ExpressionFunction { + memorized := map[string]bool{} + + return func(args ...interface{}) (interface{}, error) { + name1 := args[0].(string) + name2 := args[1].(string) + + key := "" + for index := 0; index < len(args); index++ { + key += ";" + fmt.Sprintf("%v", args[index]) + } + + v, found := memorized[key] + if found { + return v, nil + } + + if rm == nil { + v = name1 == name2 + } else if len(args) == 2 { + v, _ = rm.HasLink(name1, name2) + } else { + domain := args[2].(string) + v, _ = rm.HasLink(name1, name2, domain) + } + + memorized[key] = v + return v, nil + } +} diff --git a/vendor/github.com/casbin/casbin/v2/util/util.go b/vendor/github.com/casbin/casbin/v2/util/util.go new file mode 100644 index 00000000..7e48ff16 --- /dev/null +++ b/vendor/github.com/casbin/casbin/v2/util/util.go @@ -0,0 +1,173 @@ +// Copyright 2017 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "regexp" + "sort" + "strings" +) + +var evalReg *regexp.Regexp = regexp.MustCompile(`\beval\((?P[^),]*)\)`) + +// EscapeAssertion escapes the dots in the assertion, because the expression evaluation doesn't support such variable names. +func EscapeAssertion(s string) string { + //Replace the first dot, because it can't be recognized by the regexp. + if strings.HasPrefix(s, "r") || strings.HasPrefix(s, "p") { + s = strings.Replace(s, ".", "_", 1) + } + var regex = regexp.MustCompile(`(\|| |=|\)|\(|&|<|>|,|\+|-|!|\*|\/)(r|p)\.`) + s = regex.ReplaceAllStringFunc(s, func(m string) string { + return strings.Replace(m, ".", "_", 1) + }) + return s +} + +// RemoveComments removes the comments starting with # in the text. +func RemoveComments(s string) string { + pos := strings.Index(s, "#") + if pos == -1 { + return s + } + return strings.TrimSpace(s[0:pos]) +} + +// ArrayEquals determines whether two string arrays are identical. +func ArrayEquals(a []string, b []string) bool { + if len(a) != len(b) { + return false + } + + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} + +// Array2DEquals determines whether two 2-dimensional string arrays are identical. +func Array2DEquals(a [][]string, b [][]string) bool { + if len(a) != len(b) { + return false + } + + for i, v := range a { + if !ArrayEquals(v, b[i]) { + return false + } + } + return true +} + +// ArrayRemoveDuplicates removes any duplicated elements in a string array. +func ArrayRemoveDuplicates(s *[]string) { + found := make(map[string]bool) + j := 0 + for i, x := range *s { + if !found[x] { + found[x] = true + (*s)[j] = (*s)[i] + j++ + } + } + *s = (*s)[:j] +} + +// ArrayToString gets a printable string for a string array. +func ArrayToString(s []string) string { + return strings.Join(s, ", ") +} + +// ParamsToString gets a printable string for variable number of parameters. +func ParamsToString(s ...string) string { + return strings.Join(s, ", ") +} + +// SetEquals determines whether two string sets are identical. +func SetEquals(a []string, b []string) bool { + if len(a) != len(b) { + return false + } + + sort.Strings(a) + sort.Strings(b) + + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} + +// JoinSlice joins a string and a slice into a new slice. +func JoinSlice(a string, b ...string) []string { + res := make([]string, 0, len(b)+1) + + res = append(res, a) + for _, s := range b { + res = append(res, s) + } + + return res +} + +// JoinSliceAny joins a string and a slice into a new interface{} slice. +func JoinSliceAny(a string, b ...string) []interface{} { + res := make([]interface{}, 0, len(b)+1) + + res = append(res, a) + for _, s := range b { + res = append(res, s) + } + + return res +} + +// SetSubtract returns the elements in `a` that aren't in `b`. +func SetSubtract(a []string, b []string) []string { + mb := make(map[string]struct{}, len(b)) + for _, x := range b { + mb[x] = struct{}{} + } + var diff []string + for _, x := range a { + if _, found := mb[x]; !found { + diff = append(diff, x) + } + } + return diff +} + +// HasEval determine whether matcher contains function eval +func HasEval(s string) bool { + return evalReg.MatchString(s) +} + +// ReplaceEval replace function eval with the value of its parameters +func ReplaceEval(s string, rule string) string { + return evalReg.ReplaceAllString(s, "("+rule+")") +} + +// GetEvalValue returns the parameters of function eval +func GetEvalValue(s string) []string { + subMatch := evalReg.FindAllStringSubmatch(s, -1) + var rules []string + for _, rule := range subMatch { + rules = append(rules, rule[1]) + } + return rules +} diff --git a/vendor/github.com/dgrijalva/jwt-go/request/doc.go b/vendor/github.com/dgrijalva/jwt-go/request/doc.go new file mode 100644 index 00000000..c01069c9 --- /dev/null +++ b/vendor/github.com/dgrijalva/jwt-go/request/doc.go @@ -0,0 +1,7 @@ +// Utility package for extracting JWT tokens from +// HTTP requests. +// +// The main function is ParseFromRequest and it's WithClaims variant. +// See examples for how to use the various Extractor implementations +// or roll your own. +package request diff --git a/vendor/github.com/dgrijalva/jwt-go/request/extractor.go b/vendor/github.com/dgrijalva/jwt-go/request/extractor.go new file mode 100644 index 00000000..14414fe2 --- /dev/null +++ b/vendor/github.com/dgrijalva/jwt-go/request/extractor.go @@ -0,0 +1,81 @@ +package request + +import ( + "errors" + "net/http" +) + +// Errors +var ( + ErrNoTokenInRequest = errors.New("no token present in request") +) + +// Interface for extracting a token from an HTTP request. +// The ExtractToken method should return a token string or an error. +// If no token is present, you must return ErrNoTokenInRequest. +type Extractor interface { + ExtractToken(*http.Request) (string, error) +} + +// Extractor for finding a token in a header. Looks at each specified +// header in order until there's a match +type HeaderExtractor []string + +func (e HeaderExtractor) ExtractToken(req *http.Request) (string, error) { + // loop over header names and return the first one that contains data + for _, header := range e { + if ah := req.Header.Get(header); ah != "" { + return ah, nil + } + } + return "", ErrNoTokenInRequest +} + +// Extract token from request arguments. This includes a POSTed form or +// GET URL arguments. Argument names are tried in order until there's a match. +// This extractor calls `ParseMultipartForm` on the request +type ArgumentExtractor []string + +func (e ArgumentExtractor) ExtractToken(req *http.Request) (string, error) { + // Make sure form is parsed + req.ParseMultipartForm(10e6) + + // loop over arg names and return the first one that contains data + for _, arg := range e { + if ah := req.Form.Get(arg); ah != "" { + return ah, nil + } + } + + return "", ErrNoTokenInRequest +} + +// Tries Extractors in order until one returns a token string or an error occurs +type MultiExtractor []Extractor + +func (e MultiExtractor) ExtractToken(req *http.Request) (string, error) { + // loop over header names and return the first one that contains data + for _, extractor := range e { + if tok, err := extractor.ExtractToken(req); tok != "" { + return tok, nil + } else if err != ErrNoTokenInRequest { + return "", err + } + } + return "", ErrNoTokenInRequest +} + +// Wrap an Extractor in this to post-process the value before it's handed off. +// See AuthorizationHeaderExtractor for an example +type PostExtractionFilter struct { + Extractor + Filter func(string) (string, error) +} + +func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) { + if tok, err := e.Extractor.ExtractToken(req); tok != "" { + return e.Filter(tok) + } else { + return "", err + } +} diff --git a/vendor/github.com/dgrijalva/jwt-go/request/oauth2.go b/vendor/github.com/dgrijalva/jwt-go/request/oauth2.go new file mode 100644 index 00000000..5948694a --- /dev/null +++ b/vendor/github.com/dgrijalva/jwt-go/request/oauth2.go @@ -0,0 +1,28 @@ +package request + +import ( + "strings" +) + +// Strips 'Bearer ' prefix from bearer token string +func stripBearerPrefixFromTokenString(tok string) (string, error) { + // Should be a bearer token + if len(tok) > 6 && strings.ToUpper(tok[0:7]) == "BEARER " { + return tok[7:], nil + } + return tok, nil +} + +// Extract bearer token from Authorization header +// Uses PostExtractionFilter to strip "Bearer " prefix from header +var AuthorizationHeaderExtractor = &PostExtractionFilter{ + HeaderExtractor{"Authorization"}, + stripBearerPrefixFromTokenString, +} + +// Extractor for OAuth2 access tokens. Looks in 'Authorization' +// header then 'access_token' argument for a token. +var OAuth2Extractor = &MultiExtractor{ + AuthorizationHeaderExtractor, + ArgumentExtractor{"access_token"}, +} diff --git a/vendor/github.com/dgrijalva/jwt-go/request/request.go b/vendor/github.com/dgrijalva/jwt-go/request/request.go new file mode 100644 index 00000000..70525cfa --- /dev/null +++ b/vendor/github.com/dgrijalva/jwt-go/request/request.go @@ -0,0 +1,68 @@ +package request + +import ( + "github.com/dgrijalva/jwt-go" + "net/http" +) + +// Extract and parse a JWT token from an HTTP request. +// This behaves the same as Parse, but accepts a request and an extractor +// instead of a token string. The Extractor interface allows you to define +// the logic for extracting a token. Several useful implementations are provided. +// +// You can provide options to modify parsing behavior +func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc, options ...ParseFromRequestOption) (token *jwt.Token, err error) { + // Create basic parser struct + p := &fromRequestParser{req, extractor, nil, nil} + + // Handle options + for _, option := range options { + option(p) + } + + // Set defaults + if p.claims == nil { + p.claims = jwt.MapClaims{} + } + if p.parser == nil { + p.parser = &jwt.Parser{} + } + + // perform extract + tokenString, err := p.extractor.ExtractToken(req) + if err != nil { + return nil, err + } + + // perform parse + return p.parser.ParseWithClaims(tokenString, p.claims, keyFunc) +} + +// ParseFromRequest but with custom Claims type +// DEPRECATED: use ParseFromRequest and the WithClaims option +func ParseFromRequestWithClaims(req *http.Request, extractor Extractor, claims jwt.Claims, keyFunc jwt.Keyfunc) (token *jwt.Token, err error) { + return ParseFromRequest(req, extractor, keyFunc, WithClaims(claims)) +} + +type fromRequestParser struct { + req *http.Request + extractor Extractor + claims jwt.Claims + parser *jwt.Parser +} + +type ParseFromRequestOption func(*fromRequestParser) + +// Parse with custom claims +func WithClaims(claims jwt.Claims) ParseFromRequestOption { + return func(p *fromRequestParser) { + p.claims = claims + } +} + +// Parse using a custom parser +func WithParser(parser *jwt.Parser) ParseFromRequestOption { + return func(p *fromRequestParser) { + p.parser = parser + } +} diff --git a/vendor/github.com/gorilla/mux/README.md b/vendor/github.com/gorilla/mux/README.md index 92e422ee..35eea9f1 100644 --- a/vendor/github.com/gorilla/mux/README.md +++ b/vendor/github.com/gorilla/mux/README.md @@ -1,11 +1,10 @@ # gorilla/mux [![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) -[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux) [![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux) [![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) -![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png) +![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png) https://www.gorillatoolkit.org/pkg/mux @@ -26,6 +25,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv * [Examples](#examples) * [Matching Routes](#matching-routes) * [Static Files](#static-files) +* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.) * [Registered URLs](#registered-urls) * [Walking Routes](#walking-routes) * [Graceful Shutdown](#graceful-shutdown) @@ -212,6 +212,93 @@ func main() { } ``` +### Serving Single Page Applications + +Most of the time it makes sense to serve your SPA on a separate web server from your API, +but sometimes it's desirable to serve them both from one place. It's possible to write a simple +handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage +mux's powerful routing for your API endpoints. + +```go +package main + +import ( + "encoding/json" + "log" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/gorilla/mux" +) + +// spaHandler implements the http.Handler interface, so we can use it +// to respond to HTTP requests. The path to the static directory and +// path to the index file within that static directory are used to +// serve the SPA in the given static directory. +type spaHandler struct { + staticPath string + indexPath string +} + +// ServeHTTP inspects the URL path to locate a file within the static dir +// on the SPA handler. If a file is found, it will be served. If not, the +// file located at the index path on the SPA handler will be served. This +// is suitable behavior for serving an SPA (single page application). +func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // get the absolute path to prevent directory traversal + path, err := filepath.Abs(r.URL.Path) + if err != nil { + // if we failed to get the absolute path respond with a 400 bad request + // and stop + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // prepend the path with the path to the static directory + path = filepath.Join(h.staticPath, path) + + // check whether a file exists at the given path + _, err = os.Stat(path) + if os.IsNotExist(err) { + // file does not exist, serve index.html + http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath)) + return + } else if err != nil { + // if we got an error (that wasn't that the file doesn't exist) stating the + // file, return a 500 internal server error and stop + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // otherwise, use http.FileServer to serve the static dir + http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) +} + +func main() { + router := mux.NewRouter() + + router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { + // an example API handler + json.NewEncoder(w).Encode(map[string]bool{"ok": true}) + }) + + spa := spaHandler{staticPath: "build", indexPath: "index.html"} + router.PathPrefix("/").Handler(spa) + + srv := &http.Server{ + Handler: router, + Addr: "127.0.0.1:8000", + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) +} +``` + ### Registered URLs Now let's see how to build registered URLs. diff --git a/vendor/github.com/gorilla/mux/context.go b/vendor/github.com/gorilla/mux/context.go deleted file mode 100644 index 665940a2..00000000 --- a/vendor/github.com/gorilla/mux/context.go +++ /dev/null @@ -1,18 +0,0 @@ -package mux - -import ( - "context" - "net/http" -) - -func contextGet(r *http.Request, key interface{}) interface{} { - return r.Context().Value(key) -} - -func contextSet(r *http.Request, key, val interface{}) *http.Request { - if val == nil { - return r - } - - return r.WithContext(context.WithValue(r.Context(), key, val)) -} diff --git a/vendor/github.com/gorilla/mux/go.mod b/vendor/github.com/gorilla/mux/go.mod index cfc8ede5..df170a39 100644 --- a/vendor/github.com/gorilla/mux/go.mod +++ b/vendor/github.com/gorilla/mux/go.mod @@ -1 +1,3 @@ module github.com/gorilla/mux + +go 1.12 diff --git a/vendor/github.com/gorilla/mux/middleware.go b/vendor/github.com/gorilla/mux/middleware.go index cf2b26dc..cb51c565 100644 --- a/vendor/github.com/gorilla/mux/middleware.go +++ b/vendor/github.com/gorilla/mux/middleware.go @@ -58,22 +58,17 @@ func CORSMethodMiddleware(r *Router) MiddlewareFunc { func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) { var allMethods []string - err := r.Walk(func(route *Route, _ *Router, _ []*Route) error { - for _, m := range route.matchers { - if _, ok := m.(*routeRegexp); ok { - if m.Match(req, &RouteMatch{}) { - methods, err := route.GetMethods() - if err != nil { - return err - } - - allMethods = append(allMethods, methods...) - } - break + for _, route := range r.routes { + var match RouteMatch + if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch { + methods, err := route.GetMethods() + if err != nil { + return nil, err } + + allMethods = append(allMethods, methods...) } - return nil - }) + } - return allMethods, err + return allMethods, nil } diff --git a/vendor/github.com/gorilla/mux/mux.go b/vendor/github.com/gorilla/mux/mux.go index a2cd193e..c9ba6470 100644 --- a/vendor/github.com/gorilla/mux/mux.go +++ b/vendor/github.com/gorilla/mux/mux.go @@ -5,6 +5,7 @@ package mux import ( + "context" "errors" "fmt" "net/http" @@ -58,8 +59,7 @@ type Router struct { // If true, do not clear the request context after handling the request. // - // Deprecated: No effect when go1.7+ is used, since the context is stored - // on the request itself. + // Deprecated: No effect, since the context is stored on the request itself. KeepContext bool // Slice of middlewares to be called after a match is found @@ -111,10 +111,8 @@ func copyRouteConf(r routeConf) routeConf { c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q)) } - c.matchers = make([]matcher, 0, len(r.matchers)) - for _, m := range r.matchers { - c.matchers = append(c.matchers, m) - } + c.matchers = make([]matcher, len(r.matchers)) + copy(c.matchers, r.matchers) return c } @@ -197,8 +195,8 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { var handler http.Handler if r.Match(req, &match) { handler = match.Handler - req = setVars(req, match.Vars) - req = setCurrentRoute(req, match.Route) + req = requestWithVars(req, match.Vars) + req = requestWithRoute(req, match.Route) } if handler == nil && match.MatchErr == ErrMethodMismatch { @@ -428,7 +426,7 @@ const ( // Vars returns the route variables for the current request, if any. func Vars(r *http.Request) map[string]string { - if rv := contextGet(r, varsKey); rv != nil { + if rv := r.Context().Value(varsKey); rv != nil { return rv.(map[string]string) } return nil @@ -440,18 +438,20 @@ func Vars(r *http.Request) map[string]string { // after the handler returns, unless the KeepContext option is set on the // Router. func CurrentRoute(r *http.Request) *Route { - if rv := contextGet(r, routeKey); rv != nil { + if rv := r.Context().Value(routeKey); rv != nil { return rv.(*Route) } return nil } -func setVars(r *http.Request, val interface{}) *http.Request { - return contextSet(r, varsKey, val) +func requestWithVars(r *http.Request, vars map[string]string) *http.Request { + ctx := context.WithValue(r.Context(), varsKey, vars) + return r.WithContext(ctx) } -func setCurrentRoute(r *http.Request, val interface{}) *http.Request { - return contextSet(r, routeKey, val) +func requestWithRoute(r *http.Request, route *Route) *http.Request { + ctx := context.WithValue(r.Context(), routeKey, route) + return r.WithContext(ctx) } // ---------------------------------------------------------------------------- diff --git a/vendor/github.com/gorilla/mux/regexp.go b/vendor/github.com/gorilla/mux/regexp.go index ac1abcd4..96dd94ad 100644 --- a/vendor/github.com/gorilla/mux/regexp.go +++ b/vendor/github.com/gorilla/mux/regexp.go @@ -181,21 +181,21 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { } } return r.regexp.MatchString(host) - } else { - if r.regexpType == regexpTypeQuery { - return r.matchQueryString(req) - } - path := req.URL.Path - if r.options.useEncodedPath { - path = req.URL.EscapedPath() - } - return r.regexp.MatchString(path) } + + if r.regexpType == regexpTypeQuery { + return r.matchQueryString(req) + } + path := req.URL.Path + if r.options.useEncodedPath { + path = req.URL.EscapedPath() + } + return r.regexp.MatchString(path) } // url builds a URL part using the given values. func (r *routeRegexp) url(values map[string]string) (string, error) { - urlValues := make([]interface{}, len(r.varsN)) + urlValues := make([]interface{}, len(r.varsN), len(r.varsN)) for k, v := range r.varsN { value, ok := values[v] if !ok { @@ -230,14 +230,51 @@ func (r *routeRegexp) getURLQuery(req *http.Request) string { return "" } templateKey := strings.SplitN(r.template, "=", 2)[0] - for key, vals := range req.URL.Query() { - if key == templateKey && len(vals) > 0 { - return key + "=" + vals[0] - } + val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey) + if ok { + return templateKey + "=" + val } return "" } +// findFirstQueryKey returns the same result as (*url.URL).Query()[key][0]. +// If key was not found, empty string and false is returned. +func findFirstQueryKey(rawQuery, key string) (value string, ok bool) { + query := []byte(rawQuery) + for len(query) > 0 { + foundKey := query + if i := bytes.IndexAny(foundKey, "&;"); i >= 0 { + foundKey, query = foundKey[:i], foundKey[i+1:] + } else { + query = query[:0] + } + if len(foundKey) == 0 { + continue + } + var value []byte + if i := bytes.IndexByte(foundKey, '='); i >= 0 { + foundKey, value = foundKey[:i], foundKey[i+1:] + } + if len(foundKey) < len(key) { + // Cannot possibly be key. + continue + } + keyString, err := url.QueryUnescape(string(foundKey)) + if err != nil { + continue + } + if keyString != key { + continue + } + valueString, err := url.QueryUnescape(string(value)) + if err != nil { + continue + } + return valueString, true + } + return "", false +} + func (r *routeRegexp) matchQueryString(req *http.Request) bool { return r.regexp.MatchString(r.getURLQuery(req)) } diff --git a/vendor/github.com/gorilla/mux/route.go b/vendor/github.com/gorilla/mux/route.go index 8479c68c..750afe57 100644 --- a/vendor/github.com/gorilla/mux/route.go +++ b/vendor/github.com/gorilla/mux/route.go @@ -74,7 +74,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { return false } - if match.MatchErr == ErrMethodMismatch { + if match.MatchErr == ErrMethodMismatch && r.handler != nil { // We found a route which matches request method, clear MatchErr match.MatchErr = nil // Then override the mis-matched handler @@ -412,11 +412,30 @@ func (r *Route) Queries(pairs ...string) *Route { type schemeMatcher []string func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchInArray(m, r.URL.Scheme) + scheme := r.URL.Scheme + // https://golang.org/pkg/net/http/#Request + // "For [most] server requests, fields other than Path and RawQuery will be + // empty." + // Since we're an http muxer, the scheme is either going to be http or https + // though, so we can just set it based on the tls termination state. + if scheme == "" { + if r.TLS == nil { + scheme = "http" + } else { + scheme = "https" + } + } + return matchInArray(m, scheme) } // Schemes adds a matcher for URL schemes. // It accepts a sequence of schemes to be matched, e.g.: "http", "https". +// If the request's URL has a scheme set, it will be matched against. +// Generally, the URL scheme will only be set if a previous handler set it, +// such as the ProxyHeaders handler from gorilla/handlers. +// If unset, the scheme will be determined based on the request's TLS +// termination state. +// The first argument to Schemes will be used when constructing a route URL. func (r *Route) Schemes(schemes ...string) *Route { for k, v := range schemes { schemes[k] = strings.ToLower(v) @@ -493,8 +512,8 @@ func (r *Route) Subrouter() *Router { // This also works for host variables: // // r := mux.NewRouter() -// r.Host("{subdomain}.domain.com"). -// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Host("{subdomain}.domain.com"). // Name("article") // // // url.String() will be "http://news.domain.com/articles/technology/42" @@ -502,6 +521,13 @@ func (r *Route) Subrouter() *Router { // "category", "technology", // "id", "42") // +// The scheme of the resulting url will be the first argument that was passed to Schemes: +// +// // url.String() will be "https://example.com" +// r := mux.NewRouter() +// url, err := r.Host("example.com") +// .Schemes("https", "http").URL() +// // All variables defined in the route are required, and their values must // conform to the corresponding patterns. func (r *Route) URL(pairs ...string) (*url.URL, error) { @@ -635,7 +661,7 @@ func (r *Route) GetQueriesRegexp() ([]string, error) { if r.regexp.queries == nil { return nil, errors.New("mux: route doesn't have queries") } - var queries []string + queries := make([]string, 0, len(r.regexp.queries)) for _, query := range r.regexp.queries { queries = append(queries, query.regexp.String()) } @@ -654,7 +680,7 @@ func (r *Route) GetQueriesTemplates() ([]string, error) { if r.regexp.queries == nil { return nil, errors.New("mux: route doesn't have queries") } - var queries []string + queries := make([]string, 0, len(r.regexp.queries)) for _, query := range r.regexp.queries { queries = append(queries, query.template) } diff --git a/vendor/github.com/gorilla/mux/test_helpers.go b/vendor/github.com/gorilla/mux/test_helpers.go index 32ecffde..5f5c496d 100644 --- a/vendor/github.com/gorilla/mux/test_helpers.go +++ b/vendor/github.com/gorilla/mux/test_helpers.go @@ -15,5 +15,5 @@ import "net/http" // can be set by making a route that captures the required variables, // starting a server and sending the request to that server. func SetURLVars(r *http.Request, val map[string]string) *http.Request { - return setVars(r, val) + return requestWithVars(r, val) } diff --git a/vendor/github.com/ntk148v/etcd-adapter/.gitignore b/vendor/github.com/ntk148v/etcd-adapter/.gitignore new file mode 100644 index 00000000..a1a84059 --- /dev/null +++ b/vendor/github.com/ntk148v/etcd-adapter/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +vendor/ + +.idea/ +.vscode/ diff --git a/vendor/github.com/ntk148v/etcd-adapter/.travis.yml b/vendor/github.com/ntk148v/etcd-adapter/.travis.yml new file mode 100644 index 00000000..cbeb0d7d --- /dev/null +++ b/vendor/github.com/ntk148v/etcd-adapter/.travis.yml @@ -0,0 +1,20 @@ +language: go + +sudo: required + +go: + - tip + +service: + - docker + +before_install: + - echo $TRAVIS_GO_VERSION + - docker pull quay.io/coreos/etcd:v3.3.10 + - docker run -d -p 6379:2379 quay.io/coreos/etcd:v3.3.10 etcd --name test-etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2380 + +install: + - dep ensure + +script: + - $HOME/gopath/bin/goveralls -service=travis-ci diff --git a/vendor/github.com/ntk148v/etcd-adapter/LICENSE b/vendor/github.com/ntk148v/etcd-adapter/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/ntk148v/etcd-adapter/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/ntk148v/etcd-adapter/README.md b/vendor/github.com/ntk148v/etcd-adapter/README.md new file mode 100644 index 00000000..149b76fb --- /dev/null +++ b/vendor/github.com/ntk148v/etcd-adapter/README.md @@ -0,0 +1,32 @@ +# etcd-adapter + +[![Build Status](https://travis-ci.org/ntk148v/etcd-adapter.svg?branch=master)](https://travis-ci.org/ntk148v/etcd-adapter) +[![Coverage Status](https://coveralls.io/repos/github/ntk148v/etcd-adapter/badge.svg)](https://coveralls.io/github/ntk148v/etcd-adapter) +[![Godoc](https://godoc.org/github.com/ntk148v/etcd-adapter?status.svg)](https://godoc.org/github.com/ntk148v/etcd-adapter) + +ETCD adapter is the policy storage adapter for [Casbin](https://github.com/casbin/casbin). With this library, Casbin can load policy from ETCD and save policy to it. ETCD adapter support the __Auto-Save__ feature for Casbin policy. This means it can support: +- Add a single policy rule to the storage +- Remove a single policy rule from the storage. +- Add a set of policies rule to the storage. +- Remove a set of policies rule to the storage. + +Additional, this adapter allows user to: +- Fully control the Etcd configuration. It will be useful if Etcd is enable authentication. +- Support Etcd namespace. + +## Installation +```bash +go get github.com/ntk148v/etcd-adapter +``` + +## Sample Example + +Check the [examples folder](./examples) + +## Getting Help + +- [Casbin](https://github.com/casbin/casbin) + +## License + +This project is under Apache 2.0 License. See the [LICENSE](LICENSE) file for the full license text. diff --git a/vendor/github.com/ntk148v/etcd-adapter/adapter.go b/vendor/github.com/ntk148v/etcd-adapter/adapter.go new file mode 100644 index 00000000..540baed6 --- /dev/null +++ b/vendor/github.com/ntk148v/etcd-adapter/adapter.go @@ -0,0 +1,373 @@ +// etcdadapter will simulate the table structure of Relational DB in ETCD which is a kv-based storage. +// Under a basic path, we will build a key for each policy, and the value is the Json format string for each Casbin Rule. + +package etcdadapter + +import ( + "context" + "encoding/json" + "fmt" + "regexp" + "runtime" + "strings" + "time" + + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" + client "go.etcd.io/etcd/clientv3" + clientnamespace "go.etcd.io/etcd/clientv3/namespace" +) + +const ( + REQUESTTIMEOUT = 5 * time.Second + + // PLACEHOLDER represent the NULL value in the Casbin Rule. + PLACEHOLDER = "_" + + // DEFAULT_KEY is the root path in ETCD, if not provided. + DEFAULT_KEY = "casbin_policy" +) + +type CasbinRule struct { + Key string `json:"key"` + PType string `json:"ptype"` + V0 string `json:"v0"` + V1 string `json:"v1"` + V2 string `json:"v2"` + V3 string `json:"v3"` + V4 string `json:"v4"` + V5 string `json:"v5"` +} + +// Adapter represents the ETCD adapter for policy storage. +type Adapter struct { + etcdCfg client.Config + key string + + // etcd connection client + conn *client.Client +} + +func NewAdapter(etcdCfg client.Config, namespace string, key string) *Adapter { + return newAdapter(etcdCfg, namespace, key) +} + +func newAdapter(etcdCfg client.Config, namespace string, key string) *Adapter { + if key == "" { + key = DEFAULT_KEY + } + a := &Adapter{ + etcdCfg: etcdCfg, + key: key, + } + a.connect(namespace) + + // Call the destructor when the object is released. + runtime.SetFinalizer(a, finalizer) + + return a +} + +func (a *Adapter) connect(namespace string) { + connection, err := client.New(a.etcdCfg) + if err != nil { + panic(err) + } + if namespace != "" { + connection.Watcher = clientnamespace.NewWatcher(connection.Watcher, namespace) + connection.Lease = clientnamespace.NewLease(connection.Lease, namespace) + connection.KV = clientnamespace.NewKV(connection.KV, namespace) + } + a.conn = connection +} + +// finalizer is the destructor for Adapter. +func finalizer(a *Adapter) { + a.conn.Close() +} + +func (a *Adapter) close() { + a.conn.Close() +} + +// LoadPolicy loads all of policys from ETCD +func (a *Adapter) LoadPolicy(model model.Model) error { + var rule CasbinRule + ctx, cancel := context.WithTimeout(context.Background(), REQUESTTIMEOUT) + defer cancel() + getResp, err := a.conn.Get(ctx, a.getRootKey(), client.WithPrefix()) + if err != nil { + return err + } + for _, kv := range getResp.Kvs { + err = json.Unmarshal(kv.Value, &rule) + if err != nil { + return err + } + a.loadPolicy(rule, model) + } + return nil +} + +func (a *Adapter) getRootKey() string { + return fmt.Sprintf("/%s", a.key) +} + +func (a *Adapter) loadPolicy(rule CasbinRule, model model.Model) { + lineText := rule.PType + if rule.V0 != "" { + lineText += ", " + rule.V0 + } + if rule.V1 != "" { + lineText += ", " + rule.V1 + } + if rule.V2 != "" { + lineText += ", " + rule.V2 + } + if rule.V3 != "" { + lineText += ", " + rule.V3 + } + if rule.V4 != "" { + lineText += ", " + rule.V4 + } + if rule.V5 != "" { + lineText += ", " + rule.V5 + } + + persist.LoadPolicyLine(lineText, model) +} + +// This will rewrite all of policies in ETCD with the current data in Casbin +func (a *Adapter) SavePolicy(model model.Model) error { + // clean old rule data + a.destroy() + + var rules []CasbinRule + + for ptype, ast := range model["p"] { + for _, line := range ast.Policy { + rules = append(rules, a.convertRule(ptype, line)) + } + } + + for ptype, ast := range model["g"] { + for _, line := range ast.Policy { + rules = append(rules, a.convertRule(ptype, line)) + } + } + + return a.savePolicy(rules) +} + +// destroy or clean all of policy +func (a *Adapter) destroy() error { + ctx, cancel := context.WithTimeout(context.Background(), REQUESTTIMEOUT) + defer cancel() + _, err := a.conn.Delete(ctx, a.getRootKey(), client.WithPrefix()) + return err +} + +func (a *Adapter) convertRule(ptype string, line []string) (rule CasbinRule) { + rule = CasbinRule{} + rule.PType = ptype + policys := []string{ptype} + length := len(line) + + if len(line) > 0 { + rule.V0 = line[0] + policys = append(policys, line[0]) + } + if len(line) > 1 { + rule.V1 = line[1] + policys = append(policys, line[1]) + } + if len(line) > 2 { + rule.V2 = line[2] + policys = append(policys, line[2]) + } + if len(line) > 3 { + rule.V3 = line[3] + policys = append(policys, line[3]) + } + if len(line) > 4 { + rule.V4 = line[4] + policys = append(policys, line[4]) + } + if len(line) > 5 { + rule.V5 = line[5] + policys = append(policys, line[5]) + } + + for i := 0; i < 6-length; i++ { + policys = append(policys, PLACEHOLDER) + } + + rule.Key = strings.Join(policys, "::") + + return rule +} + +func (a *Adapter) savePolicy(rules []CasbinRule) error { + ctx, cancel := context.WithTimeout(context.Background(), REQUESTTIMEOUT) + defer cancel() + for _, rule := range rules { + ruleData, _ := json.Marshal(rule) + _, err := a.conn.Put(ctx, a.constructPath(rule.Key), string(ruleData)) + if err != nil { + return err + } + } + return nil +} + +func (a *Adapter) constructPath(key string) string { + return fmt.Sprintf("/%s/%s", a.key, key) +} + +// AddPolicy adds a policy rule to the storage. +// Part of the Auto-Save feature. +func (a *Adapter) AddPolicy(sec string, ptype string, line []string) error { + rule := a.convertRule(ptype, line) + ctx, cancel := context.WithTimeout(context.Background(), REQUESTTIMEOUT) + defer cancel() + ruleData, _ := json.Marshal(rule) + _, err := a.conn.Put(ctx, a.constructPath(rule.Key), string(ruleData)) + return err +} + +// AddPolicies adds policy rules to the storage. +// This is part of the Auto-Save feature. +func (a *Adapter) AddPolicies(sec string, ptype string, rules [][]string) error { + for _, rule := range rules { + if err := a.AddPolicy(sec, ptype, rule); err != nil { + return err + } + } + return nil +} + +// RemovePolicy removes a policy rule from the storage. +// Part of the Auto-Save feature. +func (a *Adapter) RemovePolicy(sec string, ptype string, line []string) error { + rule := a.convertRule(ptype, line) + ctx, cancel := context.WithTimeout(context.Background(), REQUESTTIMEOUT) + defer cancel() + _, err := a.conn.Delete(ctx, a.constructPath(rule.Key)) + return err +} + +// RemovePolicies removes policy rules from the storage. +// This is part of the Auto-Save feature. +func (a *Adapter) RemovePolicies(sec string, ptype string, rules [][]string) error { + for _, rule := range rules { + if err := a.RemovePolicy(sec, ptype, rule); err != nil { + return err + } + } + return nil +} + +// RemoveFilteredPolicy removes policy rules that match the filter from the storage. +// Part of the Auto-Save feature. +func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error { + rule := CasbinRule{} + + rule.PType = ptype + if fieldIndex <= 0 && 0 < fieldIndex+len(fieldValues) { + rule.V0 = fieldValues[0-fieldIndex] + } + if fieldIndex <= 1 && 1 < fieldIndex+len(fieldValues) { + rule.V1 = fieldValues[1-fieldIndex] + } + if fieldIndex <= 2 && 2 < fieldIndex+len(fieldValues) { + rule.V2 = fieldValues[2-fieldIndex] + } + if fieldIndex <= 3 && 3 < fieldIndex+len(fieldValues) { + rule.V3 = fieldValues[3-fieldIndex] + } + if fieldIndex <= 4 && 4 < fieldIndex+len(fieldValues) { + rule.V4 = fieldValues[4-fieldIndex] + } + if fieldIndex <= 5 && 5 < fieldIndex+len(fieldValues) { + rule.V5 = fieldValues[5-fieldIndex] + } + + filter := a.constructFilter(rule) + + return a.removeFilteredPolicy(filter) +} + +func (a *Adapter) constructFilter(rule CasbinRule) string { + var filter string + if rule.PType != "" { + filter = fmt.Sprintf("/%s/%s", a.key, rule.PType) + } else { + filter = fmt.Sprintf("/%s/.*", a.key) + } + + if rule.V0 != "" { + filter = fmt.Sprintf("%s::%s", filter, rule.V0) + } else { + filter = fmt.Sprintf("%s::.*", filter) + } + + if rule.V1 != "" { + filter = fmt.Sprintf("%s::%s", filter, rule.V1) + } else { + filter = fmt.Sprintf("%s::.*", filter) + } + + if rule.V2 != "" { + filter = fmt.Sprintf("%s::%s", filter, rule.V2) + } else { + filter = fmt.Sprintf("%s::.*", filter) + } + + if rule.V3 != "" { + filter = fmt.Sprintf("%s::%s", filter, rule.V3) + } else { + filter = fmt.Sprintf("%s::.*", filter) + } + + if rule.V4 != "" { + filter = fmt.Sprintf("%s::%s", filter, rule.V4) + } else { + filter = fmt.Sprintf("%s::.*", filter) + } + + if rule.V5 != "" { + filter = fmt.Sprintf("%s::%s", filter, rule.V5) + } else { + filter = fmt.Sprintf("%s::.*", filter) + } + + return filter +} + +func (a *Adapter) removeFilteredPolicy(filter string) error { + ctx, cancel := context.WithTimeout(context.Background(), REQUESTTIMEOUT) + defer cancel() + // get all policy key + getResp, err := a.conn.Get(ctx, a.constructPath(""), client.WithPrefix(), client.WithKeysOnly()) + if err != nil { + return err + } + var filteredKeys []string + for _, kv := range getResp.Kvs { + matched, err := regexp.MatchString(filter, string(kv.Key)) + if err != nil { + return err + } + if matched { + filteredKeys = append(filteredKeys, string(kv.Key)) + } + } + + for _, key := range filteredKeys { + _, err := a.conn.Delete(ctx, key) + if err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/ntk148v/etcd-adapter/docker-compose.yml b/vendor/github.com/ntk148v/etcd-adapter/docker-compose.yml new file mode 100644 index 00000000..729ad677 --- /dev/null +++ b/vendor/github.com/ntk148v/etcd-adapter/docker-compose.yml @@ -0,0 +1,16 @@ +version: '2' + +services: + etcd: + image: bitnami/etcd:3 + environment: + - ALLOW_NONE_AUTHENTICATION=yes + volumes: + - etcd_data:/bitnami/etcd + ports: + - 6379:2379 + - 6380:2380 + +volumes: + etcd_data: + driver: local diff --git a/vendor/github.com/ntk148v/etcd-adapter/go.mod b/vendor/github.com/ntk148v/etcd-adapter/go.mod new file mode 100644 index 00000000..ec923122 --- /dev/null +++ b/vendor/github.com/ntk148v/etcd-adapter/go.mod @@ -0,0 +1,8 @@ +module github.com/ntk148v/etcd-adapter + +go 1.14 + +require ( + github.com/casbin/casbin/v2 v2.6.10 + go.etcd.io/etcd v0.0.0-20200401174654-e694b7bb0875 +) diff --git a/vendor/github.com/ntk148v/etcd-adapter/go.sum b/vendor/github.com/ntk148v/etcd-adapter/go.sum new file mode 100644 index 00000000..19c9cace --- /dev/null +++ b/vendor/github.com/ntk148v/etcd-adapter/go.sum @@ -0,0 +1,184 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/casbin/casbin/v2 v2.6.10 h1:Thp6H74ykPjCwr3cxpG4iWrPxV9Uege0gTmWgnYKdT8= +github.com/casbin/casbin/v2 v2.6.10/go.mod h1:XXtYGrs/0zlOsJMeRteEdVi/FsB0ph7KgNfjoCoJUD8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf h1:CAKfRE2YtTUIjjh1bkBtyYFaUT/WmOqsJjgtihT0vMI= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c h1:Lh2aW+HnU2Nbe1gqD9SOJLJxW1jBMmQOktN2acDyJk8= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20200401174654-e694b7bb0875 h1:61WXaq6CI2RsDa1qZEWkW4KruLdtp0EVLwrFRfsd8Qo= +go.etcd.io/etcd v0.0.0-20200401174654-e694b7bb0875/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/vendor/github.com/ntk148v/jwt-middleware/.gitignore b/vendor/github.com/ntk148v/jwt-middleware/.gitignore new file mode 100644 index 00000000..e7ca3ac1 --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/.gitignore @@ -0,0 +1,19 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Editor +.idea/ +.vscode/ diff --git a/vendor/github.com/ntk148v/jwt-middleware/LICENSE b/vendor/github.com/ntk148v/jwt-middleware/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/ntk148v/jwt-middleware/README.md b/vendor/github.com/ntk148v/jwt-middleware/README.md new file mode 100644 index 00000000..37151e2d --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/README.md @@ -0,0 +1,38 @@ +# JWT Middleware + +- [JWT Middleware](#jwt-middleware) + - [1. Intro](#1-intro) + - [2. Key features](#2-key-features) + - [3. Installation](#3-installation) + - [4. Examples](#4-examples) + - [5. References](#5-references) + +## 1. Intro + +A middleware that will check that a [JWT](https://jwt.io) is sent on the request header (the header key is customizable) and will then set the content of the JWT into the context. + +This module lets you authenticate HTTP requests using JWT tokens in your Go Programming Language applications. + +## 2. Key features + +- Ability to **check the request header for a JWT**. +- **Decode the JWT** and set the content of it to the request context. +- **Generate the token string** for login handler. + +## 3. Installation + +```bash +go get github.com/ntk148v/jwt-middleware +``` + +## 4. Examples + +You can check out working examples in the [examples folder](./examples) for the basic usage. + +## 5. References + +This module is strong inspired by: + +- https://github.com/auth0/go-jwt-middleware +- https://github.com/adam-hanna/goLang-jwt-auth-example +- https://github.com/go-pandora/pkg/blob/master/auth/jwt/ diff --git a/vendor/github.com/ntk148v/jwt-middleware/errors.go b/vendor/github.com/ntk148v/jwt-middleware/errors.go new file mode 100644 index 00000000..554dbea5 --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/errors.go @@ -0,0 +1,33 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jwt + +import "errors" + +var ( + ErrInvalidSigningMethod = errors.New("JWT: invalid signing method") + ErrNoHMACKey = errors.New("JWT: no a HMAC key") + ErrNoRSAKey = errors.New("JWT: no a RSA key") + ErrNoECKey = errors.New("JWT: no a EC key") + ErrInvalidToken = errors.New("JWT: invalid token") + ErrGetTokenId = errors.New("JWT: can not get id from token") + ErrGetIssuedTime = errors.New("JWT: can not get issued time from token") + ErrGetData = errors.New("JWT: can not get data from token") + ErrNoStore = errors.New("JWT: no store provided") + ErrUnexpectedSigningMethod = errors.New("JWT: unexpected signing method") + ErrTokenMalformed = errors.New("JWT: token is malformed") + ErrTokenNotActive = errors.New("JWT: token is not valid yet") + ErrTokenExpired = errors.New("JWT: token is expired") +) diff --git a/vendor/github.com/ntk148v/jwt-middleware/go.mod b/vendor/github.com/ntk148v/jwt-middleware/go.mod new file mode 100644 index 00000000..d17e243b --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/go.mod @@ -0,0 +1,8 @@ +module github.com/ntk148v/jwt-middleware + +go 1.14 + +require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/gorilla/mux v1.7.4 +) diff --git a/vendor/github.com/ntk148v/jwt-middleware/go.sum b/vendor/github.com/ntk148v/jwt-middleware/go.sum new file mode 100644 index 00000000..e121bd0e --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/go.sum @@ -0,0 +1,4 @@ +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/vendor/github.com/ntk148v/jwt-middleware/middleware.go b/vendor/github.com/ntk148v/jwt-middleware/middleware.go new file mode 100644 index 00000000..b03d9012 --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/middleware.go @@ -0,0 +1,45 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jwt + +import ( + "context" + "net/http" +) + +// Authenticator verifies authentication provided in the request's header +func Authenticator(t *Token) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, req *http.Request) { + tokenString, err := t.GetToken(req) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + data, err := t.CheckToken(tokenString) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + // If we get here, everything worked and we can set the + // user data in context + newReq := req.WithContext(context.WithValue(req.Context(), t.options.UserProperty, data)) + // update the current request with the new context information + *req = *newReq + next.ServeHTTP(w, req) + } + return http.HandlerFunc(fn) + } +} diff --git a/vendor/github.com/ntk148v/jwt-middleware/options.go b/vendor/github.com/ntk148v/jwt-middleware/options.go new file mode 100644 index 00000000..6a69076a --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/options.go @@ -0,0 +1,31 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jwt + +import ( + "time" +) + +// Options is a struct for specifying configuration options +type Options struct { + PrivateKeyLocation string + PublicKeyLocation string + HMACKey []byte + SigningMethod string + TTL time.Duration + IsBearerToken bool + Header string + UserProperty string +} diff --git a/vendor/github.com/ntk148v/jwt-middleware/store.go b/vendor/github.com/ntk148v/jwt-middleware/store.go new file mode 100644 index 00000000..728bb83d --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/store.go @@ -0,0 +1,25 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jwt + +type Store interface { + // Check checks whether a token has been revoked + // If not, it will return some user data and nil. + Check(tokenId string, issuedAt float64) (data map[string]interface{}, err error) + // Revoke revokes a token which is no longer in use. + // This case often happens when a user logs out. + // or an authorization ends. + Revoke(tokenId string) error +} diff --git a/vendor/github.com/ntk148v/jwt-middleware/token.go b/vendor/github.com/ntk148v/jwt-middleware/token.go new file mode 100644 index 00000000..16d4f3d0 --- /dev/null +++ b/vendor/github.com/ntk148v/jwt-middleware/token.go @@ -0,0 +1,279 @@ +// Copyright (c) 2020 Kien Nguyen-Tuan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jwt + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "io/ioutil" + "net/http" + "time" + + "github.com/dgrijalva/jwt-go" + "github.com/dgrijalva/jwt-go/request" +) + +type Token struct { + privateKey interface{} + publicKey interface{} + signingMethod jwt.SigningMethod + store Store + options Options +} + +type TokenInfo struct { + Id string + IssuedAt float64 + Data map[string]interface{} +} + +// Claims holds the claims encoded in a JWT +type Claims struct { + // Standard claims are the standard jwt claims from the ietf standard + // https://tools.ietf.org/html/rfc7519 + *jwt.StandardClaims + Data map[string]interface{} `json:"data,omitempty"` +} + +// NewToken constructs a new Token instance +func NewToken(o Options, s Store) (*Token, error) { + signingMethod := jwt.GetSigningMethod(o.SigningMethod) + + var ( + privateKey interface{} + publicKey interface{} + err error + ) + + switch signingMethod { + case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512: + if o.HMACKey == nil { + return nil, ErrNoHMACKey + } + privateKey = o.HMACKey + publicKey = o.HMACKey + case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512: + if o.PrivateKeyLocation == "" || o.PublicKeyLocation == "" { + return nil, ErrNoRSAKey + } + privateKey, publicKey, err = getRSAKeys(o.PrivateKeyLocation, o.PublicKeyLocation) + if err != nil { + return nil, err + } + case jwt.SigningMethodES256, jwt.SigningMethodES384, jwt.SigningMethodES512: + if o.PrivateKeyLocation == "" || o.PublicKeyLocation == "" { + return nil, ErrNoECKey + } + privateKey, publicKey, err = getECKeys(o.PrivateKeyLocation, o.PublicKeyLocation) + if err != nil { + return nil, err + } + default: + return nil, ErrInvalidSigningMethod + } + if o.UserProperty == "" { + o.UserProperty = "user" + } + return &Token{ + privateKey: privateKey, + publicKey: publicKey, + signingMethod: signingMethod, + store: s, + options: o, + }, nil +} + +// GetToken extracts a token string from the header. +func (t *Token) GetToken(req *http.Request) (string, error) { + if t.options.IsBearerToken { + return request.AuthorizationHeaderExtractor.ExtractToken(req) + } + header := req.Header.Get(t.options.Header) + return header, nil +} + +// GenerateToken generate a JWT. +// Please don't add sensitive data such as password to payload of JWT. +func (t *Token) GenerateToken(data map[string]interface{}) (string, error) { + id, err := generateRandString(32) + if err != nil { + return "", fmt.Errorf("JWT: unable to generate JWT id, %s", err) + } + claims := Claims{ + StandardClaims: &jwt.StandardClaims{ + ExpiresAt: time.Now().Add(t.options.TTL).Unix(), + IssuedAt: time.Now().Unix(), + Id: id, + }, + Data: data, + } + unsigned := jwt.NewWithClaims(t.signingMethod, claims) + return unsigned.SignedString(t.privateKey) +} + +func (t *Token) CheckToken(tokenString string) (map[string]interface{}, error) { + tokenInfo, err := t.validateJWT(tokenString) + if err != nil { + return nil, err + } + // When there is no storage, we should like to return information from token. + if t.store == nil { + return tokenInfo.Data, nil + } + return t.store.Check(tokenInfo.Id, tokenInfo.IssuedAt) +} + +// ValidateJWT validates whether a jwt string is valid. +// If so, it returns data included in the token and nil error. +func (t *Token) ValidateToken(tokenString string) (*TokenInfo, error) { + return t.ValidateToken(tokenString) +} + +// RevokeToken revokes a token which is no longer in use. +// This case often happens when a user logs out. +// or an authorization ends. +func (t *Token) RevokeToken(id string) error { + if t.store == nil { + return ErrNoStore + } + return t.store.Revoke(id) +} + +// RefreshToken regenerate the token after check the given token +// string is valid. +func (t *Token) RefreshToken(tokenString string) (string, error) { + tokenInfo, err := t.validateJWT(tokenString) + if err != nil { + return "", err + } + return t.GenerateToken(tokenInfo.Data) +} + +func (t *Token) validateJWT(tokenString string) (*TokenInfo, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Validate the algorithm is what you expect + if token.Method.Alg() != t.signingMethod.Alg() { + return nil, ErrUnexpectedSigningMethod + } + return t.publicKey, nil + }) + if err != nil { + if e, ok := err.(*jwt.ValidationError); ok { + switch { + case e.Errors&jwt.ValidationErrorMalformed != 0: + // Token is malformed + return nil, ErrTokenMalformed + case e.Errors&jwt.ValidationErrorExpired != 0: + // Token is expired + return nil, ErrTokenExpired + case e.Errors&jwt.ValidationErrorNotValidYet != 0: + // Token is not active yet + return nil, ErrTokenNotActive + case e.Inner != nil: + // report e.Inner + return nil, e.Inner + } + } + return nil, err + } + + if !token.Valid { + return nil, ErrInvalidToken + } + + claims := token.Claims.(jwt.MapClaims) + if claims["jti"] == nil || claims["iat"] == nil { + return nil, ErrInvalidToken + } + + jti, ok := claims["jti"].(string) + if !ok { + return nil, ErrGetTokenId + } + + iat, ok := claims["iat"].(float64) + if !ok { + return nil, ErrGetIssuedTime + } + + if claims["data"] == nil { + return nil, nil + } + + data, ok := claims["data"].(map[string]interface{}) + if !ok { + return nil, ErrGetData + } + return &TokenInfo{ + Id: jti, + IssuedAt: iat, + Data: data, + }, nil +} + +func getKeyContent(keyLocation string) ([]byte, error) { + keyContent, err := ioutil.ReadFile(keyLocation) + if err != nil { + return nil, fmt.Errorf("JWT: failed to load a key from %s, %s", keyLocation, err) + } + return keyContent, nil +} + +func getRSAKeys(privateKeyLocation, publicKeyLocation string) (interface{}, interface{}, error) { + privateKeyContent, err := getKeyContent(privateKeyLocation) + if err != nil { + return nil, nil, err + } + publicKeyContent, err := getKeyContent(publicKeyLocation) + if err != nil { + return nil, nil, err + } + privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyContent) + if err != nil { + return nil, nil, fmt.Errorf("JWT: failed to generate a private RSA key, %s", err) + } + publicKey, err := jwt.ParseRSAPublicKeyFromPEM(publicKeyContent) + if err != nil { + return nil, nil, fmt.Errorf("JWT: failed to generate a public RSA key, %s", err) + } + return privateKey, publicKey, nil +} + +func getECKeys(privateKeyLocation, publicKeyLocation string) (interface{}, interface{}, error) { + privateKeyContent, err := getKeyContent(privateKeyLocation) + if err != nil { + return nil, nil, err + } + publicKeyContent, err := getKeyContent(publicKeyLocation) + if err != nil { + return nil, nil, err + } + privateKey, err := jwt.ParseECPrivateKeyFromPEM(privateKeyContent) + if err != nil { + return nil, nil, fmt.Errorf("JWT: failed to generate a private EC key, %s", err) + } + publicKey, err := jwt.ParseECPublicKeyFromPEM(publicKeyContent) + if err != nil { + return nil, nil, fmt.Errorf("JWT: failed to generate a public EC key, %s", err) + } + return privateKey, publicKey, nil +} + +func generateRandString(n int) (string, error) { + b := make([]byte, n) + _, err := rand.Read(b) + return base64.URLEncoding.EncodeToString(b), err +} diff --git a/vendor/golang.org/x/crypto/AUTHORS b/vendor/golang.org/x/crypto/AUTHORS new file mode 100644 index 00000000..2b00ddba --- /dev/null +++ b/vendor/golang.org/x/crypto/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at https://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/crypto/CONTRIBUTORS b/vendor/golang.org/x/crypto/CONTRIBUTORS new file mode 100644 index 00000000..1fbd3e97 --- /dev/null +++ b/vendor/golang.org/x/crypto/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at https://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/crypto/LICENSE b/vendor/golang.org/x/crypto/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/vendor/golang.org/x/crypto/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/crypto/PATENTS b/vendor/golang.org/x/crypto/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/vendor/golang.org/x/crypto/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/crypto/bcrypt/base64.go b/vendor/golang.org/x/crypto/bcrypt/base64.go new file mode 100644 index 00000000..fc311609 --- /dev/null +++ b/vendor/golang.org/x/crypto/bcrypt/base64.go @@ -0,0 +1,35 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bcrypt + +import "encoding/base64" + +const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +var bcEncoding = base64.NewEncoding(alphabet) + +func base64Encode(src []byte) []byte { + n := bcEncoding.EncodedLen(len(src)) + dst := make([]byte, n) + bcEncoding.Encode(dst, src) + for dst[n-1] == '=' { + n-- + } + return dst[:n] +} + +func base64Decode(src []byte) ([]byte, error) { + numOfEquals := 4 - (len(src) % 4) + for i := 0; i < numOfEquals; i++ { + src = append(src, '=') + } + + dst := make([]byte, bcEncoding.DecodedLen(len(src))) + n, err := bcEncoding.Decode(dst, src) + if err != nil { + return nil, err + } + return dst[:n], nil +} diff --git a/vendor/golang.org/x/crypto/bcrypt/bcrypt.go b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go new file mode 100644 index 00000000..aeb73f81 --- /dev/null +++ b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go @@ -0,0 +1,295 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing +// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf +package bcrypt // import "golang.org/x/crypto/bcrypt" + +// The code is a port of Provos and Mazières's C implementation. +import ( + "crypto/rand" + "crypto/subtle" + "errors" + "fmt" + "io" + "strconv" + + "golang.org/x/crypto/blowfish" +) + +const ( + MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword + MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword + DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword +) + +// The error returned from CompareHashAndPassword when a password and hash do +// not match. +var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password") + +// The error returned from CompareHashAndPassword when a hash is too short to +// be a bcrypt hash. +var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password") + +// The error returned from CompareHashAndPassword when a hash was created with +// a bcrypt algorithm newer than this implementation. +type HashVersionTooNewError byte + +func (hv HashVersionTooNewError) Error() string { + return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion) +} + +// The error returned from CompareHashAndPassword when a hash starts with something other than '$' +type InvalidHashPrefixError byte + +func (ih InvalidHashPrefixError) Error() string { + return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) +} + +type InvalidCostError int + +func (ic InvalidCostError) Error() string { + return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) +} + +const ( + majorVersion = '2' + minorVersion = 'a' + maxSaltSize = 16 + maxCryptedHashSize = 23 + encodedSaltSize = 22 + encodedHashSize = 31 + minHashSize = 59 +) + +// magicCipherData is an IV for the 64 Blowfish encryption calls in +// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes. +var magicCipherData = []byte{ + 0x4f, 0x72, 0x70, 0x68, + 0x65, 0x61, 0x6e, 0x42, + 0x65, 0x68, 0x6f, 0x6c, + 0x64, 0x65, 0x72, 0x53, + 0x63, 0x72, 0x79, 0x44, + 0x6f, 0x75, 0x62, 0x74, +} + +type hashed struct { + hash []byte + salt []byte + cost int // allowed range is MinCost to MaxCost + major byte + minor byte +} + +// GenerateFromPassword returns the bcrypt hash of the password at the given +// cost. If the cost given is less than MinCost, the cost will be set to +// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package, +// to compare the returned hashed password with its cleartext version. +func GenerateFromPassword(password []byte, cost int) ([]byte, error) { + p, err := newFromPassword(password, cost) + if err != nil { + return nil, err + } + return p.Hash(), nil +} + +// CompareHashAndPassword compares a bcrypt hashed password with its possible +// plaintext equivalent. Returns nil on success, or an error on failure. +func CompareHashAndPassword(hashedPassword, password []byte) error { + p, err := newFromHash(hashedPassword) + if err != nil { + return err + } + + otherHash, err := bcrypt(password, p.cost, p.salt) + if err != nil { + return err + } + + otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} + if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { + return nil + } + + return ErrMismatchedHashAndPassword +} + +// Cost returns the hashing cost used to create the given hashed +// password. When, in the future, the hashing cost of a password system needs +// to be increased in order to adjust for greater computational power, this +// function allows one to establish which passwords need to be updated. +func Cost(hashedPassword []byte) (int, error) { + p, err := newFromHash(hashedPassword) + if err != nil { + return 0, err + } + return p.cost, nil +} + +func newFromPassword(password []byte, cost int) (*hashed, error) { + if cost < MinCost { + cost = DefaultCost + } + p := new(hashed) + p.major = majorVersion + p.minor = minorVersion + + err := checkCost(cost) + if err != nil { + return nil, err + } + p.cost = cost + + unencodedSalt := make([]byte, maxSaltSize) + _, err = io.ReadFull(rand.Reader, unencodedSalt) + if err != nil { + return nil, err + } + + p.salt = base64Encode(unencodedSalt) + hash, err := bcrypt(password, p.cost, p.salt) + if err != nil { + return nil, err + } + p.hash = hash + return p, err +} + +func newFromHash(hashedSecret []byte) (*hashed, error) { + if len(hashedSecret) < minHashSize { + return nil, ErrHashTooShort + } + p := new(hashed) + n, err := p.decodeVersion(hashedSecret) + if err != nil { + return nil, err + } + hashedSecret = hashedSecret[n:] + n, err = p.decodeCost(hashedSecret) + if err != nil { + return nil, err + } + hashedSecret = hashedSecret[n:] + + // The "+2" is here because we'll have to append at most 2 '=' to the salt + // when base64 decoding it in expensiveBlowfishSetup(). + p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) + copy(p.salt, hashedSecret[:encodedSaltSize]) + + hashedSecret = hashedSecret[encodedSaltSize:] + p.hash = make([]byte, len(hashedSecret)) + copy(p.hash, hashedSecret) + + return p, nil +} + +func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { + cipherData := make([]byte, len(magicCipherData)) + copy(cipherData, magicCipherData) + + c, err := expensiveBlowfishSetup(password, uint32(cost), salt) + if err != nil { + return nil, err + } + + for i := 0; i < 24; i += 8 { + for j := 0; j < 64; j++ { + c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) + } + } + + // Bug compatibility with C bcrypt implementations. We only encode 23 of + // the 24 bytes encrypted. + hsh := base64Encode(cipherData[:maxCryptedHashSize]) + return hsh, nil +} + +func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { + csalt, err := base64Decode(salt) + if err != nil { + return nil, err + } + + // Bug compatibility with C bcrypt implementations. They use the trailing + // NULL in the key string during expansion. + // We copy the key to prevent changing the underlying array. + ckey := append(key[:len(key):len(key)], 0) + + c, err := blowfish.NewSaltedCipher(ckey, csalt) + if err != nil { + return nil, err + } + + var i, rounds uint64 + rounds = 1 << cost + for i = 0; i < rounds; i++ { + blowfish.ExpandKey(ckey, c) + blowfish.ExpandKey(csalt, c) + } + + return c, nil +} + +func (p *hashed) Hash() []byte { + arr := make([]byte, 60) + arr[0] = '$' + arr[1] = p.major + n := 2 + if p.minor != 0 { + arr[2] = p.minor + n = 3 + } + arr[n] = '$' + n++ + copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) + n += 2 + arr[n] = '$' + n++ + copy(arr[n:], p.salt) + n += encodedSaltSize + copy(arr[n:], p.hash) + n += encodedHashSize + return arr[:n] +} + +func (p *hashed) decodeVersion(sbytes []byte) (int, error) { + if sbytes[0] != '$' { + return -1, InvalidHashPrefixError(sbytes[0]) + } + if sbytes[1] > majorVersion { + return -1, HashVersionTooNewError(sbytes[1]) + } + p.major = sbytes[1] + n := 3 + if sbytes[2] != '$' { + p.minor = sbytes[2] + n++ + } + return n, nil +} + +// sbytes should begin where decodeVersion left off. +func (p *hashed) decodeCost(sbytes []byte) (int, error) { + cost, err := strconv.Atoi(string(sbytes[0:2])) + if err != nil { + return -1, err + } + err = checkCost(cost) + if err != nil { + return -1, err + } + p.cost = cost + return 3, nil +} + +func (p *hashed) String() string { + return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) +} + +func checkCost(cost int) error { + if cost < MinCost || cost > MaxCost { + return InvalidCostError(cost) + } + return nil +} diff --git a/vendor/golang.org/x/crypto/blowfish/block.go b/vendor/golang.org/x/crypto/blowfish/block.go new file mode 100644 index 00000000..9d80f195 --- /dev/null +++ b/vendor/golang.org/x/crypto/blowfish/block.go @@ -0,0 +1,159 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blowfish + +// getNextWord returns the next big-endian uint32 value from the byte slice +// at the given position in a circular manner, updating the position. +func getNextWord(b []byte, pos *int) uint32 { + var w uint32 + j := *pos + for i := 0; i < 4; i++ { + w = w<<8 | uint32(b[j]) + j++ + if j >= len(b) { + j = 0 + } + } + *pos = j + return w +} + +// ExpandKey performs a key expansion on the given *Cipher. Specifically, it +// performs the Blowfish algorithm's key schedule which sets up the *Cipher's +// pi and substitution tables for calls to Encrypt. This is used, primarily, +// by the bcrypt package to reuse the Blowfish key schedule during its +// set up. It's unlikely that you need to use this directly. +func ExpandKey(key []byte, c *Cipher) { + j := 0 + for i := 0; i < 18; i++ { + // Using inlined getNextWord for performance. + var d uint32 + for k := 0; k < 4; k++ { + d = d<<8 | uint32(key[j]) + j++ + if j >= len(key) { + j = 0 + } + } + c.p[i] ^= d + } + + var l, r uint32 + for i := 0; i < 18; i += 2 { + l, r = encryptBlock(l, r, c) + c.p[i], c.p[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s0[i], c.s0[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s1[i], c.s1[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s2[i], c.s2[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s3[i], c.s3[i+1] = l, r + } +} + +// This is similar to ExpandKey, but folds the salt during the key +// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero +// salt passed in, reusing ExpandKey turns out to be a place of inefficiency +// and specializing it here is useful. +func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) { + j := 0 + for i := 0; i < 18; i++ { + c.p[i] ^= getNextWord(key, &j) + } + + j = 0 + var l, r uint32 + for i := 0; i < 18; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.p[i], c.p[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s0[i], c.s0[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s1[i], c.s1[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s2[i], c.s2[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s3[i], c.s3[i+1] = l, r + } +} + +func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { + xl, xr := l, r + xl ^= c.p[0] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16] + xr ^= c.p[17] + return xr, xl +} + +func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { + xl, xr := l, r + xl ^= c.p[17] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1] + xr ^= c.p[0] + return xr, xl +} diff --git a/vendor/golang.org/x/crypto/blowfish/cipher.go b/vendor/golang.org/x/crypto/blowfish/cipher.go new file mode 100644 index 00000000..213bf204 --- /dev/null +++ b/vendor/golang.org/x/crypto/blowfish/cipher.go @@ -0,0 +1,99 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm. +// +// Blowfish is a legacy cipher and its short block size makes it vulnerable to +// birthday bound attacks (see https://sweet32.info). It should only be used +// where compatibility with legacy systems, not security, is the goal. +// +// Deprecated: any new system should use AES (from crypto/aes, if necessary in +// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from +// golang.org/x/crypto/chacha20poly1305). +package blowfish // import "golang.org/x/crypto/blowfish" + +// The code is a port of Bruce Schneier's C implementation. +// See https://www.schneier.com/blowfish.html. + +import "strconv" + +// The Blowfish block size in bytes. +const BlockSize = 8 + +// A Cipher is an instance of Blowfish encryption using a particular key. +type Cipher struct { + p [18]uint32 + s0, s1, s2, s3 [256]uint32 +} + +type KeySizeError int + +func (k KeySizeError) Error() string { + return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k)) +} + +// NewCipher creates and returns a Cipher. +// The key argument should be the Blowfish key, from 1 to 56 bytes. +func NewCipher(key []byte) (*Cipher, error) { + var result Cipher + if k := len(key); k < 1 || k > 56 { + return nil, KeySizeError(k) + } + initCipher(&result) + ExpandKey(key, &result) + return &result, nil +} + +// NewSaltedCipher creates a returns a Cipher that folds a salt into its key +// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is +// sufficient and desirable. For bcrypt compatibility, the key can be over 56 +// bytes. +func NewSaltedCipher(key, salt []byte) (*Cipher, error) { + if len(salt) == 0 { + return NewCipher(key) + } + var result Cipher + if k := len(key); k < 1 { + return nil, KeySizeError(k) + } + initCipher(&result) + expandKeyWithSalt(key, salt, &result) + return &result, nil +} + +// BlockSize returns the Blowfish block size, 8 bytes. +// It is necessary to satisfy the Block interface in the +// package "crypto/cipher". +func (c *Cipher) BlockSize() int { return BlockSize } + +// Encrypt encrypts the 8-byte buffer src using the key k +// and stores the result in dst. +// Note that for amounts of data larger than a block, +// it is not safe to just call Encrypt on successive blocks; +// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go). +func (c *Cipher) Encrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + l, r = encryptBlock(l, r, c) + dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) + dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) +} + +// Decrypt decrypts the 8-byte buffer src using the key k +// and stores the result in dst. +func (c *Cipher) Decrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + l, r = decryptBlock(l, r, c) + dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) + dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) +} + +func initCipher(c *Cipher) { + copy(c.p[0:], p[0:]) + copy(c.s0[0:], s0[0:]) + copy(c.s1[0:], s1[0:]) + copy(c.s2[0:], s2[0:]) + copy(c.s3[0:], s3[0:]) +} diff --git a/vendor/golang.org/x/crypto/blowfish/const.go b/vendor/golang.org/x/crypto/blowfish/const.go new file mode 100644 index 00000000..d0407759 --- /dev/null +++ b/vendor/golang.org/x/crypto/blowfish/const.go @@ -0,0 +1,199 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The startup permutation array and substitution boxes. +// They are the hexadecimal digits of PI; see: +// https://www.schneier.com/code/constants.txt. + +package blowfish + +var s0 = [256]uint32{ + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, + 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, + 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, + 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, + 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, + 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, + 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, + 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, + 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, + 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, + 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, + 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, + 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, + 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, + 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, + 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, + 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, + 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, + 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, + 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, + 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, + 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, +} + +var s1 = [256]uint32{ + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, + 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, + 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, + 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, + 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, + 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, + 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, + 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, + 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, + 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, + 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, + 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, + 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, + 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, + 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, + 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, + 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, + 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, + 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, + 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, + 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, + 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, +} + +var s2 = [256]uint32{ + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, + 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, + 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, + 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, + 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, + 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, + 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, + 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, + 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, + 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, + 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, + 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, + 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, + 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, + 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, + 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, + 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, + 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, + 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, + 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, + 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, + 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, +} + +var s3 = [256]uint32{ + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, + 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, + 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, + 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, + 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, + 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, + 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, + 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, + 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, + 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, + 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, + 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, + 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, + 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, + 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, + 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, + 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, + 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, + 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, + 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, + 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, + 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6, +} + +var p = [18]uint32{ + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, + 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b, +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 323288ed..a692b240 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,5 +1,7 @@ # github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml +# github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible +github.com/Knetic/govaluate # github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/alecthomas/template github.com/alecthomas/template/parse @@ -9,6 +11,18 @@ github.com/alecthomas/units github.com/avast/retry-go # github.com/beorn7/perks v1.0.1 github.com/beorn7/perks/quantile +# github.com/casbin/casbin/v2 v2.6.10 +github.com/casbin/casbin/v2 +github.com/casbin/casbin/v2/config +github.com/casbin/casbin/v2/effect +github.com/casbin/casbin/v2/errors +github.com/casbin/casbin/v2/log +github.com/casbin/casbin/v2/model +github.com/casbin/casbin/v2/persist +github.com/casbin/casbin/v2/persist/file-adapter +github.com/casbin/casbin/v2/rbac +github.com/casbin/casbin/v2/rbac/default-role-manager +github.com/casbin/casbin/v2/util # github.com/cespare/xxhash/v2 v2.1.0 github.com/cespare/xxhash/v2 # github.com/coreos/go-semver v0.3.0 @@ -19,6 +33,7 @@ github.com/coreos/go-systemd/journal github.com/coreos/pkg/capnslog # github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go +github.com/dgrijalva/jwt-go/request # github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify # github.com/go-kit/kit v0.9.0 @@ -49,7 +64,7 @@ github.com/gophercloud/gophercloud/openstack/utils github.com/gophercloud/gophercloud/openstack/workflow/v2/executions github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows github.com/gophercloud/gophercloud/pagination -# github.com/gorilla/mux v1.7.3 +# github.com/gorilla/mux v1.7.4 github.com/gorilla/mux # github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 github.com/jinzhu/copier @@ -63,6 +78,10 @@ github.com/matttproud/golang_protobuf_extensions/pbutil github.com/modern-go/concurrent # github.com/modern-go/reflect2 v1.0.1 github.com/modern-go/reflect2 +# github.com/ntk148v/etcd-adapter v1.1.1 +github.com/ntk148v/etcd-adapter +# github.com/ntk148v/jwt-middleware v0.0.0-20200609034444-e0f55c7eda1b +github.com/ntk148v/jwt-middleware # github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 github.com/orcaman/concurrent-map # github.com/pkg/errors v0.8.1 @@ -119,6 +138,9 @@ go.uber.org/zap/internal/bufferpool go.uber.org/zap/internal/color go.uber.org/zap/internal/exit go.uber.org/zap/zapcore +# golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e +golang.org/x/crypto/bcrypt +golang.org/x/crypto/blowfish # golang.org/x/lint v0.0.0-20190930215403-16217165b5de golang.org/x/lint golang.org/x/lint/golint