Skip to content

Commit c380e1e

Browse files
committed
feat(me): add support for SSO configuration
1 parent f7667c2 commit c380e1e

File tree

5 files changed

+456
-0
lines changed

5 files changed

+456
-0
lines changed

ovh/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ func Provider() *schema.Provider {
208208
"ovh_iploadbalancing_tcp_route_rule": resourceIPLoadbalancingTcpRouteRule(),
209209
"ovh_iploadbalancing_vrack_network": resourceIPLoadbalancingVrackNetwork(),
210210
"ovh_me_identity_group": resourceMeIdentityGroup(),
211+
"ovh_me_identity_provider": resourceMeIdentityProvider(),
211212
"ovh_me_identity_user": resourceMeIdentityUser(),
212213
"ovh_me_installation_template": resourceMeInstallationTemplate(),
213214
"ovh_me_installation_template_partition_scheme": resourceMeInstallationTemplatePartitionScheme(),

ovh/resource_me_identity_provider.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package ovh
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
)
9+
10+
func resourceMeIdentityProvider() *schema.Resource {
11+
return &schema.Resource{
12+
CreateContext: resourceMeIdentityProviderCreate,
13+
ReadContext: resourceMeIdentityProviderRead,
14+
UpdateContext: resourceMeIdentityProviderUpdate,
15+
DeleteContext: resourceMeIdentityProviderDelete,
16+
17+
Importer: &schema.ResourceImporter{
18+
StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
19+
return []*schema.ResourceData{d}, nil
20+
},
21+
},
22+
23+
Schema: map[string]*schema.Schema{
24+
"metadata": {
25+
Type: schema.TypeString,
26+
Required: true,
27+
ForceNew: true,
28+
},
29+
"group_attribute_name": {
30+
Type: schema.TypeString,
31+
Optional: true,
32+
},
33+
"requested_attributes": {
34+
Type: schema.TypeList,
35+
Optional: true,
36+
Elem: &schema.Resource{
37+
Schema: map[string]*schema.Schema{
38+
"is_required": {
39+
Type: schema.TypeBool,
40+
Required: true,
41+
},
42+
"name": {
43+
Type: schema.TypeString,
44+
Required: true,
45+
},
46+
"name_format": {
47+
Type: schema.TypeString,
48+
Required: true,
49+
},
50+
"values": {
51+
Type: schema.TypeList,
52+
Elem: &schema.Schema{
53+
Type: schema.TypeString,
54+
},
55+
Required: true,
56+
},
57+
},
58+
},
59+
},
60+
"disable_users": {
61+
Type: schema.TypeBool,
62+
Optional: true,
63+
Default: true,
64+
},
65+
"creation": {
66+
Type: schema.TypeString,
67+
Computed: true,
68+
},
69+
"last_update": {
70+
Type: schema.TypeString,
71+
Computed: true,
72+
},
73+
},
74+
}
75+
}
76+
77+
// Common function with the datasource
78+
func resourceMeIdentityProviderRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
79+
config := meta.(*Config)
80+
81+
providerConfDetails := &MeIdentityProviderResponse{}
82+
if err := config.OVHClient.GetWithContext(ctx, "/me/identity/provider", providerConfDetails); err != nil {
83+
return diag.FromErr(err)
84+
}
85+
86+
d.Set("group_attribute_name", providerConfDetails.GroupAttributeName)
87+
d.Set("disable_users", providerConfDetails.DisableUsers)
88+
d.Set("requested_attributes", requestedAttributesToMapList(providerConfDetails.Extensions.RequestedAttributes))
89+
d.Set("creation", providerConfDetails.Creation)
90+
d.Set("last_update", providerConfDetails.LastUpdate)
91+
92+
return nil
93+
}
94+
95+
func requestedAttributesToMapList(attributes []MeIdentityProviderAttribute) []map[string]interface{} {
96+
requestedAttributes := []map[string]interface{}{}
97+
for _, v := range attributes {
98+
requestedAttributes = append(requestedAttributes, map[string]interface{}{
99+
"is_required": v.IsRequired,
100+
"name": v.Name,
101+
"name_format": v.NameFormat,
102+
"values": v.Values,
103+
})
104+
}
105+
return requestedAttributes
106+
}
107+
108+
func resourceMeIdentityProviderCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
109+
config := meta.(*Config)
110+
111+
metadata := d.Get("metadata").(string)
112+
113+
groupAttributeName := d.Get("group_attribute_name").(string)
114+
disableUsers := d.Get("disable_users").(bool)
115+
requestedAttributes, err := loadMeIdentityProviderAttributeListFromResource(d.Get("requested_attributes"))
116+
if err != nil {
117+
return diag.FromErr(err)
118+
}
119+
120+
params := &MeIdentityProviderCreateOpts{
121+
Metadata: metadata,
122+
GroupAttributeName: groupAttributeName,
123+
DisableUsers: disableUsers,
124+
Extensions: MeIdentityProviderExtensions{
125+
RequestedAttributes: requestedAttributes,
126+
},
127+
}
128+
129+
err = config.OVHClient.PostWithContext(ctx, "/me/identity/provider", params, nil)
130+
if err != nil {
131+
return diag.Errorf("Error creating identity provider:\n\t %v", err)
132+
}
133+
134+
d.SetId("ovh_sso")
135+
136+
return resourceMeIdentityProviderRead(ctx, d, meta)
137+
}
138+
139+
func resourceMeIdentityProviderUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
140+
config := meta.(*Config)
141+
142+
groupAttributeName := d.Get("group_attribute_name").(string)
143+
disableUsers := d.Get("disable_users").(bool)
144+
requestedAttributes, err := loadMeIdentityProviderAttributeListFromResource(d.Get("requested_attributes"))
145+
if err != nil {
146+
return diag.FromErr(err)
147+
}
148+
149+
params := &MeIdentityProviderUpdateOpts{
150+
GroupAttributeName: groupAttributeName,
151+
DisableUsers: disableUsers,
152+
Extensions: MeIdentityProviderExtensions{
153+
RequestedAttributes: requestedAttributes,
154+
},
155+
}
156+
err = config.OVHClient.PutWithContext(ctx,
157+
"/me/identity/provider",
158+
params,
159+
nil,
160+
)
161+
if err != nil {
162+
return diag.Errorf("Unable to update identity provider:\n\t %q", err)
163+
}
164+
165+
return resourceMeIdentityProviderRead(ctx, d, meta)
166+
}
167+
168+
func resourceMeIdentityProviderDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
169+
config := meta.(*Config)
170+
171+
err := config.OVHClient.DeleteWithContext(ctx,
172+
"/me/identity/provider",
173+
nil,
174+
)
175+
if err != nil {
176+
return diag.Errorf("Unable to delete identity provider:\n\t %q", err)
177+
}
178+
179+
d.SetId("")
180+
return nil
181+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package ovh
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"testing"
7+
"time"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
11+
)
12+
13+
const samlIDPMetadata string = `<?xml version="1.0" encoding="UTF-8"?>
14+
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://ovhcloud.com/">
15+
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
16+
<KeyDescriptor use="signing">
17+
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
18+
<X509Data>
19+
<X509Certificate>MIIFlTCCA32gAwIBAgIUP8WQwHQwrvTa00RU9JROZAJj9ccwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCRlIxEzARBgNVBAgMClNvbWUtU3RhdGUxDDAKBgNVBAcMA1JCWDERMA8GA1UECgwIT1ZIY2xvdWQxFTATBgNVBAMMDG92aGNsb3VkLmNvbTAeFw0yMzExMDkxMDA2MjRaFw0zMzExMDYxMDA2MjRaMFoxCzAJBgNVBAYTAkZSMRMwEQYDVQQIDApTb21lLVN0YXRlMQwwCgYDVQQHDANSQlgxETAPBgNVBAoMCE9WSGNsb3VkMRUwEwYDVQQDDAxvdmhjbG91ZC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC4V3HulFBksxpgkgR6KgDaSSIKkKRgyDGCF06oQN/WPxGDSHTQQHTMN7jnsbr2uJieNKh+iasGvE9JFmd6nutloL1UHoO/ecrE8P2PYgpgezl7WfyoscBDZAjWM8E9FdENnonhvlga2DgV2DGIB4+D7aN6TIPfWukOB2MjfQloA9Iw71+peO9R55S7x7zixgpLO9NovbmaAyClbz06Tsm/7ezM+Vte7BfFqGUnwuNzqgOYfQm88EqXTpCT3QfR8i2IydGgAFLMFs9YvMnCaNLw9PCN7U6VPkY6M6cFQhO/moRb3H/euJnLNRMsXp99K8ruUnQ6902NXpOOnQu5Ewzfahmx0WWvlpFGdJK34oXjaWeTodGuvHtDxCY4tiHr8jCHf9h4cmC20xAyd/V7XBtu1Pc5UAg4I0w5ehWvHtVdxCsuPEh7c4qtuLyN9Qh15r+eRbiqnWTH/xJTwfo6q6iafXXcFOlTn7WoWmmeq0R8whg6XjcxMIzBXjtynTDbQa4LVq3T8iJiGfuDgwv5OwDPRN1CsawxefETsCUQ+jf/Iw/4nZpD/YqCI5xvYtDgPSt3v2TsoOnwOSjOqKmEOoHxGTN3mhbcD+I1QKJW79zqu6GVXVwMkgWdP4pkIWGccB0FqhIVzY19xQ40DbfnCkMTv2XN4t53c/q7CYhtvyN3XwIDAQABo1MwUTAdBgNVHQ4EFgQUC8yuX4Ub/Od5jSaz7NdwHUSlq5wwHwYDVR0jBBgwFoAUC8yuX4Ub/Od5jSaz7NdwHUSlq5wwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAR4MzroH8kEwqcZeB94hetY/NQGZI+kZ26iKnvLaZa8r56UeiIrEGdEeys5JdQh/XJDWsEU6piJ0dwrIkkpgZELmUmToylcxndzjjcHbiKLlqkL+kBu9QeO/r6JTHaNyWs0An2VvCUfo+Frt8hvrJCINlCDylOaWIxHH3P0TG7ThFGWSy8nW+VMMXDS8vQIGRM66HqgYlu6HBryecf0SsCkVYbUb1zYJ+lEhYK0pj4RORainJX+PU+mIMUwQtfBByuI7RP0a2Vny0gffrtPuNfhRJb8Pwt2UYw2niWUDOfXuk9RYgqX/1wLVqk72KJJlD3c7+abZ6BcNEJax5e/icilUrxcs4MymDPjk63kQURRVzcC4hCXYqJVQmRfVT4fdLLKPmeg3ysl+U4eJZ8odmaqoVGqZryncdAC+nT5lnLRm6m2lv3v+YhConctLxzCwV/xA8jU2w9VVRw2gkY8bdkvOb7c2OpXU6J3TYtaltG7foQiuXbRd37GWzzzEspxiAI9y8uIEJTsASaufsEdpR+a1sPy3rYJom/Li3dH9p9Ch+tp51pMYhSRGEiNu9g5918zMbrKvwkl6h/PQlTOlb65qUUoNKC5Baxhz3VkGxSKMUwS4Lj/WHvCGU5OteGFHglDgDm125FDakOYU1dnMm/P55yNhnSUH2sXngybxnw/w=</X509Certificate>
20+
</X509Data>
21+
</KeyInfo>
22+
</KeyDescriptor>
23+
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
24+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ovhcloud.com/"></SingleSignOnService>
25+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://ovhcloud.com/"></SingleSignOnService>
26+
</IDPSSODescriptor>
27+
</EntityDescriptor>`
28+
29+
func init() {
30+
resource.AddTestSweepers("ovh_me_identity_provider", &resource.Sweeper{
31+
Name: "ovh_me_identity_provider",
32+
F: testSweepMeIdentityProvider,
33+
})
34+
}
35+
36+
func testSweepMeIdentityProvider(region string) error {
37+
client, err := sharedClientForRegion(region)
38+
if err != nil {
39+
return fmt.Errorf("error getting client: %s", err)
40+
}
41+
42+
err = resource.Retry(5*time.Minute, func() *resource.RetryError {
43+
log.Printf("[INFO] Deleting identity provider")
44+
if err := client.Delete("/me/identity/provider", nil); err != nil {
45+
return resource.RetryableError(err)
46+
}
47+
48+
// Successful delete
49+
return nil
50+
})
51+
52+
return err
53+
}
54+
55+
func TestAccMeIdentityProvider_basic(t *testing.T) {
56+
groupeAttribute := acctest.RandomWithPrefix(test_prefix)
57+
disableUsers := "false"
58+
config := fmt.Sprintf(testAccMeIdentityProviderConfig_basic, groupeAttribute, disableUsers, samlIDPMetadata)
59+
60+
resource.Test(t, resource.TestCase{
61+
PreCheck: func() { testAccPreCheckCredentials(t) },
62+
Providers: testAccProviders,
63+
Steps: []resource.TestStep{
64+
{
65+
Config: config,
66+
Check: resource.ComposeTestCheckFunc(
67+
checkIdentityProviderResourceAttr("ovh_me_identity_provider.my_provider", groupeAttribute, disableUsers, samlIDPMetadata, nil)...,
68+
),
69+
},
70+
},
71+
})
72+
}
73+
74+
func TestAccMeIdentityProvider_requestedAttributes(t *testing.T) {
75+
groupeAttribute := acctest.RandomWithPrefix(test_prefix)
76+
disableUsers := "false"
77+
requestedAttribute := map[string]string{
78+
"is_required": "false",
79+
"name": "test1",
80+
"name_format": "test2",
81+
"values": "test3",
82+
}
83+
config := fmt.Sprintf(testAccMeIdentityProviderConfig_requestedAttribute, groupeAttribute, disableUsers, samlIDPMetadata, requestedAttribute["is_required"], requestedAttribute["name"], requestedAttribute["name_format"], requestedAttribute["values"])
84+
85+
resource.Test(t, resource.TestCase{
86+
PreCheck: func() { testAccPreCheckCredentials(t) },
87+
Providers: testAccProviders,
88+
Steps: []resource.TestStep{
89+
{
90+
Config: config,
91+
Check: resource.ComposeTestCheckFunc(
92+
checkIdentityProviderResourceAttr("ovh_me_identity_provider.my_provider", groupeAttribute, disableUsers, samlIDPMetadata, requestedAttribute)...,
93+
),
94+
},
95+
},
96+
})
97+
}
98+
99+
func checkIdentityProviderResourceAttr(name, group_attribute, disable_users, metadata string, requestedAttributes map[string]string) []resource.TestCheckFunc {
100+
checks := []resource.TestCheckFunc{}
101+
checks = append(checks, resource.TestCheckResourceAttr(name, "group_attribute_name", group_attribute))
102+
checks = append(checks, resource.TestCheckResourceAttr(name, "disable_users", disable_users))
103+
checks = append(checks, resource.TestCheckResourceAttr(name, "metadata", metadata+"\n"))
104+
if requestedAttributes != nil {
105+
checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.is_required", requestedAttributes["is_required"]))
106+
checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.name", requestedAttributes["name"]))
107+
checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.name_format", requestedAttributes["name_format"]))
108+
checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.values.0", requestedAttributes["values"]))
109+
}
110+
return checks
111+
}
112+
113+
const testAccMeIdentityProviderConfig_basic = `
114+
resource "ovh_me_identity_provider" "my_provider" {
115+
group_attribute_name = "%s"
116+
disable_users = %s
117+
metadata = <<EOT
118+
%s
119+
EOT
120+
}
121+
`
122+
const testAccMeIdentityProviderConfig_requestedAttribute = `
123+
resource "ovh_me_identity_provider" "my_provider" {
124+
group_attribute_name = "%s"
125+
disable_users = %s
126+
metadata = <<EOT
127+
%s
128+
EOT
129+
requested_attributes {
130+
is_required = %s
131+
name = "%s"
132+
name_format = "%s"
133+
values = [ "%s" ]
134+
}
135+
}
136+
`

0 commit comments

Comments
 (0)