2020-05-08 11:36:28 -04:00
package iam
import (
"context"
2020-08-19 16:47:18 -04:00
"crypto/rand"
2020-05-08 11:36:28 -04:00
"errors"
"fmt"
2020-06-12 15:22:23 -04:00
"strings"
2020-05-11 16:21:09 -04:00
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/db"
dbcommon "github.com/hashicorp/boundary/internal/db/common"
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"
2020-08-19 16:47:18 -04:00
wrapping "github.com/hashicorp/go-kms-wrapping"
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-06-26 11:00:14 -04:00
if s == nil {
2020-08-25 12:18:14 -04:00
return nil , fmt . Errorf ( "create scope: missing scope %w" , db . ErrInvalidParameter )
2020-05-08 11:36:28 -04:00
}
2020-06-26 11:00:14 -04:00
if s . Scope == nil {
2020-08-25 12:18:14 -04:00
return nil , fmt . Errorf ( "create scope: missing scope store %w" , db . ErrInvalidParameter )
2020-06-12 15:22:23 -04:00
}
2020-06-26 11:00:14 -04:00
if s . PublicId != "" {
2020-06-12 15:22:23 -04:00
return nil , fmt . Errorf ( "create scope: public id not empty: %w" , db . ErrInvalidParameter )
}
2020-08-19 16:47:18 -04:00
var parentOplogWrapper wrapping . Wrapper
var externalWrappers * kms . ExternalWrappers
var err error
2020-07-08 17:20:20 -04:00
switch s . Type {
case scope . Unknown . String ():
return nil , fmt . Errorf ( "create scope: unknown type: %w" , db . ErrInvalidParameter )
case scope . Global . String ():
return nil , fmt . Errorf ( "create scope: invalid type: %w" , db . ErrInvalidParameter )
2020-08-19 16:47:18 -04:00
default :
switch s . ParentId {
case "" :
2020-08-25 12:18:14 -04:00
return nil , fmt . Errorf ( "create scope: missing parent id: %w" , db . ErrInvalidParameter )
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 )
}
externalWrappers = r . kms . GetExternalWrappers ()
}
if err != nil {
return nil , fmt . Errorf ( "create scope: unable to get oplog wrapper: %w" , err )
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
var scopeRaw interface {}
{
scopeType := scope . Map [ s . Type ]
if opts . withPublicId != "" {
if ! strings . HasPrefix ( opts . withPublicId , scopeType . Prefix () + "_" ) {
return nil , fmt . Errorf ( "create scope: passed-in public ID %q has wrong prefix for type %q which uses prefix %q" , opts . withPublicId , scopeType . String (), scopeType . Prefix ())
}
scopePublicId = opts . withPublicId
} else {
scopePublicId , err = newScopeId ( scopeType )
if err != nil {
return nil , fmt . Errorf ( "create scope: error generating public id for new scope: %w" , err )
}
}
sc := s . Clone ().( * Scope )
sc . PublicId = scopePublicId
scopeRaw = sc
scopeMetadata , err = r . stdMetadata ( ctx , sc )
if err != nil {
return nil , fmt . Errorf ( "create scope: error getting metadata for scope create: %w" , err )
}
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
var adminRoleRaw interface {}
2020-08-26 23:27:25 -04:00
switch {
case userId == "" ,
userId == "u_anon" ,
userId == "u_auth" ,
userId == "u_recovery" ,
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 :
2020-09-29 20:38:16 -04:00
adminRole , err = NewRole ( scopePublicId )
2020-08-13 17:39:48 -04:00
if err != nil {
2020-09-29 20:38:16 -04:00
return nil , fmt . Errorf ( "create scope: error instantiating new admin role: %w" , err )
2020-06-12 15:22:23 -04:00
}
2020-09-29 20:38:16 -04:00
adminRolePublicId , err = newRoleId ()
2020-06-12 15:22:23 -04:00
if err != nil {
2020-09-29 20:38:16 -04:00
return nil , fmt . Errorf ( "create scope: error generating public id for new admin role: %w" , err )
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
var defaultRoleRaw interface {}
if ! opts . withSkipDefaultRoleCreation {
defaultRole , err = NewRole ( scopePublicId )
if err != nil {
return nil , fmt . Errorf ( "create scope: error instantiating new default role: %w" , err )
}
defaultRolePublicId , err = newRoleId ()
if err != nil {
return nil , fmt . Errorf ( "create scope: error generating public id for new default role: %w" , err )
}
defaultRole . PublicId = defaultRolePublicId
2020-10-01 10:51:54 -04:00
defaultRole . Name = "Login and Default Grants"
defaultRole . Description = fmt . Sprintf ( "Role created for login capability and account self-management 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 {
return fmt . Errorf ( "error creating scope: %w" , err )
}
2020-08-19 16:47:18 -04:00
s := scopeRaw .( * Scope )
2020-09-02 14:37:19 -04:00
// Create the scope's keys
_ , err = kms . CreateKeysTx ( ctx , dbr , w , externalWrappers . Root (), reader , s . PublicId )
2020-08-19 16:47:18 -04:00
if err != nil {
2020-09-02 14:37:19 -04:00
return fmt . Errorf ( "error creating scope keys: %w" , err )
2020-08-19 16:47:18 -04:00
}
kmsRepo , err := kms . NewRepository ( dbr , w )
if err != nil {
return fmt . Errorf ( "error creating new kms repo: %w" , err )
}
childOplogWrapper , err := r . kms . GetWrapper ( ctx , s . PublicId , kms . KeyPurposeOplog , kms . WithRepository ( kmsRepo ))
if err != nil {
return fmt . Errorf ( "error fetching new scope oplog wrapper: %w" , err )
}
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 {
return fmt . Errorf ( "error creating role: %w" , err )
}
2020-09-29 20:38:16 -04:00
adminRole = adminRoleRaw .( * Role )
2020-08-13 17:39:48 -04:00
msgs := make ([] * oplog . Message , 0 , 3 )
2020-09-29 20:38:16 -04:00
roleTicket , err := w . GetTicket ( adminRole )
2020-08-13 17:39:48 -04:00
if err != nil {
return fmt . Errorf ( "unable to get ticket: %w" , err )
}
// 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 {
return fmt . Errorf ( "unable to update role version for adding grant: %w" , err )
}
if rowsUpdated != 1 {
return fmt . Errorf ( "updated role but %d rows updated" , rowsUpdated )
}
msgs = append ( msgs , & roleOplogMsg )
2020-10-01 01:49:20 -04:00
roleGrant , err := NewRoleGrant ( adminRolePublicId , "id=*;type=*;actions=*" )
2020-08-13 17:39:48 -04:00
if err != nil {
return fmt . Errorf ( "unable to create in memory role grant: %w" , err )
}
roleGrantOplogMsgs := make ([] * oplog . Message , 0 , 1 )
if err := w . CreateItems ( ctx , [] interface {}{ roleGrant }, db . NewOplogMsgs ( & roleGrantOplogMsgs )); err != nil {
return fmt . Errorf ( "unable to add grants: %w" , err )
}
msgs = append ( msgs , roleGrantOplogMsgs ... )
2020-09-29 20:38:16 -04:00
rolePrincipal , err := NewUserRole ( adminRolePublicId , userId )
2020-08-13 17:39:48 -04:00
if err != nil {
return fmt . Errorf ( "unable to create in memory role user: %w" , err )
}
roleUserOplogMsgs := make ([] * oplog . Message , 0 , 1 )
if err := w . CreateItems ( ctx , [] interface {}{ rolePrincipal }, db . NewOplogMsgs ( & roleUserOplogMsgs )); err != nil {
return fmt . Errorf ( "unable to add grants: %w" , err )
}
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 {
return fmt . Errorf ( "unable to write oplog: %w" , err )
}
}
// 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 {
return fmt . Errorf ( "error creating role: %w" , err )
}
defaultRole = defaultRoleRaw .( * Role )
msgs := make ([] * oplog . Message , 0 , 6 )
roleTicket , err := w . GetTicket ( defaultRole )
if err != nil {
return fmt . Errorf ( "unable to get ticket: %w" , err )
}
// 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 {
return fmt . Errorf ( "unable to update role version for adding grant: %w" , err )
}
if rowsUpdated != 1 {
return fmt . Errorf ( "updated role but %d rows updated" , rowsUpdated )
}
msgs = append ( msgs , & roleOplogMsg )
// Grants
{
grants := [] interface {}{}
2020-10-01 01:49:20 -04:00
roleGrant , err := NewRoleGrant ( defaultRolePublicId , "type=scope;actions=list" )
2020-09-29 20:38:16 -04:00
if err != nil {
return fmt . Errorf ( "unable to create in memory role grant: %w" , err )
}
grants = append ( grants , roleGrant )
2020-09-29 22:55:22 -04:00
roleGrant , err = NewRoleGrant ( defaultRolePublicId , "id=*;type=auth-method;actions=authenticate,list" )
2020-09-29 20:38:16 -04:00
if err != nil {
return fmt . Errorf ( "unable to create in memory role grant: %w" , err )
}
grants = append ( grants , roleGrant )
2020-10-01 10:51:54 -04:00
roleGrant , err = NewRoleGrant ( defaultRolePublicId , "id={{account.id}};actions=read,change-password" )
if err != nil {
return fmt . Errorf ( "unable to create in memory role grant: %w" , err )
}
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 {
return fmt . Errorf ( "unable to add grants: %w" , err )
}
msgs = append ( msgs , roleGrantOplogMsgs ... )
}
// Principals
{
principals := [] interface {}{}
rolePrincipal , err := NewUserRole ( defaultRolePublicId , "u_anon" )
if err != nil {
return fmt . Errorf ( "unable to create in memory role user: %w" , err )
}
principals = append ( principals , rolePrincipal )
roleUserOplogMsgs := make ([] * oplog . Message , 0 , 2 )
if err := w . CreateItems ( ctx , principals , db . NewOplogMsgs ( & roleUserOplogMsgs )); err != nil {
return fmt . Errorf ( "unable to add grants: %w" , err )
}
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 },
"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 {
2020-08-13 17:39:48 -04:00
return fmt . Errorf ( "unable to write oplog: %w" , err )
}
}
return nil
},
)
2020-05-08 11:36:28 -04:00
if err != nil {
2020-06-12 15:22:23 -04:00
if db . IsUniqueError ( err ) {
2020-08-13 17:39:48 -04:00
return nil , fmt . Errorf ( "create scope: scope %s/%s already exists: %w" , scopePublicId , s . Name , db . ErrNotUnique )
2020-06-12 15:22:23 -04:00
}
2020-08-13 17:39:48 -04:00
return nil , fmt . Errorf ( "create scope: id %s got error: %w" , scopePublicId , err )
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-08-07 12:55:02 -04:00
func ( r * Repository ) UpdateScope ( ctx context . Context , scope * Scope , version uint32 , fieldMaskPaths [] string , opt ... Option ) ( * Scope , int , error ) {
2020-05-08 11:36:28 -04:00
if scope == nil {
2020-08-25 12:18:14 -04:00
return nil , db . NoRowsAffected , fmt . Errorf ( "update scope: missing scope: %w" , db . ErrInvalidParameter )
2020-05-18 17:51:09 -04:00
}
if scope . PublicId == "" {
2020-08-25 12:18:14 -04:00
return nil , db . NoRowsAffected , fmt . Errorf ( "update scope: missing public id: %w" , db . ErrInvalidParameter )
2020-05-08 11:36:28 -04:00
}
2020-05-31 19:55:56 -04:00
if contains ( fieldMaskPaths , "ParentId" ) {
return nil , db . NoRowsAffected , fmt . Errorf ( "update scope: you cannot change a scope's parent: %w" , db . ErrInvalidFieldMask )
}
var dbMask , nullFields [] string
2020-07-31 14:35:56 -07:00
dbMask , nullFields = dbcommon . BuildUpdatePaths (
2020-05-31 19:55:56 -04:00
map [ string ] interface {}{
"name" : scope . Name ,
"description" : scope . Description ,
},
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 {
return nil , db . NoRowsAffected , fmt . Errorf ( "update scope: %w" , db . ErrEmptyFieldMask )
}
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-06-01 10:27:49 -04:00
if db . IsUniqueError ( err ) {
2020-05-31 19:55:56 -04:00
return nil , db . NoRowsAffected , fmt . Errorf ( "update scope: %s name %s already exists: %w" , scope . PublicId , scope . Name , db . ErrNotUnique )
}
return nil , db . NoRowsAffected , fmt . Errorf ( "update scope: failed for public id %s: %w" , scope . PublicId , err )
2020-05-08 11:36:28 -04:00
}
2020-05-12 11:57:28 -04:00
return resource .( * Scope ), rowsUpdated , err
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-05-18 17:51:09 -04:00
func ( r * Repository ) LookupScope ( ctx context . Context , withPublicId string , opt ... Option ) ( * Scope , error ) {
if withPublicId == "" {
2020-06-12 15:22:23 -04:00
return nil , fmt . Errorf ( "lookup scope: missing public id %w" , db . ErrInvalidParameter )
2020-05-18 17:51:09 -04:00
}
scope := allocScope ()
scope . PublicId = withPublicId
if err := r . reader . LookupByPublicId ( ctx , & scope ); err != nil {
if err == db . ErrRecordNotFound {
return nil , nil
2020-05-08 11:36:28 -04:00
}
2020-06-29 08:18:57 -04:00
return nil , fmt . Errorf ( "lookup scope: failed %w fo %s" , err , withPublicId )
2020-05-08 11:36:28 -04:00
}
2020-05-18 17:51:09 -04:00
return & scope , nil
}
// DeleteScope will delete a scope from the repository
func ( r * Repository ) DeleteScope ( ctx context . Context , withPublicId string , opt ... Option ) ( int , error ) {
if withPublicId == "" {
2020-06-12 15:22:23 -04:00
return db . NoRowsAffected , fmt . Errorf ( "delete scope: missing public id %w" , db . ErrInvalidParameter )
2020-05-18 17:51:09 -04:00
}
2020-07-08 17:20:20 -04:00
if withPublicId == scope . Global . String () {
return db . NoRowsAffected , fmt . Errorf ( "delete scope: invalid to delete global scope: %w" , db . ErrInvalidParameter )
}
2020-05-18 17:51:09 -04:00
scope := allocScope ()
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
}
2020-06-12 15:22:23 -04:00
return db . NoRowsAffected , fmt . Errorf ( "delete scope: failed %w for %s" , err , 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
2020-07-11 12:00:49 -04:00
// ListProjects in an org and supports the WithLimit option.
func ( r * Repository ) ListProjects ( ctx context . Context , withOrgId string , opt ... Option ) ([] * Scope , error ) {
if withOrgId == "" {
return nil , fmt . Errorf ( "list projects: missing org id %w" , db . ErrInvalidParameter )
2020-06-18 14:53:11 -04:00
}
var projects [] * Scope
2020-07-11 12:00:49 -04:00
err := r . list ( ctx , & projects , "parent_id = ? and type = ?" , [] interface {}{ withOrgId , scope . Project . String ()}, opt ... )
2020-06-18 14:53:11 -04:00
if err != nil {
return nil , fmt . Errorf ( "list projects: %w" , err )
}
return projects , nil
}
2020-07-11 12:00:49 -04:00
// ListOrgs and supports the WithLimit option.
func ( r * Repository ) ListOrgs ( ctx context . Context , opt ... Option ) ([] * Scope , error ) {
var orgs [] * Scope
err := r . list ( ctx , & orgs , "parent_id = ? and type = ?" , [] interface {}{ "global" , scope . Org . String ()}, opt ... )
2020-06-18 14:53:11 -04:00
if err != nil {
2020-07-11 12:00:49 -04:00
return nil , fmt . Errorf ( "list orgs: %w" , err )
2020-06-18 14:53:11 -04:00
}
2020-07-11 12:00:49 -04:00
return orgs , nil
2020-06-18 14:53:11 -04:00
}