├── .github ├── CODEOWNERS └── workflows │ ├── golangci-lint.yml │ └── release.yml ├── docs ├── data-sources │ ├── application.md │ ├── idp.md │ ├── application_role.md │ ├── user_group_membership.md │ ├── tenant.md │ ├── sms_message_template.md │ ├── email.md │ ├── application_oauth_scope.md │ ├── form.md │ ├── twilio_messenger.md │ ├── form_field.md │ ├── generic_messenger.md │ ├── generic_connector.md │ ├── lambda.md │ ├── consent.md │ ├── ldap_connector.md │ └── user.md ├── resources │ ├── reactor.md │ ├── user_group_membership.md │ ├── form.md │ ├── group.md │ ├── application_role.md │ ├── entity_type.md │ ├── key.md │ ├── sms_message_template.md │ ├── twilio_messenger.md │ ├── entity_type_permision.md │ ├── registration.md │ ├── form_field.md │ ├── lambda.md │ ├── imported_key.md │ ├── generic_connector.md │ ├── application_oauth_scope.md │ ├── generic_messenger.md │ ├── user_action.md │ ├── entity_grant.md │ ├── email.md │ ├── api_key.md │ ├── ldap_connector.md │ ├── consent.md │ ├── entity.md │ ├── idp_xbox.md │ ├── idp_twitch.md │ ├── idp_sony_psn.md │ ├── idp_apple.md │ ├── idp_steam.md │ ├── idp_samlv2_idp_initiated.md │ └── system_configuration.md ├── index.md └── guides │ └── handling_default_resources.md ├── .gitignore ├── main.go ├── .golangci.yml ├── fusionauth ├── client.go ├── datasource_fusionauth_tenant.go ├── helpers_validate.go ├── datasource_fusionauth_application_role.go ├── keyhelpers.go ├── helpers_diff_suppress.go ├── datasource_fusionauth_application.go ├── datasource_fusionauth_user_group_membership.go ├── helpers_test.go ├── datasource_fusionauth_idp.go ├── resource_fusionauth_form_field_test.go ├── resource_fusionauth_application_crud.go ├── datasource_fusionauth_form.go ├── resource_fusionauth_user_schema_test.go ├── datasource_fusionauth_application_oauth_scope.go ├── resource_fusionauth_reactor.go ├── resource_fusionauth_key.go ├── provider.go ├── provider_test.go ├── datasource_fusionauth_lambda.go ├── datasource_fusionauth_email.go ├── datasource_fusionauth_sms_message_template.go ├── idphelpers.go ├── resource_fusionauth_twilio_messenger_test.go └── resource_fusionauth_sms_message_template_test.go ├── CONTRIBUTING.md ├── .goreleaser.yml ├── README.md └── go.mod /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a managed file. Manual changes will be overwritten. 2 | # https://github.com/FusionAuth/fusionauth-public-repos/ 3 | 4 | .github/ @fusionauth/owners @fusionauth/platform 5 | -------------------------------------------------------------------------------- /docs/data-sources/application.md: -------------------------------------------------------------------------------- 1 | # Application Resource 2 | 3 | [Applications API](https://fusionauth.io/docs/v1/tech/apis/applications) 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | data "fusionauth_application" "FusionAuth"{ 9 | name = "FusionAuth" 10 | } 11 | ``` 12 | 13 | ## Argument Reference 14 | 15 | * `name` - (Required) The name of the Application. 16 | -------------------------------------------------------------------------------- /docs/data-sources/idp.md: -------------------------------------------------------------------------------- 1 | # Application Resource 2 | 3 | [Identity Providers API](https://fusionauth.io/docs/v1/tech/apis/identity-providers/) 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | data "fusionauth_idp" "FusionAuth"{ 9 | name = "Apple" 10 | type = "Apple" 11 | } 12 | ``` 13 | 14 | ## Argument Reference 15 | 16 | * `name` - (Optional) The name of the identity provider. This is only used for display purposes. Will be the type for types: `Apple`, `Facebook`, `Google`, `HYPR`, `Twitter` 17 | * `type` - (Optional) The type of the identity provider. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | *.log 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | # Folders 19 | .idea 20 | .vscode 21 | bin 22 | vendor 23 | env 24 | 25 | # Files 26 | .DS_Store 27 | .AppleSingle 28 | .AppleDouble 29 | *.sublime-project 30 | *.sublime-workspace 31 | Thumbs.db 32 | coverage.out 33 | __debug_bin -------------------------------------------------------------------------------- /docs/data-sources/application_role.md: -------------------------------------------------------------------------------- 1 | # Application Role Resource 2 | 3 | This Resource is used to create a role for an Application. 4 | 5 | [Application Roles API](https://fusionauth.io/docs/v1/tech/apis/applications) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_application_role" "admin" { 11 | application_id = data.fusionauth_application.FusionAuth.id 12 | name = "admin" 13 | } 14 | ``` 15 | 16 | ## Argument Reference 17 | 18 | * `application_id` - (Required) ID of the application that this role is for. 19 | * `name` - (Required) The name of the Role. 20 | -------------------------------------------------------------------------------- /docs/resources/reactor.md: -------------------------------------------------------------------------------- 1 | # Reactor Resource 2 | 3 | The Reactor is FusionAuth’s license system. Reactor is used to activate features based upon your licensing tier. 4 | 5 | [Reactor API](https://fusionauth.io/docs/v1/tech/apis/reactor) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_reactor" "reactor" { 11 | license_id = "xyz" 12 | license = "abc" 13 | } 14 | ``` 15 | 16 | ## Argument Reference 17 | 18 | * `license_id` - (Required) The license Id to activate. 19 | 20 | --- 21 | 22 | * `license` - (Optional) The Base64 encoded license value. This value is necessary in an air gapped configuration where outbound network access is not available. 23 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | 8 | "github.com/FusionAuth/terraform-provider-fusionauth/fusionauth" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 10 | ) 11 | 12 | func main() { 13 | var debugMode bool 14 | 15 | flag.BoolVar(&debugMode, "debuggable", false, "set to true to run the provider with support for debuggers like delve") 16 | flag.Parse() 17 | 18 | opts := &plugin.ServeOpts{ProviderFunc: fusionauth.Provider} 19 | 20 | if debugMode { 21 | err := plugin.Debug(context.Background(), "registry.terraform.io/FusionAuth/fusionauth", opts) //nolint:staticcheck 22 | if err != nil { 23 | log.Println(err.Error()) 24 | } 25 | return 26 | } 27 | 28 | plugin.Serve(opts) 29 | } 30 | -------------------------------------------------------------------------------- /docs/resources/user_group_membership.md: -------------------------------------------------------------------------------- 1 | # User Group Membership Resource 2 | 3 | [User Group Membership API](https://fusionauth.io/docs/apis/groups#request-5) 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | resource "fusionauth_user_group_membership" "this" { 9 | group_id = fusionauth_group.this.id 10 | user_id = fusionauth_user.this.id 11 | } 12 | ``` 13 | 14 | ## Argument Reference 15 | 16 | * `group_id` - (Required) The Id of the Group of this membership. 17 | * `user_id` - (Required) "The Id of the User of this membership. 18 | 19 | --- 20 | 21 | * `data` - (Optional) A JSON string that can hold any information about the User for this membership that should be persisted. 22 | * `membership_id` - (Optional) The Id of the User Group Membership. If not provided, a random UUID will be generated. 23 | -------------------------------------------------------------------------------- /docs/data-sources/user_group_membership.md: -------------------------------------------------------------------------------- 1 | # User Group Membership Resource 2 | 3 | [User Group Membership API](https://fusionauth.io/docs/apis/groups#request-5) 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | data "fusionauth_user_group_membership" "this" { 9 | group_id = fusionauth_group.this.id 10 | user_id = fusionauth_user.this.id 11 | } 12 | ``` 13 | 14 | ## Argument Reference 15 | 16 | * `group_id` - (Required) The Id of the Group of this membership. 17 | * `user_id` - (Required) "The Id of the User of this membership. 18 | 19 | ## Attributes Reference 20 | 21 | All of the argument attributes are also exported as result attributes. 22 | 23 | The following additional attributes are exported: 24 | 25 | * `data` - A JSON string that can hold any information about the User for this membership that should be persisted. 26 | * `membership_id` - The Id of the User Group Membership. If not provided, a random UUID will be generated. 27 | -------------------------------------------------------------------------------- /docs/data-sources/tenant.md: -------------------------------------------------------------------------------- 1 | # Tenant Resource 2 | 3 | A FusionAuth Tenant is a named object that represents a discrete namespace for Users, Applications and Groups. A user is unique by email address or username within a tenant. 4 | 5 | Tenants may be useful to support a multi-tenant application where you wish to use a single instance of FusionAuth but require the ability to have duplicate users across the tenants in your own application. In this scenario a user may exist multiple times with the same email address and different passwords across tenants. 6 | 7 | Tenants may also be useful in a test or staging environment to allow multiple users to call APIs and create and modify users without possibility of collision. 8 | 9 | [Tenants API](https://fusionauth.io/docs/v1/tech/apis/tenants) 10 | 11 | ## Example Usage 12 | 13 | ```hcl 14 | data "fusionauth_tenant" "default"{ 15 | name = "Default" 16 | } 17 | ``` 18 | 19 | ## Argument Reference 20 | 21 | * `name` - (Required) The name of the Tenant. 22 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - bodyclose 6 | - dogsled 7 | - errcheck 8 | - gochecknoglobals 9 | - gochecknoinits 10 | - gocognit 11 | - goconst 12 | - gocritic 13 | - gocyclo 14 | - gosec 15 | - govet 16 | - ineffassign 17 | - misspell 18 | - nakedret 19 | - prealloc 20 | - predeclared 21 | - revive 22 | - staticcheck 23 | - unconvert 24 | - unparam 25 | - unused 26 | - whitespace 27 | exclusions: 28 | generated: lax 29 | presets: 30 | - comments 31 | - common-false-positives 32 | - legacy 33 | - std-error-handling 34 | rules: 35 | - linters: 36 | - funlen 37 | - gochecknoglobals 38 | - gochecknoinits 39 | - lll 40 | path: _test\.go 41 | paths: 42 | - third_party$ 43 | - builtin$ 44 | - examples$ 45 | formatters: 46 | enable: 47 | - gofmt 48 | - goimports 49 | exclusions: 50 | generated: lax 51 | paths: 52 | - third_party$ 53 | - builtin$ 54 | - examples$ 55 | -------------------------------------------------------------------------------- /fusionauth/client.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | 10 | "github.com/FusionAuth/go-client/pkg/fusionauth" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 13 | ) 14 | 15 | type Client struct { 16 | FAClient fusionauth.FusionAuthClient 17 | Host string 18 | APIKey string 19 | } 20 | 21 | func configureClient(_ context.Context, data *schema.ResourceData) (client interface{}, diags diag.Diagnostics) { 22 | host := data.Get("host").(string) 23 | apiKey := data.Get("api_key").(string) 24 | 25 | hostURL, err := url.Parse(host) 26 | if err != nil { 27 | diags = append(diags, diag.Diagnostic{ 28 | Severity: diag.Error, 29 | Summary: "Unable to create Fusionauth client", 30 | Detail: fmt.Sprintf("Unable to parse the provided Fusionauth hostname to a URL: %s", err), 31 | }) 32 | return nil, diags 33 | } 34 | 35 | client = Client{ 36 | Host: host, 37 | APIKey: apiKey, 38 | FAClient: *fusionauth.NewClient( 39 | &http.Client{ 40 | Timeout: time.Second * 30, 41 | }, 42 | hostURL, 43 | apiKey, 44 | ), 45 | } 46 | 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_tenant.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FusionAuth/go-client/pkg/fusionauth" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | ) 10 | 11 | func dataSourceTenant() *schema.Resource { 12 | return &schema.Resource{ 13 | ReadContext: dataSourceTenantRead, 14 | Schema: map[string]*schema.Schema{ 15 | "name": { 16 | Type: schema.TypeString, 17 | Required: true, 18 | ForceNew: true, 19 | Description: "The name of the Tenant.", 20 | }, 21 | }, 22 | } 23 | } 24 | 25 | func dataSourceTenantRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 26 | client := i.(Client) 27 | 28 | resp, err := client.FAClient.RetrieveTenants() 29 | if err != nil { 30 | return diag.FromErr(err) 31 | } 32 | if err := checkResponse(resp.StatusCode, nil); err != nil { 33 | return diag.FromErr(err) 34 | } 35 | name := data.Get("name").(string) 36 | var t *fusionauth.Tenant 37 | 38 | for i := range resp.Tenants { 39 | if resp.Tenants[i].Name == name { 40 | t = &resp.Tenants[i] 41 | } 42 | } 43 | if t == nil { 44 | return diag.Errorf("couldn't find tenant %s", name) 45 | } 46 | data.SetId(t.Id) 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /docs/resources/form.md: -------------------------------------------------------------------------------- 1 | # Form Resource 2 | 3 | A FusionAuth Form is a customizable object that contains one-to-many ordered steps. Each step is comprised of one or more Form Fields. 4 | 5 | [Form API](https://fusionauth.io/docs/v1/tech/apis/forms/) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_form" "form" { 11 | data = jsonencode({ 12 | createdBy = "jared@fusionauth.io" 13 | }) 14 | name = "Custom Registration Form" 15 | steps { 16 | fields = ["91909721-7d4f-b110-8f21-cfdee2a1edb8"] 17 | } 18 | steps { 19 | fields = ["8ed89a31-c325-3156-72ed-6e89183af917", "a977cfd4-a9ed-c4cf-650f-f4539268ac38"] 20 | } 21 | } 22 | ``` 23 | 24 | ## Argument Reference 25 | 26 | * `name` - (Required) The unique name of the Form Field. 27 | * `steps` - (Required) An ordered list of objects containing one or more Form Fields. A Form must have at least one step defined. 28 | * `fields` - (Required) An ordered list of Form Field Ids assigned to this step. 29 | 30 | --- 31 | 32 | * `data` - (Optional) A JSON string that can hold any information about the Form Field that should be persisted. 33 | * `form_id` - (Optional) The Id to use for the new Form. If not specified a secure random UUID will be generated. 34 | * `type` - (Optional) The type of form being created, a form type cannot be changed after the form has been created. 35 | -------------------------------------------------------------------------------- /docs/resources/group.md: -------------------------------------------------------------------------------- 1 | # Group Resource 2 | 3 | A FusionAuth Group is a named object that optionally contains one to many Application Roles. 4 | 5 | When a Group does not contain any Application Roles it can still be utilized to logically associate users. Assigning Application Roles to a group allow it to be used to dynamically manage Role assignment to registered Users. In this second scenario as long as a User is registered to an Application the Group membership will allow them to inherit the corresponding Roles from the Group. 6 | 7 | [Groups API](https://fusionauth.io/docs/v1/tech/apis/groups) 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | resource "fusionauth_group" "my_group" { 13 | name = "My Group" 14 | tenant_id = fusionauth_tenant.my_tenant.id 15 | role_ids = [ 16 | fusionauth_application_role.admins.id, 17 | ] 18 | } 19 | ``` 20 | 21 | ## Argument Reference 22 | 23 | * `name` - (Required) The name of the Group. 24 | * `tenant_id` - (Required) The unique Id of the tenant used to scope this API request. 25 | 26 | --- 27 | 28 | * `data` - (Optional) A JSON string that can hold any information about the Group that should be persisted. 29 | * `group_id` - (Optional) The Id to use for the new Group. If not specified a secure random UUID will be generated. 30 | * `role_ids` - (Optional) The Application Roles to assign to this group. 31 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # FusionAuth Provider 2 | 3 | This provider is used for setting up [FusionAuth](https://fusionauth.io). 4 | 5 | Learn more about [using FusionAuth and Terraform together](https://fusionauth.io/docs/operate/deploy/terraform). 6 | 7 | ## Example Usage - Provider Configuration via `Terraform variables` 8 | 9 | ```hcl 10 | terraform { 11 | required_providers { 12 | fusionauth = { 13 | source = "fusionauth/fusionauth" 14 | version = "~> 1.0.0" 15 | } 16 | } 17 | } 18 | 19 | // Provider configuration set via tfvars variables 20 | provider "fusionauth" { 21 | api_key = var.fusionauth_api_key 22 | host = var.fusionauth_host 23 | } 24 | ``` 25 | 26 | ## Example Usage - Provider Configuration via `environment variables` 27 | 28 | ```hcl 29 | terraform { 30 | required_providers { 31 | fusionauth = { 32 | source = "fusionauth/fusionauth" 33 | version = "~> 1.0.0" 34 | } 35 | } 36 | } 37 | 38 | // Provider configuration set via environment variables 39 | provider "fusionauth" {} 40 | ``` 41 | 42 | ## Argument Reference 43 | 44 | * `api_key` - (Required) The API Key for the FusionAuth instance. Alternatively, can be configured using the `FA_API_KEY` environment variable. 45 | * `host` - (Required) Host for FusionAuth instance. Alternatively, can be configured using the `FA_DOMAIN` environment variable. 46 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | workflow_dispatch: 8 | jobs: 9 | golangci: 10 | name: lint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-go@v5 15 | with: 16 | go-version: "1.24" 17 | - name: golangci-lint 18 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 19 | with: 20 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 21 | version: v2.3.0 22 | # Optional: working directory, useful for monorepos 23 | # working-directory: somedir 24 | # Optional: golangci-lint command line arguments. 25 | # args: --issues-exit-code=0 26 | # Optional: show only new issues if it's a pull request. The default value is `false`. 27 | # only-new-issues: true 28 | # Optional: if set to true then the action will use pre-installed Go. 29 | # skip-go-installation: true 30 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 31 | # skip-pkg-cache: true 32 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 33 | # skip-build-cache: true 34 | -------------------------------------------------------------------------------- /docs/data-sources/sms_message_template.md: -------------------------------------------------------------------------------- 1 | # SMS Message Template Data Source 2 | 3 | This data source can be used to fetch information about a specific SMS Message Template. 4 | 5 | [SMS Message Templates API](https://fusionauth.io/docs/v1/tech/apis/message-templates) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_sms_message_template" "example" { 11 | message_template_id = "f3a91c71-ee0b-476a-92db-0a16f983a47f" 12 | } 13 | 14 | data "fusionauth_sms_message_template" "example" { 15 | name = "Example" 16 | } 17 | ``` 18 | 19 | ## Argument Reference 20 | 21 | * `message_template_id` - (Optional) The unique Id of the SMS Message Template to retrieve. If this is not specified, the `name` argument must be specified. 22 | * `name` - (Optional) The case-insensitive string to search for in the SMS Message Template name. If this is not specified, the `message_template_id` argument must be specified. 23 | 24 | ## Attributes Reference 25 | 26 | * `data` - An object that can hold any information about the Message Template that should be persisted. Represented as a JSON string. 27 | * `default_template` - The default Message Template. 28 | * `localized_templates` - The Message Template used when sending messages to users who speak other languages. This overrides the default Message Template based on the user's list of preferred languages. 29 | * `type` - The type of Message Template. This is always 'SMS'. 30 | -------------------------------------------------------------------------------- /docs/data-sources/email.md: -------------------------------------------------------------------------------- 1 | # Email Resource 2 | 3 | This data source is used to fetch information about a specific Email Template. 4 | 5 | [Emails API](https://fusionauth.io/docs/v1/tech/apis/emails) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_email" "default_breached_password" { 11 | name = "[FusionAuth Default] Breached Password Notification" 12 | } 13 | ``` 14 | 15 | ## Argument Reference 16 | 17 | * `name` - (Required) The name of the Email Template. 18 | 19 | ## Attributes Reference 20 | 21 | All the argument attributes are also exported as result attributes. 22 | 23 | * `default_from_name` - The default From Name used when sending emails. 24 | * `default_html_template` - The default HTML Email Template. 25 | * `default_subject` - The default Subject used when sending emails. 26 | * `default_text_template` - The default Text Email Template. 27 | * `from_email` - The email address that this email will be sent from. 28 | * `id` - The Id of the Email Template. 29 | * `localized_from_names` - The From Name used when sending emails to users who speak other languages. 30 | * `localized_html_templates` - The HTML Email Template used when sending emails to users who speak other languages. 31 | * `localized_subjects` - The Subject used when sending emails to users who speak other languages. 32 | * `localized_text_templates` - The Text Email Template used when sending emails to users who speak other languages. 33 | -------------------------------------------------------------------------------- /docs/data-sources/application_oauth_scope.md: -------------------------------------------------------------------------------- 1 | # Application OAuth Scope Resource 2 | 3 | The Application OAuth Scope resource allows you to define the scopes that an application can request when using OAuth. 4 | 5 | [Application OAuth Scope API](https://fusionauth.io/docs/apis/scopes) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_application_oauth_scope" "this" { 11 | application_id = data.fusionauth_application.this.id 12 | name = "data:read" 13 | } 14 | ``` 15 | 16 | ## Argument Reference 17 | 18 | * `application_id` - (Required) ID of the application that this role is for. 19 | * `name` - (Required) The name of the Role. 20 | 21 | ## Attributes Reference 22 | 23 | All of the argument attributes are also exported as result attributes. 24 | 25 | The following additional attributes are exported: 26 | 27 | * `data` - A JSON string that can hold any information about the OAuth Scope that should be persisted. 28 | * `default_consent_detail` - The default detail to display on the OAuth consent screen if one cannot be found in the theme. 29 | * `default_consent_message` - The default message to display on the OAuth consent screen if one cannot be found in the theme. 30 | * `description` - A description of the OAuth Scope. This is used for display purposes only. 31 | * `required` - Determines if the OAuth Scope is required when requested in an OAuth workflow. 32 | * `scope_id` - he Id to use for the new OAuth Scope. If not specified a secure random UUID will be generated. 33 | -------------------------------------------------------------------------------- /docs/data-sources/form.md: -------------------------------------------------------------------------------- 1 | # Form Resource 2 | 3 | A FusionAuth Form is a customizable object that contains one-to-many ordered steps. Each step is comprised of one or more Form Fields. 4 | 5 | [Forms API](https://fusionauth.io/docs/v1/tech/apis/forms) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_form" "default" { 11 | name = "Default User Self Service provided by FusionAuth" 12 | } 13 | ``` 14 | 15 | ## Argument Reference 16 | 17 | * `form_id` - (Optional) The unique id of the Form. Either `form_id` or `name` must be specified. 18 | * `name` - (Optional) The name of the Form. Either `form_id` or `name` must be specified. 19 | 20 | ## Attributes Reference 21 | 22 | All the argument attributes are also exported as result attributes. 23 | 24 | The following additional attributes are exported: 25 | 26 | * `data` - A JSON string that can hold any information about the Form that should be persisted. 27 | * `id` - The unique Id of the Form. 28 | * `name` - The unique name of the Form. 29 | * `steps` - An ordered list of objects containing one or more Form Fields. 30 | * `type` - The form type. The possible values are: 31 | * `adminRegistration` - This form be used to customize the add and edit User Registration form in the FusionAuth UI. 32 | * `adminUser` - This form can be used to customize the add and edit User form in the FusionAuth UI. 33 | * `registration` - This form will be used for self service registration. 34 | * `selfServiceUser` - This form will be used to for self service user management. 35 | -------------------------------------------------------------------------------- /docs/data-sources/twilio_messenger.md: -------------------------------------------------------------------------------- 1 | # Twilio Messenger Data Source 2 | 3 | This data source can be used to fetch information about a specific Twilio Messenger. 4 | 5 | [Messengers API](https://fusionauth.io/docs/apis/messengers/twilio) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_twilio_messenger" "example" { 11 | id = "75a068fd-e94b-451a-9aeb-3ddb9a3b5987" 12 | } 13 | 14 | data "fusionauth_twilio_messenger" "example" { 15 | name = "My Twilio Messenger" 16 | } 17 | ``` 18 | 19 | ## Argument Reference 20 | 21 | * `id` - (Optional) The unique Id of the Twilio Messenger to retrieve. If this is not specified, the `name` argument must be specified. 22 | * `name` - (Optional) The case-insensitive string to search for in the Twilio Messenger name. If this is not specified, the `id` argument must be specified. 23 | 24 | ## Attributes Reference 25 | 26 | * `account_sid` - The Twilio Account ID used when connecting to the Twilio API. 27 | * `auth_token` - The Twilio Auth Token used when connecting to the Twilio API. 28 | * `data` - An object that can hold any information about the Twilio Messenger that should be persisted. Represented as a JSON string. 29 | * `debug` - Determines if debug is enabled to create an event log to assist in debugging messenger errors. 30 | * `from_phone_number` - The configured Twilio phone number used to send messages. 31 | * `messaging_service_sid` - The Twilio message service Id used when using Twilio Copilot to load balance between numbers. 32 | * `url` - The Twilio URL that FusionAuth uses to communicate with the Twilio API. 33 | -------------------------------------------------------------------------------- /fusionauth/helpers_validate.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/hashicorp/go-cty/cty" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | // WarnStringInSlice returns a SchemaValidateFunc which tests if the provided 13 | // value is of type string and will not error, but rather, return a warning if 14 | // the string provided is not in the valid slice. This enables soft-constraints, 15 | // where a plugin may expose extra values over and above the core values. 16 | // Will test using case-insensitivity if ignoreCase is true. 17 | func WarnStringInSlice(valid []string, ignoreCase bool) schema.SchemaValidateDiagFunc { 18 | return func(i interface{}, path cty.Path) (diags diag.Diagnostics) { 19 | value, ok := i.(string) 20 | if !ok { 21 | return diag.Diagnostics{diag.Diagnostic{ 22 | Severity: diag.Error, 23 | Summary: "expected type of string", 24 | Detail: fmt.Sprintf("expected string, got: %+#v instead", value), 25 | AttributePath: path, 26 | }} 27 | } 28 | 29 | for _, validator := range valid { 30 | if value == validator || (ignoreCase && strings.EqualFold(value, validator)) { 31 | return 32 | } 33 | } 34 | 35 | return diag.Diagnostics{diag.Diagnostic{ 36 | Severity: diag.Warning, 37 | Summary: "provided value not in expected list (may be expected)", 38 | Detail: fmt.Sprintf("expected the provided value to be one of %s, got %s", valid, value), 39 | AttributePath: path, 40 | }} 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_application_role.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FusionAuth/go-client/pkg/fusionauth" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | ) 10 | 11 | func dataSourceApplicationRole() *schema.Resource { 12 | return &schema.Resource{ 13 | ReadContext: dataSourceApplicationRoleRead, 14 | Schema: map[string]*schema.Schema{ 15 | "name": { 16 | Type: schema.TypeString, 17 | Required: true, 18 | ForceNew: true, 19 | Description: "The name of the Application.", 20 | }, 21 | "application_id": { 22 | Type: schema.TypeString, 23 | Required: true, 24 | ForceNew: true, 25 | Description: "ID of the application that this role is for.", 26 | }, 27 | }, 28 | } 29 | } 30 | 31 | func dataSourceApplicationRoleRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 32 | client := i.(Client) 33 | aid := data.Get("application_id").(string) 34 | resp, err := client.FAClient.RetrieveApplication(aid) 35 | if err != nil { 36 | return diag.FromErr(err) 37 | } 38 | if err := checkResponse(resp.StatusCode, nil); err != nil { 39 | return diag.FromErr(err) 40 | } 41 | name := data.Get("name").(string) 42 | var role *fusionauth.ApplicationRole 43 | 44 | for i := range resp.Application.Roles { 45 | if name == resp.Application.Roles[i].Name { 46 | role = &resp.Application.Roles[i] 47 | } 48 | } 49 | 50 | if role == nil { 51 | return diag.Errorf("couldn't find role %s in application %s", name, aid) 52 | } 53 | data.SetId(role.Id) 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action can publish assets for release when a tag is created. 2 | # Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0). 3 | # 4 | # This uses an action (crazy-max/ghaction-import-gpg) that assumes you set your 5 | # private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE` 6 | # secret. If you would rather own your own GPG handling, please fork this action 7 | # or use an alternative one for key handling. 8 | # 9 | # You will need to pass the `--batch` flag to `gpg` in your signing step 10 | # in `goreleaser` to indicate this is being used in a non-interactive mode. 11 | # 12 | name: release 13 | on: 14 | push: 15 | tags: 16 | - "v*" 17 | jobs: 18 | goreleaser: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | - name: Unshallow 24 | run: git fetch --prune --unshallow 25 | - name: Set up Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: "1.24" 29 | - name: Import GPG key 30 | id: import_gpg 31 | uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0 32 | with: 33 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 34 | passphrase: ${{ secrets.PASSPHRASE }} 35 | - name: Run GoReleaser 36 | uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811 # v5.1.0 37 | with: 38 | version: latest 39 | args: release --clean 40 | env: 41 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /docs/resources/application_role.md: -------------------------------------------------------------------------------- 1 | # Application Role Resource 2 | 3 | This Resource is used to create a role for an Application. 4 | 5 | [Application Roles API](https://fusionauth.io/docs/v1/tech/apis/applications) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_application_role" "my_app_admin_role" { 11 | application_id = fusionauth_application.my_app.id 12 | description = "" 13 | is_default = false 14 | is_super_role = true 15 | name = "admin" 16 | } 17 | ``` 18 | 19 | ## Argument Reference 20 | 21 | * `application_id` - (Required) ID of the application that this role is for. 22 | * `name` - (Required) The name of the Role. 23 | 24 | --- 25 | 26 | * `description` - (Optional) A description for the role. 27 | * `is_default` - (Optional) Whether or not the Role is a default role. A default role is automatically assigned to a user during registration if no roles are provided. 28 | * `is_super_role` - (Optional) Whether or not the Role is a considered to be a super user role. This is a marker to indicate that it supersedes all other roles. FusionAuth will attempt to enforce this contract when using the web UI, it is not enforced programmatically when using the API. 29 | 30 | ## Import 31 | 32 | In Terraform v1.5.0 and later, use an `import` block to import application roles using the application ID and role ID, separated by a colon. For example: 33 | 34 | ```hcl 35 | import { 36 | to = fusionauth_application_role.name 37 | id = "application_id:role_id" 38 | } 39 | ``` 40 | 41 | Using terraform import, import application roles using the application ID and role ID. For example: 42 | 43 | ```shell 44 | terraform import fusionauth_application_role.name application_id:role_id 45 | -------------------------------------------------------------------------------- /docs/data-sources/form_field.md: -------------------------------------------------------------------------------- 1 | # Form Field Resource 2 | 3 | A FusionAuth Form Field is an object that can be customized to receive input within a FusionAuth [Form](https://fusionauth.io/docs/v1/tech/apis/forms). 4 | 5 | [Form Field API](https://fusionauth.io/docs/v1/tech/apis/form-fields) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_form_field" "default" { 11 | name = "Email" 12 | } 13 | ``` 14 | 15 | ## Argument Reference 16 | 17 | - `form_field_id` - (Optional) The unique id of the Form Field. Either `form_field_id` or `name` must be specified. 18 | - `name` - (Optional) The name of the Form field. Either `form_field_id` or `name` must be specified. 19 | 20 | ## Attributes Reference 21 | 22 | All the argument attributes are also exported as result attributes. 23 | 24 | The following additional attributes are exported: 25 | 26 | - `confirm` - Determines if the user input should be confirmed by requiring the value to be entered twice. 27 | - consent_id 28 | - control 29 | - `data` - A JSON string that can hold any information about the Form Field that should be persisted. 30 | - description 31 | - key 32 | - `id` - The unique Id of the Form Field. 33 | - `name` - The unique name of the Form Field. 34 | - `options` - A list of options that are applied to checkbox, radio, or select controls. 35 | - `required` - Determines if a value is required to complete the form. 36 | - `type` - The form field type. The possible values are: 37 | - `bool` 38 | - `consent` 39 | - `date` 40 | - `email` 41 | - `number` 42 | - `string` 43 | - `validator` 44 | - `enabled` - Determines if user input should be validated. 45 | - `expression` - A regular expression used to validate user input. Must be a valid regular expression pattern. 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing Guide 2 | 3 | ## Current Maintainers 4 | - Caleb @cdavisgpsi 5 | - Josiah @Jbcampbe 6 | - Drew @drewlesueur 7 | - Daniel @robotdan (FusionAuth) 8 | 9 | ## Guidline for Updating 10 | 11 | 1. Scroll through [FusionAuth's API Docs](https://fusionauth.io/docs/v1/tech/apis/) 12 | 2. Update each resouce and make sure to update the docs!!! 13 | 3. Make sure tests work (and maybe add new ones!) 14 | 3. Submit PR to this repo 15 | 4. Upon merge, maintainer will create new git tag kicking off the build process. 16 | 5. [Terraform Registry](https://registry.terraform.io/providers/FusionAuth/fusionauth/latest) will pick up the changes 17 | 18 | ``` 19 | git tag v0.1.71 20 | git push origin --tag 21 | ``` 22 | 23 | ## Running tests 24 | 25 | The tests require 3 variables set in order to run. 26 | ``` 27 | TF_ACC=true 28 | FA_DOMAIN=https://YOUR.fusionauth.io 29 | FA_API_KEY=YOUR_API_KEY 30 | ``` 31 | 32 | If you add these to your computer/shell environment variables then executing the tests are as simple as: 33 | ``` 34 | go test ./... 35 | ``` 36 | 37 | Alternately you can supply on the command line when executing the tests. 38 | 39 | ``` 40 | TF_ACC=true FA_DOMAIN=https://YOUR.fusionauth.io FA_API_KEY=YOUR_API_KEY go test ./... 41 | ``` 42 | 43 | ## Running lint 44 | 45 | If you want to head off lint errors before hitting CI you can execute them locally. 46 | 47 | First, install golangci-lint. Instructions can be found here: https://golangci-lint.run/usage/install/#local-installation 48 | 49 | Then run it: 50 | ``` 51 | golangci-lint run 52 | ``` 53 | 54 | ## To uppdate the FusionAuth go-client 55 | 56 | In this example, we are pulling the go-client at version `1.42.1` 57 | 58 | ``` 59 | go get -u github.com/FusionAuth/go-client@1.42.1 60 | ``` -------------------------------------------------------------------------------- /fusionauth/keyhelpers.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/FusionAuth/go-client/pkg/fusionauth" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | type keyReadFunc func(*schema.ResourceData, fusionauth.Key) diag.Diagnostics 13 | type keyBuildFunc func(*schema.ResourceData) fusionauth.Key 14 | 15 | func keyUpdate(data *schema.ResourceData, f keyBuildFunc, i interface{}) diag.Diagnostics { 16 | client := i.(Client) 17 | l := f(data) 18 | 19 | resp, faErrs, err := client.FAClient.UpdateKey(data.Id(), fusionauth.KeyRequest{Key: l}) 20 | if err != nil { 21 | return diag.FromErr(err) 22 | } 23 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 24 | return diag.FromErr(err) 25 | } 26 | 27 | return nil 28 | } 29 | 30 | func keyDelete(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 31 | client := i.(Client) 32 | id := data.Id() 33 | 34 | resp, faErrs, err := client.FAClient.DeleteKey(id) 35 | if err != nil { 36 | return diag.FromErr(err) 37 | } 38 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 39 | return diag.FromErr(err) 40 | } 41 | 42 | return nil 43 | } 44 | 45 | func keyRead(data *schema.ResourceData, f keyReadFunc, i interface{}) diag.Diagnostics { 46 | client := i.(Client) 47 | id := data.Id() 48 | 49 | resp, faErrs, err := client.FAClient.RetrieveKey(id) 50 | if err != nil { 51 | return diag.FromErr(err) 52 | } 53 | 54 | if resp.StatusCode == http.StatusNotFound { 55 | data.SetId("") 56 | return nil 57 | } 58 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 59 | return diag.FromErr(err) 60 | } 61 | 62 | return f(data, resp.Key) 63 | } 64 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | builds: 8 | - env: 9 | # goreleaser does not work with CGO, it could also complicate 10 | # usage by users in CI/CD systems like Terraform Cloud where 11 | # they are unable to install libraries. 12 | - CGO_ENABLED=0 13 | mod_timestamp: "{{ .CommitTimestamp }}" 14 | flags: 15 | - -trimpath 16 | ldflags: 17 | - "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}" 18 | goos: 19 | - freebsd 20 | - windows 21 | - linux 22 | - darwin 23 | goarch: 24 | - amd64 25 | - "386" 26 | - arm 27 | - arm64 28 | ignore: 29 | - goos: darwin 30 | goarch: "386" 31 | binary: "{{ .ProjectName }}_v{{ .Version }}" 32 | archives: 33 | - format: zip 34 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 35 | checksum: 36 | name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS" 37 | algorithm: sha256 38 | signs: 39 | - artifacts: checksum 40 | args: 41 | # if you are using this is a GitHub action or some other automated pipeline, you 42 | # need to pass the batch flag to indicate its not interactive. 43 | - "--batch" 44 | - "--local-user" 45 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 46 | - "--output" 47 | - "${signature}" 48 | - "--detach-sign" 49 | - "${artifact}" 50 | release: 51 | # If you want to manually examine the release before its live, uncomment this line: 52 | # draft: true 53 | changelog: 54 | skip: true 55 | -------------------------------------------------------------------------------- /docs/resources/entity_type.md: -------------------------------------------------------------------------------- 1 | # Entity Type Resource 2 | 3 | Entity Types categorize Entities. For example, an Entity Type could be `Device`, `API` or `Company`. 4 | 5 | [Entity Type API](https://fusionauth.io/docs/v1/tech/apis/entity-management/entity-types/#create-an-entity-type) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_entity_type" "company" { 11 | name = "Company" 12 | data = jsonencode({ 13 | createdBy = "jared@fusionauth.io" 14 | }) 15 | jwt_configuration { 16 | access_token_key_id = "a7516c7c-6234-4021-b0b4-8870c807aeb2" 17 | enabled = true 18 | time_to_live_in_seconds = 3600 19 | } 20 | } 21 | ``` 22 | 23 | ## Argument Reference 24 | 25 | * `name` - (Required) A descriptive name for the entity type (i.e. `Customer` or `Email_Service`). 26 | 27 | --- 28 | 29 | * `data` - (Optional) A JSON string that can hold any information about the Entity Type that should be persisted. Must be aJSON string. 30 | * `entity_type_id` - (Optional) The ID to use for the new Entity Type. If not specified a secure random UUID will be generated. 31 | * `jwt_configuration` - (Optional) A block to configure JSON Web Token (JWT) options. 32 | * `enabled` - (Optional) Indicates if this application is using the JWT configuration defined here or the global JWT 33 | configuration defined by the Tenant. If this is false the signing algorithm configured in the Tenant will be used. 34 | If true the signing algorithm defined in this application will be used. 35 | * `access_token_key_id` - (Required) The unique ID of the signing key used to sign the access token. Required when 36 | enabled is set to true. 37 | * `time_to_live_in_seconds` - (Required) The length of time in seconds the JWT will live before it is expired and no longer valid. Required when enabled is set to true. 38 | -------------------------------------------------------------------------------- /docs/data-sources/generic_messenger.md: -------------------------------------------------------------------------------- 1 | # Generic Messenger Data Source 2 | 3 | This data source can be used to fetch information about a specific Generic Messenger. 4 | 5 | [Messengers API](https://fusionauth.io/docs/apis/messengers/generic) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_generic_messenger" "example" { 11 | messenger_id = "4dcbdbb0-385a-4980-ab9f-0c575f6815e0" 12 | } 13 | 14 | data "fusionauth_generic_messenger" "example" { 15 | name = "Generic Messenger" 16 | } 17 | ``` 18 | 19 | ## Argument Reference 20 | 21 | * `messenger_id` - (Optional) The unique Id of the Generic Messenger to retrieve. If this is not specified, the `name` argument must be specified. 22 | * `name` - (Optional) The case-insensitive string to search for in the Generic Messenger name. If this is not specified, the `messenger_id` argument must be specified. 23 | 24 | ## Attributes Reference 25 | 26 | * `connect_timeout` - The connect timeout for the HTTP connection, in milliseconds. Value must be greater than 0. 27 | * `data` - An object that can hold any information about the Generic Messenger that should be persisted. Represented as a JSON string. 28 | * `debug` - Determines if debug should be enabled to create an event log to assist in debugging integration errors. 29 | * `headers` - An object that can hold HTTPHeader key and value pairs. 30 | * `http_authentication_password` - The basic authentication password to use for requests to the Messenger. 31 | * `http_authentication_username` - The basic authentication username to use for requests to the Messenger. 32 | * `read_timeout` - The read timeout for the HTTP connection, in milliseconds. Value must be greater than 0. 33 | * `ssl_certificate` - An SSL certificate. The certificate is used for client certificate authentication in requests to the Messenger. 34 | * `url` - The fully qualified URL used to send an HTTP request. 35 | -------------------------------------------------------------------------------- /docs/data-sources/generic_connector.md: -------------------------------------------------------------------------------- 1 | # Generic Connector Data Source 2 | 3 | This data source can be used to fetch information about a specific Generic Connector. 4 | 5 | [Connectors API](https://fusionauth.io/docs/apis/connectors/generic) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_generic_connector" "example" { 11 | id = "75a068fd-e94b-451a-9aeb-3ddb9a3b5987" 12 | } 13 | 14 | data "fusionauth_generic_connector" "example" { 15 | name = "My Generic Connector" 16 | } 17 | ``` 18 | 19 | ## Argument Reference 20 | 21 | * `id` - (Optional) The unique Id of the Generic Connector to retrieve. If this is not specified, the `name` argument must be specified. 22 | * `name` - (Optional) The case-insensitive string to search for in the Generic Connector name. If this is not specified, the `id` argument must be specified. 23 | 24 | ## Attributes Reference 25 | 26 | * `authentication_url` - The fully qualified URL used to send an HTTP request to authenticate the user. 27 | * `connect_timeout` - The connect timeout for the HTTP connection, in milliseconds. Value must be greater than 0. 28 | * `data` - An object that can hold any information about the Generic Connector that should be persisted. Represented as a JSON string. 29 | * `debug` - Determines if debug should be enabled to create an event log to assist in debugging integration errors. 30 | * `headers` - An object that can hold HTTPHeader key and value pairs. 31 | * `http_authentication_password` - The basic authentication password to use for requests to the Connector. 32 | * `http_authentication_username` - The basic authentication username to use for requests to the Connector. 33 | * `read_timeout` - The read timeout for the HTTP connection, in milliseconds. Value must be greater than 0. 34 | * `ssl_certificate_key_id` - The Id of an existing Key. The X509 certificate is used for client certificate authentication in requests to the Connector. 35 | -------------------------------------------------------------------------------- /docs/resources/key.md: -------------------------------------------------------------------------------- 1 | # Key Resource 2 | 3 | Cryptographic keys are used in signing and verifying JWTs and verifying responses for third party identity providers. It is more likely you will interact with keys using the FusionAuth UI in the Key Master menu. 4 | 5 | [Keys API](https://fusionauth.io/docs/v1/tech/apis/keys) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_key" "admin_id" { 11 | algorithm = "RS256" 12 | name = "Id token signing key generated for application Administrator Login" 13 | length = 2048 14 | } 15 | ``` 16 | 17 | ## Argument Reference 18 | 19 | * `algorithm` - (Required) The algorithm used to encrypt the Key. The following values represent algorithms supported by FusionAuth: 20 | * `ES256` - ECDSA using P-256 curve and SHA-256 hash algorithm 21 | * `ES384` - ECDSA using P-384 curve and SHA-384 hash algorithm 22 | * `ES512` - ECDSA using P-521 curve and SHA-512 hash algorithm 23 | * `RS256` - RSA using SHA-256 hash algorithm 24 | * `RS384` - RSA using SHA-384 hash algorithm 25 | * `RS512` - RSA using SHA-512 hash algorithm 26 | * `HS256` - HMAC using SHA-256 hash algorithm 27 | * `HS384` - HMAC using SHA-384 hash algorithm 28 | * `HS512` - HMAC using SHA-512 hash algorithm 29 | * `name` - (Required) The name of the Key. 30 | 31 | --- 32 | 33 | * `issuer` - (Optional) The issuer of the RSA or EC certificate. If omitted, this value will default to the value of tenant issuer on the default tenant. For HMAC keys, this field does not apply and will be ignored if specified, and no default value will be set. 34 | * `key_id` - (Optional) The Id to use for the new key. If not specified a secure random UUID will be generated. 35 | * `length` - (Optional) 36 | 37 | ## Attribute Reference 38 | 39 | In addition to all arguments above, the following attribute is exported: 40 | 41 | * `kid` - The id used in the JWT header to identify the key used to generate the signature 42 | -------------------------------------------------------------------------------- /docs/resources/sms_message_template.md: -------------------------------------------------------------------------------- 1 | # SMS Message Template Resource 2 | 3 | A FusionAuth SMS Message Template is a named object that provides configuration for the content of SMS messages sent through configured messengers. 4 | 5 | [Message Templates API](https://fusionauth.io/docs/v1/tech/apis/message-templates/) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_sms_message_template" "two_factor" { 11 | name = "Two Factor Authentication" 12 | default_template = "Here's your Two Factor Code: ${code}" 13 | 14 | data = jsonencode({ 15 | updatedBy = "admin@example.com" 16 | }) 17 | 18 | localized_templates = { 19 | de = "Hier ist Ihr Zwei-Faktoren-Code: ${code}" 20 | es = "Este es su código de dos factores: ${code}" 21 | fr = "Voici votre code à deux facteurs: ${code}" 22 | } 23 | } 24 | ``` 25 | 26 | ## Argument Reference 27 | 28 | * `name` - (Required) A descriptive name for the Message Template (i.e. "Two Factor Code Message"). 29 | * `default_template` - (Required) The default Message Template content that will be used when sending SMS messages. 30 | 31 | --- 32 | 33 | * `message_template_id` - (Optional) The Id to use for the new Message Template. If not specified a secure random UUID will be generated. 34 | * `data` - (Optional) A JSON string that can hold any information about the Message Template that should be persisted. Must be a JSON string. 35 | * `localized_templates` - (Optional) A map of language codes to template strings used when sending messages to users who speak other languages. This overrides the default Message Template based on the user's list of preferred languages. 36 | 37 | ## Attribute Reference 38 | 39 | * `type` - The type of Message Template. This is always 'SMS'. 40 | 41 | ## Import 42 | 43 | SMS Message Templates can be imported using the template ID: 44 | 45 | ```shell 46 | terraform import fusionauth_sms_message_template.example 00000000-0000-0000-0000-000000000000 47 | -------------------------------------------------------------------------------- /docs/data-sources/lambda.md: -------------------------------------------------------------------------------- 1 | # Lambda Resource 2 | 3 | Lambdas are user defined JavaScript functions that may be executed at runtime to perform various functions. Lambdas may be used to customize the claims returned in a JWT, reconcile a SAML v2 response or an OpenID Connect response when using these external identity providers. 4 | 5 | [Lambdas API](https://fusionauth.io/docs/v1/tech/apis/lambdas) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_lambda" "default_google_reconcile" { 11 | name = "Default Google Reconcile provided by FusionAuth" 12 | type = "GoogleReconcile" 13 | } 14 | ``` 15 | 16 | ## Argument Reference 17 | 18 | * `name` - (Optional) The name of the Lambda. At least one of `id` or `name` must be specified. 19 | * `id` - (Optional) The ID of the Lambda. At least one of `id` or `name` must be specified. 20 | * `type` - (Required) The Lambda type. The possible values are: 21 | * `AppleReconcile` 22 | * `ClientCredentialsJWTPopulate` 23 | * `EpicGamesReconcile` 24 | * `ExternalJWTReconcile` 25 | * `FacebookReconcile` 26 | * `GoogleReconcile` 27 | * `HYPRReconcile` 28 | * `JWTPopulate` 29 | * `LDAPConnectorReconcile` 30 | * `LinkedInReconcile` 31 | * `LoginValidation` 32 | * `NintendoReconcile` 33 | * `OpenIDReconcile` 34 | * `SAMLv2Populate` 35 | * `SAMLv2Reconcile` 36 | * `SCIMServerGroupRequestConverter` 37 | * `SCIMServerGroupResponseConverter` 38 | * `SCIMServerUserRequestConverter` 39 | * `SCIMServerUserResponseConverter` 40 | * `SelfServiceRegistrationValidation` 41 | * `SonyPSNReconcile` 42 | * `SteamReconcile` 43 | * `TwitchReconcile` 44 | * `TwitterReconcile` 45 | * `UserInfoPopulate` 46 | * `XboxReconcile` 47 | 48 | ## Attributes Reference 49 | 50 | All of the argument attributes are also exported as result attributes. 51 | 52 | The following additional attributes are exported: 53 | 54 | * `body` - The lambda function body, a JavaScript function. 55 | * `debug` - Whether or not debug event logging is enabled for this Lambda. 56 | -------------------------------------------------------------------------------- /docs/resources/twilio_messenger.md: -------------------------------------------------------------------------------- 1 | # Twilio Messenger Resource 2 | 3 | A FusionAuth Twilio Messenger is a named object that provides configuration for sending messages through the Twilio API. 4 | 5 | [Twilio Messenger API](https://fusionauth.io/docs/v1/tech/apis/messengers/twilio/) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_twilio_messenger" "example" { 11 | account_sid = "983C6FACEBBE4D858570FADD967A9DD7" 12 | auth_token = "184C73BE8E44420EBAA0BA147A61B6A9" 13 | data = jsonencode({ 14 | foo = "bar" 15 | }) 16 | debug = false 17 | from_phone_number = "555-555-6666" 18 | name = "Twilio Messenger" 19 | url = "https://api.twilio.com" 20 | } 21 | ``` 22 | 23 | ## Argument Reference 24 | 25 | * `account_sid` - (Required) The Twilio Account ID to use when connecting to the Twilio API. This can be found in your Twilio dashboard. 26 | * `name` - (Required) The unique Messenger name. 27 | * `url` - (Required) The Twilio URL that FusionAuth will use to communicate with the Twilio API. 28 | 29 | --- 30 | 31 | * `auth_token` - (Optional) The Twilio Auth Token to use when connecting to the Twilio API. This can be found in your Twilio dashboard. 32 | * `data` - (Optional) A JSON string that can hold any information about the Twilio Messenger that should be persisted. Must be a JSON string. 33 | * `debug` - (Optional) If debug is enabled, an event log is created to assist in debugging messenger errors. Defaults to false. 34 | * `from_phone_number` - (Optional) The configured Twilio phone number that will be used to send messages. This can be found in your Twilio dashboard. 35 | * `messenger_id` - (Optional) The Id to use for the new Messenger. If not specified a secure random UUID will be generated. 36 | * `messaging_service_sid` - (Optional) The Twilio message service Id, this is used when using Twilio Copilot to load balance between numbers. This can be found in your Twilio dashboard. If this is set, the fromPhoneNumber will be ignored. 37 | -------------------------------------------------------------------------------- /docs/resources/entity_type_permision.md: -------------------------------------------------------------------------------- 1 | # Entity Type Permission Resource 2 | 3 | Permissions are defined on an Entity Type. These are arbitrary strings which can fit the business domain. A Permission 4 | could be `read`, `write`, or `file-lawsuit`. 5 | 6 | [Entity Type Permission API](https://fusionauth.io/docs/v1/tech/apis/entity-management/entity-types/#create-an-entity-type-permission) 7 | 8 | ## Example Usage 9 | 10 | ```hcl 11 | resource "fusionauth_entity_type_permission" "file_lawsuit" { 12 | entity_type_id = fusionauth_entity_type.company.id 13 | data = jsonencode({ 14 | foo = "bar" 15 | }) 16 | description = "Enables the ability to file lawsuits" 17 | is_default = true 18 | name = "file-lawsuit" 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | * `entity_type_id` - (Required) The ID of the Entity Type. 25 | * `name` - (Required) The name of the Permission. 26 | 27 | --- 28 | 29 | * `data` - (Optional) A JSON string that can hold any information about the Permission that should be persisted. Must be a JSON string. 30 | * `description` - (Optional) The description of the Permission. 31 | * `is_default` - (Optional) Whether or not the Permission is a default permission. A default permission is automatically granted to an entity of this type if no permissions are provided in a grant request. 32 | * `permission_id` - (Optional) The ID to use for the new permission. If not specified a secure random UUID will be generated. 33 | 34 | ## Import 35 | 36 | In Terraform v1.5.0 and later, use an `import` block to import entity type permission resources using the entity type ID and entity type permission ID, separated by a colon. For example: 37 | 38 | ```hcl 39 | import { 40 | to = fusionauth_entity_type_permission.name 41 | id = "entity_type_id:entity_type_permission_id" 42 | } 43 | ``` 44 | 45 | Using terraform import, import entity resources using the entity type ID and entity type permission id. For example: 46 | 47 | ```shell 48 | terraform import fusionauth_entity_type_permission.name entity_type_id:entity_type_permission_id 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FusionAuth Provider 2 | 3 | This provider is used for setting up [FusionAuth](https://fusionauth.io). 4 | 5 | For the rendered provider usage documentation, visit the [Terraform Registry](https://registry.terraform.io/providers/FusionAuth/fusionauth/latest/docs). 6 | 7 | ## Please Read 8 | 9 | November 16th, 2023 10 | This Terraform Provider has moved to the [FusionAuth](https://github.com/FusionAuth) organization. 11 | 12 | FusionAuth would like to thank [GPS Insight](https://github.com/gpsinsight) for all of their efforts to build and maintain this provider for the past three years! 13 | 14 | The purpose of this change in ownership is to allow FusionAuth to be in a better position to manage pull requests, and work towards full parity with the FusionAuth API. 15 | 16 | Please continue to use and provide feedback on this provider as you have in the past, we are happy to accept pull requests. 17 | 18 | ## Argument Reference 19 | 20 | * `api_key` - (Required) The API Key for the FusionAuth instance 21 | * `host` - (Required) Host for FusionAuth instance 22 | 23 | ## Resources Available 24 | 25 | * API Key 26 | * application 27 | * application/{application_id}/role 28 | * application/{application_id}/oauth/scope 29 | * consent 30 | * email 31 | * entity 32 | * entity grant 33 | * entity type 34 | * entity type permission 35 | * form 36 | * form field 37 | * group 38 | * generic connector 39 | * generic messenger 40 | * key 41 | * imported key 42 | * lambda 43 | * LDAP connector 44 | * identity provider 45 | * OpenID Connect 46 | * Google 47 | * Apple 48 | * External JWT 49 | * Facebook 50 | * LinkedIn 51 | * SAML v2 52 | * SAML v2 IdP Initiated 53 | * Sony PSN 54 | * Steam 55 | * Twitch 56 | * Xbox 57 | * reactor 58 | * registration 59 | * SMS message template 60 | * system configuration 61 | * themes 62 | * Twilio messenger 63 | * tenant 64 | * user 65 | * user action 66 | * user group membership 67 | * webhook 68 | 69 | ## Testing 70 | 71 | Please add tests to the relevant files. 72 | 73 | To run tests: 74 | 75 | ``` 76 | cd fusionauth 77 | go test 78 | `` 79 | -------------------------------------------------------------------------------- /docs/resources/registration.md: -------------------------------------------------------------------------------- 1 | # Registration Resource 2 | 3 | A registration is the association between a User and an Application that they log into. 4 | 5 | [Registrations API](https://fusionauth.io/docs/v1/tech/apis/registrations) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_registration" "example" { 11 | user_id = fusionauth_user.example.id 12 | application_id = data.fusionauth_application.FusionAuth.id 13 | roles = ["admin"] 14 | username = "theadmin" 15 | } 16 | ``` 17 | 18 | ## Argument Reference 19 | 20 | * `application_id` - (Required) The Id of the Application that this registration is for. 21 | * `user_id` - (Required) The Id of the User that is registering for the Application. 22 | 23 | --- 24 | 25 | * `authentication_token` - (Optional) The authentication token that may be used in place of the User’s password when authenticating against this application represented by this registration. This parameter is ignored if generateAuthenticationToken is set to true and instead the value will be generated. 26 | * `data` - (Optional) A JSON string that can hold any information about the User for this registration that should be persisted. 27 | * `generate_authentication_token` - (Optional) Determines if FusionAuth should generate an Authentication Token for this registration. 28 | * `preferred_languages` - (Optional) An array of locale strings that give, in order, the User’s preferred languages for this registration. These are important for email templates and other localizable text. 29 | * `registration_id` - (Optional) The Id of this registration. If not specified a secure random UUID will be generated. 30 | * `roles` - (Optional) The list of roles that the User has for this registration. 31 | * `skip_registration_validation` - (Optional) Indicates to FusionAuth that it should skip registration verification even if it is enabled for the Application. 32 | * `timezone` - (Optional) The User’s preferred timezone for this registration. The string will be in an IANA time zone format. 33 | * `username` - (Optional) The username of the User for this registration only. 34 | -------------------------------------------------------------------------------- /docs/resources/form_field.md: -------------------------------------------------------------------------------- 1 | # Form Field Resource 2 | 3 | A FusionAuth Form Field is an object that can be customized to receive input within a FusionAuth Form. 4 | 5 | [Form Field API](https://fusionauth.io/docs/v1/tech/apis/form-fields/) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_form_field" "field" { 11 | data = jsonencode({ 12 | createdBy = "jared@fusionauth.io" 13 | }) 14 | description = "Information about this custom field" 15 | key = "user.firstName" 16 | name = "Custom first-name Form Field" 17 | required = true 18 | confirm = true 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | * `name` - (Required) The unique name of the Form Field. 25 | 26 | --- 27 | 28 | * `confirm` - (Optional) Determines if the user input should be confirmed by requiring the value to be entered twice. If true, a confirmation field is included. 29 | * `consent_id` - (Optional) The Id of an existing Consent. This field will be required when the type is set to consent. 30 | * `control` - (Optional) The Form Field control 31 | * `data` - (Optional) A JSON string that can hold any information about the Form Field that should be persisted. 32 | * `description` - (Optional) A description of the Form Field. 33 | * `form_field_id` - (Optional) The Id to use for the new Form Field. If not specified a secure random UUID will be generated. 34 | * `key` - (Optional) The key is the path to the value in the user or registration object. Required for all fields except `consent` Not required when type is `consent`, as FusionAuth will generate the key automatically for consent fields. 35 | * `options` - (Optional) A list of options that are applied to checkbox, radio, or select controls. 36 | * `required` - (Optional) Determines if a value is required to complete the form. 37 | * `type` - (Optional) The data type used to store the value in FusionAuth. 38 | * `validator` - (Optional) 39 | * `enabled` - (Optional) Determines if user input should be validated. 40 | * `expression` - (Optional) A regular expression used to validate user input. Must be a valid regular expression pattern. 41 | -------------------------------------------------------------------------------- /docs/data-sources/consent.md: -------------------------------------------------------------------------------- 1 | # Consent Data Source 2 | 3 | This data source can be used to fetch information about a specific consent. 4 | 5 | [Consents API](https://fusionauth.io/docs/v1/tech/apis/consents) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "fusionauth_consent" "example" { 11 | consent_id = "34ee46ad-60a7-44f6-8edf-d818c2209072" 12 | } 13 | 14 | data "fusionauth_consent" "by_name" { 15 | name = "Issue197" 16 | } 17 | ``` 18 | 19 | ## Argument Reference 20 | 21 | * `consent_id` - (Optional) The unique Id of the Consent to retrieve. This is mutually exclusive with `name`. 22 | * `name` - (Optional) The case-insensitive string to search for in the Consent name. This is mutually exclusive with `consent_id`. 23 | 24 | ## Attributes Reference 25 | 26 | * `consent_email_template_id` - The Id of the Email Template that is used to send confirmation to the end user. 27 | * `country_minimum_age_for_self_consent` - This property optionally overrides the value provided in defaultMinimumAgeForSelfConsent if a more specific value is defined. 28 | * `data` - An object that can hold any information about the Consent that should be persisted. Must be a JSON string. 29 | * `default_minimum_age_for_self_consent` - The default age of self consent used when granting this consent to a user unless a more specific one is provided by the countryMinimumAgeForSelfConsent. 30 | * `email_plus` - Email Plus provides and additional opportunity to notify the giver that consent was provided. 31 | * `email_template_id` - The Id of the Email Template that is used to send the reminder notice to the consent giver. 32 | * `enabled` - Set to true when the Email Plus workflow is enabled. 33 | * `maximum_time_to_send_email_in_hours` - The maximum number of hours to wait until sending the reminder notice the consent giver. 34 | * `minimum_time_to_send_email_in_hours` - The minimum number of hours to wait until sending the reminder notice the consent giver. 35 | * `multiple_values_allowed` - Set to true if more than one value may be used when granting this consent to a User. 36 | * `values` - The list of values that may be assigned to this consent. 37 | -------------------------------------------------------------------------------- /docs/resources/lambda.md: -------------------------------------------------------------------------------- 1 | # Lambda Resource 2 | 3 | Lambdas are user defined JavaScript functions that may be executed at runtime to perform various functions. Lambdas may be used to customize the claims returned in a JWT, reconcile a SAML v2 response or an OpenID Connect response when using these external identity providers. 4 | 5 | [Lambdas API](https://fusionauth.io/docs/v1/tech/apis/lambdas) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_lambda" "preferred_username" { 11 | name = "Preferred Username" 12 | type = "JWTPopulate" 13 | enabled = true 14 | body = <). 30 | * `email_id` - (Optional) The Id to use for the new Email Template. If not specified a secure random UUID will be generated. 31 | * `from_email` - (Optional) The email address that this email will be sent from. If not provided, the default value for the tenant will be used. This is the address part email address (i.e. Jared Dunn ). 32 | * `localized_from_names` - (Optional) The From Name used when sending emails to users who speak other languages. This overrides the default From Name based on the user’s list of preferred languages. 33 | * `localized_html_templates` - (Optional) The HTML Email Template used when sending emails to users who speak other languages. This overrides the default HTML Email Template based on the user’s list of preferred languages. 34 | * `localized_subjects` - (Optional) The Subject used when sending emails to users who speak other languages. This overrides the default Subject based on the user’s list of preferred languages. 35 | * `localized_text_templates` - (Optional) The Text Email Template used when sending emails to users who speak other languages. This overrides the default Text Email Template based on the user’s list of preferred languages. 36 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_application.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/FusionAuth/go-client/pkg/fusionauth" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | func dataSourceApplication() *schema.Resource { 13 | return &schema.Resource{ 14 | ReadContext: dataSourceApplicationRead, 15 | Schema: map[string]*schema.Schema{ 16 | "name": { 17 | Type: schema.TypeString, 18 | Required: true, 19 | ForceNew: true, 20 | Description: "The name of the Application.", 21 | }, 22 | "tenant_id": { 23 | Type: schema.TypeString, 24 | Computed: true, 25 | Description: "The ID of the Tenant that owns the Application.", 26 | }, 27 | "webauthn_configuration": { 28 | Type: schema.TypeList, 29 | Computed: true, 30 | Description: "The WebAuthnConfiguration for the Application.", 31 | Elem: &schema.Resource{ 32 | Schema: map[string]*schema.Schema{ 33 | "bootstrap_workflow_enabled": { 34 | Computed: true, 35 | Type: schema.TypeBool, 36 | }, 37 | "enabled": { 38 | Computed: true, 39 | Type: schema.TypeBool, 40 | }, 41 | "reauthentication_workflow_enabled": { 42 | Computed: true, 43 | Type: schema.TypeBool, 44 | }, 45 | }, 46 | }, 47 | }, 48 | }, 49 | } 50 | } 51 | 52 | func dataSourceApplicationRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 53 | client := i.(Client) 54 | 55 | resp, err := client.FAClient.RetrieveApplications() 56 | if err != nil { 57 | return diag.FromErr(err) 58 | } 59 | if err := checkResponse(resp.StatusCode, nil); err != nil { 60 | return diag.FromErr(err) 61 | } 62 | name := data.Get("name").(string) 63 | var app *fusionauth.Application 64 | 65 | for i := range resp.Applications { 66 | if resp.Applications[i].Name == name { 67 | app = &resp.Applications[i] 68 | } 69 | } 70 | if app == nil { 71 | return diag.Errorf("couldn't find application %s", name) 72 | } 73 | data.SetId(app.Id) 74 | err = data.Set("tenant_id", app.TenantId) 75 | if err != nil { 76 | return diag.FromErr(fmt.Errorf("error setting tenant_id: %v", err)) 77 | } 78 | // Properly structure WebAuthn configuration 79 | webauthnConfig := []map[string]interface{}{ 80 | { 81 | "bootstrap_workflow_enabled": app.WebAuthnConfiguration.BootstrapWorkflow.Enabled, 82 | "enabled": app.WebAuthnConfiguration.Enabled, 83 | "reauthentication_workflow_enabled": app.WebAuthnConfiguration.ReauthenticationWorkflow.Enabled, 84 | }, 85 | } 86 | 87 | if err := data.Set("webauthn_configuration", webauthnConfig); err != nil { 88 | return diag.FromErr(fmt.Errorf("error setting webauthn_configuration: %v", err)) 89 | } 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_user_group_membership.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/FusionAuth/go-client/pkg/fusionauth" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | func dataSourceUserGroupMembership() *schema.Resource { 13 | return &schema.Resource{ 14 | ReadContext: dataSourceUserGroupMembershipRead, 15 | Schema: map[string]*schema.Schema{ 16 | "data": { 17 | Type: schema.TypeString, 18 | Computed: true, 19 | Description: "An object that can hold any information about the User for this membership that should be persisted.", 20 | }, 21 | "group_id": { 22 | Type: schema.TypeString, 23 | Required: true, 24 | Description: "The Id of the Group of this membership.", 25 | }, 26 | "membership_id": { 27 | Type: schema.TypeString, 28 | Computed: true, 29 | Description: "The Id of the Membership.", 30 | }, 31 | "user_id": { 32 | Type: schema.TypeString, 33 | Required: true, 34 | Description: "The Id of the User of this membership.", 35 | }, 36 | }, 37 | } 38 | } 39 | 40 | func dataSourceUserGroupMembershipRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 41 | client := i.(Client) 42 | 43 | gmsreq := fusionauth.GroupMemberSearchRequest{ 44 | Search: fusionauth.GroupMemberSearchCriteria{ 45 | GroupId: data.Get("group_id").(string), 46 | UserId: data.Get("user_id").(string), 47 | }, 48 | } 49 | 50 | resp, faErrs, err := client.FAClient.SearchGroupMembers(gmsreq) 51 | if err != nil { 52 | return diag.Errorf("RetrieveUserGroupMembership err: %v", err) 53 | } 54 | 55 | if resp.StatusCode == http.StatusNotFound { 56 | data.SetId("") 57 | return nil 58 | } 59 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 60 | return diag.FromErr(err) 61 | } 62 | 63 | gmsresp := resp.Members 64 | if resp.Total == 0 { 65 | data.SetId("") 66 | return nil 67 | } 68 | if resp.Total == 1 { 69 | data.SetId(gmsresp[0].Id) 70 | dataJSON, diags := mapStringInterfaceToJSONString(gmsresp[0].Data) 71 | if diags != nil { 72 | return diags 73 | } 74 | err = data.Set("data", dataJSON) 75 | if err != nil { 76 | return diag.Errorf("Error setting data: %v", err) 77 | } 78 | if err := data.Set("group_id", gmsresp[0].GroupId); err != nil { 79 | return diag.Errorf("Error setting group_id: %v", err) 80 | } 81 | if err := data.Set("membership_id", gmsresp[0].Id); err != nil { 82 | return diag.Errorf("Error setting membership_id: %v", err) 83 | } 84 | if err := data.Set("user_id", gmsresp[0].UserId); err != nil { 85 | return diag.Errorf("Error setting user_id: %v", err) 86 | } 87 | return nil 88 | } 89 | return diag.Errorf("Found %d memberships for user %s in group %s", resp.Total, data.Get("user_id").(string), data.Get("group_id").(string)) 90 | } 91 | -------------------------------------------------------------------------------- /docs/resources/api_key.md: -------------------------------------------------------------------------------- 1 | # API Key 2 | 3 | The FusionAuth APIs are primarily secured using API keys. In order to retrieve, create, update, or delete an API key, an API key set as a keyManager with equal or greater permissions than the API to be created must be used. A "tenant-scoped" API key can retrieve, create, update or delete an API key for the same tenant. This page describes APIs that are used to manage API keys. 4 | 5 | [API Key](https://fusionauth.io/docs/v1/tech/apis/api-keys/) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_api_key" "example" { 11 | tenant_id = "94f751c5-4883-4684-a817-6b106778edec" 12 | description = "my super secret key" 13 | key = "super-secret-key" 14 | permissions_endpoints { 15 | endpoint = "/api/application" 16 | get = true 17 | delete = true 18 | patch = true 19 | post = true 20 | put = true 21 | } 22 | } 23 | ``` 24 | 25 | ## Argument Reference 26 | 27 | * `description` - (Optional) Description of the key. 28 | * `expiration_instant` - (Optional) The expiration instant of this API key. Using an expired API key for API Authentication will result in a 401 response code. 29 | * `ip_access_control_list_id` - (Optional) The Id of the IP Access Control List limiting access to this API key. 30 | * `key` - (Optional) API key string. When you create an API key the key is defaulted to a secure random value but the API key is simply a string, so you may call it super-secret-key if you’d like. However a long and random value makes a good API key in that it is unique and difficult to guess. 31 | * `key_id` - (Optional) The Id to use for the new Form. If not specified a secure random UUID will be generated. 32 | * `name` - (Optional) The name of the API key. Must be unique. If `retrievable` is `false` then this field is required. 33 | * `permissions_endpoints` - (Optional) Endpoint permissions for this key. Each key of the object is an endpoint, with the value being an array of the HTTP methods which can be used against the endpoint. An Empty permissions_endpoints object mean that this is a super key that authorizes this key for all the endpoints. 34 | * `endpoint` - (Optional) 35 | * `delete` - (Optional) HTTP DELETE Verb. 36 | * `get` - (Optional) HTTP GET Verb. 37 | * `patch` - (Optional) HTTP PATCH Verb 38 | * `post` - (Optional) HTTP POST Verb 39 | * `put` - (Optional) HTTP PUT Verb 40 | * `retrievable` - (Optional) Indicates whether this key is retrievable. If this value is false, the key will not be returned in the API response. This value is cannot be updated once the key is created and will be a replacement operation if updated. If this value is set to `false` then the `name` field is required. 41 | * `tenant_id` - (Optional) The unique Id of the Tenant. This value is required if the key is meant to be tenant scoped. Tenant scoped keys can only be used to access users and other tenant scoped objects for the specified tenant. This value is read-only once the key is created. 42 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/FusionAuth/terraform-provider-fusionauth 2 | 3 | go 1.24 4 | 5 | toolchain go1.24.5 6 | 7 | require ( 8 | github.com/FusionAuth/go-client v1.59.0 9 | github.com/hashicorp/go-cty v1.5.0 10 | github.com/hashicorp/go-uuid v1.0.3 11 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 12 | ) 13 | 14 | require ( 15 | github.com/ProtonMail/go-crypto v1.3.0 // indirect 16 | github.com/agext/levenshtein v1.2.3 // indirect 17 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 18 | github.com/cloudflare/circl v1.6.1 // indirect 19 | github.com/fatih/color v1.18.0 // indirect 20 | github.com/golang/protobuf v1.5.4 // indirect 21 | github.com/google/go-cmp v0.7.0 // indirect 22 | github.com/hashicorp/errwrap v1.1.0 // indirect 23 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 24 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 25 | github.com/hashicorp/go-hclog v1.6.3 // indirect 26 | github.com/hashicorp/go-multierror v1.1.1 // indirect 27 | github.com/hashicorp/go-plugin v1.6.3 // indirect 28 | github.com/hashicorp/go-retryablehttp v0.7.8 // indirect 29 | github.com/hashicorp/go-version v1.7.0 // indirect 30 | github.com/hashicorp/hc-install v0.9.2 // indirect 31 | github.com/hashicorp/hcl/v2 v2.24.0 // indirect 32 | github.com/hashicorp/logutils v1.0.0 // indirect 33 | github.com/hashicorp/terraform-exec v0.23.0 // indirect 34 | github.com/hashicorp/terraform-json v0.25.0 // indirect 35 | github.com/hashicorp/terraform-plugin-go v0.28.0 // indirect 36 | github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect 37 | github.com/hashicorp/terraform-registry-address v0.3.0 // indirect 38 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 39 | github.com/hashicorp/yamux v0.1.2 // indirect 40 | github.com/mattn/go-colorable v0.1.14 // indirect 41 | github.com/mattn/go-isatty v0.0.20 // indirect 42 | github.com/mitchellh/copystructure v1.2.0 // indirect 43 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 44 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 45 | github.com/mitchellh/mapstructure v1.5.0 // indirect 46 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 47 | github.com/oklog/run v1.2.0 // indirect 48 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect 49 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 50 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 51 | github.com/zclconf/go-cty v1.16.3 // indirect 52 | golang.org/x/crypto v0.40.0 // indirect 53 | golang.org/x/mod v0.26.0 // indirect 54 | golang.org/x/net v0.42.0 // indirect 55 | golang.org/x/sync v0.16.0 // indirect 56 | golang.org/x/sys v0.34.0 // indirect 57 | golang.org/x/text v0.27.0 // indirect 58 | golang.org/x/tools v0.35.0 // indirect 59 | google.golang.org/appengine v1.6.8 // indirect 60 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect 61 | google.golang.org/grpc v1.74.2 // indirect 62 | google.golang.org/protobuf v1.36.6 // indirect 63 | ) 64 | -------------------------------------------------------------------------------- /fusionauth/helpers_test.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func Test_handleStringSliceFromList(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | list []interface{} 12 | want []string 13 | wantPanic bool 14 | }{ 15 | { 16 | name: "empty list", 17 | list: []interface{}{}, 18 | want: []string{}, 19 | wantPanic: false, 20 | }, 21 | { 22 | name: "all strings", 23 | list: []interface{}{"hello", "world"}, 24 | want: []string{"hello", "world"}, 25 | wantPanic: false, 26 | }, 27 | { 28 | name: "mixed types", 29 | list: []interface{}{"string1", 42, "string2"}, 30 | want: nil, 31 | wantPanic: true, 32 | }, 33 | { 34 | name: "nil element", 35 | list: []interface{}{"valid", nil, "also valid"}, 36 | want: []string{"valid", "also valid"}, 37 | wantPanic: false, 38 | }, 39 | { 40 | name: "multiple nil elements", 41 | list: []interface{}{nil, "middle", nil}, 42 | want: []string{"middle"}, 43 | wantPanic: false, 44 | }, 45 | { 46 | name: "all nil elements", 47 | list: []interface{}{nil, nil, nil}, 48 | want: []string{}, 49 | wantPanic: false, 50 | }, 51 | } 52 | 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | var got []string 56 | var didPanic bool 57 | 58 | defer func() { 59 | if r := recover(); r != nil { 60 | didPanic = true 61 | } 62 | 63 | if didPanic != tt.wantPanic { 64 | t.Errorf("handleStringSliceFromList() panic = %v, wantPanic %v", didPanic, tt.wantPanic) 65 | return 66 | } 67 | 68 | if !didPanic && !reflect.DeepEqual(got, tt.want) { 69 | t.Errorf("handleStringSliceFromList() = %v, want %v", got, tt.want) 70 | } 71 | }() 72 | 73 | got = handleStringSliceFromList(tt.list) 74 | }) 75 | } 76 | } 77 | 78 | func Test_intMapToStringMap(t *testing.T) { 79 | type args struct { 80 | intMap map[string]interface{} 81 | } 82 | tests := []struct { 83 | name string 84 | args args 85 | want map[string]string 86 | }{ 87 | { 88 | name: "FA Issues #1482", 89 | args: args{ 90 | intMap: map[string]interface{}{ 91 | "ar": "Test", 92 | }, 93 | }, 94 | want: map[string]string{ 95 | "ar": "Test", 96 | }, 97 | }, 98 | { 99 | name: "FA Issues #1482", 100 | args: args{ 101 | intMap: map[string]interface{}{ 102 | "ar": "Test", 103 | "aaass": 2, 104 | }, 105 | }, 106 | want: map[string]string{ 107 | "ar": "Test", 108 | }, 109 | }, 110 | } 111 | for _, tt := range tests { 112 | t.Run(tt.name, func(t *testing.T) { 113 | if got := intMapToStringMap(tt.args.intMap); !reflect.DeepEqual(got, tt.want) { 114 | t.Errorf("intMapToStringMap() = %v, want %v", got, tt.want) 115 | } 116 | }) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /docs/resources/ldap_connector.md: -------------------------------------------------------------------------------- 1 | # LDAP Connector Resource 2 | 3 | A FusionAuth LDAP Connector is a named object that provides configuration for allowing authentication against external LDAP systems. 4 | 5 | [LDAP Connector API](https://fusionauth.io/docs/apis/connectors/ldap) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_ldap_connector" "example" { 11 | authentication_url = "ldap://localhost:389" 12 | base_structure = "dc=example,dc=com" 13 | connect_timeout = 1000 14 | identifying_attribute = "uid" 15 | lambda_configuration { 16 | reconcile_id = "5bb52c51-bd10-4afc-84d3-e55c62d94987" 17 | } 18 | login_id_attribute = "uid" 19 | name = "example" 20 | read_timeout = 1000 21 | requested_attributes = [ 22 | "uid", 23 | "cn", 24 | "sn", 25 | "mail" 26 | ] 27 | security_method = "None" 28 | system_account_dn = "cn=admin,dc=example,dc=com" 29 | system_account_password = "password" 30 | } 31 | ``` 32 | 33 | ## Argument Reference 34 | 35 | * `authentication_url` - (Required) The fully qualified LDAP URL to authenticate. 36 | * `base_structure` - (Required) The top of the LDAP directory hierarchy. Typically this contains the `dc` (domain component) element. 37 | * `connect_timeout` - (Required) The connect timeout for the HTTP connection, in milliseconds. Value must be greater than `0`. 38 | * `identifying_attribute` - (Required) The entry attribute name which is the first component of the distinguished name of entries in the directory. 39 | * `lambda_configuration` - (Required) 40 | * `reconcile_id` - (Required) The Id of a Lambda. The lambda is executed after the user authenticates with the connector. This lambda can create a user, registrations, and group memberships in FusionAuth based on attributes returned from the connector. 41 | * `login_id_attribute` - (Required) The entity attribute name which stores the identifier that is used for logging the user in. 42 | * `name` - (Required) The unique LDAP Connector name. 43 | * `read_timeout` - (Required) The read timeout for the HTTP connection, in milliseconds. Value must be greater than `0`. 44 | * `requested_attributes` - (Required) The list of attributes to request from the LDAP server. This is a list of strings. 45 | * `security_method` - (Required) The LDAP security method. Possible values are: `None` (Requests will be made without encryption), `LDAPS` (A secure connection will be made to a secure port over using the LDAPS protocol) or `StartTLS` (An un-secured connection will initially be established, followed by secure connection established using the StartTLS extension). 46 | * `system_account_dn` - (Required) The distinguished name of an entry that has read access to the directory. 47 | * `system_account_password` - (Required) The password of an entry that has read access to the directory. 48 | 49 | --- 50 | 51 | * `data` - (Optional) A JSON string that can hold any information about the Connector that should be persisted. 52 | * `debug` - (Optional) Determines if debug should be enabled to create an event log to assist in debugging integration errors. 53 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_idp.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "strings" 10 | "time" 11 | 12 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 13 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 14 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 15 | ) 16 | 17 | type IdentityProvidersResponse struct { 18 | IdentityProviders []IdentityProvider `json:"identityProviders"` 19 | } 20 | 21 | type IdentityProvider struct { 22 | ID string `json:"id"` 23 | Name string `json:"name"` 24 | Type string `json:"type"` 25 | } 26 | 27 | func dataSourceIDP() *schema.Resource { 28 | return &schema.Resource{ 29 | ReadContext: dataSourceIDPRead, 30 | Schema: map[string]*schema.Schema{ 31 | "name": { 32 | Type: schema.TypeString, 33 | Required: true, 34 | ForceNew: true, 35 | Description: "The name of the identity provider. This is only used for display purposes. Will be the type for types: `Apple`, `Facebook`, `Google`, `HYPR`, `Twitter`", 36 | }, 37 | "type": { 38 | Type: schema.TypeString, 39 | Required: true, 40 | ForceNew: true, 41 | Description: "The type of the identity provider.", 42 | ValidateFunc: validation.StringInSlice([]string{ 43 | "Apple", 44 | "Facebook", 45 | "Google", 46 | "HYPR", 47 | "Twitter", 48 | "OpenIDConnect", 49 | "SAMLv2", 50 | }, false), 51 | }, 52 | }, 53 | } 54 | } 55 | 56 | func dataSourceIDPRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 57 | client := i.(Client) 58 | b, err := readIdentityProviders(client) 59 | if err != nil { 60 | return diag.FromErr(err) 61 | } 62 | 63 | var idps IdentityProvidersResponse 64 | _ = json.Unmarshal(b, &idps) 65 | 66 | n := data.Get("name").(string) 67 | t := data.Get("type").(string) 68 | switch t { 69 | case "Apple", "Facebook", "Google", "HYPR", "Twitter": 70 | n = t 71 | } 72 | var idp *IdentityProvider 73 | for i := range idps.IdentityProviders { 74 | if idps.IdentityProviders[i].Name == n && idps.IdentityProviders[i].Type == t { 75 | idp = &idps.IdentityProviders[i] 76 | } 77 | } 78 | 79 | if idp == nil { 80 | return diag.Errorf("couldn't find identity provider name %s, type %s", n, t) 81 | } 82 | data.SetId(idp.ID) 83 | return nil 84 | } 85 | 86 | func readIdentityProviders(client Client) ([]byte, error) { 87 | req, err := http.NewRequest( 88 | http.MethodGet, 89 | fmt.Sprintf("%s/%s", strings.TrimRight(client.Host, "/"), "api/identity-provider"), 90 | nil, 91 | ) 92 | 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | req.Header.Add("Authorization", client.APIKey) 98 | 99 | hc := http.Client{ 100 | Timeout: 30 * time.Second, 101 | } 102 | 103 | resp, err := hc.Do(req) 104 | if err != nil { 105 | return nil, err 106 | } 107 | defer resp.Body.Close() 108 | 109 | b, _ := io.ReadAll(resp.Body) 110 | 111 | if err := checkResponse(resp.StatusCode, nil); err != nil { 112 | return b, err 113 | } 114 | return b, nil 115 | } 116 | -------------------------------------------------------------------------------- /fusionauth/resource_fusionauth_form_field_test.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func Test_validateKey(t *testing.T) { 10 | type args struct { 11 | i interface{} 12 | k string 13 | } 14 | tests := []struct { 15 | name string 16 | args args 17 | wantWarnings []string 18 | wantErrors []error 19 | }{ 20 | { 21 | name: "predefined", 22 | args: args{ 23 | i: "user.birthDate", 24 | k: "key", 25 | }, 26 | wantErrors: nil, 27 | wantWarnings: nil, 28 | }, 29 | { 30 | name: "custom", 31 | args: args{ 32 | i: "user.data.", 33 | k: "key", 34 | }, 35 | wantErrors: nil, 36 | wantWarnings: nil, 37 | }, 38 | { 39 | name: "invalid type", 40 | args: args{ 41 | i: false, 42 | k: "key", 43 | }, 44 | 45 | wantErrors: []error{fmt.Errorf(`expected type of "key" to be string`)}, 46 | wantWarnings: nil, 47 | }, 48 | { 49 | name: "invalid custom", 50 | args: args{ 51 | i: "user.invalid.", 52 | k: "key", 53 | }, 54 | wantErrors: []error{fmt.Errorf(`valid options for "key" are: ["registration.preferredLanguages" "registration.roles" "registration.timezone" "registration.username" "user.birthDate" "user.email" "user.firstname" "user.fullName" "user.imageUrl" "user.lastName" "user.middleName" "user.mobilePhone" "user.password" "user.phoneNumber" "user.preferredLanguages" "user.timezone" "user.username"] or start with ["user.data." "registration.data."]`)}, 55 | wantWarnings: nil, 56 | }, 57 | } 58 | for i := range tests { 59 | tt := tests[i] 60 | t.Run(tt.name, func(t *testing.T) { 61 | gotWarnings, gotErrors := validateKey(tt.args.i, tt.args.k) 62 | if !reflect.DeepEqual(gotWarnings, tt.wantWarnings) { 63 | t.Errorf("validateKey() gotWarnings = %v, want %v", gotWarnings, tt.wantWarnings) 64 | } 65 | if len(gotErrors) != len(tt.wantErrors) { 66 | t.Errorf("validateKey() gotErrors = %v, want %v", gotErrors, tt.wantErrors) 67 | } 68 | }) 69 | } 70 | } 71 | 72 | func Test_validateRegex(t *testing.T) { 73 | type args struct { 74 | i interface{} 75 | k string 76 | } 77 | tests := []struct { 78 | name string 79 | args args 80 | wantWarnings []string 81 | wantErrors []error 82 | }{ 83 | { 84 | name: "valid", 85 | args: args{ 86 | i: "a*b", 87 | k: "test", 88 | }, 89 | wantErrors: nil, 90 | wantWarnings: nil, 91 | }, 92 | { 93 | name: "invalid", 94 | args: args{ 95 | i: "[", 96 | k: "test", 97 | }, 98 | wantErrors: []error{fmt.Errorf("error parsing regexp: missing closing ]: `[`")}, 99 | wantWarnings: nil, 100 | }, 101 | } 102 | for i := range tests { 103 | tt := tests[i] 104 | t.Run(tt.name, func(t *testing.T) { 105 | gotWarnings, gotErrors := validateRegex(tt.args.i, tt.args.k) 106 | if !reflect.DeepEqual(gotWarnings, tt.wantWarnings) { 107 | t.Errorf("validateRegex() gotWarnings = %v, want %v", gotWarnings, tt.wantWarnings) 108 | } 109 | if len(gotErrors) != len(tt.wantErrors) { 110 | t.Errorf("validateKey() gotErrors = %v, want %v", gotErrors, tt.wantErrors) 111 | } 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /docs/resources/consent.md: -------------------------------------------------------------------------------- 1 | # Consent Resource 2 | 3 | Consents are used to define and manage user consent for data usage within your applications. They can enforce age restrictions, require parent/guardian approval for minors, and track what users have agreed to. 4 | 5 | [Consents API](https://fusionauth.io/docs/v1/tech/apis/consents) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_consent" "example" { 11 | name = "Newsletter Signup" 12 | default_minimum_age_for_self_consent = 16 13 | country_minimum_age_for_self_consent = { 14 | "us" = 13 15 | "uk" = 16 16 | "de" = 17 17 | } 18 | consent_email_template_id = "04881b0a-f543-4218-ae1d-ee009591a4b4" 19 | multiple_values_allowed = true 20 | values = [ 21 | "Email", 22 | "SMS", 23 | "Phone" 24 | ] 25 | 26 | email_plus { 27 | enabled = true 28 | email_template_id = "847de2b9-14ba-45fc-9726-9d9c9fcee4b6" 29 | minimum_time_to_send_email_in_hours = 24 30 | maximum_time_to_send_email_in_hours = 48 31 | } 32 | 33 | data = jsonencode({ 34 | description = "Marketing consent for newsletter" 35 | internal_id = "MKT-001" 36 | }) 37 | } 38 | ``` 39 | 40 | ## Argument Reference 41 | 42 | * `name` - (Required) The unique name of the consent. 43 | * `default_minimum_age_for_self_consent` - (Required) The default age of self consent used when granting this consent to a user unless a more specific one is provided by the `country_minimum_age_for_self_consent`. 44 | 45 | --- 46 | 47 | * `consent_email_template_id` - (Optional) The Id of the Email Template that is used to send confirmation to the end user. If omitted, an email will not be sent to the user. 48 | * `consent_id` - (Optional) The Id to use for the new Consent. If not specified a secure random UUID will be generated. 49 | * `country_minimum_age_for_self_consent` - (Optional) This property optionally overrides the value provided in `default_minimum_age_for_self_consent` if a more specific value is defined. This can be useful when the age of self consent varies by country. 50 | * `data` - (Optional) An object that can hold any information about the Consent that should be persisted. Must be a JSON string. 51 | * `email_plus` - (Optional) Email Plus provides an additional opportunity to notify the giver that consent was provided. Configuration block detailed below. 52 | * `multiple_values_allowed` - (Optional) Set to `true` if more than one value may be used when granting this consent to a User. Default is `false`. 53 | * `values` - (Optional) The list of values that may be assigned to this consent. Required when `multiple_values_allowed` is set to `true`. 54 | 55 | ### Email Plus Configuration Block 56 | 57 | * `email_template_id` - (Required) The Id of the Email Template that is used to send the reminder notice to the consent giver. 58 | * `enabled` - (Optional) Set to `true` to enable the Email Plus workflow. Default is `false`. 59 | * `maximum_time_to_send_email_in_hours` - (Optional) The maximum number of hours to wait until sending the reminder notice to the consent giver. Default is `48`. 60 | * `minimum_time_to_send_email_in_hours` - (Optional) The minimum number of hours to wait until sending the reminder notice to the consent giver. Default is `24`. 61 | 62 | ## Import 63 | 64 | Consents can be imported using the consent ID: 65 | 66 | ```shell 67 | terraform import fusionauth_consent.example 34ee46ad-60a7-44f6-8edf-d818c2209072 68 | ``` 69 | -------------------------------------------------------------------------------- /fusionauth/resource_fusionauth_application_crud.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | 8 | "github.com/FusionAuth/go-client/pkg/fusionauth" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | func createApplication(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 14 | client := i.(Client) 15 | ar := fusionauth.ApplicationRequest{ 16 | Application: buildApplication(data), 17 | } 18 | 19 | oldTenantID := client.FAClient.TenantId 20 | client.FAClient.TenantId = ar.Application.TenantId 21 | defer func() { 22 | client.FAClient.TenantId = oldTenantID 23 | }() 24 | 25 | var aid string 26 | if a, ok := data.GetOk("application_id"); ok { 27 | aid = a.(string) 28 | } 29 | 30 | resp, faErrs, err := client.FAClient.CreateApplication(aid, ar) 31 | if err != nil { 32 | return diag.Errorf("CreateApplication errors: %v", err) 33 | } 34 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 35 | return diag.FromErr(err) 36 | } 37 | 38 | data.SetId(resp.Application.Id) 39 | return buildResourceDataFromApplication(resp.Application, data) 40 | } 41 | 42 | func readApplication(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 43 | client := i.(Client) 44 | id := data.Id() 45 | 46 | resp, err := client.FAClient.RetrieveApplication(id) 47 | if err != nil { 48 | return diag.FromErr(err) 49 | } 50 | 51 | if resp.StatusCode == http.StatusNotFound { 52 | data.SetId("") 53 | return nil 54 | } 55 | if err := checkResponse(resp.StatusCode, nil); err != nil { 56 | return diag.FromErr(err) 57 | } 58 | 59 | return buildResourceDataFromApplication(resp.Application, data) 60 | } 61 | 62 | func updateApplication(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 63 | client := i.(Client) 64 | ar := fusionauth.ApplicationRequest{ 65 | Application: buildApplication(data), 66 | } 67 | 68 | resp, faErrs, err := client.FAClient.UpdateApplication(data.Id(), ar) 69 | if err != nil { 70 | return diag.Errorf("UpdateApplication err: %v", err) 71 | } 72 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 73 | return diag.FromErr(err) 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func deleteApplication(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 80 | client := i.(Client) 81 | resp, faErrs, err := client.FAClient.DeleteApplication(data.Id()) 82 | if err != nil { 83 | return diag.FromErr(err) 84 | } 85 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 86 | return diag.FromErr(err) 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func resourceApplicationV0() *schema.Resource { 93 | return &schema.Resource{ 94 | Schema: map[string]*schema.Schema{ 95 | "data": { 96 | Type: schema.TypeMap, 97 | Optional: true, 98 | Elem: &schema.Schema{Type: schema.TypeString}, 99 | }, 100 | }, 101 | } 102 | } 103 | 104 | func resourceApplicationUpgradeV0(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { 105 | if v, ok := rawState["data"]; ok { 106 | if dataMap, ok := v.(map[string]interface{}); ok { 107 | jsonBytes, err := json.Marshal(dataMap) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | rawState["data"] = string(jsonBytes) 113 | } 114 | } 115 | 116 | return rawState, nil 117 | } 118 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_form.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/FusionAuth/go-client/pkg/fusionauth" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 11 | ) 12 | 13 | func dataSourceForm() *schema.Resource { 14 | return &schema.Resource{ 15 | ReadContext: dataSourceFormRead, 16 | Schema: map[string]*schema.Schema{ 17 | "form_id": { 18 | Type: schema.TypeString, 19 | Optional: true, 20 | Computed: true, 21 | ExactlyOneOf: []string{"form_id", "name"}, 22 | Description: "The unique Id of the Form.", 23 | ValidateFunc: validation.IsUUID, 24 | }, 25 | "data": { 26 | Type: schema.TypeString, 27 | Optional: true, 28 | Description: "An object that can hold any information about the Form that should be persisted.", 29 | }, 30 | "name": { 31 | Type: schema.TypeString, 32 | Optional: true, 33 | ExactlyOneOf: []string{"form_id", "name"}, 34 | Description: "The unique name of the Form.", 35 | }, 36 | "steps": { 37 | Type: schema.TypeList, 38 | Description: "An ordered list of objects containing one or more Form Fields. A Form must have at least one step defined.", 39 | MinItems: 1, 40 | Optional: true, 41 | Elem: &schema.Resource{ 42 | Schema: map[string]*schema.Schema{ 43 | "fields": { 44 | Type: schema.TypeList, 45 | Elem: &schema.Schema{Type: schema.TypeString}, 46 | MinItems: 1, 47 | Required: true, 48 | Description: "An ordered list of Form Field Ids assigned to this step.", 49 | }, 50 | }, 51 | }, 52 | }, 53 | "type": { 54 | Type: schema.TypeString, 55 | Optional: true, 56 | Description: "The type of form being created, a form type cannot be changed after the form has been created.", 57 | Default: "registration", 58 | ForceNew: true, 59 | ValidateFunc: validation.StringInSlice([]string{ 60 | "registration", 61 | "adminRegistration", 62 | "adminUser", 63 | "selfServiceUser", 64 | }, false), 65 | }, 66 | }, 67 | } 68 | } 69 | 70 | func dataSourceFormRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 71 | client := i.(Client) 72 | 73 | var searchTerm string 74 | var res *fusionauth.FormResponse 75 | var err error 76 | 77 | // Either `form_id` or `name` are guaranteed to be set 78 | if entityID, ok := data.GetOk("form_id"); ok { 79 | searchTerm = entityID.(string) 80 | res, err = client.FAClient.RetrieveForm(searchTerm) 81 | } else { 82 | searchTerm = data.Get("name").(string) 83 | res, err = client.FAClient.RetrieveForms() 84 | } 85 | if err != nil { 86 | return diag.FromErr(err) 87 | } 88 | if res.StatusCode == http.StatusNotFound { 89 | return diag.Errorf("couldn't find form '%s'", searchTerm) 90 | } 91 | if err := checkResponse(res.StatusCode, nil); err != nil { 92 | return diag.FromErr(err) 93 | } 94 | 95 | foundEntity := res.Form 96 | if len(res.Forms) > 0 { 97 | // search based on name 98 | var found = false 99 | for _, entity := range res.Forms { 100 | if entity.Name == searchTerm { 101 | found = true 102 | foundEntity = entity 103 | break 104 | } 105 | } 106 | if !found { 107 | return diag.Errorf("couldn't find form with name '%s'", searchTerm) 108 | } 109 | } 110 | 111 | data.SetId(foundEntity.Id) 112 | return buildResourceDataFromForm(data, foundEntity) 113 | } 114 | -------------------------------------------------------------------------------- /fusionauth/resource_fusionauth_user_schema_test.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func Test_upgradeUserSchemaV0ToV1(t *testing.T) { 10 | type args struct { 11 | rawState map[string]interface{} 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want map[string]interface{} 17 | wantErr bool 18 | }{ 19 | { 20 | name: "Should handle nil state", 21 | args: args{ 22 | rawState: nil, 23 | }, 24 | want: map[string]interface{}{}, 25 | wantErr: false, 26 | }, 27 | { 28 | name: "Should handle empty state", 29 | args: args{ 30 | rawState: map[string]interface{}{}, 31 | }, 32 | want: map[string]interface{}{}, 33 | wantErr: false, 34 | }, 35 | { 36 | name: "Should not touch other properties", 37 | args: args{ 38 | rawState: map[string]interface{}{ 39 | "first_name": "John", 40 | "last_name": "Doe", 41 | "username": "user@example.com", 42 | }, 43 | }, 44 | want: map[string]interface{}{ 45 | "first_name": "John", 46 | "last_name": "Doe", 47 | "username": "user@example.com", 48 | }, 49 | wantErr: false, 50 | }, 51 | { 52 | name: "Should remove deprecated state properties", 53 | args: args{ 54 | rawState: map[string]interface{}{ 55 | "two_factor_delivery": "TextMessage", 56 | "two_factor_enabled": "false", 57 | "two_factor_secret": "UEBzc3cwcmQ=", 58 | }, 59 | }, 60 | want: map[string]interface{}{}, 61 | wantErr: false, 62 | }, 63 | { 64 | name: "Should upgrade user.data from TypeMap to TypeString", 65 | args: args{ 66 | rawState: map[string]interface{}{ 67 | "data": map[string]interface{}{ 68 | "test": "string", 69 | "should": "upgrade", 70 | "numbersAreStillStringy": "2", 71 | }, 72 | }, 73 | }, 74 | want: map[string]interface{}{ 75 | "data": "{\"numbersAreStillStringy\":\"2\",\"should\":\"upgrade\",\"test\":\"string\"}", 76 | }, 77 | wantErr: false, 78 | }, 79 | { 80 | name: "Should upgrade from V0 to V1", 81 | args: args{ 82 | rawState: map[string]interface{}{ 83 | "first_name": "John", 84 | "last_name": "Doe", 85 | "username": "user@example.com", 86 | "data": map[string]interface{}{ 87 | "test": "string", 88 | "should": "upgrade", 89 | "numbersAreStillStringy": "2", 90 | }, 91 | "two_factor_delivery": "TextMessage", 92 | "two_factor_enabled": "false", 93 | "two_factor_secret": "UEBzc3cwcmQ=", 94 | }, 95 | }, 96 | want: map[string]interface{}{ 97 | "first_name": "John", 98 | "last_name": "Doe", 99 | "username": "user@example.com", 100 | "data": "{\"numbersAreStillStringy\":\"2\",\"should\":\"upgrade\",\"test\":\"string\"}", 101 | }, 102 | wantErr: false, 103 | }, 104 | { 105 | name: "Should handle empty user.data from TypeMap to TypeString", 106 | args: args{ 107 | rawState: map[string]interface{}{ 108 | "data": map[string]interface{}{}, 109 | }, 110 | }, 111 | want: map[string]interface{}{ 112 | "data": "", 113 | }, 114 | wantErr: false, 115 | }, 116 | } 117 | 118 | for _, tt := range tests { 119 | t.Run(tt.name, func(t *testing.T) { 120 | got, err := upgradeUserSchemaV0ToV1(context.Background(), tt.args.rawState, nil) 121 | if (err != nil) != tt.wantErr { 122 | t.Errorf("upgradeUserSchemaV0ToV1() error = %v, wantErr %v", err, tt.wantErr) 123 | return 124 | } 125 | if !reflect.DeepEqual(got, tt.want) { 126 | t.Errorf("upgradeUserSchemaV0ToV1() got = %v, want %v", got, tt.want) 127 | } 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /docs/resources/entity.md: -------------------------------------------------------------------------------- 1 | # Entity Resource 2 | 3 | Entities are arbitrary objects which can be modeled in FusionAuth. Anything which is not a user but might need 4 | permissions managed by FusionAuth is a possible entity. Examples might include devices, cars, computers, customers, 5 | companies, etc. 6 | 7 | FusionAuth’s Entity Management has the following major concepts: 8 | 9 | * Entity Types categorize Entities. An Entity Type could be `Device`, `API` or `Company`. 10 | * Permissions are defined on an Entity Type. These are arbitrary strings which can fit the business domain. A Permission 11 | could be `read`, `write`, or `file-lawsuit`. 12 | * Entities are instances of a single type. An Entity could be a `nest device`, an `Email API` or `Raviga`. 13 | * Entities can have Grants. Grants are relationships between a target Entity and one of two other types: a recipient 14 | Entity or a User. Grants can have zero or more Permissions associated with them. 15 | 16 | You can use the Client Credentials grant to see if an Entity has permission to access another Entity. 17 | 18 | [Entity API](https://fusionauth.io/docs/v1/tech/apis/entity-management/entities) 19 | 20 | ## Example Usage 21 | 22 | ```hcl 23 | // Create an entity for Raviga... 24 | resource "fusionauth_entity" "raviga" { 25 | tenant_id = fusionauth_tenant.default.id 26 | client_id = "092dbded-30af-4149-9c61-b578f2c72f59" 27 | client_secret = "+fcXet9Iu2kQi61yWD9Tu4ReZ113P6yEAkr32v6WKOQ=" 28 | data = jsonencode({ 29 | companyType = "Legal" 30 | }) 31 | entity_type_id = fusionauth_entity_type.company.id 32 | name = "Raviga" 33 | } 34 | 35 | // With a bound entity type of "Company"... 36 | resource "fusionauth_entity_type" "company" { 37 | name = "Company" 38 | data = jsonencode({ 39 | createdBy = "jared@fusionauth.io" 40 | }) 41 | jwt_configuration { 42 | access_token_key_id = "a7516c7c-6234-4021-b0b4-8870c807aeb2" 43 | enabled = true 44 | time_to_live_in_seconds = 3600 45 | } 46 | } 47 | 48 | // Which holds a permission to "file-lawsuit"... 49 | resource "fusionauth_entity_permission" "file_lawsuit" { 50 | entity_type_id = fusionauth_entity_type.company.id 51 | data = jsonencode({ 52 | foo = "bar" 53 | }) 54 | description = "Enables the ability to file lawsuits" 55 | is_default = true 56 | name = "file-lawsuit" 57 | } 58 | ``` 59 | 60 | ## Argument Reference 61 | 62 | * `entity_type_id` - (Required) The ID of the Entity Type. Types are consulted for permission checks. 63 | * `name` - (Required) A descriptive name for the Entity (i.e. "Raviga" or "Email Service"). 64 | 65 | --- 66 | 67 | * `client_id` - (Optional) The OAuth 2.0 client ID. If you leave this blank on create, the value of the Entity ID will 68 | be used. Must be a UUID. 69 | * `client_secret` - (Optional) The OAuth 2.0 client secret. If you leave this blank on create, a secure secret will be 70 | generated for you. If you leave this blank during an update, the previous value will be maintained. For both create 71 | and update you can provide a value and it will be stored. 72 | * `data` - (Optional) A JSON string that can hold any information about the Entity that should be persisted. Please review 73 | the limits on data field types as you plan for and build your custom data schema. Must be a JSON serialised string. 74 | * `entity_id` - (Optional) The ID to use for the new Entity. If not specified a secure random UUID will be generated. 75 | * `tenant_id` - (Optional) The unique ID of the tenant used to scope this API request. 76 | 77 | For more information see: 78 | [FusionAuth Entity Management API Overview](https://fusionauth.io/docs/v1/tech/apis/entity-management/) 79 | 80 | ## Import 81 | 82 | In Terraform v1.5.0 and later, use an `import` block to import entity resources using the tenant ID and entity ID, separated by a colon. For example: 83 | 84 | ```hcl 85 | import { 86 | to = fusionauth_entity.name 87 | id = "tenant_id:entity_id" 88 | } 89 | ``` 90 | 91 | Using terraform import, import entity resources using the tenant ID and entity ID. For example: 92 | 93 | ```shell 94 | terraform import fusionauth_entity.name tenant_id:entity_id 95 | ``` 96 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_application_oauth_scope.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FusionAuth/go-client/pkg/fusionauth" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | ) 10 | 11 | func dataSourceApplicationOAuthScope() *schema.Resource { 12 | return &schema.Resource{ 13 | ReadContext: dataSourceApplicationOAuthScopeRead, 14 | Schema: map[string]*schema.Schema{ 15 | "name": { 16 | Type: schema.TypeString, 17 | Required: true, 18 | ForceNew: true, 19 | Description: "The name of the Application.", 20 | }, 21 | "application_id": { 22 | Type: schema.TypeString, 23 | Required: true, 24 | Description: "The Id of the application to which this scope belongs.", 25 | }, 26 | "scope_id": { 27 | Type: schema.TypeString, 28 | Computed: true, 29 | Description: "The Id to use for the new OAuth Scope. If not specified a secure random UUID will be generated.", 30 | }, 31 | "data": { 32 | Type: schema.TypeString, 33 | Computed: true, 34 | Description: "The data to associate with the scope.", 35 | }, 36 | "default_consent_detail": { 37 | Type: schema.TypeString, 38 | Computed: true, 39 | Description: "The default detail to display on the OAuth consent screen if one cannot be found in the theme.", 40 | }, 41 | "default_consent_message": { 42 | Type: schema.TypeString, 43 | Computed: true, 44 | Description: "The default message to display on the OAuth consent screen if one cannot be found in the theme.", 45 | }, 46 | "description": { 47 | Type: schema.TypeString, 48 | Computed: true, 49 | Description: "The description of the scope.", 50 | }, 51 | "required": { 52 | Type: schema.TypeBool, 53 | Computed: true, 54 | Description: "Determines if the OAuth Scope is required when requested in an OAuth workflow.", 55 | }, 56 | }, 57 | } 58 | } 59 | 60 | func dataSourceApplicationOAuthScopeRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 61 | client := i.(Client) 62 | aid := data.Get("application_id").(string) 63 | resp, err := client.FAClient.RetrieveApplication(aid) 64 | if err != nil { 65 | return diag.FromErr(err) 66 | } 67 | if err := checkResponse(resp.StatusCode, nil); err != nil { 68 | return diag.FromErr(err) 69 | } 70 | name := data.Get("name").(string) 71 | var scope *fusionauth.ApplicationOAuthScope 72 | 73 | for i := range resp.Application.Scopes { 74 | if name == resp.Application.Scopes[i].Name { 75 | scope = &resp.Application.Scopes[i] 76 | } 77 | } 78 | 79 | if scope == nil { 80 | return diag.Errorf("couldn't find role %s in application oauth scope %s", name, aid) 81 | } 82 | data.SetId(scope.Id) 83 | 84 | if err := data.Set("application_id", scope.ApplicationId); err != nil { 85 | return diag.Errorf("scope.application_id: %s", err.Error()) 86 | } 87 | if err := data.Set("scope_id", scope.Id); err != nil { 88 | return diag.Errorf("scope.scope_id: %s", err.Error()) 89 | } 90 | dataJSON, diags := mapStringInterfaceToJSONString(scope.Data) 91 | if diags != nil { 92 | return diags 93 | } 94 | err = data.Set("data", dataJSON) 95 | if err != nil { 96 | return diag.Errorf("scope.data: %s", err.Error()) 97 | } 98 | if err := data.Set("default_consent_detail", scope.DefaultConsentDetail); err != nil { 99 | return diag.Errorf("scope.default_consent_detail: %s", err.Error()) 100 | } 101 | if err := data.Set("default_consent_message", scope.DefaultConsentMessage); err != nil { 102 | return diag.Errorf("scope.default_consent_message: %s", err.Error()) 103 | } 104 | if err := data.Set("description", scope.Description); err != nil { 105 | return diag.Errorf("scope.description: %s", err.Error()) 106 | } 107 | if err := data.Set("name", scope.Name); err != nil { 108 | return diag.Errorf("scope.name: %s", err.Error()) 109 | } 110 | if err := data.Set("required", scope.Required); err != nil { 111 | return diag.Errorf("scope.required: %s", err.Error()) 112 | } 113 | 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /docs/data-sources/user.md: -------------------------------------------------------------------------------- 1 | # User Data Source 2 | 3 | This data source can be used to fetch information about a specific user. 4 | 5 | [Users API](https://fusionauth.io/docs/v1/tech/apis/users) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | # Fetch user by username 11 | data "fusionauth_user" "example" { 12 | username = "foo@example.com" 13 | } 14 | ``` 15 | 16 | ## Argument Reference 17 | 18 | * `tenant_id` - (Optional) The Id of the tenant used to scope this API request. 19 | * `email` - (Optional) The email address of the user. Either `email`, `'username` or `user_id` must be specified. 20 | * `user_id` - (Optional) The Id of the user. Either `email`, `'username` or `user_id` must be specified. 21 | * `username` - (Optional) The username of the user. Either `email`, `'username` or `user_id` must be specified. 22 | 23 | ## Attributes Reference 24 | 25 | All of the argument attributes are also exported as result attributes. 26 | 27 | The following additional attributes are exported: 28 | 29 | * `active` - True if the user is active. False if the user has been deactivated. Deactivated users will not be able to login. 30 | * `birth_date` - An ISO-8601 formatted date of the user’s birthdate such as YYYY-MM-DD. 31 | * `data` - A JSON string that can hold any information about the user. 32 | * `expiry` - The expiration instant of the user’s account. An expired user is not permitted to login. 33 | * `first_name` - The first name of the user. 34 | * `full_name` - The user’s full name. 35 | * `identities` - The list of identities that exist for a User. 36 | * `display_value` - The display value for the identity. Only used for username type identities. If the unique username feature is not enabled, this value will be the same as user.identities[x].value. Otherwise, it will be the username the User has chosen. For primary username identities, this will be the same value as user.username. 37 | * `insert_instant` - The instant when the identity was created. 38 | * `last_login_instant` - The instant when the identity was last used to log in. If a User has multiple identity types (username, email, and phoneNumber), then this value will represent the specific identity they last used to log in. This contrasts with user.lastLoginInstant, which represents the last time any of the User’s identities was used to log in. 39 | * `last_update_instant` - The instant when the identity was last updated. 40 | * `moderation_status` - The current status of the username. This is used if you are moderating usernames via CleanSpeak. 41 | * `type` - he identity type. 42 | * `value` - The value represented by the identity. 43 | * `verified` - Whether verification was actually performed on the identity by FusionAuth. 44 | * `verified_instant` - The instant when verification was performed on the identity. 45 | * `verified_reason` - The reason the User’s identity was verified or not verified. 46 | * `image_url` - The URL that points to an image file that is the user’s profile image. 47 | * `last_name` - The user’s last name. 48 | * `middle_name` - The user’s middle name. 49 | * `mobile_phone` - The user’s mobile phone number. 50 | * `parent_email` - The email address of the user’s parent or guardian. 51 | * `password_change_required` - Indicates that the user’s password needs to be changed during their next login attempt. 52 | * `phone_number` - The user’s phone number. 53 | * `preferred_languages` - An array of locale strings that give, in order, the user’s preferred languages. 54 | * `timezone` - The user’s preferred timezone. 55 | * `username_status` - The current status of the username. This is used if you are moderating usernames via CleanSpeak. 56 | * `verification_ids` - The list of all verifications that exist for a user. This includes the email and phone identities that a user may have. The values from emailVerificationId and emailVerificationOneTimeCode are legacy fields and will also be present in this list. 57 | * `verification_id` - A verification Id. 58 | * `one_time_code` - A one time code that will be paired with the verificationIds[x].id. 59 | * `type` - The identity type that the verification Id is for. This identity type, along with verificationIds[x].value , matches exactly one identity via user.identities[x].type. 60 | * `value` - The identity value that the verification Id is for. This identity value, along with verificationIds[x].type , matches exactly one identity via user.identities[x].value. 61 | -------------------------------------------------------------------------------- /fusionauth/resource_fusionauth_reactor.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FusionAuth/go-client/pkg/fusionauth" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | ) 10 | 11 | func newReactor() *schema.Resource { 12 | return &schema.Resource{ 13 | CreateContext: createReactor, 14 | ReadContext: readReactor, 15 | UpdateContext: updateReactor, 16 | DeleteContext: deleteReactor, 17 | Schema: map[string]*schema.Schema{ 18 | "license_id": { 19 | Type: schema.TypeString, 20 | Required: true, 21 | Sensitive: true, 22 | Description: "The license Id to activate", 23 | }, 24 | "license": { 25 | Type: schema.TypeString, 26 | Optional: true, 27 | Sensitive: true, 28 | Description: "The Base64 encoded license value. This value is necessary in an air gapped configuration where outbound network access is not available.", 29 | }, 30 | }, 31 | } 32 | } 33 | 34 | func buildReactor(data *schema.ResourceData) fusionauth.ReactorRequest { 35 | reactor := fusionauth.ReactorRequest{ 36 | LicenseId: data.Get("license_id").(string), 37 | License: data.Get("license").(string), 38 | } 39 | return reactor 40 | } 41 | 42 | func createReactor(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 43 | client := i.(Client) 44 | reactor := buildReactor(data) 45 | 46 | resp, faErrs, err := client.FAClient.ActivateReactor(reactor) 47 | if err != nil { 48 | return diag.Errorf("CreateReactor err: %v", err) 49 | } 50 | 51 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 52 | return diag.FromErr(err) 53 | } 54 | 55 | // Every terraform resource needs an id. Since none of the fusion auth reactor apis return 56 | // the id of the reactor, we will need to make one up. There can be only one reactor 57 | // so this should be safe. 58 | data.SetId("fusion-auth-reactor-id") 59 | 60 | return nil 61 | } 62 | 63 | func updateReactor(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 64 | client := i.(Client) 65 | reactor := buildReactor(data) 66 | 67 | resp, faErrs, err := client.FAClient.ActivateReactor(fusionauth.ReactorRequest{ 68 | LicenseId: reactor.LicenseId, 69 | License: reactor.License, 70 | }) 71 | if err != nil { 72 | return diag.Errorf("UpdateReactor err: %v", err) 73 | } 74 | 75 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 76 | data.Partial(true) 77 | return diag.FromErr(err) 78 | } 79 | return nil 80 | } 81 | 82 | func deleteReactor(_ context.Context, _ *schema.ResourceData, i interface{}) diag.Diagnostics { 83 | client := i.(Client) 84 | 85 | resp, err := client.FAClient.DeactivateReactor() 86 | if err != nil { 87 | return diag.FromErr(err) 88 | } 89 | 90 | if err := checkResponse(resp.StatusCode, nil); err != nil { 91 | return diag.FromErr(err) 92 | } 93 | 94 | return nil 95 | } 96 | 97 | func readReactor(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 98 | client := i.(Client) 99 | 100 | resp, err := client.FAClient.RetrieveReactorStatus() 101 | if err != nil { 102 | return diag.FromErr(err) 103 | } 104 | 105 | if err := checkResponse(resp.StatusCode, nil); err != nil { 106 | return diag.FromErr(err) 107 | } 108 | 109 | // The reactor read api does not return the license_id or license values. So we 110 | // cannot update the state using this response like other resources. However, the 111 | // reactor response does include a boolean attribute telling us if it is licensed. 112 | if resp.Status.Licensed { 113 | // If the reactor is licensed, not setting the attributes here should leave 114 | // the state unchanged. Adding a warning here so that users know this doesn't 115 | // work like a normal resource. 116 | return diag.Diagnostics{ 117 | diag.Diagnostic{ 118 | Severity: diag.Warning, 119 | Summary: "Unable to set reactor license attributes from FusionAuth response", 120 | }, 121 | } 122 | } 123 | 124 | // If the reactor is not licensed, we know these attributes must be blank. 125 | if err := data.Set("license_id", nil); err != nil { 126 | return diag.Errorf("reactor.license_id: %s", err.Error()) 127 | } 128 | if err := data.Set("license", nil); err != nil { 129 | return diag.Errorf("reactor.license: %s", err.Error()) 130 | } 131 | 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /docs/resources/idp_xbox.md: -------------------------------------------------------------------------------- 1 | # Xbox Identity Provider Resource 2 | 3 | The Xbox identity provider type will use the Xbox OAuth v2.0 login API. It will also provide a Login with Xbox button on FusionAuth’s login page that will direct a user to the Xbox login page. 4 | 5 | This identity provider will call Xbox’s API to load the user’s email and gtg (Gamer Tag) and use those as email and username to lookup or create a user in FusionAuth depending on the linking strategy configured for this identity provider. Additional claims returned by Xbox can be used to reconcile the user to FusionAuth by using an Xbox Reconcile Lambda. 6 | 7 | FusionAuth will also store the Xbox refresh_token returned from the Xbox API in the link between the user and the identity provider. This token can be used by an application to make further requests to Xbox APIs on behalf of the user. 8 | 9 | [Xbox Identity Provider APIs](https://fusionauth.io/docs/v1/tech/apis/identity-providers/xbox/) 10 | 11 | ## Example Usage 12 | 13 | ```hcl 14 | resource "fusionauth_idp_xbox" "xbox" { 15 | application_configuration { 16 | application_id = fusionauth_application.GPS_Insight.id 17 | create_registration = true 18 | enabled = true 19 | } 20 | button_text = "Login with Xbox" 21 | client_id = "0eb1ce3c-2fb1-4ae9-b361-d49fc6e764cc" 22 | client_secret = "693s000cbn66k0mxtqzr_c_NfLy3~6_SEA" 23 | scope = "Xboxlive.signin Xboxlive.offline_access" 24 | } 25 | ``` 26 | 27 | ## Argument Reference 28 | 29 | * `button_text` - (Required) The top-level button text to use on the FusionAuth login page for this Identity Provider. 30 | * `client_id` - (Required) The top-level Xbox client id for your Application. This value is retrieved from the Xbox developer website when you setup your Xbox developer account. 31 | * `client_secret` - (Required) The top-level client secret to use with the Xbox Identity Provider when retrieving the long-lived token. This value is retrieved from the Xbox developer website when you setup your Xbox developer account. 32 | 33 | --- 34 | 35 | * `application_configuration` - (Optional) The configuration for each Application that the identity provider is enabled for. 36 | * `application_id` - (Optional) ID of the Application to apply this configuration to. 37 | * `button_text` - (Optional) This is an optional Application specific override for the top level button text. 38 | * `client_id` - (Optional) This is an optional Application specific override for the top level client_id. 39 | * `client_secret` - (Optional) This is an optional Application specific override for the top level client_secret. 40 | * `create_registration` - (Optional) Determines if a UserRegistration is created for the User automatically or not. If a user doesn’t exist in FusionAuth and logs in through an identity provider, this boolean controls whether or not FusionAuth creates a registration for the User in the Application they are logging into. 41 | * `enabled` - (Optional) Determines if this identity provider is enabled for the Application specified by the applicationId key. 42 | * `scope` - (Optional)This is an optional Application specific override for the top level scope. 43 | * `debug` - (Optional) Determines if debug is enabled for this provider. When enabled, each time this provider is invoked to reconcile a login an Event Log will be created. 44 | * `enabled` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 45 | * `idp_id` - (Optional) The ID to use for the new identity provider. If not specified a secure random UUID will be generated. 46 | * `lambda_reconcile_id` - (Optional) The unique Id of the lambda to used during the user reconcile process to map custom claims from the external identity provider to the FusionAuth user. 47 | * `linking_strategy` - (Optional) The linking strategy to use when creating the link between the {idp_display_name} Identity Provider and the user. 48 | * `scope` - (Optional) The top-level scope that you are requesting from Xbox. 49 | * `tenant_configuration` - (Optional) The configuration for each Tenant that limits the number of links a user may have for a particular identity provider. 50 | * `tenant_id` - (Optional) The unique Id of the tenant that this configuration applies to. 51 | * `limit_user_link_count_enabled` - (Optional) When enabled, the number of identity provider links a user may create is enforced by maximumLinks. 52 | * `limit_user_link_count_maximum_links` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 53 | -------------------------------------------------------------------------------- /docs/resources/idp_twitch.md: -------------------------------------------------------------------------------- 1 | # Twitch Identity Provider Resource 2 | 3 | The Twitch identity provider type will use the Twitch OAuth v2.0 login API. It will also provide a Login with Twitch button on FusionAuth’s login page that will direct a user to the Twitch login page. 4 | 5 | This identity provider will call Twitch’s API to load the user’s email and preferred_username and use those as email and username to lookup or create a user in FusionAuth depending on the linking strategy configured for this identity provider. Additional claims returned by Twitch can be used to reconcile the user to FusionAuth by using a Twitch Reconcile Lambda. 6 | 7 | FusionAuth will also store the Twitch refresh_token returned from the Twitch API in the link between the user and the identity provider. This token can be used by an application to make further requests to Twitch APIs on behalf of the user. 8 | 9 | [Twitch Identity Provider APIs](https://fusionauth.io/docs/v1/tech/apis/identity-providers/twitch/) 10 | 11 | ## Example Usage 12 | 13 | ```hcl 14 | resource "fusionauth_idp_twitch" "twitch" { 15 | application_configuration { 16 | application_id = fusionauth_application.my_app.id 17 | create_registration = true 18 | enabled = true 19 | } 20 | button_text = "Login with Twitch" 21 | client_id = "0eb1ce3c-2fb1-4ae9-b361-d49fc6e764cc" 22 | client_secret = "693s000cbn66k0mxtqzr_c_NfLy3~6_SEA" 23 | scope = "Xboxlive.signin Xboxlive.offline_access" 24 | } 25 | ``` 26 | 27 | ## Argument Reference 28 | 29 | * `button_text` - (Required) The top-level button text to use on the FusionAuth login page for this Identity Provider. 30 | * `client_id` - (Required) The top-level Xbox client id for your Application. This value is retrieved from the Xbox developer website when you setup your Xbox developer account. 31 | * `client_secret` - (Required) The top-level client secret to use with the Xbox Identity Provider when retrieving the long-lived token. This value is retrieved from the Xbox developer website when you setup your Xbox developer account. 32 | 33 | --- 34 | 35 | * `application_configuration` - (Optional) The configuration for each Application that the identity provider is enabled for. 36 | * `application_id` - (Optional) ID of the Application to apply this configuration to. 37 | * `button_text` - (Optional) This is an optional Application specific override for the top level button text. 38 | * `client_id` - (Optional) This is an optional Application specific override for the top level client_id. 39 | * `client_secret` - (Optional) This is an optional Application specific override for the top level client_secret. 40 | * `create_registration` - (Optional) Determines if a UserRegistration is created for the User automatically or not. If a user doesn’t exist in FusionAuth and logs in through an identity provider, this boolean controls whether or not FusionAuth creates a registration for the User in the Application they are logging into. 41 | * `enabled` - (Optional) Determines if this identity provider is enabled for the Application specified by the applicationId key. 42 | * `scope` - (Optional)This is an optional Application specific override for the top level scope. 43 | * `debug` - (Optional) Determines if debug is enabled for this provider. When enabled, each time this provider is invoked to reconcile a login an Event Log will be created. 44 | * `enabled` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 45 | * `idp_id` - (Optional) The ID to use for the new identity provider. If not specified a secure random UUID will be generated. 46 | * `lambda_reconcile_id` - (Optional) The unique Id of the lambda to used during the user reconcile process to map custom claims from the external identity provider to the FusionAuth user. 47 | * `linking_strategy` - (Optional) The linking strategy to use when creating the link between the {idp_display_name} Identity Provider and the user. 48 | * `scope` - (Optional) The top-level scope that you are requesting from Xbox. 49 | * `tenant_configuration` - (Optional) The configuration for each Tenant that limits the number of links a user may have for a particular identity provider. 50 | * `tenant_id` - (Optional) The unique Id of the tenant that this configuration applies to. 51 | * `limit_user_link_count_enabled` - (Optional) When enabled, the number of identity provider links a user may create is enforced by maximumLinks. 52 | * `limit_user_link_count_maximum_links` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 53 | -------------------------------------------------------------------------------- /fusionauth/resource_fusionauth_key.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FusionAuth/go-client/pkg/fusionauth" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 10 | ) 11 | 12 | func newKey() *schema.Resource { 13 | return &schema.Resource{ 14 | CreateContext: createKey, 15 | ReadContext: func(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 16 | return keyRead(data, buildResourceDataFromKey, i) 17 | }, 18 | UpdateContext: func(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 19 | return keyUpdate(data, buildKey, i) 20 | }, 21 | DeleteContext: keyDelete, 22 | Schema: map[string]*schema.Schema{ 23 | "key_id": { 24 | Type: schema.TypeString, 25 | Optional: true, 26 | Computed: true, 27 | Description: "The Id to use for the new Key. If not specified a secure random UUID will be generated.", 28 | ValidateFunc: validation.IsUUID, 29 | }, 30 | "algorithm": { 31 | Type: schema.TypeString, 32 | Required: true, 33 | ForceNew: true, 34 | ValidateFunc: validation.StringInSlice([]string{ 35 | "ES256", 36 | "ES384", 37 | "ES512", 38 | "RS256", 39 | "RS384", 40 | "RS512", 41 | "HS256", 42 | "HS384", 43 | "HS512", 44 | }, false), 45 | Description: "The algorithm used to encrypt the Key.", 46 | }, 47 | "issuer": { 48 | Type: schema.TypeString, 49 | Computed: true, 50 | Optional: true, 51 | ForceNew: true, 52 | Description: "The issuer of the RSA or EC certificate. If omitted, this value will default to the value of tenant issuer on the default tenant. For HMAC keys, this field does not apply and will be ignored if specified, and no default value will be set.", 53 | }, 54 | "name": { 55 | Type: schema.TypeString, 56 | Required: true, 57 | Description: "The name of the Key.", 58 | }, 59 | "length": { 60 | Type: schema.TypeInt, 61 | Optional: true, 62 | ForceNew: true, 63 | Description: "The length of the RSA or EC certificate. This field is required when generating RSA key types.", 64 | }, 65 | "kid": { 66 | Type: schema.TypeString, 67 | Computed: true, 68 | Description: "The id used in the JWT header to identify the key used to generate the signature", 69 | }, 70 | }, 71 | Importer: &schema.ResourceImporter{ 72 | StateContext: schema.ImportStatePassthroughContext, 73 | }, 74 | } 75 | } 76 | 77 | func buildKey(data *schema.ResourceData) fusionauth.Key { 78 | l := fusionauth.Key{ 79 | Algorithm: fusionauth.KeyAlgorithm(data.Get("algorithm").(string)), 80 | Issuer: data.Get("issuer").(string), 81 | Name: data.Get("name").(string), 82 | Length: data.Get("length").(int), 83 | } 84 | return l 85 | } 86 | 87 | func createKey(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 88 | client := i.(Client) 89 | l := buildKey(data) 90 | 91 | var keyID string 92 | if a, ok := data.GetOk("key_id"); ok { 93 | keyID = a.(string) 94 | } 95 | 96 | resp, faErrs, err := client.FAClient.GenerateKey(keyID, fusionauth.KeyRequest{ 97 | Key: l, 98 | }) 99 | if err != nil { 100 | return diag.Errorf("CreateKey err: %v", err) 101 | } 102 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 103 | return diag.FromErr(err) 104 | } 105 | 106 | data.SetId(resp.Key.Id) 107 | return buildResourceDataFromKey(data, resp.Key) 108 | } 109 | 110 | func buildResourceDataFromKey(data *schema.ResourceData, res fusionauth.Key) diag.Diagnostics { 111 | if err := data.Set("key_id", res.Id); err != nil { 112 | return diag.Errorf("key.key_id: %s", err.Error()) 113 | } 114 | if err := data.Set("algorithm", res.Algorithm); err != nil { 115 | return diag.Errorf("key.algorithm: %s", err.Error()) 116 | } 117 | if err := data.Set("issuer", res.Issuer); err != nil { 118 | return diag.Errorf("key.issuer: %s", err.Error()) 119 | } 120 | if err := data.Set("name", res.Name); err != nil { 121 | return diag.Errorf("key.name: %s", err.Error()) 122 | } 123 | if err := data.Set("length", res.Length); err != nil { 124 | return diag.Errorf("key.length: %s", err.Error()) 125 | } 126 | if err := data.Set("kid", res.Kid); err != nil { 127 | return diag.Errorf("key.kid: %s", err.Error()) 128 | } 129 | 130 | return nil 131 | } 132 | -------------------------------------------------------------------------------- /fusionauth/provider.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 5 | ) 6 | 7 | // Provider configures and returns a fusionauth terraform provider. 8 | func Provider() *schema.Provider { 9 | return &schema.Provider{ 10 | Schema: map[string]*schema.Schema{ 11 | "host": { 12 | Type: schema.TypeString, 13 | Required: true, 14 | DefaultFunc: schema.EnvDefaultFunc("FA_DOMAIN", nil), 15 | }, 16 | "api_key": { 17 | Type: schema.TypeString, 18 | Required: true, 19 | DefaultFunc: schema.EnvDefaultFunc("FA_API_KEY", nil), 20 | }, 21 | }, 22 | ResourcesMap: map[string]*schema.Resource{ 23 | "fusionauth_api_key": resourceAPIKey(), 24 | "fusionauth_application": newApplication(), 25 | "fusionauth_application_role": newApplicationRole(), 26 | "fusionauth_application_oauth_scope": newApplicationOAuthScope(), 27 | "fusionauth_consent": newConsent(), 28 | "fusionauth_email": newEmail(), 29 | "fusionauth_entity": resourceEntity(), 30 | "fusionauth_entity_grant": resourceEntityGrant(), 31 | "fusionauth_entity_type": resourceEntityType(), 32 | "fusionauth_entity_type_permission": resourceEntityTypePermission(), 33 | "fusionauth_generic_connector": newGenericConnector(), 34 | "fusionauth_generic_messenger": newGenericMessenger(), 35 | "fusionauth_form": resourceForm(), 36 | "fusionauth_form_field": resourceFormField(), 37 | "fusionauth_group": newGroup(), 38 | "fusionauth_idp_apple": resourceIDPApple(), 39 | "fusionauth_idp_external_jwt": resourceIDPExternalJWT(), 40 | "fusionauth_idp_facebook": resourceIDPFacebook(), 41 | "fusionauth_idp_google": newIDPGoogle(), 42 | "fusionauth_idp_linkedin": resourceIDPLinkedIn(), 43 | "fusionauth_idp_open_id_connect": newIDPOpenIDConnect(), 44 | "fusionauth_idp_saml_v2": resourceIDPSAMLv2(), 45 | "fusionauth_idp_saml_v2_idp_initated": resourceIDPSAMLv2IdPInitiated(), 46 | "fusionauth_idp_sony_psn": resourceIDPSonyPSN(), 47 | "fusionauth_idp_steam": resourceIDPSteam(), 48 | "fusionauth_idp_twitch": resourceIDPTwitch(), 49 | "fusionauth_idp_xbox": resourceIDPXbox(), 50 | "fusionauth_imported_key": resourceImportedKey(), 51 | "fusionauth_key": newKey(), 52 | "fusionauth_lambda": newLambda(), 53 | "fusionauth_ldap_connector": newLDAPConnector(), 54 | "fusionauth_reactor": newReactor(), 55 | "fusionauth_registration": newRegistration(), 56 | "fusionauth_sms_message_template": newSMSMessageTemplate(), 57 | "fusionauth_system_configuration": resourceSystemConfiguration(), 58 | "fusionauth_theme": newTheme(), 59 | "fusionauth_tenant": newTenant(), 60 | "fusionauth_twilio_messenger": newTwilioMessenger(), 61 | "fusionauth_user": newUser(), 62 | "fusionauth_user_action": resourceUserAction(), 63 | "fusionauth_user_group_membership": newUserGroupMembership(), 64 | "fusionauth_webhook": newWebhook(), 65 | }, 66 | DataSourcesMap: map[string]*schema.Resource{ 67 | "fusionauth_application": dataSourceApplication(), 68 | "fusionauth_application_oauth_scope": dataSourceApplicationOAuthScope(), 69 | "fusionauth_application_role": dataSourceApplicationRole(), 70 | "fusionauth_consent": dataSourceConsent(), 71 | "fusionauth_email": dataSourceEmail(), 72 | "fusionauth_form": dataSourceForm(), 73 | "fusionauth_form_field": dataSourceFormField(), 74 | "fusionauth_generic_connector": dataSourceGenericConnector(), 75 | "fusionauth_generic_messenger": dataSourceGenericMessenger(), 76 | "fusionauth_idp": dataSourceIDP(), 77 | "fusionauth_lambda": dataSourceLambda(), 78 | "fusionauth_ldap_connector": dataSourceLDAPConnector(), 79 | "fusionauth_sms_message_template": dataSourceSMSMessageTemplate(), 80 | "fusionauth_tenant": dataSourceTenant(), 81 | "fusionauth_theme": dataSourceTheme(), 82 | "fusionauth_twilio_messenger": dataSourceTwilioMessenger(), 83 | "fusionauth_user": dataSourceUser(), 84 | "fusionauth_user_group_membership": dataSourceUserGroupMembership(), 85 | }, 86 | ConfigureContextFunc: configureClient, 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /docs/resources/idp_sony_psn.md: -------------------------------------------------------------------------------- 1 | # Sony Playstation Network Identity Provider Resource 2 | 3 | The Sony PlayStation Network identity provider type will use the Sony OAuth v2.0 login API. It will also provide a Login with Sony PlayStation Network button on FusionAuth’s login page that will direct a user to the Sony login page. 4 | 5 | This identity provider will call Sony’s API to load the user’s email and online_id and use those as email and username to lookup or create a user in FusionAuth depending on the linking strategy configured for this identity provider. Additional claims returned by Sony PlayStation Network can be used to reconcile the user to FusionAuth by using a Sony PlayStation Network Reconcile Lambda. 6 | 7 | FusionAuth will also store the Sony PlayStation Network access_token returned from the Sony PlayStation Network API in the link between the user and the identity provider. This token can be used by an application to make further requests to Sony PlayStation Network APIs on behalf of the user. 8 | 9 | [Sony PlayStation Network Identity Provider APIs](https://fusionauth.io/docs/v1/tech/apis/identity-providers/sonypsn/) 10 | 11 | ## Example Usage 12 | 13 | ```hcl 14 | resource "fusionauth_idp_sony_psn" "sony_psn" { 15 | application_configuration { 16 | application_id = fusionauth_application.my_app.id 17 | create_registration = true 18 | enabled = true 19 | } 20 | button_text = "Login with Playstation" 21 | client_id = "0eb1ce3c-2fb1-4ae9-b361-d49fc6e764cc" 22 | client_secret = "693s000cbn66k0mxtqzr_c_NfLy3~6_SEA" 23 | } 24 | ``` 25 | 26 | ## Argument Reference 27 | 28 | * `button_text` - (Required) The top-level button text to use on the FusionAuth login page for this Identity Provider. 29 | * `client_id` - (Required) The top-level Sony PlayStation Network client id for your Application. This value is retrieved from the Sony PlayStation Network developer website when you setup your Sony PlayStation Network developer account. 30 | * `client_secret` - (Required) The top-level client secret to use with the Sony PlayStation Network Identity Provider when retrieving the long-lived token. This value is retrieved from the Sony PlayStation Network developer website when you setup your Sony PlayStation Network developer account. 31 | 32 | --- 33 | 34 | * `application_configuration` - (Optional) The configuration for each Application that the identity provider is enabled for. 35 | * `application_id` - (Optional) ID of the Application to apply this configuration to. 36 | * `button_text` - (Optional) This is an optional Application specific override for the top level button text. 37 | * `client_id` - (Optional) This is an optional Application specific override for the top level client_id. 38 | * `client_secret` - (Optional) This is an optional Application specific override for the top level client_secret. 39 | * `create_registration` - (Optional) Determines if a UserRegistration is created for the User automatically or not. If a user doesn’t exist in FusionAuth and logs in through an identity provider, this boolean controls whether or not FusionAuth creates a registration for the User in the Application they are logging into. 40 | * `enabled` - (Optional) Determines if this identity provider is enabled for the Application specified by the applicationId key. 41 | * `scope` - (Optional)This is an optional Application specific override for the top level scope. 42 | * `debug` - (Optional) Determines if debug is enabled for this provider. When enabled, each time this provider is invoked to reconcile a login an Event Log will be created. 43 | * `enabled` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 44 | * `idp_id` - (Optional) The ID to use for the new identity provider. If not specified a secure random UUID will be generated. 45 | * `lambda_reconcile_id` - (Optional) The unique Id of the lambda to used during the user reconcile process to map custom claims from the external identity provider to the FusionAuth user. 46 | * `linking_strategy` - (Optional) The linking strategy to use when creating the link between the {idp_display_name} Identity Provider and the user. 47 | * `scope` - (Optional) The top-level scope that you are requesting from Sony PlayStation Network. 48 | * `tenant_configuration` - (Optional) The configuration for each Tenant that limits the number of links a user may have for a particular identity provider. 49 | * `tenant_id` - (Optional) The unique Id of the tenant that this configuration applies to. 50 | * `limit_user_link_count_enabled` - (Optional) When enabled, the number of identity provider links a user may create is enforced by maximumLinks. 51 | * `limit_user_link_count_maximum_links` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 52 | -------------------------------------------------------------------------------- /docs/resources/idp_apple.md: -------------------------------------------------------------------------------- 1 | # Apple Identity Provider Resource 2 | 3 | The Apple identity provider type will use the Sign in with Apple APIs and will provide a Sign with Apple button on FusionAuth’s login page that will either redirect to an Apple sign in page or leverage native controls when using Safari on macOS or iOS. Additionally, this identity provider will call Apple’s /auth/token API to load additional details about the user and store them in FusionAuth. 4 | 5 | FusionAuth will also store the Apple refresh_token that is returned from the /auth/token endpoint in the UserRegistration object inside the tokens Map. This Map stores the tokens from the various identity providers so that you can use them in your application to call their APIs. 6 | 7 | [Apple Identity Providers API](https://fusionauth.io/docs/v1/tech/apis/identity-providers/apple/#create-the-apple-identity-provider) 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | resource "fusionauth_idp_apple" "apple" { 13 | application_configuration { 14 | application_id = "1c212e59-0d0e-6b1a-ad48-f4f92793be32" 15 | create_registration = true 16 | enabled = true 17 | } 18 | button_text = "Sign in with Apple" 19 | debug = false 20 | enabled = true 21 | key_id = "2f81529c-4d39-4ce2-982e-cf5fbb1325f6" 22 | scope = "email name" 23 | services_id = "com.piedpiper.webapp" 24 | team_id = "R4NQ1P4UEB" 25 | } 26 | ``` 27 | 28 | ## Argument Reference 29 | 30 | * `application_configuration` - (Optional) The configuration for each Application that the identity provider is enabled for. 31 | * `application_id` - (Optional) ID of the Application to apply this configuration to. 32 | * `button_text` - (Optional) This is an optional Application specific override for the top level button text. 33 | * `create_registration` - (Optional) Determines if a UserRegistration is created for the User automatically or not. If a user doesn’t exist in FusionAuth and logs in through an identity provider, this boolean controls whether or not FusionAuth creates a registration for the User in the Application they are logging into. 34 | * `enabled` - (Optional) Determines if this identity provider is enabled for the Application specified by the applicationId key. 35 | * `key_id` - (Optional) This is an optional Application specific override for the top level keyId. 36 | * `scope` - (Optional) This is an optional Application specific override for for the top level scope. 37 | * `services_id` - (Optional) This is an optional Application specific override for for the top level servicesId. 38 | * `team_id` - (Optional) This is an optional Application specific override for for the top level teamId. 39 | * `bundle_id` - (Optional) The Apple Bundle identifier found in your Apple Developer Account which has been configured for Sign in with Apple. The Bundle identifier is used to Sign in with Apple from native applications. The request must include `bundle_id` or `services_id` . If `services_id` is omitted, this field is required. 40 | * `button_text` - (Required) The top-level button text to use on the FusionAuth login page for this Identity Provider. 41 | * `key_id` - (Required) The unique Id of the private key downloaded from Apple and imported into Key Master that will be used to sign the client secret. 42 | * `services_id` - (Required) The unique Id of the private key downloaded from Apple and imported into Key Master that will be used to sign the client secret. 43 | * `team_id` - (Required) The Apple App ID Prefix, or Team ID found in your Apple Developer Account which has been configured for Sign in with Apple. 44 | 45 | --- 46 | 47 | * `debug` - (Optional) Determines if debug is enabled for this provider. When enabled, each time this provider is invoked to reconcile a login an Event Log will be created. 48 | * `enabled` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 49 | * `lambda_reconcile_id` - (Optional) The unique Id of the lambda to used during the user reconcile process to map custom claims from the external identity provider to the FusionAuth user. 50 | * `linking_strategy` - (Optional) The linking strategy to use when creating the link between the {idp_display_name} Identity Provider and the user. 51 | * `scope` - (Optional) The top-level space separated scope that you are requesting from Apple. 52 | * `tenant_configuration` - (Optional) The configuration for each Tenant that limits the number of links a user may have for a particular identity provider. 53 | * `tenant_id` - (Optional) The unique Id of the tenant that this configuration applies to. 54 | * `limit_user_link_count_enabled` - (Optional) When enabled, the number of identity provider links a user may create is enforced by maximumLinks. 55 | * `limit_user_link_count_maximum_links` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 56 | -------------------------------------------------------------------------------- /fusionauth/provider_test.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "os" 9 | "testing" 10 | "time" 11 | 12 | "github.com/FusionAuth/go-client/pkg/fusionauth" 13 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 14 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 15 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 18 | ) 19 | 20 | const ( 21 | providerFusionauth = "fusionauth" 22 | retryTimeout = 10 * time.Second 23 | ) 24 | 25 | // testAccProviderFactories is a static map containing only the main provider instance 26 | var testAccProviderFactories map[string]func() (*schema.Provider, error) 27 | 28 | func init() { 29 | // Always allocate a new provider instance each invocation, otherwise gRPC 30 | // ProviderConfigure() can overwrite configuration during concurrent testing. 31 | testAccProviderFactories = map[string]func() (*schema.Provider, error){ 32 | providerFusionauth: func() (*schema.Provider, error) { 33 | // Create a provider instance... 34 | p := Provider() 35 | 36 | // Then pump the provider with the required Terraform configuration. 37 | if diags := p.Configure(context.Background(), terraform.NewResourceConfigRaw(nil)); diags.HasError() { 38 | return nil, fmt.Errorf("error configuring provider: %#+v", diags) 39 | } 40 | 41 | return p, nil 42 | }, 43 | } 44 | } 45 | 46 | // fusionauthClient extracts the underlying client from a configured provider 47 | func fusionauthClient() fusionauth.FusionAuthClient { 48 | provider, err := testAccProviderFactories[providerFusionauth]() 49 | if err != nil { 50 | log.Println("[ERROR] error getting Fusionauth Provider") 51 | } 52 | 53 | return provider.Meta().(Client).FAClient 54 | } 55 | 56 | // testAccPreCheck validates the necessary test API keys exist in the testing 57 | // environment 58 | func testAccPreCheck(t *testing.T) { 59 | if v := os.Getenv("FA_DOMAIN"); v == "" { 60 | t.Fatal("FA_DOMAIN must be set for acceptance tests") 61 | } 62 | if v := os.Getenv("FA_API_KEY"); v == "" { 63 | t.Fatal("FA_API_KEY must be set for acceptance tests") 64 | } 65 | } 66 | 67 | // testCheckResourceAttrJSON compares the specified resource's JSON serialized 68 | // attribute data against the expected data. 69 | // 70 | //nolint:unparam 71 | func testCheckResourceAttrJSON(resourceName, attributeName, expectedData string) resource.TestCheckFunc { 72 | return func(s *terraform.State) error { 73 | rs, ok := s.RootModule().Resources[resourceName] 74 | if !ok { 75 | return fmt.Errorf("resource %s not found", resourceName) 76 | } 77 | 78 | if rs.Primary.ID == "" { 79 | return fmt.Errorf("resource ID is not set") 80 | } 81 | 82 | data, ok := rs.Primary.Attributes[attributeName] 83 | if !ok { 84 | return fmt.Errorf("user data is not set") 85 | } 86 | 87 | equal, err := isEqualJSON(data, expectedData) 88 | if err != nil { 89 | return err 90 | } else if !equal { 91 | return fmt.Errorf("resource %s.%s expected %s, got %s", resourceName, attributeName, expectedData, data) 92 | } 93 | 94 | return nil 95 | } 96 | } 97 | 98 | // checkFusionauthErrors checks for low-level client errors and then any 99 | // reported fusionauth errors. 100 | func checkFusionauthErrors(faErrs *fusionauth.Errors, err error) error { 101 | if err != nil { 102 | // low-level error performing api request 103 | return err 104 | } 105 | 106 | if faErrs != nil && faErrs.Present() { 107 | // Fusionauth has errors to report 108 | return fmt.Errorf("fusionauth errors: %#+v", faErrs) 109 | } 110 | 111 | return nil 112 | } 113 | 114 | // checkFusionauthRetryErrors wraps checking for fusionauth or low-level client 115 | // errors and returns a non-retryable error on failure. 116 | func checkFusionauthRetryErrors(faErrs *fusionauth.Errors, err error) *retry.RetryError { 117 | if anErr := checkFusionauthErrors(faErrs, err); anErr != nil { 118 | return retry.NonRetryableError(anErr) 119 | } 120 | 121 | return nil 122 | } 123 | 124 | // stringSliceToHCL takes a string slice and marshals it to JSON in order to 125 | // generate an HCL syntactically compatible array. 126 | func stringSliceToHCL(values []string) string { 127 | output := "[]" 128 | if len(values) > 0 { 129 | bytes, _ := json.Marshal(values) 130 | output = string(bytes) 131 | } 132 | 133 | return output 134 | } 135 | 136 | // randString10 returns a random alpha-numeric string of 10 characters. 137 | func randString10() string { 138 | return acctest.RandString(10) 139 | } 140 | 141 | // randString20 returns a random alpha-numeric string of 20 characters. 142 | func randString20() string { 143 | return acctest.RandString(20) 144 | } 145 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_lambda.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/FusionAuth/go-client/pkg/fusionauth" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 12 | ) 13 | 14 | func dataSourceLambda() *schema.Resource { 15 | return &schema.Resource{ 16 | ReadContext: dataSourceLambdaRead, 17 | Schema: map[string]*schema.Schema{ 18 | "id": { 19 | Type: schema.TypeString, 20 | Optional: true, 21 | ForceNew: true, 22 | Computed: true, 23 | AtLeastOneOf: []string{"id", "name"}, 24 | Description: "The ID of the Lambda.", 25 | }, 26 | "body": { 27 | Type: schema.TypeString, 28 | Computed: true, 29 | Description: "The lambda function body, a JavaScript function.", 30 | }, 31 | "debug": { 32 | Type: schema.TypeBool, 33 | Computed: true, 34 | Description: "Whether or not debug event logging is enabled for this Lambda.", 35 | }, 36 | "name": { 37 | Type: schema.TypeString, 38 | Optional: true, 39 | ForceNew: true, 40 | AtLeastOneOf: []string{"id", "name"}, 41 | Description: "The name of the Lambda.", 42 | }, 43 | "type": { 44 | Type: schema.TypeString, 45 | Required: true, 46 | ForceNew: true, 47 | ValidateFunc: validation.StringInSlice([]string{ 48 | string(fusionauth.LambdaType_AppleReconcile), 49 | string(fusionauth.LambdaType_ClientCredentialsJWTPopulate), 50 | string(fusionauth.LambdaType_EpicGamesReconcile), 51 | string(fusionauth.LambdaType_ExternalJWTReconcile), 52 | string(fusionauth.LambdaType_FacebookReconcile), 53 | string(fusionauth.LambdaType_GoogleReconcile), 54 | string(fusionauth.LambdaType_HYPRReconcile), 55 | string(fusionauth.LambdaType_JWTPopulate), 56 | string(fusionauth.LambdaType_LDAPConnectorReconcile), 57 | string(fusionauth.LambdaType_LinkedInReconcile), 58 | string(fusionauth.LambdaType_LoginValidation), 59 | string(fusionauth.LambdaType_NintendoReconcile), 60 | string(fusionauth.LambdaType_OpenIDReconcile), 61 | string(fusionauth.LambdaType_SAMLv2Populate), 62 | string(fusionauth.LambdaType_SAMLv2Reconcile), 63 | string(fusionauth.LambdaType_SCIMServerGroupRequestConverter), 64 | string(fusionauth.LambdaType_SCIMServerGroupResponseConverter), 65 | string(fusionauth.LambdaType_SCIMServerUserRequestConverter), 66 | string(fusionauth.LambdaType_SCIMServerUserResponseConverter), 67 | string(fusionauth.LambdaType_SelfServiceRegistrationValidation), 68 | string(fusionauth.LambdaType_SonyPSNReconcile), 69 | string(fusionauth.LambdaType_SteamReconcile), 70 | string(fusionauth.LambdaType_TwitchReconcile), 71 | string(fusionauth.LambdaType_TwitterReconcile), 72 | string(fusionauth.LambdaType_UserInfoPopulate), 73 | string(fusionauth.LambdaType_XboxReconcile), 74 | }, false), 75 | Description: "The Lambda type.", 76 | }, 77 | }, 78 | } 79 | } 80 | 81 | func dataSourceLambdaRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 82 | client := i.(Client) 83 | 84 | lambdaType := data.Get("type").(string) 85 | resp, err := client.FAClient.RetrieveLambdasByType(fusionauth.LambdaType(lambdaType)) 86 | if err != nil { 87 | return diag.FromErr(err) 88 | } 89 | if err := checkResponse(resp.StatusCode, nil); err != nil { 90 | return diag.FromErr(err) 91 | } 92 | 93 | name := data.Get("name").(string) 94 | preFilteredLambdas := resp.Lambdas 95 | var filteredLambdas []fusionauth.Lambda 96 | 97 | if id, ok := data.GetOk("id"); ok { 98 | for _, l := range preFilteredLambdas { 99 | if l.Id == id.(string) { 100 | filteredLambdas = append(filteredLambdas, l) 101 | } 102 | } 103 | preFilteredLambdas = filteredLambdas 104 | } 105 | 106 | if name, ok := data.GetOk("name"); ok { 107 | filteredLambdas = []fusionauth.Lambda{} 108 | for _, l := range preFilteredLambdas { 109 | if l.Name == name.(string) { 110 | filteredLambdas = append(filteredLambdas, l) 111 | } 112 | } 113 | } 114 | 115 | if len(filteredLambdas) < 1 { 116 | return diag.FromErr(fmt.Errorf("couldn't find lambda %s of type %s", name, lambdaType)) 117 | } 118 | if len(filteredLambdas) > 1 { 119 | return diag.FromErr(errors.New("query returned more than one lambda. Use a more specific search creteria")) 120 | } 121 | 122 | l := filteredLambdas[0] 123 | 124 | data.SetId(l.Id) 125 | err = data.Set("body", l.Body) 126 | if err != nil { 127 | return diag.FromErr(err) 128 | } 129 | err = data.Set("debug", l.Debug) 130 | if err != nil { 131 | return diag.FromErr(err) 132 | } 133 | err = data.Set("name", l.Name) 134 | if err != nil { 135 | return diag.FromErr(err) 136 | } 137 | return nil 138 | } 139 | -------------------------------------------------------------------------------- /docs/resources/idp_steam.md: -------------------------------------------------------------------------------- 1 | # Steam Identity Provider Resource 2 | 3 | The Steam identity provider type will use the Steam OAuth login API. It will also provide a Login with Steam button on FusionAuth’s login page that will direct a user to the Steam login page. The Steam login uses the implicit OAuth grant and will return to the callback URL with token and state in the URL Fragment. This is handled by the FusionAuth /oauth2/implicit javascript function to pass those values to the /oauth2/callback endpoint. 4 | 5 | This identity provider will call Steam’s API to load the Steam user’s personaname and use that as username to lookup or create a user in FusionAuth depending on the linking strategy configured for this identity provider. However, Steam does not allow access to user emails, so neither email linking strategy can be used and user’s will not be able to login or be created. 6 | 7 | FusionAuth will also store the Steam token that is returned from the Steam login in the link between the user and the identity provider. This token can be used by an application to make further requests to Steam APIs on behalf of the user. 8 | 9 | [Steam Identity Provider APIs](https://fusionauth.io/docs/v1/tech/apis/identity-providers/steam/ ) 10 | 11 | ## Example Usage 12 | 13 | ```hcl 14 | resource "fusionauth_idp_steam" "steam" { 15 | application_configuration { 16 | application_id = fusionauth_application.GPS_Insight.id 17 | create_registration = true 18 | enabled = true 19 | } 20 | button_text = "Login with Steam" 21 | client_id = "0eb1ce3c-2fb1-4ae9-b361-d49fc6e764cc" 22 | scope = "Xboxlive.signin Xboxlive.offline_access" 23 | web_api_key = "HG0A97ACKPQ5ZLPU0007BN6674OA25TY" 24 | } 25 | ``` 26 | 27 | ## Argument Reference 28 | 29 | * `button_text` - (Required) The top-level button text to use on the FusionAuth login page for this Identity Provider. 30 | * `client_id` - (Required) The top-level Steam client id for your Application. This value is retrieved from the Steam developer website when you setup your Steam developer account. 31 | * `web_api_key` - (Required) The top-level web API key to use with the Steam Identity Provider when retrieving the player summary info. This value is retrieved from the Steam developer website when you setup your Steam developer account. 32 | 33 | --- 34 | 35 | * `api_mode` - (Optional) Determines which Steam API to utilize. The possible values are: `Partner` and `Public` 36 | * `application_configuration` - (Optional) The configuration for each Application that the identity provider is enabled for. 37 | * `api_mode` - (Optional) This is an optional Application specific override for the top level apiMode. 38 | * `application_id` - (Optional) ID of the Application to apply this configuration to. 39 | * `button_text` - (Optional) This is an optional Application specific override for the top level button text. 40 | * `client_id` - (Optional) This is an optional Application specific override for the top level client_id. 41 | * `create_registration` - (Optional) Determines if a UserRegistration is created for the User automatically or not. If a user doesn’t exist in FusionAuth and logs in through an identity provider, this boolean controls whether or not FusionAuth creates a registration for the User in the Application they are logging into. 42 | * `enabled` - (Optional) Determines if this identity provider is enabled for the Application specified by the applicationId key. 43 | * `scope` - (Optional)This is an optional Application specific override for the top level scope. 44 | * `web_api_key` - (Optional) This is an optional Application specific override for the top level webAPIKey. 45 | * `debug` - (Optional) Determines if debug is enabled for this provider. When enabled, each time this provider is invoked to reconcile a login an Event Log will be created. 46 | * `enabled` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 47 | * `idp_id` - (Optional) The ID to use for the new identity provider. If not specified a secure random UUID will be generated. 48 | * `lambda_reconcile_id` - (Optional) The unique Id of the lambda to used during the user reconcile process to map custom claims from the external identity provider to the FusionAuth user. 49 | * `linking_strategy` - (Optional) The linking strategy to use when creating the link between the {idp_display_name} Identity Provider and the user. 50 | * `scope` - (Optional) The top-level scope that you are requesting from Steam. 51 | * `tenant_configuration` - (Optional) The configuration for each Tenant that limits the number of links a user may have for a particular identity provider. 52 | * `tenant_id` - (Optional) The unique Id of the tenant that this configuration applies to. 53 | * `limit_user_link_count_enabled` - (Optional) When enabled, the number of identity provider links a user may create is enforced by maximumLinks. 54 | * `limit_user_link_count_maximum_links` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 55 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_email.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FusionAuth/go-client/pkg/fusionauth" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 10 | ) 11 | 12 | func dataSourceEmail() *schema.Resource { 13 | return &schema.Resource{ 14 | ReadContext: dataSourceEmailRead, 15 | Schema: map[string]*schema.Schema{ 16 | "name": { 17 | Type: schema.TypeString, 18 | Required: true, 19 | Description: "The name of the Email Template.", 20 | ValidateFunc: validation.StringIsNotEmpty, 21 | }, 22 | "id": { 23 | Type: schema.TypeString, 24 | Computed: true, 25 | Description: "The unique Id of the Email Template", 26 | }, 27 | "default_from_name": { 28 | Type: schema.TypeString, 29 | Computed: true, 30 | Description: "The default From Name used when sending emails.", 31 | }, 32 | "default_html_template": { 33 | Type: schema.TypeString, 34 | Computed: true, 35 | Description: "The default HTML Email Template.", 36 | }, 37 | "default_subject": { 38 | Type: schema.TypeString, 39 | Computed: true, 40 | Description: "The default Subject used when sending emails.", 41 | }, 42 | "default_text_template": { 43 | Type: schema.TypeString, 44 | Computed: true, 45 | Description: "The default Text Email Template.", 46 | }, 47 | "from_email": { 48 | Type: schema.TypeString, 49 | Optional: true, 50 | Description: "The email address that this email will be sent from.", 51 | }, 52 | "localized_from_names": { 53 | Type: schema.TypeMap, 54 | Computed: true, 55 | Description: "The From Name used when sending emails to users who speak other languages.", 56 | }, 57 | "localized_html_templates": { 58 | Type: schema.TypeMap, 59 | Computed: true, 60 | Description: "The HTML Email Template used when sending emails to users who speak other languages.", 61 | }, 62 | "localized_subjects": { 63 | Type: schema.TypeMap, 64 | Computed: true, 65 | Description: "The Subject used when sending emails to users who speak other languages.", 66 | }, 67 | "localized_text_templates": { 68 | Type: schema.TypeMap, 69 | Computed: true, 70 | Description: "The Text Email Template used when sending emails to users who speak other languages.", 71 | }, 72 | }, 73 | } 74 | } 75 | 76 | func dataSourceEmailRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 77 | client := i.(Client) 78 | 79 | resp, err := client.FAClient.RetrieveEmailTemplates() 80 | if err != nil { 81 | return diag.FromErr(err) 82 | } 83 | if err := checkResponse(resp.StatusCode, nil); err != nil { 84 | return diag.FromErr(err) 85 | } 86 | name := data.Get("name").(string) 87 | var t *fusionauth.EmailTemplate 88 | 89 | if len(resp.EmailTemplates) > 0 { 90 | for i := range resp.EmailTemplates { 91 | if resp.EmailTemplates[i].Name == name { 92 | t = &resp.EmailTemplates[i] 93 | break 94 | } 95 | } 96 | } 97 | if t == nil { 98 | return diag.Errorf("couldn't find email template %s", name) 99 | } 100 | data.SetId(t.Id) 101 | if err := data.Set("default_from_name", t.DefaultFromName); err != nil { 102 | return diag.Errorf("email.default_from_name: %s", err.Error()) 103 | } 104 | if err := data.Set("default_html_template", t.DefaultHtmlTemplate); err != nil { 105 | return diag.Errorf("email.default_html_template: %s", err.Error()) 106 | } 107 | if err := data.Set("default_subject", t.DefaultSubject); err != nil { 108 | return diag.Errorf("email.default_subject: %s", err.Error()) 109 | } 110 | if err := data.Set("default_text_template", t.DefaultTextTemplate); err != nil { 111 | return diag.Errorf("email.default_text_template: %s", err.Error()) 112 | } 113 | if err := data.Set("from_email", t.FromEmail); err != nil { 114 | return diag.Errorf("email.from_email: %s", err.Error()) 115 | } 116 | if err := data.Set("localized_from_names", t.LocalizedFromNames); err != nil { 117 | return diag.Errorf("email.localized_from_names: %s", err.Error()) 118 | } 119 | if err := data.Set("localized_html_templates", t.LocalizedHtmlTemplates); err != nil { 120 | return diag.Errorf("email.localized_html_templates: %s", err.Error()) 121 | } 122 | if err := data.Set("localized_subjects", t.LocalizedSubjects); err != nil { 123 | return diag.Errorf("email.localized_subjects: %s", err.Error()) 124 | } 125 | if err := data.Set("localized_text_templates", t.LocalizedTextTemplates); err != nil { 126 | return diag.Errorf("email.localized_text_templates: %s", err.Error()) 127 | } 128 | if err := data.Set("name", t.Name); err != nil { 129 | return diag.Errorf("email.name: %s", err.Error()) 130 | } 131 | return nil 132 | } 133 | -------------------------------------------------------------------------------- /docs/resources/idp_samlv2_idp_initiated.md: -------------------------------------------------------------------------------- 1 | # SAML v2 IdP InitiatedIdentity Provider Resource 2 | 3 | The SAML v2 IdP Initiated IdP initiated Identity Provider allows an external IdP to send an unsolicited AuthN request when FusionAuth is acting as the Service Provider (or SP). 4 | 5 | [SAML v2 IdP Initiated Identity Provider API](https://fusionauth.io/docs/v1/tech/apis/identity-providers/samlv2-idp-initiated/) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_idp_saml_v2_idp_initated" "Saml" { 11 | application_configuration { 12 | application_id = fusionauth_application.myapp.id 13 | create_registration = true 14 | enabled = true 15 | } 16 | debug = false 17 | email_claim = "email" 18 | issuer = "https://www.example.com/login" 19 | name = "My SAML provider" 20 | use_name_for_email = true 21 | } 22 | ``` 23 | 24 | ## Argument Reference 25 | 26 | * `issuer` - (Required) The EntityId (unique identifier) of the SAML v2 identity provider. This value should be provided to you. Prior to 1.27.1 this value was required to be a URL. 27 | * `key_id` - (Required) The id of the key stored in Key Master that is used to verify the SAML response sent back to FusionAuth from the identity provider. This key must be a verification only key or certificate (meaning that it only has a public key component). 28 | * `name` - (Required) The name of this OpenID Connect identity provider. This is only used for display purposes. 29 | 30 | --- 31 | 32 | * `application_configuration` - (Optional) The configuration for each Application that the identity provider is enabled for. 33 | * `application_id` - (Optional) ID of the Application to apply this configuration to. 34 | * `create_registration` - (Optional) Determines if a UserRegistration is created for the User automatically or not. If a user doesn't exist in FusionAuth and logs in through an identity provider, this boolean controls whether or not FusionAuth creates a registration for the User in the Application they are logging into. 35 | * `enabled` - (Optional) Determines if this identity provider is enabled for the Application specified by the applicationId key. 36 | * `assertion_configuration` - (Optional) The configuration for the SAML assertion. 37 | * `decryption` - (Optional) The configuration for the SAML assertion decryption. 38 | * `enabled` - (Optional) Determines if FusionAuth requires encrypted assertions in SAML responses from the identity provider. When true, SAML responses from the identity provider containing unencrypted assertions will be rejected by FusionAuth. 39 | * `key_transport_decryption_key_id` - (Optional) The Id of the key stored in Key Master that is used to decrypt the symmetric key on the SAML response sent to FusionAuth from the identity provider. The selected Key must contain an RSA private key. Required when `enabled` is true. 40 | * `debug` - (Optional) Determines if debug is enabled for this provider. When enabled, each time this provider is invoked to reconcile a login an Event Log will be created. 41 | * `email_claim` - (Optional) The name of the email claim (Attribute in the Assertion element) in the SAML response that FusionAuth uses to uniquely identity the user. If this is not set, the `use_name_for_email` flag must be true. 42 | * `enabled` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 43 | * `idp_id` - (Optional) The ID to use for the new identity provider. If not specified a secure random UUID will be generated. 44 | * `lambda_reconcile_id` - (Optional) The unique Id of the lambda to used during the user reconcile process to map custom claims from the external identity provider to the FusionAuth user. 45 | * `linking_strategy` - (Optional) The linking strategy to use when creating the link between the {idp_display_name} Identity Provider and the user. 46 | * `tenant_configuration` - (Optional) The configuration for each Tenant that limits the number of links a user may have for a particular identity provider. 47 | * `tenant_id` - (Optional) The unique Id of the tenant that this configuration applies to. 48 | * `limit_user_link_count_enabled` - (Optional) When enabled, the number of identity provider links a user may create is enforced by maximumLinks. 49 | * `limit_user_link_count_maximum_links` - (Optional) Determines if this provider is enabled. If it is false then it will be disabled globally. 50 | * `unique_id_claim` - (Optional) The name of the unique claim in the SAML response that FusionAuth uses to uniquely link the user. If this is not set, the `email_claim` will be used when linking user. 51 | * `use_name_for_email` - (Optional) Whether or not FusionAuth will use the NameID element value as the email address of the user for reconciliation processing. If this is false, then the `email_claim` property must be set. 52 | * `username_claim` - (Optional) The name of the claim in the SAML response that FusionAuth uses to identity the username. If this is not set, the NameId value will be used to link a user. This property is required when `linking_strategy` is set to LinkByUsername or LinkByUsernameForExistingUser 53 | -------------------------------------------------------------------------------- /fusionauth/datasource_fusionauth_sms_message_template.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/FusionAuth/go-client/pkg/fusionauth" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 11 | ) 12 | 13 | func dataSourceSMSMessageTemplate() *schema.Resource { 14 | return &schema.Resource{ 15 | ReadContext: dataSourceSMSMessageTemplateRead, 16 | Schema: map[string]*schema.Schema{ 17 | // Data Source Parameters 18 | "message_template_id": { 19 | Type: schema.TypeString, 20 | Optional: true, 21 | Computed: true, 22 | ExactlyOneOf: []string{"message_template_id", "name"}, 23 | Description: "The unique Id of the SMS Message Template to retrieve.", 24 | ValidateFunc: validation.IsUUID, 25 | }, 26 | "name": { 27 | Type: schema.TypeString, 28 | Optional: true, 29 | Computed: true, 30 | ExactlyOneOf: []string{"message_template_id", "name"}, 31 | Description: "The case-insensitive string to search for in the SMS Message Template name.", 32 | }, 33 | // Data Source Attributes 34 | "data": { 35 | Type: schema.TypeString, 36 | Computed: true, 37 | Description: "An object that can hold any information about the Message Template that should be persisted. Must be a JSON string.", 38 | }, 39 | "default_template": { 40 | Type: schema.TypeString, 41 | Computed: true, 42 | Description: "The default Message Template.", 43 | }, 44 | "localized_templates": { 45 | Type: schema.TypeMap, 46 | Computed: true, 47 | Description: "The Message Template used when sending messages to users who speak other languages. This overrides the default Message Template based on the user's list of preferred languages.", 48 | }, 49 | "type": { 50 | Type: schema.TypeString, 51 | Computed: true, 52 | Description: "The type of Message Template. This is always 'SMS'.", 53 | }, 54 | }, 55 | } 56 | } 57 | 58 | func dataSourceSMSMessageTemplateRead(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 59 | client := i.(Client) 60 | 61 | var searchTerm string 62 | var resp *SMSMessageTemplateResponse 63 | var err error 64 | 65 | if entityID, ok := data.GetOk("message_template_id"); ok { 66 | searchTerm = entityID.(string) 67 | resp, _, err = retrieveMessageTemplate(ctx, client.FAClient, searchTerm) 68 | } else if name, ok := data.GetOk("name"); ok { 69 | searchTerm = name.(string) 70 | resp, _, err = retrieveAllMessageTemplates(ctx, client.FAClient) 71 | } else { 72 | return diag.Errorf("Either 'message_template_id' or 'name' must be specified") 73 | } 74 | 75 | if err != nil { 76 | return diag.FromErr(err) 77 | } 78 | if err := checkResponse(resp.StatusCode, nil); err != nil { 79 | return diag.FromErr(err) 80 | } 81 | 82 | var template fusionauth.SMSMessageTemplate 83 | switch { 84 | case resp.MessageTemplate.Id != "": 85 | template = resp.MessageTemplate 86 | 87 | case len(resp.MessageTemplates) > 0: 88 | found := false 89 | for _, t := range resp.MessageTemplates { 90 | if t.Name == searchTerm && t.Type == fusionauth.MessageType_SMS { 91 | template = t 92 | found = true 93 | break 94 | } 95 | } 96 | if !found { 97 | return diag.Errorf("Couldn't find SMS Message Template with name '%s'", searchTerm) 98 | } 99 | 100 | default: 101 | return diag.Errorf("No SMS Message Templates found matching '%s'", searchTerm) 102 | } 103 | 104 | data.SetId(template.Id) 105 | 106 | if diags := setSMSMessageTemplateFields(data, template); diags != nil { 107 | return diags 108 | } 109 | 110 | return nil 111 | } 112 | 113 | // Helper function to set all SMS message template fields in the schema data 114 | func setSMSMessageTemplateFields(data *schema.ResourceData, template fusionauth.SMSMessageTemplate) diag.Diagnostics { 115 | dataJSON, diags := mapStringInterfaceToJSONString(template.Data) 116 | if diags != nil { 117 | return diags 118 | } 119 | 120 | fields := map[string]interface{}{ 121 | "message_template_id": template.Id, 122 | "name": template.Name, 123 | "default_template": template.DefaultTemplate, 124 | "localized_templates": template.LocalizedTemplates, 125 | "type": template.Type, 126 | "data": dataJSON, 127 | } 128 | 129 | for key, value := range fields { 130 | if err := data.Set(key, value); err != nil { 131 | return diag.Errorf("error setting message_template.%s: %s", key, err.Error()) 132 | } 133 | } 134 | 135 | return nil 136 | } 137 | 138 | // Function to retrieve all message templates 139 | func retrieveAllMessageTemplates(ctx context.Context, client fusionauth.FusionAuthClient) (*SMSMessageTemplateResponse, *fusionauth.Errors, error) { 140 | return makeMessageTemplateRequest(ctx, client, "", SMSMessageTemplateRequest{}, http.MethodGet) 141 | } 142 | -------------------------------------------------------------------------------- /fusionauth/idphelpers.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "strings" 10 | "time" 11 | 12 | "github.com/FusionAuth/go-client/pkg/fusionauth" 13 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 14 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 15 | ) 16 | 17 | func deleteIdentityProvider(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { 18 | client := i.(Client) 19 | id := data.Id() 20 | 21 | resp, faErrs, err := client.FAClient.DeleteIdentityProvider(id) 22 | if err != nil { 23 | return diag.FromErr(err) 24 | } 25 | 26 | if err := checkResponse(resp.StatusCode, faErrs); err != nil { 27 | return diag.FromErr(err) 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func readIdentityProvider(id string, client Client) ([]byte, error) { 34 | req, err := http.NewRequest( 35 | http.MethodGet, 36 | fmt.Sprintf("%s/%s/%s", strings.TrimRight(client.Host, "/"), "api/identity-provider", id), 37 | nil, 38 | ) 39 | 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | req.Header.Add("Authorization", client.APIKey) 45 | 46 | hc := http.Client{ 47 | Timeout: 30 * time.Second, 48 | } 49 | 50 | resp, err := hc.Do(req) 51 | if err != nil { 52 | return nil, err 53 | } 54 | defer resp.Body.Close() 55 | if err := checkResponse(resp.StatusCode, nil); err != nil { 56 | return nil, err 57 | } 58 | 59 | b, _ := io.ReadAll(resp.Body) 60 | 61 | if resp.StatusCode != http.StatusOK { 62 | return nil, fmt.Errorf("status: %d, response: \n\t%s", resp.StatusCode, string(b)) 63 | } 64 | return b, nil 65 | } 66 | 67 | func createIdentityProvider(b []byte, client Client, idpID string) ([]byte, error) { 68 | var u string 69 | if idpID != "" { 70 | u = fmt.Sprintf("%s/%s/%s", strings.TrimRight(client.Host, "/"), "api/identity-provider", idpID) 71 | } else { 72 | u = fmt.Sprintf("%s/%s", strings.TrimRight(client.Host, "/"), "api/identity-provider") 73 | } 74 | 75 | req, err := http.NewRequest( 76 | http.MethodPost, 77 | u, 78 | bytes.NewBuffer(b), 79 | ) 80 | 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | req.Header.Add("Authorization", client.APIKey) 86 | req.Header.Add("Content-Type", "application/json") 87 | 88 | hc := http.Client{ 89 | Timeout: 30 * time.Second, 90 | } 91 | 92 | resp, err := hc.Do(req) 93 | if err != nil { 94 | return nil, err 95 | } 96 | defer resp.Body.Close() 97 | if err := checkResponse(resp.StatusCode, nil); err != nil { 98 | return nil, err 99 | } 100 | bb, _ := io.ReadAll(resp.Body) 101 | 102 | if resp.StatusCode > 299 { 103 | return nil, fmt.Errorf("status: %d, response: \n\t%s\nreq body:\n\t%s", resp.StatusCode, string(bb), string(b)) 104 | } 105 | return bb, nil 106 | } 107 | 108 | func updateIdentityProvider(b []byte, id string, client Client) ([]byte, error) { 109 | req, err := http.NewRequest( 110 | http.MethodPut, 111 | fmt.Sprintf("%s/%s/%s", strings.TrimRight(client.Host, "/"), "api/identity-provider", id), 112 | bytes.NewBuffer(b), 113 | ) 114 | 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | req.Header.Add("Authorization", client.APIKey) 120 | req.Header.Add("Content-Type", "application/json") 121 | 122 | hc := http.Client{ 123 | Timeout: 30 * time.Second, 124 | } 125 | 126 | resp, err := hc.Do(req) 127 | if err != nil { 128 | return nil, err 129 | } 130 | defer resp.Body.Close() 131 | if err := checkResponse(resp.StatusCode, nil); err != nil { 132 | return nil, err 133 | } 134 | bb, _ := io.ReadAll(resp.Body) 135 | 136 | if resp.StatusCode > 299 { 137 | return nil, fmt.Errorf("status: %d, response: \n\t%s\nreq body:\n\t%s", resp.StatusCode, string(bb), string(b)) 138 | } 139 | return bb, nil 140 | } 141 | 142 | func buildTenantConfigurationResource(tcm map[string]fusionauth.IdentityProviderTenantConfiguration) []map[string]interface{} { 143 | t := make([]map[string]interface{}, 0, len(tcm)) 144 | for k, v := range tcm { 145 | t = append(t, map[string]interface{}{ 146 | "tenant_id": k, 147 | "limit_user_link_count_enabled": v.LimitUserLinkCount.Enabled, 148 | "limit_user_link_count_maximum_links": v.LimitUserLinkCount.MaximumLinks, 149 | }) 150 | } 151 | return t 152 | } 153 | 154 | func buildTenantConfiguration(data *schema.ResourceData) map[string]fusionauth.IdentityProviderTenantConfiguration { 155 | m := make(map[string]fusionauth.IdentityProviderTenantConfiguration) 156 | s := data.Get("tenant_configuration") 157 | set, ok := s.(*schema.Set) 158 | if !ok { 159 | return nil 160 | } 161 | 162 | l := set.List() 163 | for _, x := range l { 164 | ac := x.(map[string]interface{}) 165 | aid := ac["tenant_id"].(string) 166 | oc := fusionauth.IdentityProviderTenantConfiguration{ 167 | LimitUserLinkCount: fusionauth.IdentityProviderLimitUserLinkingPolicy{ 168 | MaximumLinks: ac["limit_user_link_count_maximum_links"].(int), 169 | }, 170 | } 171 | oc.LimitUserLinkCount.Enabled = ac["limit_user_link_count_enabled"].(bool) 172 | m[aid] = oc 173 | } 174 | 175 | return m 176 | } 177 | -------------------------------------------------------------------------------- /docs/guides/handling_default_resources.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: Handling Default Resources 3 | description: |- 4 | How to handle resources that are always present in FusionAuth 5 | --- 6 | 7 | # Handling Default Resources 8 | 9 | There are [FusionAuth default configuration elements](https://fusionauth.io/docs/get-started/core-concepts/limitations#default-configuration) present in every FusionAuth instance. If you want to manage changes to these elements via Terraform, you must tell Terraform about them by either importing the resource or setting up a datasource. 10 | 11 | ### Importing A Resource 12 | 13 | To import a resource, you must provide all required attributes. Here's an example for the default tenant: 14 | 15 | ```hcl 16 | #tag::defaultTenantImport[] 17 | import { 18 | to = fusionauth_tenant.Default 19 | id = "Replace-This-With-The-Existing-Default-Tenant-Id" 20 | } 21 | 22 | resource "fusionauth_tenant" "Default" { 23 | lifecycle { 24 | prevent_destroy = true 25 | } 26 | name = "Default" 27 | issuer = "acme.com" 28 | theme_id = "00000000-0000-0000-0000-000000000000" 29 | external_identifier_configuration { 30 | authorization_grant_id_time_to_live_in_seconds = 30 31 | change_password_id_generator { 32 | length = 32 33 | type = "randomBytes" 34 | } 35 | change_password_id_time_to_live_in_seconds = 600 36 | device_code_time_to_live_in_seconds = 300 37 | device_user_code_id_generator { 38 | length = 6 39 | type = "randomAlphaNumeric" 40 | } 41 | email_verification_id_generator { 42 | length = 32 43 | type = "randomBytes" 44 | } 45 | email_verification_id_time_to_live_in_seconds = 86400 46 | email_verification_one_time_code_generator { 47 | length = 6 48 | type = "randomAlphaNumeric" 49 | } 50 | external_authentication_id_time_to_live_in_seconds = 300 51 | login_intent_time_to_live_in_seconds = 1800 52 | one_time_password_time_to_live_in_seconds = 60 53 | passwordless_login_generator { 54 | length = 32 55 | type = "randomBytes" 56 | } 57 | passwordless_login_time_to_live_in_seconds = 180 58 | registration_verification_id_generator { 59 | length = 32 60 | type = "randomBytes" 61 | } 62 | registration_verification_id_time_to_live_in_seconds = 86400 63 | registration_verification_one_time_code_generator { 64 | length = 6 65 | type = "randomAlphaNumeric" 66 | } 67 | saml_v2_authn_request_id_ttl_seconds = 300 68 | setup_password_id_generator { 69 | length = 32 70 | type = "randomBytes" 71 | } 72 | setup_password_id_time_to_live_in_seconds = 86400 73 | two_factor_id_time_to_live_in_seconds = 300 74 | two_factor_one_time_code_id_generator { 75 | length = 6 76 | type = "randomDigits" 77 | } 78 | two_factor_trust_id_time_to_live_in_seconds = 2592000 79 | } 80 | jwt_configuration { 81 | refresh_token_time_to_live_in_minutes = 43200 82 | time_to_live_in_seconds = 3600 83 | refresh_token_revocation_policy_on_login_prevented = true 84 | refresh_token_revocation_policy_on_password_change = true 85 | access_token_key_id = "00000000-0000-0000-0000-000000000000" 86 | id_token_key_id = "00000000-0000-0000-0000-000000000000" 87 | } 88 | login_configuration { 89 | require_authentication = true 90 | } 91 | email_configuration { 92 | default_from_email = "change-me@example.com" 93 | default_from_name = "FusionAuth" 94 | host = "localhost" 95 | implicit_email_verification_allowed = true 96 | port = 25 97 | security = "NONE" 98 | verification_strategy = "ClickableLink" 99 | verify_email = false 100 | verify_email_when_changed = false 101 | } 102 | } 103 | #end::defaultTenantImport[] 104 | ``` 105 | 106 | You can set some attribute id values to `00000000-0000-0000-0000-000000000000`, then run `terraform plan` to find out the real values. Then update the import statement. This will also display any new tenant default attributes that may have been added over time. 107 | 108 | You can do the same for other default resources such as the FusionAuth application or the default theme. 109 | 110 | ## Data Sources 111 | 112 | If you don't need to manage the resource with Terraform, but just want to access its attributes from other places in your Terraform file, you can use a data source. 113 | 114 | ```hcl 115 | data "fusionauth_tenant" "Default" { 116 | name = "Default" 117 | } 118 | ``` 119 | 120 | Examples of this include: 121 | 122 | * adding applications in the default tenant 123 | * associating a JWT signing key with the FusionAuth application 124 | * setting up an IP ACL to limit access to the FusionAuth application 125 | 126 | ## Deleting Default Resources 127 | 128 | You cannot delete a default resource such as the default tenant or theme. Doing so will cause a Terraform error, since such actions are not allowed by the underlying API. 129 | -------------------------------------------------------------------------------- /fusionauth/resource_fusionauth_twilio_messenger_test.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 12 | ) 13 | 14 | func TestAccTwilioMessenger(t *testing.T) { 15 | resourceName := randString10() 16 | tfResourcePath := fmt.Sprintf("fusionauth_twilio_messenger.test_%s", resourceName) 17 | 18 | startAccountSID, endAccountSID := "SID12345678", "SID87654321" 19 | startAuthToken, endAuthToken := "token-secret-start", "token-secret-end" 20 | startFromPhoneNumber, endFromPhoneNumber := "+15551234567", "+15559876543" 21 | startName, endName := "my-test-messenger", "my-new-test-messenger" 22 | startURL, endURL := "https://api.twilio.com", "https://alt-api.twilio.com" 23 | 24 | resource.Test(t, resource.TestCase{ 25 | ProviderFactories: testAccProviderFactories, 26 | CheckDestroy: testAccCheckTwilioMessengerDestroy, 27 | Steps: []resource.TestStep{ 28 | { 29 | Config: testAccTwilioMessengerBasicConfig( 30 | resourceName, 31 | startAccountSID, 32 | startAuthToken, 33 | startFromPhoneNumber, 34 | startName, 35 | startURL, 36 | ), 37 | Check: testTwilioMessengerAccTestCheckFuncs( 38 | tfResourcePath, 39 | startAccountSID, 40 | startAuthToken, 41 | startFromPhoneNumber, 42 | startName, 43 | startURL, 44 | ), 45 | }, 46 | { 47 | Config: testAccTwilioMessengerBasicConfig( 48 | resourceName, 49 | endAccountSID, 50 | endAuthToken, 51 | endFromPhoneNumber, 52 | endName, 53 | endURL, 54 | ), 55 | Check: testTwilioMessengerAccTestCheckFuncs( 56 | tfResourcePath, 57 | endAccountSID, 58 | endAuthToken, 59 | endFromPhoneNumber, 60 | endName, 61 | endURL, 62 | ), 63 | }, 64 | }, 65 | }) 66 | } 67 | 68 | func testAccTwilioMessengerBasicConfig(resourceName, accountSID, authToken, fromPhoneNumber, name, url string) string { 69 | return fmt.Sprintf(` 70 | # Twilio messenger setup 71 | resource "fusionauth_twilio_messenger" "test_%[1]s" { 72 | account_sid = "%[2]s" 73 | auth_token = "%[3]s" 74 | data = jsonencode({ "important-key" : "important-value" }) 75 | debug = false 76 | from_phone_number = "%[4]s" 77 | name = "%[5]s" 78 | url = "%[6]s" 79 | } 80 | `, resourceName, accountSID, authToken, fromPhoneNumber, name, url) 81 | } 82 | 83 | func testTwilioMessengerAccTestCheckFuncs(tfResourcePath, accountSID, authToken, fromPhoneNumber, name, url string) resource.TestCheckFunc { 84 | return resource.ComposeTestCheckFunc( 85 | testAccCheckTwilioMessengerExists(tfResourcePath), 86 | resource.TestCheckResourceAttr(tfResourcePath, "account_sid", accountSID), 87 | resource.TestCheckResourceAttr(tfResourcePath, "auth_token", authToken), 88 | testCheckResourceAttrJSON(tfResourcePath, "data", `{"important-key":"important-value"}`), 89 | resource.TestCheckResourceAttr(tfResourcePath, "debug", "false"), 90 | resource.TestCheckResourceAttr(tfResourcePath, "from_phone_number", fromPhoneNumber), 91 | resource.TestCheckResourceAttr(tfResourcePath, "name", name), 92 | resource.TestCheckResourceAttr(tfResourcePath, "url", url), 93 | ) 94 | } 95 | 96 | func testAccCheckTwilioMessengerExists(resourceName string) resource.TestCheckFunc { 97 | return func(s *terraform.State) error { 98 | rs, ok := s.RootModule().Resources[resourceName] 99 | 100 | if !ok { 101 | return fmt.Errorf("resource not found: %s", resourceName) 102 | } 103 | 104 | if rs.Primary.ID == "" { 105 | return fmt.Errorf("no resource id is set") 106 | } 107 | 108 | messenger, faErrs, err := RetrieveTwilioMessenger(context.Background(), fusionauthClient(), rs.Primary.ID) 109 | if errs := checkFusionauthErrors(faErrs, err); errs != nil { 110 | return err 111 | } 112 | 113 | if messenger == nil || messenger.StatusCode != http.StatusOK { 114 | return fmt.Errorf("failed to get resource: %#+v", messenger) 115 | } 116 | 117 | return nil 118 | } 119 | } 120 | 121 | func testAccCheckTwilioMessengerDestroy(s *terraform.State) error { 122 | for _, rs := range s.RootModule().Resources { 123 | if rs.Type != "fusionauth_twilio_messenger" { 124 | continue 125 | } 126 | 127 | // Ensure we retry for eventual consistency in HA setups. 128 | err := retry.RetryContext(context.Background(), retryTimeout, func() *retry.RetryError { 129 | messenger, faErrs, err := RetrieveTwilioMessenger(context.Background(), fusionauthClient(), rs.Primary.ID) 130 | if errs := checkFusionauthRetryErrors(faErrs, err); errs != nil { 131 | return errs 132 | } 133 | 134 | if messenger != nil && messenger.StatusCode == http.StatusNotFound { 135 | // resource destroyed! 136 | return nil 137 | } 138 | 139 | return retry.RetryableError(fmt.Errorf("fusionauth resource still exists: %s", rs.Primary.ID)) 140 | }) 141 | 142 | if err != nil { 143 | // We failed destroying the resource... 144 | return err 145 | } 146 | } 147 | 148 | return nil 149 | } 150 | -------------------------------------------------------------------------------- /docs/resources/system_configuration.md: -------------------------------------------------------------------------------- 1 | # System Configuration Resource 2 | 3 | Settings that control various aspects of the system's behavior. 4 | 5 | [System Configuration API](https://fusionauth.io/docs/v1/tech/apis/system) 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "fusionauth_system_configuration" "example" { 11 | audit_log_configuration { 12 | delete { 13 | enabled = true 14 | number_of_days_to_retain = 367 15 | } 16 | } 17 | cors_configuration { 18 | allowed_methods = ["POST", "PUT"] 19 | } 20 | } 21 | ``` 22 | 23 | ## Argument Reference 24 | 25 | * `audit_log_configuration` - (Optional) 26 | * `delete` - (Optional) 27 | * `enabled` - (Optional) Whether or not FusionAuth should delete the Audit Log based upon this configuration. When `true` the audit_log_configuration.delete.number_of_days_to_retain will be used to identify audit logs that are eligible for deletion. When this value is set to false audit logs will be preserved forever. 28 | * `number_of_days_to_retain` - (Optional) The number of days to retain the Audit Log. 29 | * `cors_configuration` - (Optional) 30 | * `allow_credentials` - (Optional) The Access-Control-Allow-Credentials response header values as described by MDN Access-Control-Allow-Credentials. 31 | * `allowed_headers` - (Optional) The Access-Control-Allow-Headers response header values as described by MDN Access-Control-Allow-Headers. 32 | * `allowed_methods` - (Optional) The Access-Control-Allow-Methods response header values as described by MDN Access-Control-Allow-Methods. 33 | * `allowed_origins` - (Optional) The Access-Control-Allow-Origin response header values as described by MDN Access-Control-Allow-Origin. If the wildcard * is specified, no additional domains may be specified. 34 | * `debug` - (Optional) Whether or not FusionAuth will log debug messages to the event log. This is primarily useful for identifying why the FusionAuth CORS filter is rejecting a request and returning an HTTP response status code of 403. 35 | * `enabled` - (Optional) Whether the FusionAuth CORS filter will process requests made to FusionAuth. 36 | * `exposed_headers` - (Optional) The Access-Control-Expose-Headers response header values as described by MDN Access-Control-Expose-Headers. 37 | * `preflight_max_age_in_seconds` - (Optional) The Access-Control-Max-Age response header values as described by MDN Access-Control-Max-Age. 38 | * `data` - (Optional) A JSON string that can hold any information about the System that should be persisted. 39 | * `event_log_configuration` - (Optional) 40 | * `number_to_retain` - (Optional) The number of events to retain. Once the the number of event logs exceeds this configured value they will be deleted starting with the oldest event logs. 41 | * `login_record_configuration` - (Optional) 42 | * `delete` - (Optional) 43 | * `enabled` - (Optional) Whether or not FusionAuth should delete the login records based upon this configuration. When `true` the login_record_configuration.delete.number_of_days_to_retain will be used to identify login records that are eligible for deletion. When this value is set to false login records will be preserved forever. 44 | * `number_of_days_to_retain` - (Optional) The number of days to retain login records. 45 | * `report_timezone` - (Optional) The time zone used to adjust the stored UTC time when generating reports. Since reports are usually rolled up hourly, this timezone will be used for demarcating the hours. 46 | * `trusted_proxy_configuration` - (Optional) 47 | * `trust_policy` - (Optional) This setting is used to resolve the client IP address for use in logging, webhooks, and IP-based access control when an X-Forwarded-For header is provided. Because proxies are free to rewrite the X-Forwarded-For header, an untrusted proxy could write a value that allowed it to bypass IP-based ACLs, or cause an incorrect IP address to be logged or sent to a webhook. Valid values are `All` and `OnlyConfigured`. 48 | * `trusted` - (Optional) An array of IP addresses, representing the set of trusted upstream proxies. This value will be accepted but ignored when `trust_policy` is set to `All`. Values may be specified as IPv4, or IPv6 format, and ranges of addresses are also accepted in CIDR notation. 49 | * `ui_configuration` - (Optional) 50 | * `header_color` - (Optional) A hexadecimal color to override the default menu color in the user interface. 51 | * `logo_url` - (Optional) A URL of a logo to override the default FusionAuth logo in the user interface. 52 | * `menu_font_color` - (Optional) A hexadecimal color to override the default menu font color in the user interface. 53 | * `usage_data_configuration` - (Optional) **Note:** This field is only configurable in self-hosted FusionAuth deployments. 54 | * `enabled` - (Optional) Whether or not FusionAuth collects and sends usage data to improve the product. 55 | * `webhook_event_log_configuration` - (Optional) 56 | * `enabled` - (Optional) Whether or not FusionAuth should create Webhook Event Logs. When `true` FusionAuth will create an event log for each webhook event and an attempt log for each attempt at sending the event to a webhook. 57 | * `delete` - (Optional) 58 | * `enabled` - (Optional) Whether or not FusionAuth should delete the webhook event logs based upon this configuration. When `true` the webhook_event_log_configuration.delete.number_of_days_to_retain will be used to identify webhook event logs that are eligible for deletion. When this value is set to false webhook event logs will be preserved forever. 59 | * `number_of_days_to_retain` - (Optional) The number of days to retain webhook event logs. 60 | -------------------------------------------------------------------------------- /fusionauth/resource_fusionauth_sms_message_template_test.go: -------------------------------------------------------------------------------- 1 | package fusionauth 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" 12 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 13 | ) 14 | 15 | func TestAccSMSMessageTemplate(t *testing.T) { 16 | resourceName := randString10() 17 | tfResourcePath := fmt.Sprintf("fusionauth_sms_message_template.test_%s", resourceName) 18 | 19 | startName, endName := "Test SMS Template", "Updated SMS Template" 20 | startDefaultTemplate, endDefaultTemplate := "Your verification code is $${code}", "Your new verification code is $${code}" 21 | startLocalizedFR, endLocalizedFR := "Votre code de vérification est $${code}", "Votre nouveau code de vérification est $${code}" 22 | startLocalizedES, endLocalizedES := "Su código de verificación es $${code}", "Su nuevo código de verificación es $${code}" 23 | 24 | resource.Test(t, resource.TestCase{ 25 | ProviderFactories: testAccProviderFactories, 26 | CheckDestroy: testAccCheckSMSMessageTemplateDestroy, 27 | Steps: []resource.TestStep{ 28 | { 29 | Config: testAccSMSMessageTemplateBasicConfig( 30 | resourceName, 31 | startName, 32 | startDefaultTemplate, 33 | startLocalizedFR, 34 | startLocalizedES, 35 | ), 36 | Check: testSMSMessageTemplateAccTestCheckFuncs( 37 | tfResourcePath, 38 | startName, 39 | startDefaultTemplate, 40 | startLocalizedFR, 41 | startLocalizedES, 42 | ), 43 | }, 44 | { 45 | Config: testAccSMSMessageTemplateBasicConfig( 46 | resourceName, 47 | endName, 48 | endDefaultTemplate, 49 | endLocalizedFR, 50 | endLocalizedES, 51 | ), 52 | Check: testSMSMessageTemplateAccTestCheckFuncs( 53 | tfResourcePath, 54 | endName, 55 | endDefaultTemplate, 56 | endLocalizedFR, 57 | endLocalizedES, 58 | ), 59 | }, 60 | { 61 | ResourceName: tfResourcePath, 62 | ImportState: true, 63 | ImportStateVerify: true, 64 | }, 65 | }, 66 | }) 67 | } 68 | 69 | func testAccSMSMessageTemplateBasicConfig( 70 | resourceName, 71 | name, 72 | defaultTemplate, 73 | localizedFR, 74 | localizedES string) string { 75 | return fmt.Sprintf(` 76 | resource "fusionauth_sms_message_template" "test_%[1]s" { 77 | name = "%[2]s" 78 | default_template = "%[3]s" 79 | data = jsonencode({ "usage": "verification", "notes": "Used for 2FA verification" }) 80 | localized_templates = { 81 | "fr" = "%[4]s", 82 | "es" = "%[5]s" 83 | } 84 | } 85 | `, resourceName, name, defaultTemplate, localizedFR, localizedES) 86 | } 87 | 88 | func testSMSMessageTemplateAccTestCheckFuncs( 89 | tfResourcePath, 90 | name, 91 | defaultTemplate, 92 | localizedFR, 93 | localizedES string) resource.TestCheckFunc { 94 | // Replace $$ with $ to avoid escaping in the test. 95 | defaultTemplate = strings.ReplaceAll(defaultTemplate, "$$", "$") 96 | localizedFR = strings.ReplaceAll(localizedFR, "$$", "$") 97 | localizedES = strings.ReplaceAll(localizedES, "$$", "$") 98 | 99 | return resource.ComposeTestCheckFunc( 100 | testAccCheckSMSMessageTemplateExists(tfResourcePath), 101 | resource.TestCheckResourceAttr(tfResourcePath, "name", name), 102 | resource.TestCheckResourceAttr(tfResourcePath, "default_template", defaultTemplate), 103 | resource.TestCheckResourceAttr(tfResourcePath, "localized_templates.fr", localizedFR), 104 | resource.TestCheckResourceAttr(tfResourcePath, "localized_templates.es", localizedES), 105 | resource.TestCheckResourceAttrSet(tfResourcePath, "data"), 106 | ) 107 | } 108 | 109 | func testAccCheckSMSMessageTemplateExists(resourceName string) resource.TestCheckFunc { 110 | return func(s *terraform.State) error { 111 | rs, ok := s.RootModule().Resources[resourceName] 112 | 113 | if !ok { 114 | return fmt.Errorf("resource not found: %s", resourceName) 115 | } 116 | 117 | if rs.Primary.ID == "" { 118 | return fmt.Errorf("no resource id is set") 119 | } 120 | 121 | template, faErrs, err := retrieveMessageTemplate(context.Background(), fusionauthClient(), rs.Primary.ID) 122 | if errs := checkFusionauthErrors(faErrs, err); errs != nil { 123 | return err 124 | } 125 | 126 | if template == nil || template.StatusCode != http.StatusOK { 127 | return fmt.Errorf("failed to get resource: %#+v", template) 128 | } 129 | 130 | return nil 131 | } 132 | } 133 | 134 | func testAccCheckSMSMessageTemplateDestroy(s *terraform.State) error { 135 | for _, rs := range s.RootModule().Resources { 136 | if rs.Type != "fusionauth_sms_message_template" { 137 | continue 138 | } 139 | 140 | // Ensure we retry for eventual consistency in HA setups. 141 | err := retry.RetryContext(context.Background(), retryTimeout, func() *retry.RetryError { 142 | template, faErrs, err := retrieveMessageTemplate(context.Background(), fusionauthClient(), rs.Primary.ID) 143 | if errs := checkFusionauthRetryErrors(faErrs, err); errs != nil { 144 | return errs 145 | } 146 | 147 | if template != nil && template.StatusCode == http.StatusNotFound { 148 | // resource destroyed! 149 | return nil 150 | } 151 | 152 | return retry.RetryableError(fmt.Errorf("fusionauth resource still exists: %s", rs.Primary.ID)) 153 | }) 154 | 155 | if err != nil { 156 | // We failed destroying the resource... 157 | return err 158 | } 159 | } 160 | 161 | return nil 162 | } 163 | --------------------------------------------------------------------------------