diff --git a/db/collection.go b/db/collection.go index 69b00b1a86..4b366b4af3 100644 --- a/db/collection.go +++ b/db/collection.go @@ -974,7 +974,7 @@ func (c *collection) create(ctx context.Context, txn datastore.Txn, doc *client. return err } - return c.tryRegisterDocWithACP(ctx, doc) + return c.registerDocCreation(ctx, doc.ID().String()) } // Update an existing document with the new values. diff --git a/db/collection_acp.go b/db/collection_acp.go index c6ec796c14..98fd9d5ba1 100644 --- a/db/collection_acp.go +++ b/db/collection_acp.go @@ -13,39 +13,29 @@ package db import ( "context" - "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/acp" + "github.com/sourcenetwork/defradb/db/permission" ) -// tryRegisterDocWithACP handles the registeration of the document with acp module, -// according to our registration logic based on weather (1) the request is permissioned, -// (2) the collection is permissioned (has a policy), (3) acp module exists. -// -// Note: we only register the document with ACP if all (1) (2) and (3) are true. -// In all other cases, nothing is registered with ACP. -// -// Moreover 8 states, upon document creation: -// - (SignatureRequest, PermissionedCollection, ModuleExists) => Register with ACP -// - (SignatureRequest, PermissionedCollection, !ModuleExists) => Normal/Public - Don't Register with ACP -// - (SignatureRequest, !PermissionedCollection, ModuleExists) => Normal/Public - Don't Register with ACP -// - (SignatureRequest, !PermissionedCollection, !ModuleExists) => Normal/Public - Don't Register with ACP -// - (!SignatureRequest, PermissionedCollection, ModuleExists) => Normal/Public - Don't Register with ACP -// - (!SignatureRequest, !PermissionedCollection, ModuleExists) => Normal/Public - Don't Register with ACP -// - (!SignatureRequest, PermissionedCollection, !ModuleExists) => Normal/Public - Don't Register with ACP -// - (!SignatureRequest, !PermissionedCollection, !ModuleExists) => Normal/Public - Don't Register with ACP -func (c *collection) tryRegisterDocWithACP(ctx context.Context, doc *client.Document) error { - // Check if acp module exists. - if c.db.ACPModule().HasValue() { - // Check if collection has policy. - if policyID, resourceName, hasPolicy := client.IsPermissioned(c); hasPolicy { - return c.db.ACPModule().Value().RegisterDocCreation( - ctx, - "cosmos1zzg43wdrhmmk89z3pmejwete2kkd4a3vn7w969", // TODO-ACP: Replace with signature identity - policyID, - resourceName, - doc.ID().String(), - ) - } - } +func (c *collection) registerDocCreation(ctx context.Context, docID string) error { + return permission.RegisterDocCreationOnCollection( + ctx, + c.db.ACPModule(), + c, + docID, + ) +} - return nil +func (c *collection) checkDocPermissionedAccess( + ctx context.Context, + dpiPermission acp.DPIPermission, + docID string, +) (bool, error) { + return permission.CheckDocPermissionedAccessOnCollection( + ctx, + c.db.ACPModule(), + c, + dpiPermission, + docID, + ) } diff --git a/db/fetcher/fetcher.go b/db/fetcher/fetcher.go index f95824c79f..a72b441887 100644 --- a/db/fetcher/fetcher.go +++ b/db/fetcher/fetcher.go @@ -576,78 +576,6 @@ func (df *DocumentFetcher) FetchNext(ctx context.Context) (EncodedDocument, Exec return encdoc, resultExecInfo, err } -// runDocumentReadPermissionCheck handles the checking (while fetching) if the document has read access -// or not, according to our access logic based on weather (1) the request is permissioned, -// (2) the collection is permissioned (has a policy), (3) acp module exists. -// -// Note: we only need to make a call to the acp module if (2) and (3) are true, where if (1) is true -// we then check passes only if the document has proper access, otherwise if (1) is false then -// the check passes only if the document is public (is not registered with acp module at all). -// -// Moreover 8 states, upon checking access: -// (SignatureRequest, PermissionedCollection, ModuleExists) => Must pass ACP check, unless public (not registered) -// (!SignatureRequest, PermissionedCollection, ModuleExists) => Only public (No access if registered with ACP) -// (SignatureRequest, PermissionedCollection, !ModuleExists) => No check needed -// (SignatureRequest, !PermissionedCollection, ModuleExists) => No check needed -// (SignatureRequest, !PermissionedCollection, !ModuleExists) => No check needed -// (!SignatureRequest, !PermissionedCollection, ModuleExists) => No check needed -// (!SignatureRequest, PermissionedCollection, !ModuleExists) => No check needed -// (!SignatureRequest, !PermissionedCollection, !ModuleExists) => No check needed -func (df *DocumentFetcher) runDocumentReadPermissionCheck(ctx context.Context) error { - // If no acp module, then we have unrestricted access. - if !df.acp.HasValue() { - df.passedPermissionCheck = true - return nil - } - - // Even if acp module exists, but there is no policy on the collection (unpermissioned collection) - // then we still have unrestricted access. - policyID, resourceName, hasPolicy := client.IsPermissioned(df.col) - if !hasPolicy { - df.passedPermissionCheck = true - return nil - } - - // TODO-ACP: Implement signatures - hasSignature := true - - // Now that we know acp module exists and the collection is permissioned, handle based on signature. - if hasSignature { - hasAccess, err := df.acp.Value().CheckDocAccess( - ctx, - acp.ReadPermission, - "cosmos1zzg43wdrhmmk89z3pmejwete2kkd4a3vn7w969", // TODO-ACP: Replace with signature identity - policyID, - resourceName, - df.kv.Key.DocID, - ) - if err != nil { - df.passedPermissionCheck = false - return err - } - df.passedPermissionCheck = hasAccess - return nil - } - - // If does not have signature, we need to make sure we don't operate on registered documents. - // In this case actor only has access to the public (unregistered) documents. - isRegistered, err := df.acp.Value().IsDocRegistered( - ctx, - policyID, - resourceName, - df.kv.Key.DocID, - ) - if err != nil { - df.passedPermissionCheck = false - return err - } - - // Check passes if document is NOT registered. If it is registered then the - // document must not be accessed. - df.passedPermissionCheck = !isRegistered - return nil -} - func (df *DocumentFetcher) fetchNext(ctx context.Context) (EncodedDocument, ExecInfo, error) { if df.kvEnd { return nil, ExecInfo{}, nil @@ -687,7 +615,7 @@ func (df *DocumentFetcher) fetchNext(ctx context.Context) (EncodedDocument, Exec // Check if can access document with current permissions/signature. if !df.passedPermissionCheck { - if err := df.runDocumentReadPermissionCheck(ctx); err != nil { + if err := df.runDocReadPermissionCheck(ctx); err != nil { return nil, ExecInfo{}, err } } diff --git a/db/fetcher/fetcher_acp.go b/db/fetcher/fetcher_acp.go new file mode 100644 index 0000000000..ea72f75684 --- /dev/null +++ b/db/fetcher/fetcher_acp.go @@ -0,0 +1,39 @@ +// Copyright 2024 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package fetcher + +import ( + "context" + + "github.com/sourcenetwork/defradb/acp" + "github.com/sourcenetwork/defradb/db/permission" +) + +// runDocReadPermissionCheck handles the checking (while fetching) if the document has read access +// or not, according to our access logic based on weather (1) the request is permissioned, +// (2) the collection is permissioned (has a policy), (3) acp module exists. +func (df *DocumentFetcher) runDocReadPermissionCheck(ctx context.Context) error { + hasPermission, err := permission.CheckDocPermissionedAccessOnCollection( + ctx, + df.acp, + df.col, + acp.ReadPermission, + df.kv.Key.DocID, + ) + + if err != nil { + df.passedPermissionCheck = false + return err + } + + df.passedPermissionCheck = hasPermission + return nil +} diff --git a/db/permission/check.go b/db/permission/check.go new file mode 100644 index 0000000000..a82a46245e --- /dev/null +++ b/db/permission/check.go @@ -0,0 +1,105 @@ +// Copyright 2024 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +/* +Package acp utilizes the sourcehub acp module to bring the functionality to defradb, this package also helps +avoid the leakage of direct sourcehub references through out the code base, and eases in swapping +between local embedded use case and a more global on sourcehub use case. +*/ + +package permission + +import ( + "context" + + "github.com/sourcenetwork/defradb/acp" + "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/immutable" +) + +// CheckDocPermissionedAccessOnCollection handles the check, which tells us if access to the target +// document is valid, with respect to the permission type, and the specified collection. +// +// According to our access logic we have these components to worry about: +// (1) the request is permissioned (has an identity signature), +// (2) the collection is permissioned (has a policy), +// (3) acp module exists (acp is enabled). +// +// Unrestricted Access (Read + Write) to target document if: +// - Either of (2) or (3) is false. +// - Document is public (unregistered), whether signatured request or not, doesn't matter. +// +// Otherwise, check with acp module to verify signature has the appropriate access. +func CheckDocPermissionedAccessOnCollection( + ctx context.Context, + acpModuleOptional immutable.Option[acp.ACPModule], + collection client.Collection, + permission acp.DPIPermission, + docID string, +) (bool, error) { + // If no acp module, then we have unrestricted access. + if !acpModuleOptional.HasValue() { + return true, nil + } + + // Even if acp module exists, but there is no policy on the collection (unpermissioned collection) + // then we still have unrestricted access. + policyID, resourceName, hasPolicy := IsPermissioned(collection) + if !hasPolicy { + return true, nil + } + + acpModule := acpModuleOptional.Value() + + // Now that we know acp module exists and the collection is permissioned, before checking access with + // acp module directly we need to make sure that the document is not public, as public documents will + // not be regestered with acp. We give unrestricted access to public documents, so it does not matter + // whether the request has a signature identity or not at this stage of the check. + isNotPublic, err := acpModule.IsDocRegistered( + ctx, + policyID, + resourceName, + docID, + ) + if err != nil { + return false, err + } + + if !isNotPublic { + // Unrestricted access as it is a public document. + return true, nil + } + + // TODO-ACP: Implement signatures + hasSignature := true + + // At this point if the request is not signatured, then it has no access, because: + // the collection has a policy on it, the acp module is enabled/available, + // and the document is not public (is regestered with the acp module). + if !hasSignature { + return false, nil + } + + // Now actually check using the signature if this identity has access or not. + hasAccess, err := acpModule.CheckDocAccess( + ctx, + permission, + "cosmos1zzg43wdrhmmk89z3pmejwete2kkd4a3vn7w969", // TODO-ACP: Replace with signature identity + policyID, + resourceName, + docID, + ) + + if err != nil { + return false, err + } + + return hasAccess, nil +} diff --git a/db/permission/permission.go b/db/permission/permission.go new file mode 100644 index 0000000000..41a00a2fc6 --- /dev/null +++ b/db/permission/permission.go @@ -0,0 +1,35 @@ +// Copyright 2024 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +/* +Package acp utilizes the sourcehub acp module to bring the functionality to defradb, this package also helps +avoid the leakage of direct sourcehub references through out the code base, and eases in swapping +between local embedded use case and a more global on sourcehub use case. +*/ + +package permission + +import ( + "github.com/sourcenetwork/defradb/client" +) + +// IsPermissioned returns true if the collection has a policy, otherwise returns false. +// +// This tells us if access control is enabled for this collection or not. +func IsPermissioned(collection client.Collection) (string, string, bool) { + policy := collection.Definition().Description.Policy + if policy.HasValue() && + policy.Value().ID != "" && + policy.Value().ResourceName != "" { + return policy.Value().ID, policy.Value().ResourceName, true + } + + return "", "", false +} diff --git a/db/permission/register.go b/db/permission/register.go new file mode 100644 index 0000000000..0b419396dd --- /dev/null +++ b/db/permission/register.go @@ -0,0 +1,63 @@ +// Copyright 2024 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +/* +Package acp utilizes the sourcehub acp module to bring the functionality to defradb, this package also helps +avoid the leakage of direct sourcehub references through out the code base, and eases in swapping +between local embedded use case and a more global on sourcehub use case. +*/ + +package permission + +import ( + "context" + + "github.com/sourcenetwork/immutable" + + "github.com/sourcenetwork/defradb/acp" + "github.com/sourcenetwork/defradb/client" +) + +// The document is only registered with ACP if all (1) (2) and (3) are true. +// In all other cases, nothing is registered with ACP. + +// RegisterDocCreationOnCollection handles the registration of the document with acp module. +// The registering is done at document creation on the collection. +// +// According to our access logic we have these components to worry about: +// (1) the request is permissioned (has an identity signature), +// (2) the collection is permissioned (has a policy), +// (3) acp module exists (acp is enabled). +// +// The document is only registered if all (1) (2) and (3) are true. +// +// Otherwise, nothing is registered on the acp module. +func RegisterDocCreationOnCollection( + ctx context.Context, + acpModule immutable.Option[acp.ACPModule], + collection client.Collection, + docID string, +) error { + // If acp module is enabled / exists. + if acpModule.HasValue() { + // And collection has policy. + if policyID, resourceName, hasPolicy := IsPermissioned(collection); hasPolicy { + return acpModule.Value().RegisterDocCreation( + ctx, + "cosmos1zzg43wdrhmmk89z3pmejwete2kkd4a3vn7w969", // TODO-ACP: Replace with signature identity + policyID, + resourceName, + docID, + ) + } + } + + return nil +}