github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/branch_control/access.go (about) 1 // Copyright 2022 Dolthub, Inc. 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 branch_control 16 17 import ( 18 "fmt" 19 "math" 20 "strings" 21 "sync" 22 23 flatbuffers "github.com/dolthub/flatbuffers/v23/go" 24 25 "github.com/dolthub/dolt/go/gen/fb/serial" 26 ) 27 28 // Permissions are a set of flags that denote a user's allowed functionality on a branch. 29 type Permissions uint64 30 31 const ( 32 Permissions_Admin Permissions = 1 << iota // Permissions_Admin grants unrestricted control over a branch, including modification of table entries 33 Permissions_Write // Permissions_Write allows for all modifying operations on a branch, but does not allow modification of table entries 34 Permissions_Read // Permissions_Read allows for reading from a branch, which is equivalent to having no permissions 35 36 Permissions_None Permissions = 0 // Permissions_None represents a lack of permissions, which defaults to allowing reading 37 ) 38 39 // Access contains all of the expressions that comprise the "dolt_branch_control" table, which handles write Access to 40 // branches, along with write access to the branch control system tables. 41 type Access struct { 42 Root *MatchNode 43 RWMutex *sync.RWMutex 44 binlog *Binlog 45 rows []AccessRow 46 freeRows []uint32 47 } 48 49 // AccessRow contains the user-facing values of a particular row, along with the permissions for a row. 50 type AccessRow struct { 51 Database string 52 Branch string 53 User string 54 Host string 55 Permissions Permissions 56 } 57 58 // AccessRowIter is an iterator over all valid rows. 59 type AccessRowIter struct { 60 access *Access 61 idx uint32 62 } 63 64 // newAccess returns a new Access. 65 func newAccess() *Access { 66 return &Access{ 67 RWMutex: &sync.RWMutex{}, 68 } 69 } 70 71 // Match returns whether any entries match the given database, branch, user, and host, along with their permissions. 72 // Requires external synchronization handling, therefore manually manage the RWMutex. 73 func (tbl *Access) Match(database string, branch string, user string, host string) (bool, Permissions) { 74 results := tbl.Root.Match(database, branch, user, host) 75 // We use the result(s) with the longest length 76 length := uint32(0) 77 perms := Permissions_None 78 for _, result := range results { 79 if result.Length > length { 80 perms = result.Permissions 81 } else if result.Length == length { 82 perms |= result.Permissions 83 } 84 } 85 return len(results) > 0, perms 86 } 87 88 // GetBinlog returns the table's binlog. 89 func (tbl *Access) GetBinlog() *Binlog { 90 return tbl.binlog 91 } 92 93 // Serialize returns the offset for the Access table written to the given builder. 94 func (tbl *Access) Serialize(b *flatbuffers.Builder) flatbuffers.UOffsetT { 95 // Serialize the binlog 96 binlog := tbl.binlog.Serialize(b) 97 serial.BranchControlAccessStart(b) 98 serial.BranchControlAccessAddBinlog(b, binlog) 99 return serial.BranchControlAccessEnd(b) 100 } 101 102 func (tbl *Access) reinit() { 103 tbl.Root = &MatchNode{ 104 SortOrders: []int32{columnMarker}, 105 Children: make(map[int32]*MatchNode), 106 Data: nil, 107 } 108 tbl.binlog = NewAccessBinlog(nil) 109 tbl.rows = nil 110 tbl.freeRows = nil 111 } 112 113 // Deserialize populates the table with the data from the flatbuffers representation. 114 func (tbl *Access) Deserialize(fb *serial.BranchControlAccess) error { 115 // Read the binlog 116 fbBinlog, err := fb.TryBinlog(nil) 117 if err != nil { 118 return err 119 } 120 binlog := NewAccessBinlog(nil) 121 if err = binlog.Deserialize(fbBinlog); err != nil { 122 return err 123 } 124 125 tbl.reinit() 126 127 // Recreate the table from the binlog 128 for _, binlogRow := range binlog.rows { 129 if binlogRow.IsInsert { 130 tbl.Insert(binlogRow.Database, binlogRow.Branch, binlogRow.User, binlogRow.Host, Permissions(binlogRow.Permissions)) 131 } else { 132 tbl.Delete(binlogRow.Database, binlogRow.Branch, binlogRow.User, binlogRow.Host) 133 } 134 } 135 return nil 136 } 137 138 // insertDefaultRow adds a row that allows all users to access and modify all branches, but does not allow them to 139 // modify any branch control tables. This was the default behavior of Dolt before the introduction of branch permissions. 140 func (tbl *Access) insertDefaultRow() { 141 tbl.reinit() 142 tbl.Insert("%", "%", "%", "%", Permissions_Write) 143 } 144 145 // Insert adds the given expressions to the table. This does not perform any sort of validation whatsoever, so it is 146 // important to ensure that the expressions are valid before insertion. Folds all strings that are given. Overwrites any 147 // existing entries with the new permissions. Requires external synchronization handling, therefore manually manage the 148 // RWMutex. 149 func (tbl *Access) Insert(database string, branch string, user string, host string, perms Permissions) { 150 // Database, Branch, and Host are case-insensitive, while User is case-sensitive 151 database = strings.ToLower(FoldExpression(database)) 152 branch = strings.ToLower(FoldExpression(branch)) 153 user = FoldExpression(user) 154 host = strings.ToLower(FoldExpression(host)) 155 // Each expression is capped at 2¹⁶-1 values, so we truncate to 2¹⁶-2 and add the any-match character at the end if it's over 156 if len(database) > math.MaxUint16 { 157 database = string(append([]byte(database[:math.MaxUint16-1]), byte('%'))) 158 } 159 if len(branch) > math.MaxUint16 { 160 branch = string(append([]byte(branch[:math.MaxUint16-1]), byte('%'))) 161 } 162 if len(user) > math.MaxUint16 { 163 user = string(append([]byte(user[:math.MaxUint16-1]), byte('%'))) 164 } 165 if len(host) > math.MaxUint16 { 166 host = string(append([]byte(host[:math.MaxUint16-1]), byte('%'))) 167 } 168 // Add the insertion entry to the binlog 169 tbl.binlog.Insert(database, branch, user, host, uint64(perms)) 170 // Add to the rows and grab the insertion index 171 var index uint32 172 if len(tbl.freeRows) > 0 { 173 index = tbl.freeRows[len(tbl.freeRows)-1] 174 tbl.freeRows = tbl.freeRows[:len(tbl.freeRows)-1] 175 tbl.rows[index] = AccessRow{ 176 Database: database, 177 Branch: branch, 178 User: user, 179 Host: host, 180 Permissions: perms, 181 } 182 } else { 183 if len(tbl.rows) >= math.MaxUint32 { 184 // If someone has this many branches in Dolt then they're doing something very interesting, we'll probably 185 // fail elsewhere way before this point 186 panic(fmt.Errorf("branch control has a maximum limit of %d branches", math.MaxUint32-1)) 187 } 188 index = uint32(len(tbl.rows)) 189 tbl.rows = append(tbl.rows, AccessRow{ 190 Database: database, 191 Branch: branch, 192 User: user, 193 Host: host, 194 Permissions: perms, 195 }) 196 } 197 // Add the entry to the root node 198 tbl.Root.Add(database, branch, user, host, MatchNodeData{ 199 Permissions: perms, 200 RowIndex: index, 201 }) 202 } 203 204 // Delete removes the given expressions from the table. This does not perform any sort of validation whatsoever, so it 205 // is important to ensure that the expressions are valid before deletion. Folds all strings that are given. Requires 206 // external synchronization handling, therefore manually manage the RWMutex. 207 func (tbl *Access) Delete(database string, branch string, user string, host string) { 208 // Database, Branch, and Host are case-insensitive, while User is case-sensitive 209 database = strings.ToLower(FoldExpression(database)) 210 branch = strings.ToLower(FoldExpression(branch)) 211 user = FoldExpression(user) 212 host = strings.ToLower(FoldExpression(host)) 213 // Each expression is capped at 2¹⁶-1 values, so we truncate to 2¹⁶-2 and add the any-match character at the end if it's over 214 if len(database) > math.MaxUint16 { 215 database = string(append([]byte(database[:math.MaxUint16-1]), byte('%'))) 216 } 217 if len(branch) > math.MaxUint16 { 218 branch = string(append([]byte(branch[:math.MaxUint16-1]), byte('%'))) 219 } 220 if len(user) > math.MaxUint16 { 221 user = string(append([]byte(user[:math.MaxUint16-1]), byte('%'))) 222 } 223 if len(host) > math.MaxUint16 { 224 host = string(append([]byte(host[:math.MaxUint16-1]), byte('%'))) 225 } 226 // Add the deletion entry to the binlog 227 tbl.binlog.Delete(database, branch, user, host, uint64(Permissions_None)) 228 // Remove the entry from the root node 229 removedIndex := tbl.Root.Remove(database, branch, user, host) 230 // Remove from the rows 231 if removedIndex != math.MaxUint32 { 232 tbl.freeRows = append(tbl.freeRows, removedIndex) 233 } 234 } 235 236 // Iter returns an iterator that goes over all valid rows. The iterator does not acquire a read lock, therefore this 237 // requires external synchronization handling via RWMutex. 238 func (tbl *Access) Iter() *AccessRowIter { 239 return &AccessRowIter{ 240 access: tbl, 241 idx: 0, 242 } 243 } 244 245 // Next returns the next valid row. Returns false if there are no more rows. 246 func (iter *AccessRowIter) Next() (AccessRow, bool) { 247 OuterLoop: 248 for iter.idx < uint32(len(iter.access.rows)) { 249 idx := iter.idx 250 iter.idx++ 251 // Not the most efficient, but I expect this to be empty 99% of the time so it should be fine 252 for _, freeRow := range iter.access.freeRows { 253 if idx == freeRow { 254 continue OuterLoop 255 } 256 } 257 return iter.access.rows[idx], true 258 } 259 return AccessRow{}, false 260 }