├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .releaserc.json ├── .travis.yml ├── LICENSE ├── README.md ├── adapter.go ├── adapter_test.go ├── examples ├── rbac_model.conf ├── rbac_policy.csv └── rbac_tenant_service.conf ├── go.mod └── go.sum /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push,pull_request] 4 | 5 | jobs: 6 | 7 | test: 8 | runs-on: ubuntu-latest 9 | 10 | services: 11 | mongodb: 12 | image: mongo 13 | ports: 14 | - 27017:27017 15 | 16 | steps: 17 | - name: Set up Go 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: '1.20' 21 | 22 | - name: Start MongoDB 23 | uses: supercharge/mongodb-github-action@1.7.0 24 | with: 25 | mongodb-version: 4.4 26 | mongodb-replica-set: test-rs 27 | mongodb-port: 42069 28 | 29 | - uses: actions/checkout@v2 30 | - name: Run Unit tests 31 | run: go test -v -coverprofile=./profile.cov ./... 32 | 33 | - uses: shogo82148/actions-goveralls@v1 34 | with: 35 | path-to-profile: ./profile.cov 36 | 37 | semantic-release: 38 | needs: [test] 39 | runs-on: ubuntu-latest 40 | steps: 41 | 42 | - uses: actions/checkout@v2 43 | 44 | - name: Run semantic-release 45 | if: github.repository == 'casbin/mongodb-adapter' && github.event_name == 'push' 46 | run: | 47 | npm install --save-dev semantic-release@17.2.4 48 | npx semantic-release 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | .idea/ 17 | .vscode/ 18 | vendor/ 19 | *.iml -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug": true, 3 | "branches": [ 4 | "+([0-9])?(.{+([0-9]),x}).x", 5 | "master", 6 | { 7 | "name": "beta", 8 | "prerelease": true 9 | } 10 | ], 11 | "plugins": [ 12 | "@semantic-release/commit-analyzer", 13 | "@semantic-release/release-notes-generator", 14 | "@semantic-release/github" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: false 4 | 5 | go: 6 | - tip 7 | 8 | before_install: 9 | - go get github.com/mattn/goveralls 10 | 11 | script: 12 | - $HOME/gopath/bin/goveralls -service=travis-ci 13 | 14 | services: 15 | - mongodb -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MongoDB Adapter [![CI](https://github.com/casbin/mongodb-adapter/actions/workflows/ci.yml/badge.svg)](https://github.com/casbin/mongodb-adapter/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/casbin/mongodb-adapter/badge.svg?branch=master)](https://coveralls.io/github/casbin/mongodb-adapter?branch=master) [![Godoc](https://godoc.org/github.com/casbin/mongodb-adapter?status.svg)](https://godoc.org/github.com/casbin/mongodb-adapter) 2 | ==== 3 | 4 | MongoDB Adapter is the [Mongo DB](https://www.mongodb.com) adapter for [Casbin](https://github.com/casbin/casbin). With this library, Casbin can load policy from MongoDB or save policy to it. 5 | 6 | ## Installation 7 | 8 | go get -u github.com/casbin/mongodb-adapter/v4 9 | 10 | ## Simple Example 11 | 12 | ```go 13 | package main 14 | 15 | import ( 16 | "github.com/casbin/casbin/v2" 17 | "github.com/casbin/mongodb-adapter/v4" 18 | ) 19 | 20 | func main() { 21 | // Initialize a MongoDB adapter and use it in a Casbin enforcer: 22 | // The adapter will use the database named "casbin". 23 | // If it doesn't exist, the adapter will create it automatically. 24 | a,err := mongodbadapter.NewAdapter("127.0.0.1:27017") // Your MongoDB URL. 25 | if err != nil { 26 | panic(err) 27 | } 28 | // Or you can use an existing DB "abc" like this: 29 | // The adapter will use the table named "casbin_rule". 30 | // If it doesn't exist, the adapter will create it automatically. 31 | // a := mongodbadapter.NewAdapter("127.0.0.1:27017/abc") 32 | 33 | e, err := casbin.NewEnforcer("examples/rbac_model.conf", a) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | // Load the policy from DB. 39 | e.LoadPolicy() 40 | 41 | // Check the permission. 42 | e.Enforce("alice", "data1", "read") 43 | 44 | // Modify the policy. 45 | // e.AddPolicy(...) 46 | // e.RemovePolicy(...) 47 | 48 | // Save the policy back to DB. 49 | e.SavePolicy() 50 | } 51 | ``` 52 | ## Advanced Example 53 | 54 | ```go 55 | package main 56 | 57 | import ( 58 | "github.com/casbin/casbin/v2" 59 | "github.com/casbin/mongodb-adapter/v4" 60 | mongooptions "go.mongodb.org/mongo-driver/mongo/options" 61 | ) 62 | 63 | func main() { 64 | // Initialize a MongoDB adapter with NewAdapterWithClientOption: 65 | // The adapter will use custom mongo client options. 66 | // custom database name. 67 | // default collection name 'casbin_rule'. 68 | mongoClientOption := mongooptions.Client().ApplyURI("mongodb://127.0.0.1:27017") 69 | databaseName := "casbin" 70 | a,err := mongodbadapter.NewAdapterWithClientOption(mongoClientOption, databaseName) 71 | // Or you can use NewAdapterWithCollectionName for custom collection name. 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | e, err := casbin.NewEnforcer("examples/rbac_model.conf", a) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | // Load the policy from DB. 82 | e.LoadPolicy() 83 | 84 | // Check the permission. 85 | e.Enforce("alice", "data1", "read") 86 | 87 | // Modify the policy. 88 | // e.AddPolicy(...) 89 | // e.RemovePolicy(...) 90 | 91 | // Save the policy back to DB. 92 | e.SavePolicy() 93 | } 94 | ``` 95 | 96 | ## Filtered Policies 97 | 98 | ```go 99 | import "github.com/globalsign/mgo/bson" 100 | 101 | // This adapter also implements the FilteredAdapter interface. This allows for 102 | // efficent, scalable enforcement of very large policies: 103 | filter := &bson.M{"v0": "alice"} 104 | e.LoadFilteredPolicy(filter) 105 | 106 | // The loaded policy is now a subset of the policy in storage, containing only 107 | // the policy lines that match the provided filter. This filter should be a 108 | // valid MongoDB selector using BSON. A filtered policy cannot be saved. 109 | ``` 110 | 111 | ## Getting Help 112 | 113 | - [Casbin](https://github.com/casbin/casbin) 114 | 115 | ## License 116 | 117 | This project is under Apache 2.0 License. See the [LICENSE](LICENSE) file for the full license text. 118 | -------------------------------------------------------------------------------- /adapter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The casbin Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mongodbadapter 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "log" 22 | "runtime" 23 | "strings" 24 | "time" 25 | 26 | "github.com/casbin/casbin/v2/model" 27 | "github.com/casbin/casbin/v2/persist" 28 | "go.mongodb.org/mongo-driver/v2/bson" 29 | "go.mongodb.org/mongo-driver/v2/mongo" 30 | "go.mongodb.org/mongo-driver/v2/mongo/options" 31 | "go.mongodb.org/mongo-driver/v2/x/mongo/driver/connstring" 32 | ) 33 | 34 | const defaultTimeout time.Duration = 30 * time.Second 35 | const defaultDatabaseName string = "casbin" 36 | const defaultCollectionName string = "casbin_rule" 37 | 38 | // CasbinRule represents a rule in Casbin. 39 | type CasbinRule struct { 40 | PType string 41 | V0 string 42 | V1 string 43 | V2 string 44 | V3 string 45 | V4 string 46 | V5 string 47 | } 48 | 49 | // adapter represents the MongoDB adapter for policy storage. 50 | type adapter struct { 51 | client *mongo.Client 52 | collection *mongo.Collection 53 | timeout time.Duration 54 | filtered bool 55 | } 56 | 57 | // finalizer is the destructor for adapter. 58 | func finalizer(a *adapter) { 59 | a.close() 60 | } 61 | 62 | // NewAdapter is the constructor for Adapter. If database name is not provided 63 | // in the Mongo URL, 'casbin' will be used as database name. 64 | // 'casbin_rule' will be used as a collection name. 65 | func NewAdapter(url string, timeout ...interface{}) (persist.BatchAdapter, error) { 66 | if !strings.HasPrefix(url, "mongodb+srv://") && !strings.HasPrefix(url, "mongodb://") { 67 | url = fmt.Sprint("mongodb://" + url) 68 | } 69 | 70 | // Parse and validate url before apply it. 71 | connString, err := connstring.ParseAndValidate(url) 72 | 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | clientOption := options.Client().ApplyURI(url) 78 | 79 | var databaseName string 80 | // Get database name from connString. 81 | if connString.Database != "" { 82 | databaseName = connString.Database 83 | } else { 84 | databaseName = defaultDatabaseName 85 | } 86 | 87 | return baseNewAdapter(clientOption, databaseName, defaultCollectionName, timeout...) 88 | } 89 | 90 | // NewAdapterWithClientOption is an alternative constructor for Adapter 91 | // that does the same as NewAdapter, but uses mongo.ClientOption instead of a Mongo URL + a databaseName option 92 | func NewAdapterWithClientOption(clientOption *options.ClientOptions, databaseName string, timeout ...interface{}) (persist.BatchAdapter, error) { 93 | return baseNewAdapter(clientOption, databaseName, defaultCollectionName, timeout...) 94 | } 95 | 96 | // NewAdapterWithCollectionName is an alternative constructor for Adapter 97 | // that does the same as NewAdapterWithClientOption, but with an extra collectionName option 98 | func NewAdapterWithCollectionName(clientOption *options.ClientOptions, databaseName string, collectionName string, timeout ...interface{}) (persist.BatchAdapter, error) { 99 | return baseNewAdapter(clientOption, databaseName, collectionName, timeout...) 100 | } 101 | 102 | // baseNewAdapter is a base constructor for Adapter 103 | func baseNewAdapter(clientOption *options.ClientOptions, databaseName string, collectionName string, timeout ...interface{}) (persist.BatchAdapter, error) { 104 | a := &adapter{} 105 | a.filtered = false 106 | 107 | if len(timeout) == 1 { 108 | a.timeout = timeout[0].(time.Duration) 109 | } else if len(timeout) > 1 { 110 | return nil, errors.New("too many arguments") 111 | } else { 112 | a.timeout = defaultTimeout 113 | } 114 | 115 | // Open the DB, create it if not existed. 116 | err := a.open(clientOption, databaseName, collectionName) 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | // Call the destructor when the object is released. 122 | runtime.SetFinalizer(a, finalizer) 123 | 124 | return a, nil 125 | } 126 | 127 | // NewFilteredAdapter is the constructor for FilteredAdapter. 128 | // Casbin will not automatically call LoadPolicy() for a filtered adapter. 129 | func NewFilteredAdapter(url string) (persist.FilteredAdapter, error) { 130 | a, err := NewAdapter(url) 131 | if err != nil { 132 | return nil, err 133 | } 134 | a.(*adapter).filtered = true 135 | 136 | return a.(*adapter), nil 137 | } 138 | 139 | type AdapterConfig struct { 140 | DatabaseName string 141 | CollectionName string 142 | Timeout time.Duration 143 | IsFiltered bool 144 | } 145 | 146 | func NewAdapterByDB(client *mongo.Client, config *AdapterConfig) (persist.BatchAdapter, error) { 147 | if config == nil { 148 | config = &AdapterConfig{} 149 | } 150 | if config.CollectionName == "" { 151 | config.CollectionName = defaultCollectionName 152 | } 153 | if config.DatabaseName == "" { 154 | config.DatabaseName = defaultDatabaseName 155 | } 156 | if config.Timeout == 0 { 157 | config.Timeout = defaultTimeout 158 | } 159 | 160 | collection := client.Database(config.DatabaseName).Collection(config.CollectionName) 161 | 162 | a := &adapter{ 163 | client: client, 164 | collection: collection, 165 | timeout: config.Timeout, 166 | filtered: config.IsFiltered, 167 | } 168 | 169 | if err := a.prepareIndexes(); err != nil { 170 | return nil, err 171 | } 172 | 173 | // Call the destructor when the object is released. 174 | runtime.SetFinalizer(a, finalizer) 175 | 176 | return a, nil 177 | } 178 | 179 | func (a *adapter) open(clientOption *options.ClientOptions, databaseName string, collectionName string) error { 180 | client, err := mongo.Connect(clientOption) 181 | if err != nil { 182 | return err 183 | } 184 | 185 | db := client.Database(databaseName) 186 | collection := db.Collection(collectionName) 187 | 188 | a.client = client 189 | a.collection = collection 190 | 191 | if err = a.prepareIndexes(); err != nil { 192 | return err 193 | } 194 | 195 | return nil 196 | } 197 | 198 | func (a *adapter) prepareIndexes() error { 199 | indexes := []string{"ptype", "v0", "v1", "v2", "v3", "v4", "v5"} 200 | keysDoc := bson.D{} 201 | 202 | for _, k := range indexes { 203 | keyDoc := bson.E{} 204 | keyDoc.Key = k 205 | keyDoc.Value = 1 206 | keysDoc = append(keysDoc, keyDoc) 207 | } 208 | 209 | if _, err := a.collection.Indexes().CreateOne( 210 | context.Background(), 211 | mongo.IndexModel{ 212 | Keys: keysDoc, 213 | Options: options.Index().SetUnique(true), 214 | }, 215 | ); err != nil { 216 | return err 217 | } 218 | 219 | return nil 220 | } 221 | 222 | func (a *adapter) close() { 223 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 224 | defer cancel() 225 | 226 | _ = a.client.Disconnect(ctx) 227 | } 228 | 229 | func (a *adapter) dropTable() error { 230 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 231 | defer cancel() 232 | 233 | err := a.collection.Drop(ctx) 234 | if err != nil { 235 | return err 236 | } 237 | return nil 238 | } 239 | 240 | func loadPolicyLine(line CasbinRule, model model.Model) error { 241 | var p = []string{line.PType, 242 | line.V0, line.V1, line.V2, line.V3, line.V4, line.V5} 243 | 244 | index := len(p) - 1 245 | for p[index] == "" { 246 | index-- 247 | } 248 | index += 1 249 | p = p[:index] 250 | err := persist.LoadPolicyArray(p, model) 251 | if err != nil { 252 | return err 253 | } 254 | return nil 255 | } 256 | 257 | // LoadPolicy loads policy from database. 258 | func (a *adapter) LoadPolicy(model model.Model) error { 259 | return a.LoadFilteredPolicy(model, nil) 260 | } 261 | 262 | // LoadFilteredPolicy loads matching policy lines from database. If not nil, 263 | // the filter must be a valid MongoDB selector. 264 | func (a *adapter) LoadFilteredPolicy(model model.Model, filter interface{}) error { 265 | if filter == nil { 266 | a.filtered = false 267 | filter = bson.D{{}} 268 | } else { 269 | a.filtered = true 270 | } 271 | 272 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 273 | defer cancel() 274 | 275 | cursor, err := a.collection.Find(ctx, filter) 276 | if err != nil { 277 | return err 278 | } 279 | 280 | for cursor.Next(ctx) { 281 | line := CasbinRule{} 282 | err := cursor.Decode(&line) 283 | if err != nil { 284 | return err 285 | } 286 | err = loadPolicyLine(line, model) 287 | if err != nil { 288 | return err 289 | } 290 | } 291 | 292 | return cursor.Close(ctx) 293 | } 294 | 295 | // IsFiltered returns true if the loaded policy has been filtered. 296 | func (a *adapter) IsFiltered() bool { 297 | return a.filtered 298 | } 299 | 300 | func savePolicyLine(ptype string, rule []string) CasbinRule { 301 | line := CasbinRule{ 302 | PType: ptype, 303 | } 304 | 305 | if len(rule) > 0 { 306 | line.V0 = rule[0] 307 | } 308 | if len(rule) > 1 { 309 | line.V1 = rule[1] 310 | } 311 | if len(rule) > 2 { 312 | line.V2 = rule[2] 313 | } 314 | if len(rule) > 3 { 315 | line.V3 = rule[3] 316 | } 317 | if len(rule) > 4 { 318 | line.V4 = rule[4] 319 | } 320 | if len(rule) > 5 { 321 | line.V5 = rule[5] 322 | } 323 | 324 | return line 325 | } 326 | 327 | // SavePolicy saves policy to database. 328 | func (a *adapter) SavePolicy(model model.Model) error { 329 | if a.filtered { 330 | return errors.New("cannot save a filtered policy") 331 | } 332 | if err := a.dropTable(); err != nil { 333 | return err 334 | } 335 | 336 | var lines []interface{} 337 | 338 | for ptype, ast := range model["p"] { 339 | for _, rule := range ast.Policy { 340 | line := savePolicyLine(ptype, rule) 341 | lines = append(lines, &line) 342 | } 343 | } 344 | 345 | for ptype, ast := range model["g"] { 346 | for _, rule := range ast.Policy { 347 | line := savePolicyLine(ptype, rule) 348 | lines = append(lines, &line) 349 | } 350 | } 351 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 352 | defer cancel() 353 | 354 | if _, err := a.collection.InsertMany(ctx, lines); err != nil { 355 | return err 356 | } 357 | 358 | return nil 359 | } 360 | 361 | // AddPolicy adds a policy rule to the storage. 362 | func (a *adapter) AddPolicy(sec string, ptype string, rule []string) error { 363 | line := savePolicyLine(ptype, rule) 364 | 365 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 366 | defer cancel() 367 | 368 | if _, err := a.collection.InsertOne(ctx, line); err != nil { 369 | return err 370 | } 371 | 372 | return nil 373 | } 374 | 375 | // AddPolicies adds policy rules to the storage. 376 | func (a *adapter) AddPolicies(sec string, ptype string, rules [][]string) error { 377 | var lines []interface{} 378 | for _, rule := range rules { 379 | line := savePolicyLine(ptype, rule) 380 | lines = append(lines, line) 381 | } 382 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 383 | defer cancel() 384 | if _, err := a.collection.InsertMany(ctx, lines); err != nil { 385 | return err 386 | } 387 | return nil 388 | } 389 | 390 | // RemovePolicies removes policy rules from the storage. 391 | func (a *adapter) RemovePolicies(sec string, ptype string, rules [][]string) error { 392 | var lines []CasbinRule 393 | for _, rule := range rules { 394 | line := savePolicyLine(ptype, rule) 395 | lines = append(lines, line) 396 | } 397 | 398 | for _, line := range lines { 399 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 400 | defer cancel() 401 | if _, err := a.collection.DeleteOne(ctx, line); err != nil { 402 | return err 403 | } 404 | } 405 | 406 | return nil 407 | } 408 | 409 | // RemovePolicy removes a policy rule from the storage. 410 | func (a *adapter) RemovePolicy(sec string, ptype string, rule []string) error { 411 | line := savePolicyLine(ptype, rule) 412 | 413 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 414 | defer cancel() 415 | 416 | if _, err := a.collection.DeleteOne(ctx, line); err != nil { 417 | return err 418 | } 419 | 420 | return nil 421 | } 422 | 423 | // RemoveFilteredPolicy removes policy rules that match the filter from the storage. 424 | func (a *adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error { 425 | selector := make(map[string]interface{}) 426 | selector["ptype"] = ptype 427 | 428 | if fieldIndex <= 0 && 0 < fieldIndex+len(fieldValues) { 429 | if fieldValues[0-fieldIndex] != "" { 430 | selector["v0"] = fieldValues[0-fieldIndex] 431 | } 432 | } 433 | if fieldIndex <= 1 && 1 < fieldIndex+len(fieldValues) { 434 | if fieldValues[1-fieldIndex] != "" { 435 | selector["v1"] = fieldValues[1-fieldIndex] 436 | } 437 | } 438 | if fieldIndex <= 2 && 2 < fieldIndex+len(fieldValues) { 439 | if fieldValues[2-fieldIndex] != "" { 440 | selector["v2"] = fieldValues[2-fieldIndex] 441 | } 442 | } 443 | if fieldIndex <= 3 && 3 < fieldIndex+len(fieldValues) { 444 | if fieldValues[3-fieldIndex] != "" { 445 | selector["v3"] = fieldValues[3-fieldIndex] 446 | } 447 | } 448 | if fieldIndex <= 4 && 4 < fieldIndex+len(fieldValues) { 449 | if fieldValues[4-fieldIndex] != "" { 450 | selector["v4"] = fieldValues[4-fieldIndex] 451 | } 452 | } 453 | if fieldIndex <= 5 && 5 < fieldIndex+len(fieldValues) { 454 | if fieldValues[5-fieldIndex] != "" { 455 | selector["v5"] = fieldValues[5-fieldIndex] 456 | } 457 | } 458 | 459 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 460 | defer cancel() 461 | 462 | if _, err := a.collection.DeleteMany(ctx, selector); err != nil { 463 | return err 464 | } 465 | 466 | return nil 467 | } 468 | 469 | // UpdatePolicy updates a policy rule from storage. 470 | // This is part of the Auto-Save feature. 471 | func (a *adapter) UpdatePolicy(sec string, ptype string, oldRule, newPolicy []string) error { 472 | oldLine := savePolicyLine(ptype, oldRule) 473 | newLine := savePolicyLine(ptype, newPolicy) 474 | 475 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 476 | defer cancel() 477 | // Updating all the documents equals to replacing 478 | _, err := a.collection.ReplaceOne(ctx, oldLine, newLine) 479 | return err 480 | } 481 | 482 | // UpdatePolicies updates some policy rules to storage, like db, redis. 483 | func (a *adapter) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error { 484 | oldLines := make([]CasbinRule, 0, len(oldRules)) 485 | newLines := make([]CasbinRule, 0, len(oldRules)) 486 | for _, oldRule := range oldRules { 487 | oldLines = append(oldLines, savePolicyLine(ptype, oldRule)) 488 | } 489 | for _, newRule := range newRules { 490 | newLines = append(newLines, savePolicyLine(ptype, newRule)) 491 | } 492 | 493 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 494 | defer cancel() 495 | for i := range oldRules { 496 | _, err := a.collection.ReplaceOne(ctx, oldLines[i], newLines[i]) 497 | if err != nil { 498 | return err 499 | } 500 | } 501 | return nil 502 | } 503 | 504 | // UpdateFilteredPolicies deletes old rules and adds new rules. 505 | func (a *adapter) UpdateFilteredPolicies(sec string, ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) { 506 | selector := make(map[string]interface{}) 507 | selector["ptype"] = ptype 508 | 509 | if fieldIndex <= 0 && 0 < fieldIndex+len(fieldValues) { 510 | if fieldValues[0-fieldIndex] != "" { 511 | selector["v0"] = fieldValues[0-fieldIndex] 512 | } 513 | } 514 | if fieldIndex <= 1 && 1 < fieldIndex+len(fieldValues) { 515 | if fieldValues[1-fieldIndex] != "" { 516 | selector["v1"] = fieldValues[1-fieldIndex] 517 | } 518 | } 519 | if fieldIndex <= 2 && 2 < fieldIndex+len(fieldValues) { 520 | if fieldValues[2-fieldIndex] != "" { 521 | selector["v2"] = fieldValues[2-fieldIndex] 522 | } 523 | } 524 | if fieldIndex <= 3 && 3 < fieldIndex+len(fieldValues) { 525 | if fieldValues[3-fieldIndex] != "" { 526 | selector["v3"] = fieldValues[3-fieldIndex] 527 | } 528 | } 529 | if fieldIndex <= 4 && 4 < fieldIndex+len(fieldValues) { 530 | if fieldValues[4-fieldIndex] != "" { 531 | selector["v4"] = fieldValues[4-fieldIndex] 532 | } 533 | } 534 | if fieldIndex <= 5 && 5 < fieldIndex+len(fieldValues) { 535 | if fieldValues[5-fieldIndex] != "" { 536 | selector["v5"] = fieldValues[5-fieldIndex] 537 | } 538 | } 539 | 540 | oldLines := make([]CasbinRule, 0) 541 | newLines := make([]CasbinRule, 0, len(newPolicies)) 542 | for _, newPolicy := range newPolicies { 543 | newLines = append(newLines, savePolicyLine(ptype, newPolicy)) 544 | } 545 | 546 | oldPolicies, err := a.updateFilteredPoliciesTxn(oldLines, newLines, selector) 547 | if err == nil { 548 | return oldPolicies, err 549 | } 550 | // (IllegalOperation) Transaction numbers are only allowed on a replica set member or mongos 551 | if mongoErr, ok := err.(mongo.CommandError); !ok || mongoErr.Code != 20 { 552 | return nil, err 553 | } 554 | 555 | log.Println("[WARNING]: As your mongodb server doesn't allow a replica set, transaction operation is not supported. So Casbin Adapter will run non-transactional updating!") 556 | return a.updateFilteredPolicies(oldLines, newLines, selector) 557 | } 558 | 559 | func (a *adapter) updateFilteredPoliciesTxn(oldLines, newLines []CasbinRule, selector map[string]interface{}) ([][]string, error) { 560 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 561 | defer cancel() 562 | 563 | session, err := a.client.StartSession() 564 | if err != nil { 565 | return nil, err 566 | } 567 | defer session.EndSession(context.TODO()) 568 | 569 | _, err = session.WithTransaction(ctx, func(sessionCtx context.Context) (interface{}, error) { 570 | // Load old policies 571 | cursor, err := a.collection.Find(ctx, selector) 572 | if err != nil { 573 | _ = session.AbortTransaction(context.Background()) 574 | return nil, err 575 | } 576 | for cursor.Next(ctx) { 577 | line := CasbinRule{} 578 | err := cursor.Decode(&line) 579 | if err != nil { 580 | _ = session.AbortTransaction(context.Background()) 581 | return nil, err 582 | } 583 | oldLines = append(oldLines, line) 584 | } 585 | if err = cursor.Close(ctx); err != nil { 586 | _ = session.AbortTransaction(context.Background()) 587 | return nil, err 588 | } 589 | 590 | // Delete all old policies 591 | if _, err := a.collection.DeleteMany(sessionCtx, selector); err != nil { 592 | _ = session.AbortTransaction(context.Background()) 593 | return nil, err 594 | } 595 | // Insert new policies 596 | for _, newLine := range newLines { 597 | if _, err := a.collection.InsertOne(sessionCtx, &newLine); err != nil { 598 | _ = session.AbortTransaction(context.Background()) 599 | return nil, err 600 | } 601 | } 602 | return nil, nil 603 | }) 604 | if err != nil { 605 | return nil, err 606 | } 607 | 608 | // return deleted rulues 609 | oldPolicies := make([][]string, 0) 610 | for _, v := range oldLines { 611 | oldPolicy := v.toStringPolicy() 612 | oldPolicies = append(oldPolicies, oldPolicy) 613 | } 614 | return oldPolicies, nil 615 | } 616 | 617 | func (a *adapter) updateFilteredPolicies(oldLines, newLines []CasbinRule, selector map[string]interface{}) ([][]string, error) { 618 | ctx, cancel := context.WithTimeout(context.TODO(), a.timeout) 619 | defer cancel() 620 | 621 | // Load old policies 622 | cursor, err := a.collection.Find(ctx, selector) 623 | if err != nil { 624 | return nil, err 625 | } 626 | for cursor.Next(ctx) { 627 | line := CasbinRule{} 628 | err := cursor.Decode(&line) 629 | if err != nil { 630 | return nil, err 631 | } 632 | oldLines = append(oldLines, line) 633 | } 634 | if err = cursor.Close(ctx); err != nil { 635 | return nil, err 636 | } 637 | 638 | // Delete all old policies 639 | if _, err := a.collection.DeleteMany(ctx, selector); err != nil { 640 | return nil, err 641 | } 642 | // Insert new policies 643 | for _, newLine := range newLines { 644 | if _, err := a.collection.InsertOne(ctx, &newLine); err != nil { 645 | return nil, err 646 | } 647 | } 648 | 649 | // return deleted rulues 650 | oldPolicies := make([][]string, 0) 651 | for _, v := range oldLines { 652 | oldPolicy := v.toStringPolicy() 653 | oldPolicies = append(oldPolicies, oldPolicy) 654 | } 655 | return oldPolicies, nil 656 | } 657 | 658 | func (c *CasbinRule) toStringPolicy() []string { 659 | policy := make([]string, 0) 660 | if c.PType != "" { 661 | policy = append(policy, c.PType) 662 | } 663 | if c.V0 != "" { 664 | policy = append(policy, c.V0) 665 | } 666 | if c.V1 != "" { 667 | policy = append(policy, c.V1) 668 | } 669 | if c.V2 != "" { 670 | policy = append(policy, c.V2) 671 | } 672 | if c.V3 != "" { 673 | policy = append(policy, c.V3) 674 | } 675 | if c.V4 != "" { 676 | policy = append(policy, c.V4) 677 | } 678 | if c.V5 != "" { 679 | policy = append(policy, c.V5) 680 | } 681 | return policy 682 | } 683 | -------------------------------------------------------------------------------- /adapter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The casbin Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mongodbadapter 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "strings" 21 | "testing" 22 | 23 | "go.mongodb.org/mongo-driver/v2/mongo" 24 | 25 | "github.com/casbin/casbin/v2" 26 | "github.com/casbin/casbin/v2/util" 27 | "go.mongodb.org/mongo-driver/v2/bson" 28 | mongooptions "go.mongodb.org/mongo-driver/v2/mongo/options" 29 | ) 30 | 31 | var testDbURL = os.Getenv("TEST_MONGODB_URL") 32 | var testReplicaSetURL = os.Getenv("TEST_REPLICA_SET_URL") 33 | 34 | func getDbURL() string { 35 | if testDbURL == "" { 36 | testDbURL = "127.0.0.1:27017" 37 | } 38 | return testDbURL 39 | } 40 | 41 | func getReplicaSetURL() string { 42 | if testReplicaSetURL == "" { 43 | testReplicaSetURL = "127.0.0.1:42069" 44 | } 45 | return testReplicaSetURL 46 | } 47 | 48 | func testGetPolicy(t *testing.T, e *casbin.Enforcer, res [][]string) { 49 | t.Helper() 50 | myRes := e.GetPolicy() 51 | t.Log("Policy: ", myRes) 52 | 53 | if !util.Array2DEquals(res, myRes) { 54 | t.Error("Policy: ", myRes, ", supposed to be ", res) 55 | } 56 | } 57 | 58 | func testGetPolicyWithoutOrder(t *testing.T, e *casbin.Enforcer, res [][]string) { 59 | myRes := e.GetPolicy() 60 | 61 | if !arrayEqualsWithoutOrder(myRes, res) { 62 | t.Error("Policy: ", myRes, ", supposed to be ", res) 63 | } 64 | } 65 | 66 | func arrayEqualsWithoutOrder(a [][]string, b [][]string) bool { 67 | if len(a) != len(b) { 68 | return false 69 | } 70 | 71 | mapA := make(map[int]string) 72 | mapB := make(map[int]string) 73 | order := make(map[int]struct{}) 74 | l := len(a) 75 | 76 | for i := 0; i < l; i++ { 77 | mapA[i] = util.ArrayToString(a[i]) 78 | mapB[i] = util.ArrayToString(b[i]) 79 | } 80 | 81 | for i := 0; i < l; i++ { 82 | for j := 0; j < l; j++ { 83 | if _, ok := order[j]; ok { 84 | if j == l-1 { 85 | return false 86 | } else { 87 | continue 88 | } 89 | } 90 | if mapA[i] == mapB[j] { 91 | order[j] = struct{}{} 92 | break 93 | } else if j == l-1 { 94 | return false 95 | } 96 | } 97 | } 98 | return true 99 | } 100 | 101 | func initPolicy(t *testing.T, dbURL string) { 102 | // Because the DB is empty at first, 103 | // so we need to load the policy from the file adapter (.CSV) first. 104 | e, err := casbin.NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv") 105 | if err != nil { 106 | panic(err) 107 | } 108 | 109 | a, err := NewAdapter(dbURL) 110 | if err != nil { 111 | panic(err) 112 | } 113 | // This is a trick to save the current policy to the DB. 114 | // We can't call e.SavePolicy() because the adapter in the enforcer is still the file adapter. 115 | // The current policy means the policy in the Casbin enforcer (aka in memory). 116 | err = a.SavePolicy(e.GetModel()) 117 | if err != nil { 118 | panic(err) 119 | } 120 | 121 | // Clear the current policy. 122 | e.ClearPolicy() 123 | testGetPolicy(t, e, [][]string{}) 124 | 125 | // Load the policy from DB. 126 | err = a.LoadPolicy(e.GetModel()) 127 | if err != nil { 128 | panic(err) 129 | } 130 | testGetPolicy(t, e, [][]string{ 131 | {"alice", "data1", "read"}, 132 | {"bob", "data2", "write"}, 133 | {"data2_admin", "data2", "read"}, 134 | {"data2_admin", "data2", "write"}, 135 | }, 136 | ) 137 | } 138 | 139 | func TestAdapter(t *testing.T) { 140 | initPolicy(t, getDbURL()) 141 | 142 | // Note: you don't need to look at the above code 143 | // if you already have a working DB with policy inside. 144 | 145 | // Now the DB has policy, so we can provide a normal use case. 146 | // Create an adapter and an enforcer. 147 | // NewEnforcer() will load the policy automatically. 148 | a, err := NewAdapter(getDbURL()) 149 | if err != nil { 150 | panic(err) 151 | } 152 | 153 | e, err := casbin.NewEnforcer("examples/rbac_model.conf", a) 154 | if err != nil { 155 | panic(err) 156 | } 157 | testGetPolicy(t, e, [][]string{ 158 | {"alice", "data1", "read"}, 159 | {"bob", "data2", "write"}, 160 | {"data2_admin", "data2", "read"}, 161 | {"data2_admin", "data2", "write"}, 162 | }, 163 | ) 164 | // AutoSave is enabled by default. 165 | // Now we disable it. 166 | e.EnableAutoSave(false) 167 | // Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer, 168 | // it doesn't affect the policy in the storage. 169 | e.AddPolicy("alice", "data1", "write") 170 | // Reload the policy from the storage to see the effect. 171 | if err := e.LoadPolicy(); err != nil { 172 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 173 | } 174 | // This is still the original policy. 175 | testGetPolicy(t, e, [][]string{ 176 | {"alice", "data1", "read"}, 177 | {"bob", "data2", "write"}, 178 | {"data2_admin", "data2", "read"}, 179 | {"data2_admin", "data2", "write"}, 180 | }, 181 | ) 182 | 183 | // Now we enable the AutoSave. 184 | e.EnableAutoSave(true) 185 | 186 | // Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer, 187 | // but also affects the policy in the storage. 188 | e.AddPolicy("alice", "data1", "write") 189 | // Reload the policy from the storage to see the effect. 190 | if err := e.LoadPolicy(); err != nil { 191 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 192 | } 193 | // The policy has a new rule: {"alice", "data1", "write"}. 194 | testGetPolicy(t, e, [][]string{ 195 | {"alice", "data1", "read"}, 196 | {"bob", "data2", "write"}, 197 | {"data2_admin", "data2", "read"}, 198 | {"data2_admin", "data2", "write"}, 199 | {"alice", "data1", "write"}, 200 | }, 201 | ) 202 | 203 | // Remove the added rule. 204 | e.RemovePolicy("alice", "data1", "write") 205 | if err := a.RemovePolicy("p", "p", []string{"alice", "data1", "write"}); err != nil { 206 | t.Errorf("Expected RemovePolicy() to be successful; got %v", err) 207 | } 208 | if err := e.LoadPolicy(); err != nil { 209 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 210 | } 211 | testGetPolicy(t, e, [][]string{ 212 | {"alice", "data1", "read"}, 213 | {"bob", "data2", "write"}, 214 | {"data2_admin", "data2", "read"}, 215 | {"data2_admin", "data2", "write"}, 216 | }, 217 | ) 218 | 219 | // Remove "data2_admin" related policy rules via a filter. 220 | // Two rules: {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"} are deleted. 221 | e.RemoveFilteredPolicy(0, "data2_admin") 222 | if err := e.LoadPolicy(); err != nil { 223 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 224 | } 225 | testGetPolicy(t, e, [][]string{ 226 | {"alice", "data1", "read"}, 227 | {"bob", "data2", "write"}, 228 | }, 229 | ) 230 | 231 | e.RemoveFilteredPolicy(1, "data1") 232 | if err := e.LoadPolicy(); err != nil { 233 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 234 | } 235 | testGetPolicy(t, e, [][]string{{"bob", "data2", "write"}}) 236 | 237 | e.RemoveFilteredPolicy(2, "write") 238 | if err := e.LoadPolicy(); err != nil { 239 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 240 | } 241 | testGetPolicy(t, e, [][]string{}) 242 | } 243 | 244 | func TestAddPolicies(t *testing.T) { 245 | initPolicy(t, getDbURL()) 246 | 247 | a, err := NewAdapter(getDbURL()) 248 | if err != nil { 249 | panic(err) 250 | } 251 | 252 | e, err := casbin.NewEnforcer("examples/rbac_model.conf", a) 253 | if err != nil { 254 | panic(err) 255 | } 256 | 257 | testGetPolicy(t, e, [][]string{ 258 | {"alice", "data1", "read"}, 259 | {"bob", "data2", "write"}, 260 | {"data2_admin", "data2", "read"}, 261 | {"data2_admin", "data2", "write"}, 262 | }, 263 | ) 264 | a.AddPolicies("p", "p", [][]string{ 265 | {"bob", "data2", "read"}, 266 | {"alice", "data2", "write"}, 267 | {"alice", "data2", "read"}, 268 | {"bob", "data1", "write"}, 269 | {"bob", "data1", "read"}, 270 | }, 271 | ) 272 | 273 | if err := e.LoadPolicy(); err != nil { 274 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 275 | } 276 | 277 | testGetPolicy(t, e, [][]string{ 278 | {"alice", "data1", "read"}, 279 | {"bob", "data2", "write"}, 280 | {"data2_admin", "data2", "read"}, 281 | {"data2_admin", "data2", "write"}, 282 | {"bob", "data2", "read"}, 283 | {"alice", "data2", "write"}, 284 | {"alice", "data2", "read"}, 285 | {"bob", "data1", "write"}, 286 | {"bob", "data1", "read"}, 287 | }, 288 | ) 289 | 290 | // Remove the added rule. 291 | if err := a.RemovePolicies("p", "p", [][]string{ 292 | {"bob", "data2", "read"}, 293 | {"alice", "data2", "write"}, 294 | {"alice", "data2", "read"}, 295 | {"bob", "data1", "write"}, 296 | {"bob", "data1", "read"}, 297 | }); err != nil { 298 | t.Errorf("Expected RemovePolicies() to be successful; got %v", err) 299 | } 300 | if err := e.LoadPolicy(); err != nil { 301 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 302 | } 303 | testGetPolicy(t, e, [][]string{ 304 | {"alice", "data1", "read"}, 305 | {"bob", "data2", "write"}, 306 | {"data2_admin", "data2", "read"}, 307 | {"data2_admin", "data2", "write"}, 308 | }, 309 | ) 310 | } 311 | 312 | func TestDeleteFilteredAdapter(t *testing.T) { 313 | a, err := NewFilteredAdapter(getDbURL() + "/casbin_test_new") 314 | if err != nil { 315 | panic(err) 316 | } 317 | 318 | e, err := casbin.NewEnforcer("examples/rbac_tenant_service.conf", a) 319 | if err != nil { 320 | panic(err) 321 | } 322 | 323 | e.AddPolicy("domain1", "alice", "data3", "read", "accept", "service1") 324 | e.AddPolicy("domain1", "alice", "data3", "write", "accept", "service2") 325 | 326 | // Reload the policy from the storage to see the effect. 327 | if err := e.LoadPolicy(); err != nil { 328 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 329 | } 330 | // The policy has a new rule: {"alice", "data1", "write"}. 331 | testGetPolicy(t, e, [][]string{ 332 | {"domain1", "alice", "data3", "read", "accept", "service1"}, 333 | {"domain1", "alice", "data3", "write", "accept", "service2"}, 334 | }, 335 | ) 336 | // test RemoveFiltered Policy with "" fileds 337 | e.RemoveFilteredPolicy(0, "domain1", "", "", "read") 338 | if err := e.LoadPolicy(); err != nil { 339 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 340 | } 341 | testGetPolicy(t, e, [][]string{ 342 | {"domain1", "alice", "data3", "write", "accept", "service2"}, 343 | }, 344 | ) 345 | 346 | e.RemoveFilteredPolicy(0, "domain1", "", "", "", "", "service2") 347 | if err := e.LoadPolicy(); err != nil { 348 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 349 | } 350 | testGetPolicy(t, e, [][]string{}) 351 | } 352 | 353 | func TestFilteredAdapter(t *testing.T) { 354 | // Now the DB has policy, so we can provide a normal use case. 355 | // Create an adapter and an enforcer. 356 | // NewEnforcer() will load the policy automatically. 357 | a, err := NewFilteredAdapter(getDbURL()) 358 | if err != nil { 359 | panic(err) 360 | } 361 | 362 | e, err := casbin.NewEnforcer("examples/rbac_model.conf", a) 363 | if err != nil { 364 | panic(err) 365 | } 366 | 367 | // Load filtered policies from the database. 368 | e.AddPolicy("alice", "data1", "write") 369 | e.AddPolicy("bob", "data2", "write") 370 | // Reload the filtered policy from the storage. 371 | filter := &bson.M{"v0": "bob"} 372 | if err := e.LoadFilteredPolicy(filter); err != nil { 373 | t.Errorf("Expected LoadFilteredPolicy() to be successful; got %v", err) 374 | } 375 | // Only bob's policy should have been loaded 376 | testGetPolicy(t, e, [][]string{{"bob", "data2", "write"}}) 377 | 378 | // Verify that alice's policy remains intact in the database. 379 | filter = &bson.M{"v0": "alice"} 380 | if err := e.LoadFilteredPolicy(filter); err != nil { 381 | t.Errorf("Expected LoadFilteredPolicy() to be successful; got %v", err) 382 | } 383 | // Only alice's policy should have been loaded, 384 | testGetPolicy(t, e, [][]string{ 385 | {"alice", "data1", "read"}, 386 | {"alice", "data1", "write"}, 387 | }, 388 | ) 389 | 390 | // Test safe handling of SavePolicy when using filtered policies. 391 | if err := e.SavePolicy(); err == nil { 392 | t.Errorf("Expected SavePolicy() to fail for a filtered policy") 393 | } 394 | if err := e.LoadPolicy(); err != nil { 395 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 396 | } 397 | if err := e.SavePolicy(); err != nil { 398 | t.Errorf("Expected SavePolicy() to be successful; got %v", err) 399 | } 400 | 401 | e.RemoveFilteredPolicy(2, "write") 402 | if err := e.LoadPolicy(); err != nil { 403 | t.Errorf("Expected LoadPolicy() to be successful; got %v", err) 404 | } 405 | testGetPolicy(t, e, [][]string{ 406 | {"alice", "data1", "read"}, 407 | {"data2_admin", "data2", "read"}, 408 | }, 409 | ) 410 | } 411 | 412 | func TestNewAdapterWithInvalidURL(t *testing.T) { 413 | defer func() { 414 | if r := recover(); r == nil { 415 | t.Error("Expected recovery from panic") 416 | } 417 | }() 418 | 419 | _, err := NewAdapter("localhost:40001?foo=1&bar=2") 420 | if err != nil { 421 | panic(err) 422 | } 423 | } 424 | 425 | func TestNewAdapterWithUnknownURL(t *testing.T) { 426 | defer func() { 427 | if r := recover(); r == nil { 428 | t.Error("Expected recovery from panic") 429 | } 430 | }() 431 | 432 | _, err := NewAdapter("fakeserver:27017") 433 | if err != nil { 434 | panic(err) 435 | } 436 | } 437 | 438 | func TestNewAdapterWithDatabase(t *testing.T) { 439 | _, err := NewAdapter(fmt.Sprint(getDbURL() + "/abc")) 440 | if err != nil { 441 | panic(err) 442 | } 443 | } 444 | 445 | func TestNewAdapterWithClientOption(t *testing.T) { 446 | uri := getDbURL() 447 | if !strings.HasPrefix(uri, "mongodb+srv://") && !strings.HasPrefix(uri, "mongodb://") { 448 | uri = fmt.Sprint("mongodb://" + uri) 449 | } 450 | mongoClientOption := mongooptions.Client().ApplyURI(uri) 451 | databaseName := "casbin_custom" 452 | _, err := NewAdapterWithClientOption(mongoClientOption, databaseName) 453 | if err != nil { 454 | panic(err) 455 | } 456 | } 457 | 458 | func TestNewAdapterWithCollectionName(t *testing.T) { 459 | uri := getDbURL() 460 | if !strings.HasPrefix(uri, "mongodb+srv://") && !strings.HasPrefix(uri, "mongodb://") { 461 | uri = fmt.Sprint("mongodb://" + uri) 462 | } 463 | mongoClientOption := mongooptions.Client().ApplyURI(uri) 464 | databaseName := "casbin_custom" 465 | collectionName := "casbin_rule_custom" 466 | _, err := NewAdapterWithCollectionName(mongoClientOption, databaseName, collectionName) 467 | if err != nil { 468 | panic(err) 469 | } 470 | } 471 | 472 | func TestNewAdapterByDB(t *testing.T) { 473 | uri := getDbURL() 474 | if !strings.HasPrefix(uri, "mongodb+srv://") && !strings.HasPrefix(uri, "mongodb://") { 475 | uri = fmt.Sprint("mongodb://" + uri) 476 | } 477 | mongoClientOption := mongooptions.Client().ApplyURI(uri) 478 | client, err := mongo.Connect(mongoClientOption) 479 | if err != nil { 480 | panic(err) 481 | } 482 | 483 | config := AdapterConfig{ 484 | DatabaseName: "casbin_custom", 485 | CollectionName: "casbin_rule_custom", 486 | } 487 | _, err = NewAdapterByDB(client, &config) 488 | if err != nil { 489 | panic(err) 490 | } 491 | } 492 | 493 | func TestUpdatePolicy(t *testing.T) { 494 | initPolicy(t, getDbURL()) 495 | 496 | a, err := NewAdapter(getDbURL()) 497 | if err != nil { 498 | panic(err) 499 | } 500 | 501 | e, err := casbin.NewEnforcer("examples/rbac_model.conf", a) 502 | if err != nil { 503 | panic(err) 504 | } 505 | 506 | testGetPolicy(t, e, [][]string{ 507 | {"alice", "data1", "read"}, 508 | {"bob", "data2", "write"}, 509 | {"data2_admin", "data2", "read"}, 510 | {"data2_admin", "data2", "write"}, 511 | }, 512 | ) 513 | testUpdatePolicy(t, a.(*adapter)) 514 | testUpdatePolicies(t, a.(*adapter)) 515 | } 516 | 517 | func testUpdatePolicy(t *testing.T, a *adapter) { 518 | // NewEnforcer() will load the policy automatically. 519 | e, _ := casbin.NewEnforcer("examples/rbac_model.conf", a) 520 | 521 | e.EnableAutoSave(true) 522 | e.UpdatePolicy([]string{"alice", "data1", "read"}, []string{"alice", "data1", "write"}) 523 | e.LoadPolicy() 524 | testGetPolicy(t, e, [][]string{{"alice", "data1", "write"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}}) 525 | } 526 | 527 | func testUpdatePolicies(t *testing.T, a *adapter) { 528 | // NewEnforcer() will load the policy automatically. 529 | e, _ := casbin.NewEnforcer("examples/rbac_model.conf", a) 530 | 531 | e.EnableAutoSave(true) 532 | e.UpdatePolicies([][]string{{"alice", "data1", "write"}, {"bob", "data2", "write"}}, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "read"}}) 533 | e.LoadPolicy() 534 | testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "read"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}}) 535 | } 536 | 537 | func initUpdateFilteredPolicies(sec string, ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) ([]CasbinRule, []CasbinRule, map[string]interface{}) { 538 | selector := make(map[string]interface{}) 539 | selector["ptype"] = ptype 540 | 541 | if fieldIndex <= 0 && 0 < fieldIndex+len(fieldValues) { 542 | if fieldValues[0-fieldIndex] != "" { 543 | selector["v0"] = fieldValues[0-fieldIndex] 544 | } 545 | } 546 | if fieldIndex <= 1 && 1 < fieldIndex+len(fieldValues) { 547 | if fieldValues[1-fieldIndex] != "" { 548 | selector["v1"] = fieldValues[1-fieldIndex] 549 | } 550 | } 551 | if fieldIndex <= 2 && 2 < fieldIndex+len(fieldValues) { 552 | if fieldValues[2-fieldIndex] != "" { 553 | selector["v2"] = fieldValues[2-fieldIndex] 554 | } 555 | } 556 | if fieldIndex <= 3 && 3 < fieldIndex+len(fieldValues) { 557 | if fieldValues[3-fieldIndex] != "" { 558 | selector["v3"] = fieldValues[3-fieldIndex] 559 | } 560 | } 561 | if fieldIndex <= 4 && 4 < fieldIndex+len(fieldValues) { 562 | if fieldValues[4-fieldIndex] != "" { 563 | selector["v4"] = fieldValues[4-fieldIndex] 564 | } 565 | } 566 | if fieldIndex <= 5 && 5 < fieldIndex+len(fieldValues) { 567 | if fieldValues[5-fieldIndex] != "" { 568 | selector["v5"] = fieldValues[5-fieldIndex] 569 | } 570 | } 571 | 572 | oldLines := make([]CasbinRule, 0) 573 | newLines := make([]CasbinRule, 0, len(newPolicies)) 574 | for _, newPolicy := range newPolicies { 575 | newLines = append(newLines, savePolicyLine(ptype, newPolicy)) 576 | } 577 | return oldLines, newLines, selector 578 | } 579 | 580 | func TestUpdateFilteredPolicies(t *testing.T) { 581 | initPolicy(t, getDbURL()) 582 | 583 | a, err := NewAdapter(getDbURL()) 584 | if err != nil { 585 | panic(err) 586 | } 587 | 588 | e, err := casbin.NewEnforcer("examples/rbac_model.conf", a) 589 | if err != nil { 590 | panic(err) 591 | } 592 | 593 | testGetPolicy(t, e, [][]string{ 594 | {"alice", "data1", "read"}, 595 | {"bob", "data2", "write"}, 596 | {"data2_admin", "data2", "read"}, 597 | {"data2_admin", "data2", "write"}, 598 | }, 599 | ) 600 | 601 | initUpdateFilteredPolicies("p", "p", [][]string{{"alice", "data1", "write"}}, 0, "alice", "data1", "read") 602 | 603 | e.EnableAutoSave(true) 604 | e.UpdateFilteredPolicies([][]string{{"alice", "data1", "write"}}, 0, "alice", "data1", "read") 605 | e.UpdateFilteredPolicies([][]string{{"bob", "data2", "read"}}, 0, "bob", "data2", "write") 606 | e.LoadPolicy() 607 | testGetPolicyWithoutOrder(t, e, [][]string{{"alice", "data1", "write"}, {"bob", "data2", "read"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}}) 608 | } 609 | 610 | func TestUpdateFilteredPoliciesTxn(t *testing.T) { 611 | initPolicy(t, getReplicaSetURL()) 612 | 613 | a, err := NewAdapter(getReplicaSetURL()) 614 | if err != nil { 615 | panic(err) 616 | } 617 | 618 | e, err := casbin.NewEnforcer("examples/rbac_model.conf", a) 619 | if err != nil { 620 | panic(err) 621 | } 622 | 623 | testGetPolicy(t, e, [][]string{ 624 | {"alice", "data1", "read"}, 625 | {"bob", "data2", "write"}, 626 | {"data2_admin", "data2", "read"}, 627 | {"data2_admin", "data2", "write"}, 628 | }, 629 | ) 630 | 631 | e.EnableAutoSave(true) 632 | e.UpdateFilteredPolicies([][]string{{"alice", "data1", "write"}}, 0, "alice", "data1", "read") 633 | e.UpdateFilteredPolicies([][]string{{"bob", "data2", "read"}}, 0, "bob", "data2", "write") 634 | e.LoadPolicy() 635 | testGetPolicyWithoutOrder(t, e, [][]string{{"alice", "data1", "write"}, {"bob", "data2", "read"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}}) 636 | } 637 | -------------------------------------------------------------------------------- /examples/rbac_model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [role_definition] 8 | g = _, _ 9 | 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | [matchers] 14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act -------------------------------------------------------------------------------- /examples/rbac_policy.csv: -------------------------------------------------------------------------------- 1 | p, alice, data1, read 2 | p, bob, data2, write 3 | p, data2_admin, data2, read 4 | p, data2_admin, data2, write 5 | g, alice, data2_admin -------------------------------------------------------------------------------- /examples/rbac_tenant_service.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = tenant, sub, obj, act, service 3 | 4 | [policy_definition] 5 | p =tenant, sub, obj, act, service, eft 6 | 7 | [role_definition] 8 | g = _, _ 9 | 10 | [policy_effect] 11 | e = priority(p.eft) || deny 12 | 13 | [matchers] 14 | m = r.tenant == p.tenant && g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*") && (r.service == p.service || p.service == "*") 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/casbin/mongodb-adapter/v4 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/casbin/casbin/v2 v2.71.1 7 | go.mongodb.org/mongo-driver/v2 v2.2.1 8 | ) 9 | 10 | require ( 11 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect 12 | github.com/golang/snappy v1.0.0 // indirect 13 | github.com/klauspost/compress v1.16.7 // indirect 14 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 15 | github.com/xdg-go/scram v1.1.2 // indirect 16 | github.com/xdg-go/stringprep v1.0.4 // indirect 17 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect 18 | golang.org/x/crypto v0.33.0 // indirect 19 | golang.org/x/sync v0.11.0 // indirect 20 | golang.org/x/text v0.22.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= 2 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 3 | github.com/casbin/casbin/v2 v2.71.1 h1:LRHyqM0S1LzM/K59PmfUIN0ZJfLgcOjL4OhOQI/FNXU= 4 | github.com/casbin/casbin/v2 v2.71.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= 7 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 8 | github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= 9 | github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 10 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 11 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= 12 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 13 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 14 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 15 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= 16 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= 17 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= 18 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= 19 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= 20 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= 21 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 22 | go.mongodb.org/mongo-driver/v2 v2.2.1 h1:w5xra3yyu/sGrziMzK1D0cRRaH/b7lWCSsoN6+WV6AM= 23 | go.mongodb.org/mongo-driver/v2 v2.2.1/go.mod h1:qQkDMhCGWl3FN509DfdPd4GRBLU/41zqF/k8eTRceps= 24 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 25 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 26 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 27 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 28 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 29 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 30 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 31 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 32 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 33 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 35 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 36 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 37 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 38 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 43 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 44 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 45 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 46 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 47 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 48 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 49 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 50 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 51 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 52 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 53 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 54 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 55 | --------------------------------------------------------------------------------