From 959a973af1ad531b5882756546eaa2b8d975d0ab Mon Sep 17 00:00:00 2001 From: Charles Kenney Date: Thu, 4 Feb 2021 15:43:13 -0500 Subject: [PATCH] add user grants --- account_user_grants.go | 99 +++++++++++ client.go | 3 + resources.go | 2 + test/integration/account_user_grants_test.go | 55 ++++++ .../fixtures/TestUpdateUserGrants.yaml | 166 ++++++++++++++++++ 5 files changed, 325 insertions(+) create mode 100644 account_user_grants.go create mode 100644 test/integration/account_user_grants_test.go create mode 100644 test/integration/fixtures/TestUpdateUserGrants.yaml diff --git a/account_user_grants.go b/account_user_grants.go new file mode 100644 index 000000000..cf0dd36b2 --- /dev/null +++ b/account_user_grants.go @@ -0,0 +1,99 @@ +package linodego + +import ( + "context" + "encoding/json" +) + +type GrantPermissionLevel string + +const ( + AccessLevelReadOnly GrantPermissionLevel = "read_only" + AccessLevelReadWrite GrantPermissionLevel = "read_write" +) + +type GlobalUserGrants struct { + AccountAccess GrantPermissionLevel `json:"account_access"` + AddDomains bool `json:"add_domains"` + AddImages bool `json:"add_images"` + AddLinodes bool `json:"add_linodes"` + AddLongview bool `json:"add_longview"` + AddNodeBalancers bool `json:"add_nodebalancers"` + AddStackScripts bool `json:"add_stackscripts"` + AddVolumes bool `json:"add_volumes"` + CancelAccount bool `json:"cancel_account"` + LongviewSubscription bool `json:"longview_subscription"` +} + +type EntityUserGrant struct { + ID int `json:"id"` + Permissions GrantPermissionLevel `json:"permissions"` +} + +type GrantedEntity struct { + ID int `json:"id"` + Label string `json:"label"` + Permissions GrantPermissionLevel `json:"permissions"` +} + +type UserGrants struct { + Domain []GrantedEntity `json:"domain"` + Image []GrantedEntity `json:"image"` + Linode []GrantedEntity `json:"linode"` + Longview []GrantedEntity `json:"longview"` + NodeBalancer []GrantedEntity `json:"nodebalancer"` + StackScript []GrantedEntity `json:"stackscript"` + Volume []GrantedEntity `json:"volume"` + + Global GlobalUserGrants `json:"global"` +} + +type UserGrantsUpdateOptions struct { + Domain []EntityUserGrant `json:"domain,omitempty"` + Image []EntityUserGrant `json:"image,omitempty"` + Linode []EntityUserGrant `json:"linode,omitempty"` + Longview []EntityUserGrant `json:"longview,omitempty"` + NodeBalancer []EntityUserGrant `json:"nodebalancer,omitempty"` + StackScript []EntityUserGrant `json:"stackscript,omitempty"` + Volume []EntityUserGrant `json:"volume,omitempty"` + + Global GlobalUserGrants `json:"global"` +} + +func (c *Client) GetUserGrants(ctx context.Context, username string) (*UserGrants, error) { + e, err := c.UserGrants.endpointWithParams(username) + if err != nil { + return nil, err + } + + r, err := coupleAPIErrors(c.R(ctx).SetResult(&UserGrants{}).Get(e)) + if err != nil { + return nil, err + } + + return r.Result().(*UserGrants), nil +} + +func (c *Client) UpdateUserGrants(ctx context.Context, username string, updateOpts UserGrantsUpdateOptions) (*UserGrants, error) { + var body string + + e, err := c.UserGrants.endpointWithParams(username) + if err != nil { + return nil, err + } + + req := c.R(ctx).SetResult(&UserGrants{}) + + if bodyData, err := json.Marshal(updateOpts); err == nil { + body = string(bodyData) + } else { + return nil, NewError(err) + } + + r, err := coupleAPIErrors(req.SetBody(body).Put(e)) + if err != nil { + return nil, err + } + + return r.Result().(*UserGrants), nil +} diff --git a/client.go b/client.go index d7edc97f3..f73978d10 100644 --- a/client.go +++ b/client.go @@ -100,6 +100,7 @@ type Client struct { Token *Resource Tokens *Resource Types *Resource + UserGrants *Resource Users *Resource VLANs *Resource Volumes *Resource @@ -327,6 +328,7 @@ func addResources(client *Client) { ticketsName: NewResource(client, ticketsName, ticketsEndpoint, false, Ticket{}, TicketsPagedResponse{}), tokensName: NewResource(client, tokensName, tokensEndpoint, false, Token{}, TokensPagedResponse{}), typesName: NewResource(client, typesName, typesEndpoint, false, LinodeType{}, LinodeTypesPagedResponse{}), + userGrantsName: NewResource(client, typesName, userGrantsEndpoint, true, UserGrants{}, nil), usersName: NewResource(client, usersName, usersEndpoint, false, User{}, UsersPagedResponse{}), vlansName: NewResource(client, vlansName, vlansEndpoint, false, VLAN{}, VLANsPagedResponse{}), volumesName: NewResource(client, volumesName, volumesEndpoint, false, Volume{}, VolumesPagedResponse{}), @@ -380,6 +382,7 @@ func addResources(client *Client) { client.Tickets = resources[ticketsName] client.Tokens = resources[tokensName] client.Types = resources[typesName] + client.UserGrants = resources[userGrantsName] client.Users = resources[usersName] client.VLANs = resources[vlansName] client.Volumes = resources[volumesName] diff --git a/resources.go b/resources.go index 5d9dcb685..b0bfccf0f 100644 --- a/resources.go +++ b/resources.go @@ -59,6 +59,7 @@ const ( ticketsName = "tickets" tokensName = "tokens" typesName = "types" + userGrantsName = "usergrants" usersName = "users" volumesName = "volumes" vlansName = "vlans" @@ -116,6 +117,7 @@ const ( ticketsEndpoint = "support/tickets" tokensEndpoint = "profile/tokens" typesEndpoint = "linode/types" + userGrantsEndpoint = "account/users/{{ .ID }}/grants" usersEndpoint = "account/users" vlansEndpoint = "networking/vlans" volumesEndpoint = "volumes" diff --git a/test/integration/account_user_grants_test.go b/test/integration/account_user_grants_test.go new file mode 100644 index 000000000..3fc6594ce --- /dev/null +++ b/test/integration/account_user_grants_test.go @@ -0,0 +1,55 @@ +package integration + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/linode/linodego" +) + +func TestUpdateUserGrants(t *testing.T) { + username := usernamePrefix + "updateusergrants" + + client, _, teardown := setupUser(t, []userModifier{ + func(createOpts *linodego.UserCreateOptions) { + createOpts.Username = username + createOpts.Email = usernamePrefix + "updateusergrants@example.com" + createOpts.Restricted = true + }, + }, "fixtures/TestUpdateUserGrants") + defer teardown() + + globalGrants := linodego.GlobalUserGrants{ + AccountAccess: linodego.AccessLevelReadOnly, + AddDomains: false, + AddImages: true, + AddLinodes: false, + AddLongview: true, + AddNodeBalancers: false, + AddStackScripts: true, + AddVolumes: true, + CancelAccount: false, + } + + expectedUserGrants := linodego.UserGrants{ + Global: globalGrants, + Domain: []linodego.GrantedEntity{}, + Image: []linodego.GrantedEntity{}, + Linode: []linodego.GrantedEntity{}, + Longview: []linodego.GrantedEntity{}, + NodeBalancer: []linodego.GrantedEntity{}, + StackScript: []linodego.GrantedEntity{}, + Volume: []linodego.GrantedEntity{}, + } + grants, err := client.UpdateUserGrants(context.TODO(), username, linodego.UserGrantsUpdateOptions{ + Global: globalGrants, + }) + if err != nil { + t.Fatalf("failed to get user grants: %s", err) + } + + if !cmp.Equal(grants, &expectedUserGrants) { + t.Errorf("expected rules to match test rules, but got diff: %s", cmp.Diff(grants, &expectedUserGrants)) + } +} diff --git a/test/integration/fixtures/TestUpdateUserGrants.yaml b/test/integration/fixtures/TestUpdateUserGrants.yaml new file mode 100644 index 000000000..f8c14778f --- /dev/null +++ b/test/integration/fixtures/TestUpdateUserGrants.yaml @@ -0,0 +1,166 @@ +--- +version: 1 +interactions: +- request: + body: '{"username":"linodegotest-updateusergrants","email":"linodegotest-updateusergrants@example.com","restricted":true}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego https://github.com/linode/linodego + url: https://api.linode.com/v4beta/account/users + method: POST + response: + body: '{"username": "linodegotest-updateusergrants", "email": "linodegotest-updateusergrants@example.com", "restricted": true, "ssh_keys": [], "tfa_enabled": false}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - "157" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - account:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"global":{"account_access":"read_only","add_domains":false,"add_images":true,"add_linodes":false,"add_longview":true,"add_nodebalancers":false,"add_stackscripts":true,"add_volumes":true,"cancel_account":false,"longview_subscription":false}}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego https://github.com/linode/linodego + url: https://api.linode.com/v4beta/account/users/linodegotest-updateusergrants/grants + method: PUT + response: + body: '{"linode": [], "nodebalancer": [], "domain": [], "stackscript": [], "longview": [], "image": [], "volume": [], "global": {"add_domains": false, "add_linodes": false, "add_longview": true, "longview_subscription": false, "add_stackscripts": true, "add_nodebalancers": false, "add_images": true, "add_volumes": true, "account_access": "read_only", "cancel_account": false}}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - account:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego https://github.com/linode/linodego + url: https://api.linode.com/v4beta/account/users/linodegotest-updateusergrants + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - account:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: ""