2021-07-29 18:52:56 -06:00
// Copyright (c) HashiCorp, Inc.
2020-08-19 16:47:18 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-05-08 11:36:28 -04:00
package iam
import (
"context"
2020-08-19 16:47:18 -04:00
"crypto/rand"
2024-01-02 17:18:47 -08:00
"database/sql"
2023-12-31 23:19:27 -08:00
stderr "errors"
2020-05-08 11:36:28 -04:00
"fmt"
2020-06-12 15:22:23 -04:00
"strings"
2024-01-02 17:18:47 -08:00
"time"
2020-05-11 16:21:09 -04:00
2022-10-28 15:52:06 -04:00
"github.com/hashicorp/boundary/globals"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/db"
2024-01-02 17:18:47 -08:00
"github.com/hashicorp/boundary/internal/db/timestamp"
2020-11-05 16:54:03 -05:00
"github.com/hashicorp/boundary/internal/errors"
2020-08-19 16:47:18 -04:00
"github.com/hashicorp/boundary/internal/kms"
2020-08-13 17:39:48 -04:00
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/types/resource"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/types/scope"
2022-08-24 14:11:49 -04:00
"github.com/hashicorp/go-dbw"
2022-03-16 17:04:07 -04:00
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
2020-05-08 11:36:28 -04:00
)
2020-06-12 15:22:23 -04:00
// CreateScope will create a scope in the repository and return the written
2020-08-19 16:47:18 -04:00
// scope. Supported options include: WithPublicId and WithRandomReader.
2020-08-13 17:39:48 -04:00
func ( r * Repository ) CreateScope ( ctx context . Context , s * Scope , userId string , opt ... Option ) ( * Scope , error ) {
2020-12-23 11:00:00 -08:00
const op = "iam.(Repository).CreateScope"
2020-06-26 11:00:14 -04:00
if s == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing scope" )
2020-05-08 11:36:28 -04:00
}
2020-06-26 11:00:14 -04:00
if s . Scope == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing scope store" )
2020-06-12 15:22:23 -04:00
}
2020-06-26 11:00:14 -04:00
if s . PublicId != "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "public id not empty" )
2020-06-12 15:22:23 -04:00
}
2020-08-19 16:47:18 -04:00
var parentOplogWrapper wrapping . Wrapper
var err error
2020-07-08 17:20:20 -04:00
switch s . Type {
case scope . Unknown . String ():
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "unknown type" )
2020-07-08 17:20:20 -04:00
case scope . Global . String ():
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "invalid type" )
2020-08-19 16:47:18 -04:00
default :
switch s . ParentId {
case "" :
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing parent id" )
2020-08-19 16:47:18 -04:00
case scope . Global . String ():
parentOplogWrapper , err = r . kms . GetWrapper ( ctx , scope . Global . String (), kms . KeyPurposeOplog )
default :
parentOplogWrapper , err = r . kms . GetWrapper ( ctx , s . ParentId , kms . KeyPurposeOplog )
}
}
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "unable to get oplog wrapper" )
2020-07-08 17:20:20 -04:00
}
2020-08-13 17:39:48 -04:00
2020-06-12 15:22:23 -04:00
opts := getOpts ( opt ... )
2020-08-13 17:39:48 -04:00
var scopePublicId string
var scopeMetadata oplog . Metadata
2022-10-25 18:29:38 -05:00
var scopeRaw any
2020-08-13 17:39:48 -04:00
{
scopeType := scope . Map [ s . Type ]
if opts . withPublicId != "" {
if ! strings . HasPrefix ( opts . withPublicId , scopeType . Prefix () + "_" ) {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "passed-in public ID %q has wrong prefix for type %q which uses prefix %q" , opts . withPublicId , scopeType . String (), scopeType . Prefix ()))
2020-08-13 17:39:48 -04:00
}
scopePublicId = opts . withPublicId
} else {
2023-06-06 17:09:08 -04:00
scopePublicId , err = newScopeId ( ctx , scopeType )
2020-08-13 17:39:48 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-08-13 17:39:48 -04:00
}
}
sc := s . Clone ().( * Scope )
sc . PublicId = scopePublicId
scopeRaw = sc
scopeMetadata , err = r . stdMetadata ( ctx , sc )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-08-13 17:39:48 -04:00
}
scopeMetadata [ "op-type" ] = [] string { oplog . OpType_OP_TYPE_CREATE . String ()}
}
2020-09-29 20:38:16 -04:00
var adminRolePublicId string
var adminRoleMetadata oplog . Metadata
var adminRole * Role
2022-10-25 18:29:38 -05:00
var adminRoleRaw any
2020-08-26 23:27:25 -04:00
switch {
case userId == "" ,
2022-10-28 15:52:06 -04:00
userId == globals . AnonymousUserId ,
userId == globals . AnyAuthenticatedUserId ,
userId == globals . RecoveryUserId ,
2020-09-29 20:38:16 -04:00
opts . withSkipAdminRoleCreation :
2020-08-13 17:39:48 -04:00
// TODO: Cause a log entry. The repo doesn't have a logger right now,
// and ideally we will be using context to pass around log info scoped
// to this request for grouped display in the server log. The only
// reason this should ever happen anyways is via the administrative
// recovery workflow so it's already a special case.
// Also, stop linter from complaining
2020-09-29 20:38:16 -04:00
_ = adminRole
2020-08-13 17:39:48 -04:00
default :
2023-06-06 17:09:08 -04:00
adminRole , err = NewRole ( ctx , scopePublicId )
2020-08-13 17:39:48 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "error instantiating new admin role" ))
2020-06-12 15:22:23 -04:00
}
2023-06-06 17:09:08 -04:00
adminRolePublicId , err = newRoleId ( ctx )
2020-06-12 15:22:23 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "error generating public id for new admin role" ))
2020-08-13 17:39:48 -04:00
}
2020-09-29 20:38:16 -04:00
adminRole . PublicId = adminRolePublicId
adminRole . Name = "Administration"
adminRole . Description = fmt . Sprintf ( "Role created for administration of scope %s by user %s at its creation time" , scopePublicId , userId )
adminRoleRaw = adminRole
adminRoleMetadata = oplog . Metadata {
"resource-public-id" : [] string { adminRolePublicId },
"scope-id" : [] string { scopePublicId },
"scope-type" : [] string { s . Type },
"resource-type" : [] string { resource . Role . String ()},
"op-type" : [] string { oplog . OpType_OP_TYPE_CREATE . String ()},
}
}
var defaultRolePublicId string
var defaultRoleMetadata oplog . Metadata
var defaultRole * Role
2022-10-25 18:29:38 -05:00
var defaultRoleRaw any
2021-02-11 13:14:18 -05:00
if ! opts . withSkipDefaultRoleCreation {
2023-06-06 17:09:08 -04:00
defaultRole , err = NewRole ( ctx , scopePublicId )
2020-09-29 20:38:16 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "error instantiating new default role" ))
2020-09-29 20:38:16 -04:00
}
2023-06-06 17:09:08 -04:00
defaultRolePublicId , err = newRoleId ( ctx )
2020-09-29 20:38:16 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "error generating public id for new default role" ))
2020-09-29 20:38:16 -04:00
}
defaultRole . PublicId = defaultRolePublicId
2021-02-11 13:14:18 -05:00
switch s . Type {
case scope . Project . String ():
defaultRole . Name = "Default Grants"
defaultRole . Description = fmt . Sprintf ( "Role created to provide default grants to users of scope %s at its creation time" , scopePublicId )
default :
defaultRole . Name = "Login and Default Grants"
defaultRole . Description = fmt . Sprintf ( "Role created for login capability, account self-management, and other default grants for users of scope %s at its creation time" , scopePublicId )
}
2020-09-29 20:38:16 -04:00
defaultRoleRaw = defaultRole
defaultRoleMetadata = oplog . Metadata {
"resource-public-id" : [] string { defaultRolePublicId },
2020-08-13 17:39:48 -04:00
"scope-id" : [] string { scopePublicId },
"scope-type" : [] string { s . Type },
"resource-type" : [] string { resource . Role . String ()},
"op-type" : [] string { oplog . OpType_OP_TYPE_CREATE . String ()},
2020-06-12 15:22:23 -04:00
}
}
2020-08-13 17:39:48 -04:00
2020-08-19 16:47:18 -04:00
reader := opts . withRandomReader
if reader == nil {
reader = rand . Reader
}
2020-08-13 17:39:48 -04:00
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
2020-08-19 16:47:18 -04:00
func ( dbr db . Reader , w db . Writer ) error {
2020-08-13 17:39:48 -04:00
if err := w . Create (
ctx ,
scopeRaw ,
2020-08-19 16:47:18 -04:00
db . WithOplog ( parentOplogWrapper , scopeMetadata ),
2020-08-13 17:39:48 -04:00
); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "error creating scope" ))
2020-08-13 17:39:48 -04:00
}
2020-08-19 16:47:18 -04:00
s := scopeRaw .( * Scope )
2022-05-13 13:53:22 -04:00
txnKms , err := kms . NewUsingReaderWriter ( ctx , dbr , w )
2020-08-19 16:47:18 -04:00
if err != nil {
2022-05-13 13:53:22 -04:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "error creating transaction's kms" ))
2020-08-19 16:47:18 -04:00
}
2022-05-13 13:53:22 -04:00
if err := txnKms . AddExternalWrappers ( ctx , kms . WithRootWrapper ( r . kms . GetExternalWrappers ( ctx ). Root ())); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "error adding external root wrapper to transaction's kms" ))
}
if err := txnKms . CreateKeys ( ctx , s . PublicId , kms . WithRandomReader ( reader ), kms . WithReaderWriter ( dbr , w )); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "error creating scope keys" ))
2020-08-19 16:47:18 -04:00
}
2022-05-13 13:53:22 -04:00
childOplogWrapper , err := txnKms . GetWrapper ( ctx , s . PublicId , kms . KeyPurposeOplog )
2020-08-19 16:47:18 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . InvalidParameter , op , "unable to get oplog wrapper" )
2020-08-19 16:47:18 -04:00
}
2020-08-13 17:39:48 -04:00
// We create a new role, then set grants and principals on it. This
// turns into a bunch of stuff sadly because the role is the
// aggregate.
2020-09-29 20:38:16 -04:00
if adminRoleRaw != nil {
2020-08-13 17:39:48 -04:00
if err := w . Create (
ctx ,
2020-09-29 20:38:16 -04:00
adminRoleRaw ,
db . WithOplog ( childOplogWrapper , adminRoleMetadata ),
2020-08-13 17:39:48 -04:00
); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "error creating role" ))
2020-08-13 17:39:48 -04:00
}
2020-09-29 20:38:16 -04:00
adminRole = adminRoleRaw .( * Role )
2020-08-13 17:39:48 -04:00
2024-01-23 14:33:09 -05:00
msgs := make ([] * oplog . Message , 0 , 4 )
2022-01-05 14:38:38 -05:00
roleTicket , err := w . GetTicket ( ctx , adminRole )
2020-08-13 17:39:48 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get ticket" ))
2020-08-13 17:39:48 -04:00
}
// We need to update the role version as that's the aggregate
var roleOplogMsg oplog . Message
2020-09-29 20:38:16 -04:00
rowsUpdated , err := w . Update ( ctx , adminRole , [] string { "Version" }, nil , db . NewOplogMsg ( & roleOplogMsg ), db . WithVersion ( & adminRole . Version ))
2020-08-13 17:39:48 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to update role version for adding grant" ))
2020-08-13 17:39:48 -04:00
}
if rowsUpdated != 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "updated role but %d rows updated" , rowsUpdated ))
2020-08-13 17:39:48 -04:00
}
msgs = append ( msgs , & roleOplogMsg )
2024-01-17 23:54:49 -08:00
roleGrant , err := NewRoleGrant ( ctx , adminRolePublicId , "ids=*;type=*;actions=*" )
2020-08-13 17:39:48 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role grant" ))
2020-08-13 17:39:48 -04:00
}
roleGrantOplogMsgs := make ([] * oplog . Message , 0 , 1 )
2022-10-25 18:29:38 -05:00
if err := w . CreateItems ( ctx , [] any { roleGrant }, db . NewOplogMsgs ( & roleGrantOplogMsgs )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add grants" ))
2020-08-13 17:39:48 -04:00
}
msgs = append ( msgs , roleGrantOplogMsgs ... )
2024-01-23 14:33:09 -05:00
roleGrantScope , err := NewRoleGrantScope ( ctx , adminRolePublicId , globals . GrantScopeThis )
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role grant scope" ))
}
roleGrantScopeOplogMsgs := make ([] * oplog . Message , 0 , 1 )
if err := w . CreateItems ( ctx , [] any { roleGrantScope }, db . NewOplogMsgs ( & roleGrantScopeOplogMsgs )); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add grant scope" ))
}
msgs = append ( msgs , roleGrantScopeOplogMsgs ... )
2023-06-06 17:09:08 -04:00
rolePrincipal , err := NewUserRole ( ctx , adminRolePublicId , userId )
2020-08-13 17:39:48 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role user" ))
2020-08-13 17:39:48 -04:00
}
roleUserOplogMsgs := make ([] * oplog . Message , 0 , 1 )
2022-10-25 18:29:38 -05:00
if err := w . CreateItems ( ctx , [] any { rolePrincipal }, db . NewOplogMsgs ( & roleUserOplogMsgs )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add grants" ))
2020-08-13 17:39:48 -04:00
}
msgs = append ( msgs , roleUserOplogMsgs ... )
metadata := oplog . Metadata {
"op-type" : [] string { oplog . OpType_OP_TYPE_CREATE . String ()},
"scope-id" : [] string { s . PublicId },
"scope-type" : [] string { s . Type },
2020-09-29 20:38:16 -04:00
"resource-public-id" : [] string { adminRole . PublicId },
}
if err := w . WriteOplogEntryWith ( ctx , childOplogWrapper , roleTicket , metadata , msgs ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to write oplog" ))
2020-09-29 20:38:16 -04:00
}
}
// We create a new role, then set grants and principals on it. This
// turns into a bunch of stuff sadly because the role is the
// aggregate.
if defaultRoleRaw != nil {
if err := w . Create (
ctx ,
defaultRoleRaw ,
db . WithOplog ( childOplogWrapper , defaultRoleMetadata ),
); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "error creating role" ))
2020-09-29 20:38:16 -04:00
}
defaultRole = defaultRoleRaw .( * Role )
2024-01-23 14:33:09 -05:00
msgs := make ([] * oplog . Message , 0 , 7 )
2022-01-05 14:38:38 -05:00
roleTicket , err := w . GetTicket ( ctx , defaultRole )
2020-09-29 20:38:16 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get ticket" ))
2020-09-29 20:38:16 -04:00
}
// We need to update the role version as that's the aggregate
var roleOplogMsg oplog . Message
rowsUpdated , err := w . Update ( ctx , defaultRole , [] string { "Version" }, nil , db . NewOplogMsg ( & roleOplogMsg ), db . WithVersion ( & defaultRole . Version ))
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to update role version for adding grant" ))
2020-09-29 20:38:16 -04:00
}
if rowsUpdated != 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "updated role but %d rows updated" , rowsUpdated ))
2020-09-29 20:38:16 -04:00
}
msgs = append ( msgs , & roleOplogMsg )
// Grants
{
2022-10-25 18:29:38 -05:00
grants := [] any {}
2021-02-02 13:37:32 -07:00
2021-02-11 13:14:18 -05:00
switch s . Type {
case scope . Project . String ():
2024-01-17 23:54:49 -08:00
roleGrant , err := NewRoleGrant ( ctx , defaultRolePublicId , "ids=*;type=session;actions=list,read:self,cancel:self" )
2021-02-11 13:14:18 -05:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role grant" ))
2022-01-13 06:26:35 -08:00
}
grants = append ( grants , roleGrant )
2023-06-06 16:13:44 -04:00
roleGrant , err = NewRoleGrant ( ctx , defaultRolePublicId , "type=target;actions=list" )
2022-01-13 06:26:35 -08:00
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role grant" ))
2021-02-11 13:14:18 -05:00
}
grants = append ( grants , roleGrant )
default :
2024-01-17 23:54:49 -08:00
roleGrant , err := NewRoleGrant ( ctx , defaultRolePublicId , "ids=*;type=scope;actions=list,no-op" )
2021-02-11 13:14:18 -05:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role grant" ))
2021-02-11 13:14:18 -05:00
}
grants = append ( grants , roleGrant )
2024-01-17 23:54:49 -08:00
roleGrant , err = NewRoleGrant ( ctx , defaultRolePublicId , "ids=*;type=auth-method;actions=authenticate,list" )
2021-02-11 13:14:18 -05:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role grant" ))
2021-02-11 13:14:18 -05:00
}
grants = append ( grants , roleGrant )
2024-01-17 23:54:49 -08:00
roleGrant , err = NewRoleGrant ( ctx , defaultRolePublicId , "ids={{.Account.Id}};actions=read,change-password" )
2021-02-11 13:14:18 -05:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role grant" ))
2021-02-11 13:14:18 -05:00
}
grants = append ( grants , roleGrant )
2021-04-23 12:45:08 -05:00
2024-01-17 23:54:49 -08:00
roleGrant , err = NewRoleGrant ( ctx , defaultRolePublicId , "ids=*;type=auth-token;actions=list,read:self,delete:self" )
2021-04-23 12:45:08 -05:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role grant" ))
2021-04-23 12:45:08 -05:00
}
grants = append ( grants , roleGrant )
2020-09-29 20:38:16 -04:00
}
2020-10-01 10:51:54 -04:00
roleGrantOplogMsgs := make ([] * oplog . Message , 0 , 3 )
2020-09-29 20:38:16 -04:00
if err := w . CreateItems ( ctx , grants , db . NewOplogMsgs ( & roleGrantOplogMsgs )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add grants" ))
2020-09-29 20:38:16 -04:00
}
msgs = append ( msgs , roleGrantOplogMsgs ... )
}
// Principals
{
2022-10-25 18:29:38 -05:00
principals := [] any {}
2022-10-28 15:52:06 -04:00
userId := globals . AnonymousUserId
2021-02-11 13:14:18 -05:00
if s . Type == scope . Project . String () {
2022-10-28 15:52:06 -04:00
userId = globals . AnyAuthenticatedUserId
2021-02-11 13:14:18 -05:00
}
2023-06-06 17:09:08 -04:00
rolePrincipal , err := NewUserRole ( ctx , defaultRolePublicId , userId )
2020-09-29 20:38:16 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role user" ))
2020-09-29 20:38:16 -04:00
}
principals = append ( principals , rolePrincipal )
roleUserOplogMsgs := make ([] * oplog . Message , 0 , 2 )
if err := w . CreateItems ( ctx , principals , db . NewOplogMsgs ( & roleUserOplogMsgs )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add grants" ))
2020-09-29 20:38:16 -04:00
}
msgs = append ( msgs , roleUserOplogMsgs ... )
}
2024-01-23 14:33:09 -05:00
roleGrantScope , err := NewRoleGrantScope ( ctx , defaultRolePublicId , globals . GrantScopeThis )
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create in memory role grant scope" ))
}
roleGrantScopeOplogMsgs := make ([] * oplog . Message , 0 , 1 )
if err := w . CreateItems ( ctx , [] any { roleGrantScope }, db . NewOplogMsgs ( & roleGrantScopeOplogMsgs )); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add grant scope" ))
}
msgs = append ( msgs , roleGrantScopeOplogMsgs ... )
2020-09-29 20:38:16 -04:00
metadata := oplog . Metadata {
"op-type" : [] string { oplog . OpType_OP_TYPE_CREATE . String ()},
"scope-id" : [] string { s . PublicId },
"scope-type" : [] string { s . Type },
"resource-public-id" : [] string { defaultRole . PublicId },
2020-08-13 17:39:48 -04:00
}
2020-08-19 16:47:18 -04:00
if err := w . WriteOplogEntryWith ( ctx , childOplogWrapper , roleTicket , metadata , msgs ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to write oplog" ))
2020-08-13 17:39:48 -04:00
}
}
return nil
},
)
2020-05-08 11:36:28 -04:00
if err != nil {
2020-11-05 16:54:03 -05:00
if errors . IsUniqueError ( err ) {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . NotUnique , op , fmt . Sprintf ( "scope %s/%s already exists" , scopePublicId , s . Name ))
2020-06-12 15:22:23 -04:00
}
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "for %s" , scopePublicId )))
2020-05-08 11:36:28 -04:00
}
2020-08-13 17:39:48 -04:00
return scopeRaw .( * Scope ), nil
2020-05-08 11:36:28 -04:00
}
2020-05-31 19:55:56 -04:00
// UpdateScope will update a scope in the repository and return the written
// scope. fieldMaskPaths provides field_mask.proto paths for fields that should
// be updated. Fields will be set to NULL if the field is a zero value and
// included in fieldMask. Name and Description are the only updatable fields,
// and everything else is ignored. If no updatable fields are included in the
// fieldMaskPaths, then an error is returned.
2020-12-23 11:00:00 -08:00
func ( r * Repository ) UpdateScope ( ctx context . Context , scope * Scope , version uint32 , fieldMaskPaths [] string , _ ... Option ) ( * Scope , int , error ) {
const op = "iam.(Repository).UpdateScope"
2020-05-08 11:36:28 -04:00
if scope == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing scope" )
2020-05-18 17:51:09 -04:00
}
if scope . PublicId == "" {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing public id" )
2020-05-08 11:36:28 -04:00
}
2020-05-31 19:55:56 -04:00
if contains ( fieldMaskPaths , "ParentId" ) {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidFieldMask , op , "you cannot change a scope's parent" )
2020-05-31 19:55:56 -04:00
}
var dbMask , nullFields [] string
2022-08-24 14:11:49 -04:00
dbMask , nullFields = dbw . BuildUpdatePaths (
2022-10-25 18:29:38 -05:00
map [ string ] any {
2021-04-08 20:54:32 -04:00
"name" : scope . Name ,
"description" : scope . Description ,
2021-09-30 08:47:34 -04:00
"PrimaryAuthMethodId" : scope . PrimaryAuthMethodId , // gorm: it's important that the field start with a capital letter.
2020-05-31 19:55:56 -04:00
},
fieldMaskPaths ,
2020-09-19 10:31:49 -04:00
nil ,
2020-05-31 19:55:56 -04:00
)
// nada to update, so reload scope from db and return it
if len ( dbMask ) == 0 && len ( nullFields ) == 0 {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . E ( ctx , errors . WithCode ( errors . EmptyFieldMask ), errors . WithOp ( op ))
2020-05-31 19:55:56 -04:00
}
2020-08-07 12:55:02 -04:00
resource , rowsUpdated , err := r . update ( ctx , scope , version , dbMask , nullFields )
2020-05-08 11:36:28 -04:00
if err != nil {
2020-11-05 16:54:03 -05:00
if errors . IsUniqueError ( err ) {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . NotUnique , op , fmt . Sprintf ( "%s name %s already exists" , scope . PublicId , scope . Name ))
2020-05-31 19:55:56 -04:00
}
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "for public id %s" , scope . PublicId )))
2020-05-08 11:36:28 -04:00
}
2023-12-31 23:19:27 -08:00
var childComponentErrs error
item := resource .( * Scope )
if err := setScopeStoragePolicyId ( ctx , r . reader , item ); err != nil && ! errors . IsNotFoundError ( err ) {
childComponentErrs = stderr . Join ( err )
}
if childComponentErrs != nil {
return nil , rowsUpdated , errors . Wrap ( ctx , childComponentErrs , op , errors . WithMsg ( fmt . Sprintf ( "failed to fetch one or more child components for %s" , item . PublicId )))
}
2020-12-23 11:00:00 -08:00
return resource .( * Scope ), rowsUpdated , nil
2020-05-08 11:36:28 -04:00
}
2020-05-11 16:21:09 -04:00
// LookupScope will look up a scope in the repository. If the scope is not
// found, it will return nil, nil.
2020-12-23 11:00:00 -08:00
func ( r * Repository ) LookupScope ( ctx context . Context , withPublicId string , _ ... Option ) ( * Scope , error ) {
const op = "iam.(Repository).LookupScope"
2020-05-18 17:51:09 -04:00
if withPublicId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing public id" )
2020-05-18 17:51:09 -04:00
}
2021-04-08 20:54:32 -04:00
scope := AllocScope ()
2020-05-18 17:51:09 -04:00
scope . PublicId = withPublicId
if err := r . reader . LookupByPublicId ( ctx , & scope ); err != nil {
2020-12-21 10:07:34 -08:00
if errors . IsNotFoundError ( err ) {
2020-05-18 17:51:09 -04:00
return nil , nil
2020-05-08 11:36:28 -04:00
}
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , withPublicId )))
2020-05-08 11:36:28 -04:00
}
2023-12-31 23:19:27 -08:00
var childComponentErrs error
if err := setScopeStoragePolicyId ( ctx , r . reader , & scope ); err != nil && ! errors . IsNotFoundError ( err ) {
childComponentErrs = stderr . Join ( err )
}
if childComponentErrs != nil {
return nil , errors . Wrap ( ctx , childComponentErrs , op , errors . WithMsg ( fmt . Sprintf ( "failed to fetch one or more child components for %s" , scope . PublicId )))
}
2020-05-18 17:51:09 -04:00
return & scope , nil
}
// DeleteScope will delete a scope from the repository
2020-12-23 11:00:00 -08:00
func ( r * Repository ) DeleteScope ( ctx context . Context , withPublicId string , _ ... Option ) ( int , error ) {
const op = "iam.(Repository).DeleteScope"
2020-05-18 17:51:09 -04:00
if withPublicId == "" {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing public id" )
2020-05-18 17:51:09 -04:00
}
2020-07-08 17:20:20 -04:00
if withPublicId == scope . Global . String () {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "invalid to delete global scope" )
2020-07-08 17:20:20 -04:00
}
2021-04-08 20:54:32 -04:00
scope := AllocScope ()
2020-05-18 17:51:09 -04:00
scope . PublicId = withPublicId
rowsDeleted , err := r . delete ( ctx , & scope )
if err != nil {
if errors . Is ( err , ErrMetadataScopeNotFound ) {
return 0 , nil
2020-05-08 11:36:28 -04:00
}
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , withPublicId )))
2020-05-08 11:36:28 -04:00
}
2020-05-18 17:51:09 -04:00
return rowsDeleted , nil
2020-05-08 11:36:28 -04:00
}
2020-06-18 14:53:11 -04:00
2024-01-02 17:18:47 -08:00
// listScopes lists scopes in the given scopes and supports WithLimit option.
func ( r * Repository ) listScopes ( ctx context . Context , withParentIds [] string , opt ... Option ) ([] * Scope , time . Time , error ) {
const op = "iam.(Repository).listScopes"
2021-01-25 19:37:40 -05:00
if len ( withParentIds ) == 0 {
2024-01-02 17:18:47 -08:00
return nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "missing parent id" )
2020-06-18 14:53:11 -04:00
}
2024-01-02 17:18:47 -08:00
opts := getOpts ( opt ... )
limit := r . defaultLimit
switch {
case opts . withLimit > 0 :
// non-zero signals an override of the default limit for the repo.
limit = opts . withLimit
case opts . withLimit < 0 :
return nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "limit must be non-negative" )
2020-06-18 14:53:11 -04:00
}
2024-01-02 17:18:47 -08:00
var args [] any
whereClause := "parent_id in @parent_ids"
args = append ( args , sql . Named ( "parent_ids" , withParentIds ))
if opts . withStartPageAfterItem != nil {
whereClause = fmt . Sprintf ( "(create_time, public_id) < (@last_item_create_time, @last_item_id) and %s" , whereClause )
args = append ( args ,
sql . Named ( "last_item_create_time" , opts . withStartPageAfterItem . GetCreateTime ()),
sql . Named ( "last_item_id" , opts . withStartPageAfterItem . GetPublicId ()),
)
}
dbOpts := [] db . Option { db . WithLimit ( limit ), db . WithOrder ( "create_time desc, public_id desc" )}
2023-12-31 23:19:27 -08:00
scopes , t , err := r . queryScopes ( ctx , whereClause , args , dbOpts ... )
if err != nil {
return scopes , t , err
}
var childComponentErrs error
for _ , scope := range scopes {
if err := setScopeStoragePolicyId ( ctx , r . reader , scope ); err != nil && ! errors . IsNotFoundError ( err ) {
childComponentErrs = stderr . Join ( err )
}
}
if childComponentErrs != nil {
return scopes , t , errors . Wrap ( ctx , childComponentErrs , op , errors . WithMsg ( "failed to fetch one or more child components" ))
}
return scopes , t , nil
2024-01-02 17:18:47 -08:00
}
// listScopesRefresh lists scopes in the given scopes and supports the
// WithLimit and WithStartPageAfterItem options.
func ( r * Repository ) listScopesRefresh ( ctx context . Context , updatedAfter time . Time , withParentIds [] string , opt ... Option ) ([] * Scope , time . Time , error ) {
const op = "iam.(Repository).listScopesRefresh"
switch {
case updatedAfter . IsZero ():
return nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "missing updated after time" )
case len ( withParentIds ) == 0 :
return nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "missing parent id" )
}
opts := getOpts ( opt ... )
limit := r . defaultLimit
switch {
case opts . withLimit > 0 :
// non-zero signals an override of the default limit for the repo.
limit = opts . withLimit
case opts . withLimit < 0 :
return nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "limit must be non-negative" )
}
var args [] any
whereClause := "update_time > @updated_after_time and parent_id in @parent_ids"
args = append ( args ,
sql . Named ( "updated_after_time" , timestamp . New ( updatedAfter )),
sql . Named ( "parent_ids" , withParentIds ),
)
if opts . withStartPageAfterItem != nil {
whereClause = fmt . Sprintf ( "(update_time, public_id) < (@last_item_update_time, @last_item_id) and %s" , whereClause )
args = append ( args ,
sql . Named ( "last_item_update_time" , opts . withStartPageAfterItem . GetUpdateTime ()),
sql . Named ( "last_item_id" , opts . withStartPageAfterItem . GetPublicId ()),
)
}
dbOpts := [] db . Option { db . WithLimit ( limit ), db . WithOrder ( "update_time desc, public_id desc" )}
2023-12-31 23:19:27 -08:00
scopes , t , err := r . queryScopes ( ctx , whereClause , args , dbOpts ... )
if err != nil {
return scopes , t , err
}
var childComponentErrs error
for _ , scope := range scopes {
if err := setScopeStoragePolicyId ( ctx , r . reader , scope ); err != nil && ! errors . IsNotFoundError ( err ) {
childComponentErrs = stderr . Join ( err )
}
}
if childComponentErrs != nil {
return scopes , t , errors . Wrap ( ctx , childComponentErrs , op , errors . WithMsg ( "failed to fetch one or more child components" ))
}
return scopes , t , nil
2024-01-02 17:18:47 -08:00
}
func ( r * Repository ) queryScopes ( ctx context . Context , whereClause string , args [] any , opt ... db . Option ) ([] * Scope , time . Time , error ) {
const op = "iam.(Repository).queryScopes"
var transactionTimestamp time . Time
var ret [] * Scope
if _ , err := r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {}, func ( rd db . Reader , w db . Writer ) error {
var inRet [] * Scope
if err := rd . SearchWhere ( ctx , & inRet , whereClause , args , opt ... ); err != nil {
return errors . Wrap ( ctx , err , op )
}
ret = inRet
var err error
transactionTimestamp , err = rd . Now ( ctx )
return err
}); err != nil {
return nil , time . Time {}, err
}
return ret , transactionTimestamp , nil
2020-06-18 14:53:11 -04:00
}
2021-01-21 21:03:30 -05:00
2021-01-23 13:39:04 -05:00
// ListScopesRecursively allows for recursive listing of scopes based on a root scope
2021-01-21 21:03:30 -05:00
// ID. It returns the root scope ID as a part of the set.
2021-01-23 13:39:04 -05:00
func ( r * Repository ) ListScopesRecursively ( ctx context . Context , rootScopeId string , opt ... Option ) ([] * Scope , error ) {
2021-01-21 21:03:30 -05:00
const op = "iam.(Repository).ListRecursively"
2021-03-25 03:15:43 -04:00
var scopes [] * Scope
2021-01-21 21:03:30 -05:00
var where string
2022-10-25 18:29:38 -05:00
var args [] any
2021-01-21 21:03:30 -05:00
switch {
2021-11-04 09:34:01 -04:00
case rootScopeId == scope . Global . String ():
2021-01-21 21:03:30 -05:00
// Nothing -- we want all scopes
case strings . HasPrefix ( rootScopeId , "o_" ):
// The org itself and any projects that have it as parent
where = "public_id = ? or parent_id = ?"
args = append ( args , rootScopeId , rootScopeId )
case strings . HasPrefix ( rootScopeId , "p_" ):
// No scopes can (currently) live under projects, so just the project
// itself
where = "public_id = ?"
args = append ( args , rootScopeId )
default :
// We have no idea what scope type this is so bail
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidPublicId , op + ":TypeSwitch" , "invalid scope ID" )
2021-01-21 21:03:30 -05:00
}
2021-03-25 03:15:43 -04:00
err := r . list ( ctx , & scopes , where , args , opt ... )
2021-01-21 21:03:30 -05:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op + ":ListQuery" )
2021-01-21 21:03:30 -05:00
}
2023-12-31 23:19:27 -08:00
var childComponentErrs error
for _ , scope := range scopes {
if err := setScopeStoragePolicyId ( ctx , r . reader , scope ); err != nil && ! errors . IsNotFoundError ( err ) {
childComponentErrs = stderr . Join ( err )
}
}
if childComponentErrs != nil {
return nil , errors . Wrap ( ctx , childComponentErrs , op , errors . WithMsg ( "failed to fetch one or more child components" ))
}
2021-03-25 03:15:43 -04:00
return scopes , nil
2021-01-21 21:03:30 -05:00
}
2024-01-02 17:18:47 -08:00
// listScopeDeletedIds lists the public IDs of any scopes deleted since the timestamp provided.
func ( r * Repository ) listScopeDeletedIds ( ctx context . Context , since time . Time ) ([] string , time . Time , error ) {
const op = "iam.(Repository).listScopeDeletedIds"
var deletedScopes [] * deletedScope
var transactionTimestamp time . Time
if _ , err := r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {}, func ( r db . Reader , w db . Writer ) error {
if err := r . SearchWhere ( ctx , & deletedScopes , "delete_time >= ?" , [] any { since }); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query deleted scopes" ))
}
var err error
transactionTimestamp , err = r . Now ( ctx )
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query transaction timestamp" ))
}
return nil
}); err != nil {
return nil , time . Time {}, err
}
var scopeIds [] string
for _ , user := range deletedScopes {
scopeIds = append ( scopeIds , user . PublicId )
}
return scopeIds , transactionTimestamp , nil
}
// estimatedScopeCount returns and estimate of the total number of items in the scopes table.
func ( r * Repository ) estimatedScopeCount ( ctx context . Context ) ( int , error ) {
const op = "iam.(Repository).estimatedScopeCount"
rows , err := r . reader . Query ( ctx , estimateCountScopes , nil )
if err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query total scopes" ))
}
var count int
for rows . Next () {
if err := r . reader . ScanRows ( ctx , rows , & count ); err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query total scopes" ))
}
}
2024-01-22 14:03:40 -05:00
if err := rows . Err (); err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query total scopes" ))
}
2024-01-02 17:18:47 -08:00
return count , nil
}