diff --git a/docs/data-sources/app.md b/docs/data-sources/app.md index 235d57c..d6233f8 100644 --- a/docs/data-sources/app.md +++ b/docs/data-sources/app.md @@ -35,6 +35,7 @@ output "app" { - `assigned_members` (List of String) Users and groups which the application is applied to - `description` (String) - `direct_sso_login` (String) IDP to use when logging into Proofpoint NaaS directly, while performing SSO login to SP +- `domain_federation` (List of Object) SSO configuration for Office-365 SP (see [below for nested schema](#nestedatt--domain_federation)) - `enabled` (Boolean) - `id` (String) The ID of this resource. - `ip_whitelist` (List of String) Users and groups which the application is applied to @@ -44,6 +45,14 @@ output "app" { - `saml` (List of Object) SAML-based app properties (see [below for nested schema](#nestedatt--saml)) - `visible` (Boolean) Application visibility, defining whether to display application to user or not + +### Nested Schema for `domain_federation` + +Read-Only: + +- `domain` (String) + + ### Nested Schema for `mapped_attributes` diff --git a/docs/resources/app.md b/docs/resources/app.md index 994d0b8..5984163 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -89,6 +89,30 @@ resource "pfptmeta_app" "app_oidc" { initiate_login_url = "https://intial-login.myApp.example.com" } } + +resource "pfptmeta_app" "app_office365" { + name = "office-365 app name" + description = "office-365 app description" + enabled = true + assigned_members = ["usr-abcd1234"] + protocol = "SAML" + + saml { + audience_uri = "https://audience.myApp.com" + sso_acs_url = "https://login.myApp.example.com" + destination = "https://login.myApp.example.com" + recipient = "https://login.myApp.example.com" + default_relay_state = "https://relay.myApp.com" + subject_name_id_attribute = "email" + subject_name_id_format = "emailAddress" + signature_algorithm = "RSA-SHA256" + digest_algorithm = "SHA256" + } + + domain_federation { + domain = "my-office365-domain.com" + } +} ``` @@ -104,6 +128,7 @@ resource "pfptmeta_app" "app_oidc" { - `assigned_members` (Set of String) Users and groups which the application is applied to - `description` (String) - `direct_sso_login` (String) IDP to use when logging into Proofpoint NaaS directly, while performing SSO login to SP +- `domain_federation` (Block List, Max: 1) SSO configuration for Office-365 SP (see [below for nested schema](#nestedblock--domain_federation)) - `enabled` (Boolean) - `ip_whitelist` (Set of String) List of IPs allowed to be authenticated by the application - `mapped_attributes` (Block List, Max: 15) User attributes to map and return to SP upon successful SAML assertion/OIDC authorization (see [below for nested schema](#nestedblock--mapped_attributes)) @@ -115,6 +140,14 @@ resource "pfptmeta_app" "app_oidc" { - `id` (String) The ID of this resource. + +### Nested Schema for `domain_federation` + +Required: + +- `domain` (String) Office-365 domain to be federated + + ### Nested Schema for `mapped_attributes` diff --git a/examples/resources/pfptmeta_app/resource.tf b/examples/resources/pfptmeta_app/resource.tf index ae88b44..b930c64 100644 --- a/examples/resources/pfptmeta_app/resource.tf +++ b/examples/resources/pfptmeta_app/resource.tf @@ -73,4 +73,28 @@ resource "pfptmeta_app" "app_oidc" { scopes = ["openid", "profile", "email"] initiate_login_url = "https://intial-login.myApp.example.com" } +} + +resource "pfptmeta_app" "app_office365" { + name = "office-365 app name" + description = "office-365 app description" + enabled = true + assigned_members = ["usr-abcd1234"] + protocol = "SAML" + + saml { + audience_uri = "https://audience.myApp.com" + sso_acs_url = "https://login.myApp.example.com" + destination = "https://login.myApp.example.com" + recipient = "https://login.myApp.example.com" + default_relay_state = "https://relay.myApp.com" + subject_name_id_attribute = "email" + subject_name_id_format = "emailAddress" + signature_algorithm = "RSA-SHA256" + digest_algorithm = "SHA256" + } + + domain_federation { + domain = "my-office365-domain.com" + } } \ No newline at end of file diff --git a/internal/client/app.go b/internal/client/app.go index 731e0bf..8728e23 100644 --- a/internal/client/app.go +++ b/internal/client/app.go @@ -43,6 +43,10 @@ type AppMappedAttributes struct { FilterValue *string `json:"filter_value"` } +type AppDomainFederation struct { + Domain string `json:"domain"` +} + type App struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` @@ -56,6 +60,7 @@ type App struct { Saml *AppSaml `json:"saml,omitempty"` Oidc *AppOidc `json:"oidc,omitempty"` MappedAttributes []AppMappedAttributes `json:"mapped_attributes,omitempty"` + DomainFederation *AppDomainFederation `json:"domain_federation,omitempty"` } func NewApp(d *schema.ResourceData) *App { @@ -188,6 +193,24 @@ func NewAppMappedAttr(d *schema.ResourceData) *[]AppMappedAttributes { return &res } +func NewAppDomainFederation(protocol string, d *schema.ResourceData) (*AppDomainFederation, error) { + res := &AppDomainFederation{} + df, exists := d.GetOk("domain_federation") + if !exists { + return nil, nil + } + if protocol != "SAML" { + return nil, fmt.Errorf("Domain federation with sso protocol %s is not supported", protocol) + } + domain_fed := df.([]interface{}) + if len(domain_fed) != 1 { + return nil, nil + } + domain_federation_conf := domain_fed[0].(map[string]interface{}) + res.Domain = domain_federation_conf["domain"].(string) + return res, nil +} + func parseApp(resp []byte) (*App, error) { app := &App{} err := json.Unmarshal(resp, app) @@ -224,6 +247,15 @@ func parseAppMappedAttributes(resp []byte) ([]AppMappedAttributes, error) { return *app_mapped_attrs, nil } +func parseAppDomainFederation(resp []byte) (*AppDomainFederation, error) { + app_domain_federation := &AppDomainFederation{} + err := json.Unmarshal(resp, app_domain_federation) + if err != nil { + return nil, fmt.Errorf("could not parse app domain federation response: %v", err) + } + return app_domain_federation, nil +} + func UpdateAppProto(ctx context.Context, c *Client, app *App, saml []byte, oidc []byte, delete_on_failure bool) (*App, error) { if saml != nil { @@ -264,6 +296,20 @@ func UpdateAppProto(ctx context.Context, c *Client, app *App, saml []byte, oidc return app, nil } +func UpdateAppDomainFederation(ctx context.Context, c *Client, app_id string, + domainFed []byte) (*AppDomainFederation, error) { + DomainFedUrl := fmt.Sprintf("%s/%s/%s/domain_federation", c.BaseURL, appEndpoint, app_id) + resp, err := c.Patch(ctx, DomainFedUrl, domainFed) + if err != nil { + return nil, err + } + domain_federation_resp, err := parseAppDomainFederation(resp) + if err != nil { + return nil, err + } + return domain_federation_resp, nil +} + func UpdateAppMappedAttrs(ctx context.Context, c *Client, app_id string, mappedAttrs []byte) ([]AppMappedAttributes, error) { MappedAttrsUrl := fmt.Sprintf("%s/%s/%s/attribute_mapping", c.BaseURL, appEndpoint, app_id) @@ -297,7 +343,7 @@ func MarshalAppProtocol(protocol string, saml *AppSaml, oidc *AppOidc) ([]byte, } func CreateApp(ctx context.Context, c *Client, app *App, saml *AppSaml, oidc *AppOidc, - mappedAttrs *[]AppMappedAttributes) (*App, error) { + mappedAttrs *[]AppMappedAttributes, domainFed *AppDomainFederation) (*App, error) { body, err := json.Marshal(app) if err != nil { return nil, fmt.Errorf("could not convert app to json: %v", err) @@ -330,11 +376,26 @@ func CreateApp(ctx context.Context, c *Client, app *App, saml *AppSaml, oidc *Ap app_resp.MappedAttributes = mappedAttrs } } + if domainFed != nil { + domain_federation_body, err := json.Marshal(domainFed) + if err != nil { + DeleteApp(ctx, c, app_resp.ID) + return nil, fmt.Errorf("could not convert app domain federation to json: %v", err) + } + if domain_federation_body != nil { + domainFed, err := UpdateAppDomainFederation(ctx, c, app_resp.ID, domain_federation_body) + if err != nil { + DeleteApp(ctx, c, app_resp.ID) + return nil, err + } + app_resp.DomainFederation = domainFed + } + } return UpdateAppProto(ctx, c, app_resp, saml_body, oidc_body, true) } func UpdateApp(ctx context.Context, c *Client, appID string, app *App, saml *AppSaml, oidc *AppOidc, - mappedAttrs *[]AppMappedAttributes) (*App, error) { + mappedAttrs *[]AppMappedAttributes, domainFed *AppDomainFederation) (*App, error) { var empty_proto string proto := app.Protocol app.Protocol = empty_proto @@ -369,6 +430,19 @@ func UpdateApp(ctx context.Context, c *Client, appID string, app *App, saml *App app_resp.MappedAttributes = mappedAttrs } } + if domainFed != nil { + domain_federation_body, err := json.Marshal(domainFed) + if err != nil { + return nil, fmt.Errorf("could not convert app domain federation to json: %v", err) + } + if domain_federation_body != nil { + domainFed, err := UpdateAppDomainFederation(ctx, c, app_resp.ID, domain_federation_body) + if err != nil { + return nil, err + } + app_resp.DomainFederation = domainFed + } + } app.Protocol = proto return UpdateAppProto(ctx, c, app_resp, saml_body, oidc_body, false) } @@ -394,6 +468,16 @@ func GetApp(ctx context.Context, c *Client, appID string, protocol string) (*App return nil, err } app_resp.Saml = saml_resp + DomainFedUrl := fmt.Sprintf("%s/%s/%s/domain_federation", c.BaseURL, appEndpoint, app_resp.ID) + resp, err = c.Get(ctx, DomainFedUrl, nil) + if err != nil { + return nil, err + } + domain_federation_resp, err := parseAppDomainFederation(resp) + if err != nil { + return nil, err + } + app_resp.DomainFederation = domain_federation_resp } else if protocol == "OIDC" { oidcUrl := fmt.Sprintf("%s/%s/%s/oidc", c.BaseURL, appEndpoint, app_resp.ID) resp, err = c.Get(ctx, oidcUrl, nil) diff --git a/internal/provider/acc_tests/app_test.go b/internal/provider/acc_tests/app_test.go index a3ae694..bf7b852 100644 --- a/internal/provider/acc_tests/app_test.go +++ b/internal/provider/acc_tests/app_test.go @@ -33,6 +33,10 @@ resource "pfptmeta_app" "app_saml" { signature_algorithm = "RSA-SHA256" digest_algorithm = "SHA256" } + + domain_federation { + domain = "my-domain.com" + } } data "pfptmeta_app" "app_saml" { @@ -170,6 +174,7 @@ func TestAccDataSourceAppSaml(t *testing.T) { resource.TestCheckResourceAttr("pfptmeta_app.app_saml", "saml.0.subject_name_id_format", "emailAddress"), resource.TestCheckResourceAttr("pfptmeta_app.app_saml", "saml.0.signature_algorithm", "RSA-SHA256"), resource.TestCheckResourceAttr("pfptmeta_app.app_saml", "saml.0.digest_algorithm", "SHA256"), + resource.TestCheckResourceAttr("pfptmeta_app.app_saml", "domain_federation.0.domain", "my-domain.com"), ), }, { @@ -190,6 +195,7 @@ func TestAccDataSourceAppSaml(t *testing.T) { resource.TestCheckResourceAttr("data.pfptmeta_app.app_saml", "saml.0.subject_name_id_format", "emailAddress"), resource.TestCheckResourceAttr("data.pfptmeta_app.app_saml", "saml.0.signature_algorithm", "RSA-SHA256"), resource.TestCheckResourceAttr("data.pfptmeta_app.app_saml", "saml.0.digest_algorithm", "SHA256"), + resource.TestCheckResourceAttr("data.pfptmeta_app.app_saml", "domain_federation.0.domain", "my-domain.com"), ), }, }, diff --git a/internal/provider/app/common.go b/internal/provider/app/common.go index 4467911..d754732 100644 --- a/internal/provider/app/common.go +++ b/internal/provider/app/common.go @@ -9,7 +9,7 @@ import ( "net/http" ) -var excludedKeys = []string{"id", "saml", "oidc", "mapped_attributes"} +var excludedKeys = []string{"id", "saml", "oidc", "mapped_attributes", "domain_federation"} const ( description = "Application for configuring SSO by SPs based on SAML or OIDC protocols" @@ -33,7 +33,8 @@ const ( samlSsoUrleDesc = "SAML url to be configured at SP side" samlAuthnCtxClassDesc = "SAML authentication context class to be configured at the SP side" samlDefRelayStateDesc = "SAML default relay state URL to use after successful assertion" - wsFedDesc = "SSO configuration for Office365 SP" + domainFedDesc = "SSO configuration for Office-365 SP" + domainFedDomainDesc = "Office-365 domain to be federated" oidcDesc = "OIDC-based app properties" oidcSigninRedUrlsDesc = "Redirect URLs which are allowed after successful authorization" oidcGrantTypesDesc = "OIDC-supported access/ID token grant types" @@ -75,13 +76,19 @@ func appCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) di app_body := client.NewApp(d) var saml_body *client.AppSaml var oidc_body *client.AppOidc + var domain_fed_body *client.AppDomainFederation if app_body.Protocol == "SAML" { saml_body = client.NewAppSaml(d) } else if app_body.Protocol == "OIDC" { oidc_body = client.NewAppOidc(d) } + domain_fed_body, err := client.NewAppDomainFederation(app_body.Protocol, d) + if err != nil { + return diag.FromErr(err) + } mapped_attrs_body := client.NewAppMappedAttr(d) - a, err := client.CreateApp(ctx, c, app_body, saml_body, oidc_body, mapped_attrs_body) + a, err := client.CreateApp(ctx, c, app_body, saml_body, oidc_body, + mapped_attrs_body, domain_fed_body) if err != nil { return diag.FromErr(err) } @@ -95,13 +102,19 @@ func appUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) di app_body := client.NewApp(d) var saml_body *client.AppSaml var oidc_body *client.AppOidc + var domain_fed_body *client.AppDomainFederation if app_body.Protocol == "SAML" { saml_body = client.NewAppSaml(d) } else if app_body.Protocol == "OIDC" { oidc_body = client.NewAppOidc(d) } + domain_fed_body, err := client.NewAppDomainFederation(app_body.Protocol, d) + if err != nil { + return diag.FromErr(err) + } mapped_attrs_body := client.NewAppMappedAttr(d) - a, err := client.UpdateApp(ctx, c, id, app_body, saml_body, oidc_body, mapped_attrs_body) + a, err := client.UpdateApp(ctx, c, id, app_body, saml_body, oidc_body, + mapped_attrs_body, domain_fed_body) if err != nil { return diag.FromErr(err) } @@ -167,5 +180,14 @@ func appToResource(d *schema.ResourceData, a *client.App) diag.Diagnostics { if err != nil { return diag.FromErr(err) } + if a.DomainFederation != nil { + domainFedToResource := []map[string]interface{}{ + {"domain": a.DomainFederation.Domain}, + } + err = d.Set("domain_federation", domainFedToResource) + if err != nil { + return diag.FromErr(err) + } + } return diags } diff --git a/internal/provider/app/data_source.go b/internal/provider/app/data_source.go index c85643f..e9a0a30 100644 --- a/internal/provider/app/data_source.go +++ b/internal/provider/app/data_source.go @@ -130,6 +130,20 @@ func DataSource() *schema.Resource { }, }, }, + "domain_federation": { + Description: domainFedDesc, + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain": { + Description: domainFedDomainDesc, + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, "oidc": { Description: oidcDesc, Type: schema.TypeList, diff --git a/internal/provider/app/resource.go b/internal/provider/app/resource.go index c1a04ab..1bd73a8 100644 --- a/internal/provider/app/resource.go +++ b/internal/provider/app/resource.go @@ -152,6 +152,23 @@ func Resource() *schema.Resource { }, ConflictsWith: []string{"oidc"}, }, + "domain_federation": { + Description: domainFedDesc, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain": { + Description: domainFedDomainDesc, + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: common.ValidatePattern(common.DomainPattern), + }, + }, + }, + ConflictsWith: []string{"oidc"}, + }, "oidc": { Description: oidcDesc, Type: schema.TypeList, @@ -203,7 +220,7 @@ func Resource() *schema.Resource { }, }, }, - ConflictsWith: []string{"saml"}, + ConflictsWith: []string{"saml", "domain_federation"}, }, "mapped_attributes": { Description: MappedAttributesDesc,