-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add access control calls #329
Merged
Merged
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
a82d5d6
Move function to help isolating tags
7241c21
Add access control for vApp and catalog
912b0e4
Improve ID comparison for user and group
1a81c32
Merge branch 'master' into access-control
4a9614d
Add VM and vApp to search query engine
a2803d1
Add methods GetVappList, QueryVappList
52657b6
Add tests for GetVappList, QueryVappList, search
4de23ed
fix code formatting
a742d10
Fix comments
78765dc
Merge branch 'master' into access-control
26a9372
add comment to LocalSubject type
0e62e1e
Merge branch 'master' into access-control
a37b4a5
Introduce tenant context for access control ops
6343440
Add org info cache to avoid repeating calls
94736dd
Merge branch 'master' into access-control
8c4a3a8
Add error checking to IsShared methods
eb9e097
format code
1627586
[skip ci] update CHANGELOG
6910170
Add total to removeLeftoverEntities item number
a3e5592
Unify code for getOrgInfo
75a0581
Add tests for getOrgInfo
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,362 @@ | ||
/* | ||
* Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. | ||
*/ | ||
|
||
package govcd | ||
|
||
import ( | ||
"bytes" | ||
"encoding/xml" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/vmware/go-vcloud-director/v2/types/v56" | ||
) | ||
|
||
// orgInfoType is the basic information about an organization (needed for tenant context) | ||
type orgInfoType struct { | ||
id string | ||
name string | ||
} | ||
|
||
// orgInfoCache is a cache to save org information, avoid repeated calls to compute the same result. | ||
// The keys to this map are the requesting objects IDs. | ||
var orgInfoCache = make(map[string]orgInfoType) | ||
|
||
// GetAccessControl retrieves the access control information for the requested entity | ||
func (client Client) GetAccessControl(href, entityType, entityName string, headerValues map[string]string) (*types.ControlAccessParams, error) { | ||
|
||
href += "/controlAccess" | ||
var controlAccess types.ControlAccessParams | ||
|
||
acUrl, err := url.ParseRequestURI(href) | ||
if err != nil { | ||
return nil, fmt.Errorf("[client.GetAccessControl] error parsing HREF %s: %s", href, err) | ||
} | ||
var additionalHeader = make(http.Header) | ||
|
||
if len(headerValues) > 0 { | ||
for k, v := range headerValues { | ||
additionalHeader.Add(k, v) | ||
} | ||
} | ||
req := client.newRequest( | ||
nil, // params | ||
nil, // notEncodedParams | ||
http.MethodGet, // method | ||
*acUrl, // reqUrl | ||
nil, // body | ||
client.APIVersion, // apiVersion | ||
additionalHeader, // additionalHeader | ||
) | ||
|
||
resp, err := checkResp(client.Http.Do(req)) | ||
if err != nil { | ||
return nil, fmt.Errorf("[client.GetAccessControl] error checking response to request %s: %s", href, err) | ||
} | ||
if resp == nil { | ||
return nil, fmt.Errorf("[client.GetAccessControl] nil response received") | ||
} | ||
if err = decodeBody(types.BodyTypeXML, resp, &controlAccess); err != nil { | ||
return nil, fmt.Errorf("[client.GetAccessControl] error decoding response: %s", err) | ||
} | ||
|
||
return &controlAccess, nil | ||
} | ||
|
||
// SetAccessControl changes the access control information for this entity | ||
// There are two ways of setting the access: | ||
// with accessControl.IsSharedToEveryone = true we give access to everyone | ||
// with accessControl.IsSharedToEveryone = false, accessControl.AccessSettings defines which subjects can access the vApp | ||
// For each setting we must provide: | ||
// * The subject (HREF and Type are mandatory) | ||
// * The access level (one of ReadOnly, Change, FullControl) | ||
func (client *Client) SetAccessControl(accessControl *types.ControlAccessParams, href, entityType, entityName string, headerValues map[string]string) error { | ||
|
||
href += "/action/controlAccess" | ||
// Make sure that subjects in the setting list are used only once | ||
if accessControl.AccessSettings != nil && len(accessControl.AccessSettings.AccessSetting) > 0 { | ||
if accessControl.IsSharedToEveryone { | ||
return fmt.Errorf("[client.SetAccessControl] can't set IsSharedToEveryone and AccessSettings at the same time for %s %s (%s)", entityType, entityName, href) | ||
} | ||
var used = make(map[string]bool) | ||
for _, setting := range accessControl.AccessSettings.AccessSetting { | ||
_, seen := used[setting.Subject.HREF] | ||
if seen { | ||
return fmt.Errorf("[client.SetAccessControl] subject %s (%s) used more than once", setting.Subject.Name, setting.Subject.HREF) | ||
} | ||
used[setting.Subject.HREF] = true | ||
if setting.Subject.Type == "" { | ||
return fmt.Errorf("[client.SetAccessControl] subject %s (%s) has no type defined", setting.Subject.Name, setting.Subject.HREF) | ||
} | ||
} | ||
} | ||
|
||
accessControl.Xmlns = types.XMLNamespaceVCloud | ||
queryUrl, err := url.ParseRequestURI(href) | ||
if err != nil { | ||
return fmt.Errorf("[client.SetAccessControl] error parsing HREF %s: %s", href, err) | ||
} | ||
|
||
var header = make(http.Header) | ||
if len(headerValues) > 0 { | ||
for k, v := range headerValues { | ||
header.Add(k, v) | ||
} | ||
} | ||
|
||
marshaledXml, err := xml.MarshalIndent(accessControl, " ", " ") | ||
if err != nil { | ||
return fmt.Errorf("[client.SetAccessControl] error marshalling xml data: %s", err) | ||
} | ||
body := bytes.NewBufferString(xml.Header + string(marshaledXml)) | ||
|
||
req := client.newRequest( | ||
nil, // params | ||
nil, // notEncodedParams | ||
http.MethodPost, // method | ||
*queryUrl, // reqUrl | ||
body, // body | ||
client.APIVersion, // apiVersion | ||
header, // additionalHeader | ||
) | ||
|
||
resp, err := checkResp(client.Http.Do(req)) | ||
|
||
if err != nil { | ||
return fmt.Errorf("[client.SetAccessControl] error checking response to HREF %s: %s", href, err) | ||
} | ||
if resp == nil { | ||
return fmt.Errorf("[client.SetAccessControl] nil response received") | ||
} | ||
_, err = checkResp(resp, err) | ||
return err | ||
} | ||
|
||
// GetAccessControl retrieves the access control information for this vApp | ||
func (vapp VApp) GetAccessControl(useTenantContext bool) (*types.ControlAccessParams, error) { | ||
|
||
if vapp.VApp.HREF == "" { | ||
return nil, fmt.Errorf("vApp HREF is empty") | ||
} | ||
// if useTenantContext is false, we use an empty header (= default behavior) | ||
// if it is true, we use a header populated with tenant context values | ||
accessControlHeader, err := vapp.getAccessControlHeader(useTenantContext) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return vapp.client.GetAccessControl(vapp.VApp.HREF, "vApp", vapp.VApp.Name, accessControlHeader) | ||
} | ||
|
||
// SetAccessControl changes the access control information for this vApp | ||
func (vapp VApp) SetAccessControl(accessControl *types.ControlAccessParams, useTenantContext bool) error { | ||
|
||
if vapp.VApp.HREF == "" { | ||
return fmt.Errorf("vApp HREF is empty") | ||
} | ||
|
||
// if useTenantContext is false, we use an empty header (= default behavior) | ||
// if it is true, we use a header populated with tenant context values | ||
accessControlHeader, err := vapp.getAccessControlHeader(useTenantContext) | ||
if err != nil { | ||
return err | ||
} | ||
return vapp.client.SetAccessControl(accessControl, vapp.VApp.HREF, "vApp", vapp.VApp.Name, accessControlHeader) | ||
|
||
} | ||
|
||
// RemoveAccessControl is a shortcut to SetAccessControl with all access disabled | ||
func (vapp VApp) RemoveAccessControl(useTenantContext bool) error { | ||
return vapp.SetAccessControl(&types.ControlAccessParams{IsSharedToEveryone: false}, useTenantContext) | ||
} | ||
|
||
// IsShared shows whether a vApp is shared or not, regardless of the number of subjects sharing it | ||
func (vapp VApp) IsShared(useTenantContext bool) (bool, error) { | ||
settings, err := vapp.GetAccessControl(useTenantContext) | ||
if err != nil { | ||
return false, err | ||
} | ||
if settings.IsSharedToEveryone { | ||
return true, nil | ||
} | ||
return settings.AccessSettings != nil, nil | ||
} | ||
|
||
// GetAccessControl retrieves the access control information for this catalog | ||
func (adminCatalog AdminCatalog) GetAccessControl(useTenantContext bool) (*types.ControlAccessParams, error) { | ||
|
||
if adminCatalog.AdminCatalog.HREF == "" { | ||
return nil, fmt.Errorf("catalog HREF is empty") | ||
} | ||
href := strings.Replace(adminCatalog.AdminCatalog.HREF, "/admin/", "/", 1) | ||
|
||
// if useTenantContext is false, we use an empty header (= default behavior) | ||
// if it is true, we use a header populated with tenant context values | ||
accessControlHeader, err := adminCatalog.getAccessControlHeader(useTenantContext) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return adminCatalog.client.GetAccessControl(href, "catalog", adminCatalog.AdminCatalog.Name, accessControlHeader) | ||
} | ||
|
||
// SetAccessControl changes the access control information for this catalog | ||
func (adminCatalog AdminCatalog) SetAccessControl(accessControl *types.ControlAccessParams, useTenantContext bool) error { | ||
|
||
if adminCatalog.AdminCatalog.HREF == "" { | ||
return fmt.Errorf("catalog HREF is empty") | ||
} | ||
href := strings.Replace(adminCatalog.AdminCatalog.HREF, "/admin/", "/", 1) | ||
|
||
// if useTenantContext is false, we use an empty header (= default behavior) | ||
// if it is true, we use a header populated with tenant context values | ||
accessControlHeader, err := adminCatalog.getAccessControlHeader(useTenantContext) | ||
if err != nil { | ||
return err | ||
} | ||
return adminCatalog.client.SetAccessControl(accessControl, href, "catalog", adminCatalog.AdminCatalog.Name, accessControlHeader) | ||
} | ||
|
||
// RemoveAccessControl is a shortcut to SetAccessControl with all access disabled | ||
func (adminCatalog AdminCatalog) RemoveAccessControl(useTenantContext bool) error { | ||
return adminCatalog.SetAccessControl(&types.ControlAccessParams{IsSharedToEveryone: false}, useTenantContext) | ||
} | ||
|
||
// IsShared shows whether a catalog is shared or not, regardless of the number of subjects sharing it | ||
func (adminCatalog AdminCatalog) IsShared(useTenantContext bool) (bool, error) { | ||
settings, err := adminCatalog.GetAccessControl(useTenantContext) | ||
if err != nil { | ||
dataclouder marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return false, err | ||
} | ||
if settings.IsSharedToEveryone { | ||
return true, nil | ||
} | ||
return settings.AccessSettings != nil, nil | ||
} | ||
|
||
// GetVappAccessControl is a convenience method to retrieve access control for a vApp | ||
// from a VDC. | ||
// The input variable vappIdentifier can be either the vApp name or its ID | ||
func (vdc *Vdc) GetVappAccessControl(vappIdentifier string, useTenantContext bool) (*types.ControlAccessParams, error) { | ||
vapp, err := vdc.GetVAppByNameOrId(vappIdentifier, true) | ||
if err != nil { | ||
return nil, fmt.Errorf("error retrieving vApp %s: %s", vappIdentifier, err) | ||
} | ||
return vapp.GetAccessControl(useTenantContext) | ||
} | ||
|
||
// GetCatalogAccessControl is a convenience method to retrieve access control for a catalog | ||
// from an organization. | ||
// The input variable catalogIdentifier can be either the catalog name or its ID | ||
func (org *AdminOrg) GetCatalogAccessControl(catalogIdentifier string, useTenantContext bool) (*types.ControlAccessParams, error) { | ||
catalog, err := org.GetAdminCatalogByNameOrId(catalogIdentifier, true) | ||
if err != nil { | ||
return nil, fmt.Errorf("error retrieving catalog %s: %s", catalogIdentifier, err) | ||
} | ||
return catalog.GetAccessControl(useTenantContext) | ||
} | ||
|
||
// GetCatalogAccessControl is a convenience method to retrieve access control for a catalog | ||
// from an organization. | ||
// The input variable catalogIdentifier can be either the catalog name or its ID | ||
func (org *Org) GetCatalogAccessControl(catalogIdentifier string, useTenantContext bool) (*types.ControlAccessParams, error) { | ||
catalog, err := org.GetCatalogByNameOrId(catalogIdentifier, true) | ||
if err != nil { | ||
return nil, fmt.Errorf("error retrieving catalog %s: %s", catalogIdentifier, err) | ||
} | ||
return catalog.GetAccessControl(useTenantContext) | ||
} | ||
|
||
// GetAccessControl retrieves the access control information for this catalog | ||
func (catalog Catalog) GetAccessControl(useTenantContext bool) (*types.ControlAccessParams, error) { | ||
|
||
if catalog.Catalog.HREF == "" { | ||
return nil, fmt.Errorf("catalog HREF is empty") | ||
} | ||
href := strings.Replace(catalog.Catalog.HREF, "/admin/", "/", 1) | ||
accessControlHeader, err := catalog.getAccessControlHeader(useTenantContext) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return catalog.client.GetAccessControl(href, "catalog", catalog.Catalog.Name, accessControlHeader) | ||
} | ||
|
||
// SetAccessControl changes the access control information for this catalog | ||
func (catalog Catalog) SetAccessControl(accessControl *types.ControlAccessParams, useTenantContext bool) error { | ||
|
||
if catalog.Catalog.HREF == "" { | ||
return fmt.Errorf("catalog HREF is empty") | ||
} | ||
|
||
href := strings.Replace(catalog.Catalog.HREF, "/admin/", "/", 1) | ||
|
||
// if useTenantContext is false, we use an empty header (= default behavior) | ||
// if it is true, we use a header populated with tenant context values | ||
accessControlHeader, err := catalog.getAccessControlHeader(useTenantContext) | ||
if err != nil { | ||
return err | ||
} | ||
return catalog.client.SetAccessControl(accessControl, href, "catalog", catalog.Catalog.Name, accessControlHeader) | ||
} | ||
|
||
// RemoveAccessControl is a shortcut to SetAccessControl with all access disabled | ||
func (catalog Catalog) RemoveAccessControl(useTenantContext bool) error { | ||
return catalog.SetAccessControl(&types.ControlAccessParams{IsSharedToEveryone: false}, useTenantContext) | ||
} | ||
|
||
// IsShared shows whether a catalog is shared or not, regardless of the number of subjects sharing it | ||
func (catalog Catalog) IsShared(useTenantContext bool) (bool, error) { | ||
settings, err := catalog.GetAccessControl(useTenantContext) | ||
if err != nil { | ||
dataclouder marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return false, err | ||
} | ||
if settings.IsSharedToEveryone { | ||
return true, nil | ||
} | ||
return settings.AccessSettings != nil, nil | ||
} | ||
|
||
// getAccessControlHeader builds the data needed to set the header when tenant context is required. | ||
// If useTenantContext is false, it returns an empty map. | ||
// Otherwise, it finds the Org ID and name (going up in the hierarchy through the VDC) | ||
// and creates the header data | ||
func (vapp *VApp) getAccessControlHeader(useTenantContext bool) (map[string]string, error) { | ||
if !useTenantContext { | ||
return map[string]string{}, nil | ||
} | ||
orgInfo, err := vapp.getOrgInfo() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return map[string]string{types.HeaderTenantContext: orgInfo.id, types.HeaderAuthContext: orgInfo.name}, nil | ||
} | ||
|
||
// getAccessControlHeader builds the data needed to set the header when tenant context is required. | ||
// If useTenantContext is false, it returns an empty map. | ||
// Otherwise, it finds the Org ID and name and creates the header data | ||
func (catalog *Catalog) getAccessControlHeader(useTenantContext bool) (map[string]string, error) { | ||
if !useTenantContext { | ||
return map[string]string{}, nil | ||
} | ||
orgInfo, err := catalog.getOrgInfo() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return map[string]string{types.HeaderTenantContext: orgInfo.id, types.HeaderAuthContext: orgInfo.name}, nil | ||
} | ||
|
||
// getAccessControlHeader builds the data needed to set the header when tenant context is required. | ||
// If useTenantContext is false, it returns an empty map. | ||
// Otherwise, it finds the Org ID and name and creates the header data | ||
func (adminCatalog *AdminCatalog) getAccessControlHeader(useTenantContext bool) (map[string]string, error) { | ||
if !useTenantContext { | ||
return map[string]string{}, nil | ||
} | ||
orgInfo, err := adminCatalog.getOrgInfo() | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
return map[string]string{types.HeaderTenantContext: orgInfo.id, types.HeaderAuthContext: orgInfo.name}, nil | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe worth adding a brief mention about
useTenantContext
feature.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added