Skip to content
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

feat(LH-86969): Generate API token for user in MSP managed tenant #149

Merged
merged 11 commits into from
Nov 1, 2024
Merged
8 changes: 8 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,11 @@ func (c *Client) ReadUsersInMspManagedTenant(ctx context.Context, readInput user
func (c *Client) DeleteUsersInMspManagedTenant(ctx context.Context, deleteInput users.MspDeleteUsersInput) (interface{}, error) {
return users.Delete(ctx, c.client, deleteInput)
}

func (c *Client) GenerateApiTokenForUserInMspManagedTenant(ctx context.Context, generateApiTokenInput users.MspGenerateApiTokenInput) (*users.MspGenerateApiTokenOutput, error) {
return users.GenerateApiToken(ctx, c.client, generateApiTokenInput)
}

func (c *Client) RevokeApiTokenForUserInMspManagedTenant(ctx context.Context, revokeApiTokenInput users.MspRevokeApiTokenInput) (interface{}, error) {
return users.RevokeApiToken(ctx, c.client, revokeApiTokenInput)
}
8 changes: 8 additions & 0 deletions client/internal/url/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ func RevokeApiToken(baseUrl string, tokenId string) string {
return fmt.Sprintf("%s/anubis/rest/v1/oauth/revoke/%s", baseUrl, tokenId)
}

func RevokeApiTokenUsingPublicApi(baseUrl string) string {
return fmt.Sprintf("%s/api/rest/v1/token/revoke", baseUrl)
}

func ReadTokenInfo(baseUrl string) string {
return fmt.Sprintf("%s/anubis/rest/v1/oauth/check_token", baseUrl)
}
Expand Down Expand Up @@ -229,3 +233,7 @@ func GetUsersInMspManagedTenant(baseUrl string, tenantUid string, limit int, off
func DeleteUsersInMspManagedTenant(baseUrl string, tenantUid string) string {
return fmt.Sprintf("%s/api/rest/v1/msp/tenants/%s/users/delete", baseUrl, tenantUid)
}

func GenerateApiTokenForUserInMspManagedTenant(baseUrl string, tenantUid string, userUid string) string {
return fmt.Sprintf("%s/api/rest/v1/msp/tenants/%s/users/%s/token", baseUrl, tenantUid, userUid)
}
15 changes: 13 additions & 2 deletions client/msp/users/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,24 @@ func Create(ctx context.Context, client http.Client, createInp MspUsersInput) (*
}
}

readUserDetrails, err := ReadCreatedUsersInTenant(ctx, client, createInp)
readUserDetails, err := ReadCreatedUsersInTenant(ctx, client, createInp)
if err != nil {
client.Logger.Println("Failed to read users from tenant after creation")
return nil, &CreateError{
Err: err,
CreatedResourceId: &transaction.EntityUid,
}
}
return readUserDetrails, nil
return readUserDetails, nil
}

func GenerateApiToken(ctx context.Context, client http.Client, generateApiTokenInp MspGenerateApiTokenInput) (*MspGenerateApiTokenOutput, error) {
client.Logger.Printf("Generating API token for user %s in tenant %s", generateApiTokenInp.UserUid, generateApiTokenInp.TenantUid)
genApiTokenUrl := url.GenerateApiTokenForUserInMspManagedTenant(client.BaseUrl(), generateApiTokenInp.TenantUid, generateApiTokenInp.UserUid)
var mspApiTokenOutput MspGenerateApiTokenOutput
req := client.NewPost(ctx, genApiTokenUrl, nil)
if err := req.Send(&mspApiTokenOutput); err != nil {
return nil, err
}
return &mspApiTokenOutput, nil
}
13 changes: 13 additions & 0 deletions client/msp/users/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,16 @@ func Delete(ctx context.Context, client http.Client, deleteInp MspDeleteUsersInp

return nil, nil
}

func RevokeApiToken(ctx context.Context, client http.Client, revokeInput MspRevokeApiTokenInput) (interface{}, error) {
revokeTokenUrl := url.RevokeApiTokenUsingPublicApi(client.BaseUrl())
client.Logger.Printf("Revoking api token at %s\n", revokeTokenUrl)
req := client.NewPost(ctx, revokeTokenUrl, nil)
// overwrite token in header with API token for the user that we are revoking
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", revokeInput.ApiToken))
if err := req.Send(&struct{}{}); err != nil {
return nil, err
}

return nil, nil
}
13 changes: 13 additions & 0 deletions client/msp/users/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ type UserPage struct {
Items []UserDetails `json:"items"`
}

type MspGenerateApiTokenInput struct {
TenantUid string `json:"tenantUid"`
UserUid string `json:"userUid"`
}

type MspRevokeApiTokenInput struct {
ApiToken string `json:"apiToken"`
}

type MspGenerateApiTokenOutput struct {
ApiToken string `json:"apiToken"`
}

type CreateError struct {
Err error
CreatedResourceId *string
Expand Down
25 changes: 25 additions & 0 deletions docs/resources/msp_managed_tenant_user_api_token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "cdo_msp_managed_tenant_user_api_token Resource - cdo"
subcategory: ""
description: |-
Provides a resource to generate an API token for a user in an MSP-managed tenant.
---

# cdo_msp_managed_tenant_user_api_token (Resource)

Provides a resource to generate an API token for a user in an MSP-managed tenant.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `tenant_uid` (String) Universally unique identifier of the tenant in which the API token for the user should be generated.
- `user_uid` (String) Universally unique identifier of the user for whom the API token should be generated.

### Read-Only

- `api_token` (String, Sensitive) The generated API token for the user.
3 changes: 2 additions & 1 deletion provider/examples/resources/msp/users/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ You need access to an MSP Portal, and API token for the MSP portal.
- Modify `providers.tf` accordingly.
- Paste CDO API token for an MSP portal into `api_token.txt`
- see https://docs.defenseorchestrator.com/#!c-api-tokens.html for how to generate this.
- Specify the name of a tenant managed by the MSP Portal. You can get the tenant name by going to Settings in the MSP portal.
- Specify the name of a tenant managed by the MSP Portal. You can get the tenant name by going to Settings in the MSP portal.
- To see the generated API token for the created user, run `terraform show -json | jq -r ".values.outputs.api_token.value"`
15 changes: 15 additions & 0 deletions provider/examples/resources/msp/users/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ resource "cdo_msp_managed_tenant_users" "example" {
username = "[email protected]",
roles = ["ROLE_ADMIN"]
api_only_user = false
},
{
username = "api-only-user",
roles = ["ROLE_SUPER_ADMIN"]
api_only_user = true
}
]
}

resource "cdo_msp_managed_tenant_user_api_token" "user_token" {
tenant_uid = data.cdo_msp_managed_tenant.tenant.id
user_uid = cdo_msp_managed_tenant_users.example.users[2].id
}

output "api_token" {
value = cdo_msp_managed_tenant_user_api_token.user_token.api_token
sensitive = true
}
11 changes: 11 additions & 0 deletions provider/internal/msp/msp_tenant_user_api_token/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package msp_tenant_user_api_token

import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

type MspManagedTenantUserApiTokenResourceModel struct {
TenantUid types.String `tfsdk:"tenant_uid"`
UserUid types.String `tfsdk:"user_uid"`
ApiToken types.String `tfsdk:"api_token"` // Additional field
}
114 changes: 114 additions & 0 deletions provider/internal/msp/msp_tenant_user_api_token/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package msp_tenant_user_api_token

import (
"context"
"fmt"
cdoClient "github.com/CiscoDevnet/terraform-provider-cdo/go-client"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/msp/users"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

func NewMspManagedTenantUserApiTokenResource() resource.Resource {
return &MspManagedTenantUserApiTokenResource{}
}

type MspManagedTenantUserApiTokenResource struct {
client *cdoClient.Client
}

func (m *MspManagedTenantUserApiTokenResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = request.ProviderTypeName + "_msp_managed_tenant_user_api_token"
}

func (m *MspManagedTenantUserApiTokenResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
response.Schema = schema.Schema{
MarkdownDescription: "Provides a resource to generate an API token for a user in an MSP-managed tenant.",
Attributes: map[string]schema.Attribute{
"tenant_uid": schema.StringAttribute{
MarkdownDescription: "Universally unique identifier of the tenant in which the API token for the user should be generated.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"user_uid": schema.StringAttribute{
MarkdownDescription: "Universally unique identifier of the user for whom the API token should be generated.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"api_token": schema.StringAttribute{
MarkdownDescription: "The generated API token for the user.",
Computed: true,
Sensitive: true,
},
},
}
}

func (m *MspManagedTenantUserApiTokenResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var planData MspManagedTenantUserApiTokenResourceModel
response.Diagnostics.Append(request.Plan.Get(ctx, &planData)...)
if response.Diagnostics.HasError() {
return
}
tflog.Debug(ctx, fmt.Sprintf("Generating an API token for a user %s in the tenant %s...", planData.UserUid, planData.TenantUid))

apiTokenInfo, err := m.client.GenerateApiTokenForUserInMspManagedTenant(ctx, users.MspGenerateApiTokenInput{
UserUid: planData.UserUid.ValueString(),
TenantUid: planData.TenantUid.ValueString(),
})
if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("failed to generate API token for user %s in MSP-managed tenant %s", planData.UserUid, planData.TenantUid), err.Error())
return
}

planData.ApiToken = types.StringValue(apiTokenInfo.ApiToken)
response.Diagnostics.Append(response.State.Set(ctx, &planData)...)
}

func (m *MspManagedTenantUserApiTokenResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
tflog.Debug(ctx, "This is a NOOP")
}

func (m *MspManagedTenantUserApiTokenResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
}

func (m *MspManagedTenantUserApiTokenResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
var stateData MspManagedTenantUserApiTokenResourceModel
response.Diagnostics.Append(request.State.Get(ctx, &stateData)...)
if response.Diagnostics.HasError() {
return
}
tflog.Debug(ctx, fmt.Sprintf("Revoking an API token for a user %s in the tenant %s...", stateData.UserUid, stateData.TenantUid))

_, err := m.client.RevokeApiTokenForUserInMspManagedTenant(ctx, users.MspRevokeApiTokenInput{ApiToken: stateData.ApiToken.ValueString()})
if err != nil {
response.Diagnostics.AddError("Failed to revoke API token for user", err.Error())
}
}

func (m *MspManagedTenantUserApiTokenResource) Configure(ctx context.Context, req resource.ConfigureRequest, res *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*cdoClient.Client)

if !ok {
res.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *cdoClient.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

m.client = client
}
2 changes: 2 additions & 0 deletions provider/internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"fmt"
"github.com/CiscoDevnet/terraform-provider-cdo/internal/msp/msp_tenant"
"github.com/CiscoDevnet/terraform-provider-cdo/internal/msp/msp_tenant_user_api_token"
"github.com/CiscoDevnet/terraform-provider-cdo/internal/msp/msp_tenant_users"
"os"

Expand Down Expand Up @@ -176,6 +177,7 @@ func (p *CdoProvider) Resources(ctx context.Context) []func() resource.Resource
tenantsettings.NewTenantSettingsResource,
msp_tenant.NewTenantResource,
msp_tenant_users.NewMspManagedTenantUsersResource,
msp_tenant_user_api_token.NewMspManagedTenantUserApiTokenResource,
}
}

Expand Down
Loading