Skip to content

Commit dc087bd

Browse files
Jacob Sifuenteskfcampbell
Jacob Sifuentes
andauthored
feat: add data source to get organization members' SAML/SCIM linked identities (#1778)
* add `github_organization_external_identities` which returns a list of github organization members and their SAML linked identity * add docs * add more fields to external_identities * docs --------- Co-authored-by: Keegan Campbell <[email protected]>
1 parent f677f36 commit dc087bd

5 files changed

+228
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package github
2+
3+
import (
4+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
5+
"github.com/shurcooL/githubv4"
6+
)
7+
8+
func dataSourceGithubOrganizationExternalIdentities() *schema.Resource {
9+
return &schema.Resource{
10+
Read: dataSourceGithubOrganizationExternalIdentitiesRead,
11+
12+
Schema: map[string]*schema.Schema{
13+
"identities": {
14+
Type: schema.TypeList,
15+
Computed: true,
16+
Elem: &schema.Resource{
17+
Schema: map[string]*schema.Schema{
18+
"login": {
19+
Type: schema.TypeString,
20+
Computed: true,
21+
},
22+
"saml_identity": {
23+
Type: schema.TypeMap,
24+
Computed: true,
25+
Elem: &schema.Schema{
26+
Type: schema.TypeString,
27+
},
28+
},
29+
"scim_identity": {
30+
Type: schema.TypeMap,
31+
Computed: true,
32+
Elem: &schema.Schema{
33+
Type: schema.TypeString,
34+
},
35+
},
36+
},
37+
},
38+
},
39+
},
40+
}
41+
}
42+
43+
func dataSourceGithubOrganizationExternalIdentitiesRead(d *schema.ResourceData, meta interface{}) error {
44+
name := meta.(*Owner).name
45+
46+
client4 := meta.(*Owner).v4client
47+
ctx := meta.(*Owner).StopContext
48+
49+
var query struct {
50+
Organization struct {
51+
SamlIdentityProvider struct {
52+
ExternalIdentities struct {
53+
Edges []struct {
54+
Node struct {
55+
User struct {
56+
Login githubv4.String
57+
}
58+
SamlIdentity struct {
59+
NameId githubv4.String
60+
Username githubv4.String
61+
GivenName githubv4.String
62+
FamilyName githubv4.String
63+
}
64+
ScimIdentity struct {
65+
Username githubv4.String
66+
GivenName githubv4.String
67+
FamilyName githubv4.String
68+
}
69+
}
70+
}
71+
PageInfo struct {
72+
EndCursor githubv4.String
73+
HasNextPage bool
74+
}
75+
} `graphql:"externalIdentities(first: 100, after: $after)"`
76+
}
77+
} `graphql:"organization(login: $login)"`
78+
}
79+
variables := map[string]interface{}{
80+
"login": githubv4.String(name),
81+
"after": (*githubv4.String)(nil),
82+
}
83+
84+
var identities []map[string]interface{}
85+
86+
for {
87+
err := client4.Query(ctx, &query, variables)
88+
if err != nil {
89+
return err
90+
}
91+
for _, edge := range query.Organization.SamlIdentityProvider.ExternalIdentities.Edges {
92+
identity := map[string]interface{}{
93+
"login": string(edge.Node.User.Login),
94+
"saml_identity": nil,
95+
"scim_identity": nil,
96+
}
97+
98+
if edge.Node.SamlIdentity.NameId != "" {
99+
identity["saml_identity"] = map[string]string{
100+
"name_id": string(edge.Node.SamlIdentity.NameId),
101+
"username": string(edge.Node.SamlIdentity.Username),
102+
"given_name": string(edge.Node.SamlIdentity.GivenName),
103+
"family_name": string(edge.Node.SamlIdentity.FamilyName),
104+
}
105+
}
106+
107+
if edge.Node.ScimIdentity.Username != "" {
108+
identity["scim_identity"] = map[string]string{
109+
"username": string(edge.Node.ScimIdentity.Username),
110+
"given_name": string(edge.Node.ScimIdentity.GivenName),
111+
"family_name": string(edge.Node.ScimIdentity.FamilyName),
112+
}
113+
}
114+
115+
identities = append(identities, identity)
116+
}
117+
if !query.Organization.SamlIdentityProvider.ExternalIdentities.PageInfo.HasNextPage {
118+
break
119+
}
120+
variables["after"] = githubv4.NewString(query.Organization.SamlIdentityProvider.ExternalIdentities.PageInfo.EndCursor)
121+
}
122+
123+
d.SetId(name)
124+
d.Set("identities", identities)
125+
126+
return nil
127+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package github
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
7+
)
8+
9+
func TestAccGithubOrganizationExternalIdentities(t *testing.T) {
10+
if isEnterprise != "true" {
11+
t.Skip("Skipping because `ENTERPRISE_ACCOUNT` is not set or set to false")
12+
}
13+
14+
t.Run("queries without error", func(t *testing.T) {
15+
config := `data "github_organization_external_identities" "test" {}`
16+
17+
check := resource.ComposeAggregateTestCheckFunc(
18+
resource.TestCheckResourceAttrSet("data.github_organization_external_identities.test", "identities.#"),
19+
resource.TestCheckResourceAttrSet("data.github_organization_external_identities.test", "identities.0.login"),
20+
resource.TestCheckResourceAttrSet("data.github_organization_external_identities.test", "identities.0.saml_identity.name_id"),
21+
)
22+
23+
testCase := func(t *testing.T, mode string) {
24+
resource.Test(t, resource.TestCase{
25+
Providers: testAccProviders,
26+
Steps: []resource.TestStep{
27+
{
28+
Config: config,
29+
Check: check,
30+
},
31+
},
32+
})
33+
}
34+
35+
t.Run("with an anonymous account", func(t *testing.T) {
36+
t.Skip("anonymous account not supported for this operation")
37+
})
38+
39+
t.Run("with an individual account", func(t *testing.T) {
40+
t.Skip("individual account not supported for this operation")
41+
})
42+
43+
t.Run("with an organization account", func(t *testing.T) {
44+
testCase(t, organization)
45+
})
46+
})
47+
}

github/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ func Provider() terraform.ResourceProvider {
192192
"github_membership": dataSourceGithubMembership(),
193193
"github_organization": dataSourceGithubOrganization(),
194194
"github_organization_custom_role": dataSourceGithubOrganizationCustomRole(),
195+
"github_organization_external_identities": dataSourceGithubOrganizationExternalIdentities(),
195196
"github_organization_ip_allow_list": dataSourceGithubOrganizationIpAllowList(),
196197
"github_organization_team_sync_groups": dataSourceGithubOrganizationTeamSyncGroups(),
197198
"github_organization_teams": dataSourceGithubOrganizationTeams(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
layout: "github"
3+
page_title: "GitHub: github_organization_external_identities"
4+
description: |-
5+
Get a list of organization members and their SAML linked external identity NameID
6+
---
7+
8+
# github_organization_external_identities
9+
10+
Use this data source to retrieve each organization member's SAML or SCIM user
11+
attributes.
12+
13+
## Example Usage
14+
15+
```hcl
16+
data "github_organization_external_identities" "all" {}
17+
```
18+
19+
## Attributes Reference
20+
21+
- `identities` - An Array of identities returned from GitHub
22+
23+
---
24+
25+
Each element in the `identities` block consists of:
26+
27+
- `login` - The username of the GitHub user
28+
- `saml_identity` - An Object containing the user's SAML data. This object will
29+
be empty if the user is not managed by SAML.
30+
- `scim_identity` - An Object contining the user's SCIM data. This object will
31+
be empty if the user is not managed by SCIM.
32+
33+
---
34+
35+
If a user is managed by SAML, the `saml_identity` object will contain:
36+
37+
- `name_id` - The member's SAML NameID
38+
- `username` - The member's SAML Username
39+
- `family_name` - The member's SAML Family Name
40+
- `given_name` - The member's SAML Given Name
41+
42+
---
43+
44+
If a user is managed by SCIM, the `scim_identity` object will contain:
45+
46+
- `scim_username` - The member's SCIM Username. (will be empty string if user is
47+
not managed by SCIM)
48+
- `scim_groups` - The member's SCIM Groups
49+
- `scim_family_name` - The member's SCIM Family Name
50+
- `scim_given_name` - The member's SCIM Given Name

website/github.erb

+3
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@
112112
<li>
113113
<a href="/docs/providers/github/d/organization_custom_role.html">github_organization_custom_role</a>
114114
</li>
115+
<li>
116+
<a href="/docs/providers/github/d/organization_external_identities.html">github_organization_external_identities</a>
117+
</li>
115118
<li>
116119
<a href="/docs/providers/github/d/organization_ip_allow_list.html">github_organization_ip_allow_list</a>
117120
</li>

0 commit comments

Comments
 (0)