-
Notifications
You must be signed in to change notification settings - Fork 931
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
542eae9
commit e97686d
Showing
12 changed files
with
648 additions
and
550 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package cluster | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/canonical/lxd/lxd/auth" | ||
"github.com/canonical/lxd/lxd/db/query" | ||
"github.com/canonical/lxd/shared/api" | ||
"github.com/canonical/lxd/shared/entity" | ||
) | ||
|
||
// AuthGroupPermission is the database representation of an api.Permission. | ||
type AuthGroupPermission struct { | ||
ID int | ||
GroupID int | ||
Entitlement auth.Entitlement | ||
EntityType EntityType | ||
EntityID int | ||
} | ||
|
||
// GetPermissionEntityURLs accepts a slice of AuthGroupPermission as input. The input AuthGroupPermission slice may include permissions | ||
// that are no longer valid because the entity against which they are defined no longer exists. This methon determines | ||
// which permissions are valid and which are not valid by attempting to retrieve their entity URL. It uses as few | ||
// queries as possible to do this. It returns a slice of valid permissions, a slice of invalid permissions and a map of | ||
// entity.Type, to entity ID, to api.URL. The returned map contains the URL of the entity of each returned valid | ||
// permission. It is used for populating api.Permission. The invalid permissions can be ignored or deleted. | ||
func GetPermissionEntityURLs(ctx context.Context, tx *sql.Tx, permissions []AuthGroupPermission) (validPermissions []AuthGroupPermission, danglingPermissions []AuthGroupPermission, entityURLs map[entity.Type]map[int]*api.URL, err error) { | ||
// To make as few calls as possible, categorize the permissions by entity type. | ||
permissionsByEntityType := map[EntityType][]AuthGroupPermission{} | ||
for _, permission := range permissions { | ||
permissionsByEntityType[permission.EntityType] = append(permissionsByEntityType[permission.EntityType], permission) | ||
} | ||
|
||
type e struct { | ||
t EntityType | ||
id int | ||
} | ||
|
||
done := make(map[e]struct{}) | ||
|
||
// For each entity type, if there is only on permission for the entity type, we'll get the URL by its entity type and ID. | ||
// If there are multiple permissions for the entity type, append the entity type to a list for later use. | ||
entityURLs = make(map[entity.Type]map[int]*api.URL) | ||
var entityTypes []entity.Type | ||
for entityType, permissions := range permissionsByEntityType { | ||
if len(permissions) > 1 { | ||
entityTypes = append(entityTypes, entity.Type(entityType)) | ||
continue | ||
} | ||
|
||
_, ok := done[e{t: entityType, id: permissions[0].EntityID}] | ||
if ok { | ||
continue | ||
} | ||
|
||
u, err := GetEntityURL(ctx, tx, entity.Type(entityType), permissions[0].EntityID) | ||
if err != nil && !api.StatusErrorCheck(err, http.StatusNotFound) { | ||
return nil, nil, nil, err | ||
} else if err != nil { | ||
continue | ||
} | ||
|
||
entityURLs[entity.Type(entityType)] = make(map[int]*api.URL) | ||
entityURLs[entity.Type(entityType)][permissions[0].EntityID] = u | ||
done[e{t: entityType, id: permissions[0].EntityID}] = struct{}{} | ||
} | ||
|
||
// If there are any entity types with multiple permissions, get all URLs for those entities. | ||
if len(entityTypes) > 0 { | ||
entityURLsAll, err := GetEntityURLs(ctx, tx, "", entityTypes...) | ||
if err != nil { | ||
return nil, nil, nil, err | ||
} | ||
|
||
for k, v := range entityURLsAll { | ||
entityURLs[k] = v | ||
} | ||
} | ||
|
||
// Iterate over the input permissions and check which ones are present in the entityURLs map. | ||
// If they are not present, the entity against which they are defined is no longer present in the DB. | ||
for _, permission := range permissions { | ||
entityIDToURL, ok := entityURLs[entity.Type(permission.EntityType)] | ||
if !ok { | ||
danglingPermissions = append(danglingPermissions, permission) | ||
continue | ||
} | ||
|
||
_, ok = entityIDToURL[permission.EntityID] | ||
if !ok { | ||
danglingPermissions = append(danglingPermissions, permission) | ||
continue | ||
} | ||
|
||
validPermissions = append(validPermissions, permission) | ||
} | ||
|
||
return validPermissions, danglingPermissions, entityURLs, nil | ||
} | ||
|
||
// DeletePermissions deletes the given permissions. | ||
func DeletePermissions(ctx context.Context, tx *sql.Tx, danglingPermissions []AuthGroupPermission) error { | ||
if len(danglingPermissions) == 0 { | ||
return nil | ||
} | ||
|
||
args := make([]any, 0, len(danglingPermissions)) | ||
for _, perm := range danglingPermissions { | ||
args = append(args, perm.ID) | ||
} | ||
|
||
q := fmt.Sprintf(`DELETE FROM auth_group_permissions WHERE auth_group_permissions.id IN %s`, query.Params(len(danglingPermissions))) | ||
res, err := tx.ExecContext(ctx, q, args...) | ||
if err != nil { | ||
return fmt.Errorf("Failed to clean up dangling permissions: %w", err) | ||
} | ||
|
||
rowsAffected, err := res.RowsAffected() | ||
if err != nil { | ||
return fmt.Errorf("Failed to validate clean up dangling permissions: %w", err) | ||
} | ||
|
||
if len(args) != int(rowsAffected) { | ||
return fmt.Errorf("Failed to delete expected number of dangling permissions on clean up (expected %d, got %d)", len(args), rowsAffected) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.