Skip to content

Commit

Permalink
Merge pull request #664 from okta/app_group_assignments
Browse files Browse the repository at this point in the history
OMG, I think this should do the trick for `okta_app_group_assignments`
  • Loading branch information
monde authored Sep 23, 2021
2 parents 574fca1 + ed5bfe4 commit 10b42b9
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 101 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ require (
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/okta/okta-sdk-golang/v2 v2.6.2
github.com/okta/okta-sdk-golang/v2 v2.6.3-0.20210923165359-20aeac44ab01
github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 // indirect
github.com/russellhaering/goxmldsig v1.1.0 // indirect
github.com/ulikunitz/xz v0.5.8 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/okta/okta-sdk-golang/v2 v2.6.2 h1:JcMjG0264ockA2RIrMA4L0fbnZAH+9qo2T/QuFApkj8=
github.com/okta/okta-sdk-golang/v2 v2.6.2/go.mod h1:0y8stgdplWMjaEbMr4mVtw0R+BdktpGZRw2sWKZWsMs=
github.com/okta/okta-sdk-golang/v2 v2.6.3-0.20210923165359-20aeac44ab01 h1:eTw4NCD4FBAAT+tqx8qq2WhPjVwauVp//mvG4c3wrUY=
github.com/okta/okta-sdk-golang/v2 v2.6.3-0.20210923165359-20aeac44ab01/go.mod h1:0y8stgdplWMjaEbMr4mVtw0R+BdktpGZRw2sWKZWsMs=
github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 h1:pSCLCl6joCFRnjpeojzOpEYs4q7Vditq8fySFG5ap3Y=
github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
4 changes: 2 additions & 2 deletions okta/resource_okta_app_group_assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ func buildAppGroupAssignment(d *schema.ResourceData) okta.ApplicationGroupAssign
var profile interface{}
rawProfile := d.Get("profile").(string)
_ = json.Unmarshal([]byte(rawProfile), &profile)
priority := d.Get("priority").(int)
priority := int64(d.Get("priority").(int))
return okta.ApplicationGroupAssignment{
Profile: profile,
Priority: int64(priority),
Priority: &priority,
}
}
224 changes: 129 additions & 95 deletions okta/resource_okta_app_group_assignments.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package okta

import (
"bytes"
"context"
"encoding/json"
"fmt"
"reflect"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand All @@ -30,20 +30,10 @@ func resourceAppGroupAssignments() *schema.Resource {
ForceNew: true,
},
"group": {
Type: schema.TypeSet,
Type: schema.TypeList,
Required: true,
Description: "A group to assign to this application",
MinItems: 1,
Set: func(v interface{}) int {
buf := bytes.NewBuffer(nil)
group := v.(map[string]interface{})

buf.WriteString(fmt.Sprintf("%s-", group["id"].(string)))
buf.WriteString(fmt.Sprintf("%s-", normalizeDataJSON(group["profile"])))
buf.WriteString(fmt.Sprintf("%d-", group["priority"].(int)))

return schema.HashString(buf.String())
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Expand All @@ -63,11 +53,13 @@ func resourceAppGroupAssignments() *schema.Resource {
Type: schema.TypeString,
ValidateDiagFunc: stringIsJSON,
StateFunc: normalizeDataJSON,
Optional: true,
Required: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return new == ""
},
Default: "{}",
DefaultFunc: func() (interface{}, error) {
return "{}", nil
},
},
},
},
Expand All @@ -77,18 +69,16 @@ func resourceAppGroupAssignments() *schema.Resource {
}

func resourceAppGroupAssignmentsCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
groups := d.Get("group").(*schema.Set).List()
client := getOktaClientFromMetadata(m)

assignments := tfGroupsToGroupAssignments(groups...)
assignments := tfGroupsToGroupAssignments(d)

// run through all groups in the set and create an assignment
for groupID, assignment := range assignments {
for i := range assignments {
_, _, err := client.Application.CreateApplicationGroupAssignment(
ctx,
d.Get("app_id").(string),
groupID,
assignment,
assignments[i].Id,
*assignments[i],
)
if err != nil {
return diag.Errorf("failed to create application group assignment: %v", err)
Expand All @@ -114,33 +104,20 @@ func resourceAppGroupAssignmentsRead(ctx context.Context, d *schema.ResourceData
d.SetId("")
return nil
}
tfFlattenedAssignments := make([]interface{}, len(assignments))
for i, assignment := range assignments {
tfAssignment, err := groupAssignmentToTFGroup(assignment)
g, ok := d.GetOk("group")
if ok {
err := setNonPrimitives(d, map[string]interface{}{"group": syncGroups(d, g.([]interface{}), assignments)})
if err != nil {
return diag.Errorf("failed to marshal group profile: %v", err)
return diag.Errorf("failed to set OAuth application properties: %v", err)
}
tfFlattenedAssignments[i] = tfAssignment
}

err = d.Set("group", tfFlattenedAssignments)
if err != nil {
return diag.Errorf("failed to set groups in tf state: %v", err)
}
return nil
}

func resourceAppGroupAssignmentsDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := getOktaClientFromMetadata(m)
for _, rawGroup := range d.Get("group").(*schema.Set).List() {
group := rawGroup.(map[string]interface{})
resp, err := client.Application.DeleteApplicationGroupAssignment(
ctx,
d.Get("app_id").(string),
group["id"].(string),
)
if err := suppressErrorOn404(resp, err); err != nil {
return diag.Errorf("failed to delete application group assignment: %v", err)
} else {
arr := make([]map[string]interface{}, len(assignments))
for i := range assignments {
arr[i] = groupAssignmentToTFGroup(assignments[i])
}
err := setNonPrimitives(d, map[string]interface{}{"group": arr})
if err != nil {
return diag.Errorf("failed to set OAuth application properties: %v", err)
}
}
return nil
Expand All @@ -149,19 +126,16 @@ func resourceAppGroupAssignmentsDelete(ctx context.Context, d *schema.ResourceDa
func resourceAppGroupAssignmentsUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := getOktaClientFromMetadata(m)
appID := d.Get("app_id").(string)

old, new := d.GetChange("group")
oldSet := old.(*schema.Set)
newSet := new.(*schema.Set)

toAdd := tfGroupsToGroupAssignments(
newSet.Difference(oldSet).List()...,
)
toRemove := tfGroupsToGroupAssignments(
oldSet.Difference(newSet).List()...,
assignments, _, err := listApplicationGroupAssignments(
ctx,
client,
d.Get("app_id").(string),
)

err := deleteGroupAssignments(
if err != nil {
return diag.Errorf("failed to fetch group assignments: %v", err)
}
toAssign, toRemove := splitAssignmentsTargets(tfGroupsToGroupAssignments(d), assignments)
err = deleteGroupAssignments(
client.Application.DeleteApplicationGroupAssignment,
ctx,
appID,
Expand All @@ -170,61 +144,121 @@ func resourceAppGroupAssignmentsUpdate(ctx context.Context, d *schema.ResourceDa
if err != nil {
return diag.Errorf("failed to delete group assignment: %v", err)
}

err = addGroupAssignments(
client.Application.CreateApplicationGroupAssignment,
ctx,
appID,
toAdd,
toAssign,
)
if err != nil {
return diag.Errorf("failed to add group assignment: %v", err)
return diag.Errorf("failed to add/update group assignment: %v", err)
}
return resourceAppGroupAssignmentsRead(ctx, d, m)
}

// groupAssignmentToTFGroup
func groupAssignmentToTFGroup(assignment *okta.ApplicationGroupAssignment) (map[string]interface{}, error) {
profile := "{}"
func resourceAppGroupAssignmentsDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := getOktaClientFromMetadata(m)
for _, rawGroup := range d.Get("group").([]interface{}) {
group := rawGroup.(map[string]interface{})
resp, err := client.Application.DeleteApplicationGroupAssignment(
ctx,
d.Get("app_id").(string),
group["id"].(string),
)
if err := suppressErrorOn404(resp, err); err != nil {
return diag.Errorf("failed to delete application group assignment: %v", err)
}
}
return nil
}

jsonProfile, err := json.Marshal(assignment.Profile)
if err != nil {
return nil, err
func syncGroups(d *schema.ResourceData, groups []interface{}, assignments []*okta.ApplicationGroupAssignment) []interface{} {
var newGroups []interface{}
for i := range groups {
present := false
for _, assignment := range assignments {
if assignment.Id == d.Get(fmt.Sprintf("group.%d.id", i)).(string) {
present = true
if assignment.Priority != nil {
groups[i].(map[string]interface{})["priority"] = int(*assignment.Priority)
}
jsonProfile, _ := json.Marshal(assignment.Profile)
if string(jsonProfile) != "" {
groups[i].(map[string]interface{})["profile"] = string(jsonProfile)
}
}
}
if present {
newGroups = append(newGroups, groups[i])
}
}
return newGroups
}

func splitAssignmentsTargets(expectedAssignments, existingAssignments []*okta.ApplicationGroupAssignment) (toAssign, toRemove []*okta.ApplicationGroupAssignment) {
for i := range expectedAssignments {
if !containsEqualAssignment(existingAssignments, expectedAssignments[i]) {
toAssign = append(toAssign, expectedAssignments[i])
}
}
for i := range existingAssignments {
if !containsAssignment(expectedAssignments, existingAssignments[i]) {
toRemove = append(toRemove, existingAssignments[i])
}
}
return
}

func containsAssignment(assignments []*okta.ApplicationGroupAssignment, assignment *okta.ApplicationGroupAssignment) bool {
for i := range assignments {
if assignments[i].Id == assignment.Id {
return true
}
}
return false
}

func containsEqualAssignment(assignments []*okta.ApplicationGroupAssignment, assignment *okta.ApplicationGroupAssignment) bool {
for i := range assignments {
if assignments[i].Id == assignment.Id && reflect.DeepEqual(assignments[i].Profile, assignment.Profile) {
if assignment.Priority != nil {
return reflect.DeepEqual(assignments[i].Priority, assignment.Priority)
}
return true
}
}
return false
}

func groupAssignmentToTFGroup(assignment *okta.ApplicationGroupAssignment) map[string]interface{} {
jsonProfile, _ := json.Marshal(assignment.Profile)
profile := "{}"
if string(jsonProfile) != "" {
profile = string(jsonProfile)
}

tfAssignment := map[string]interface{}{
return map[string]interface{}{
"id": assignment.Id,
"priority": assignment.Priority,
"profile": profile,
}
return tfAssignment, nil
}

func tfGroupsToGroupAssignments(groups ...interface{}) map[string]okta.ApplicationGroupAssignment {
assignments := map[string]okta.ApplicationGroupAssignment{}
// run through all groups in the set and create an assignment
for _, untypedGroup := range groups {
group := untypedGroup.(map[string]interface{})

id := group["id"].(string)
// skip empty groups with no id
if id == "" {
continue
}
priority := group["priority"].(int)

rawProfile := group["profile"]
func tfGroupsToGroupAssignments(d *schema.ResourceData) []*okta.ApplicationGroupAssignment {
assignments := make([]*okta.ApplicationGroupAssignment, len(d.Get("group").([]interface{})))
for i := range d.Get("group").([]interface{}) {
rawProfile := d.Get(fmt.Sprintf("group.%d.profile", i))
var profile interface{}
_ = json.Unmarshal([]byte(rawProfile.(string)), &profile)

assignments[id] = okta.ApplicationGroupAssignment{
Profile: profile,
Priority: int64(priority),
Id: id,
a := &okta.ApplicationGroupAssignment{
Id: d.Get(fmt.Sprintf("group.%d.id", i)).(string),
Profile: profile,
}
priority, ok := d.GetOk(fmt.Sprintf("group.%d.priority", i))
if ok {
p := int64(priority.(int))
a.Priority = &p
}
assignments[i] = a
}
return assignments
}
Expand All @@ -234,10 +268,10 @@ func addGroupAssignments(
add func(context.Context, string, string, okta.ApplicationGroupAssignment) (*okta.ApplicationGroupAssignment, *okta.Response, error),
ctx context.Context,
appID string,
assignments map[string]okta.ApplicationGroupAssignment,
assignments []*okta.ApplicationGroupAssignment,
) error {
for groupID, assignment := range assignments {
_, _, err := add(ctx, appID, groupID, assignment)
for _, assignment := range assignments {
_, _, err := add(ctx, appID, assignment.Id, *assignment)
if err != nil {
return err
}
Expand All @@ -250,14 +284,14 @@ func deleteGroupAssignments(
delete func(context.Context, string, string) (*okta.Response, error),
ctx context.Context,
appID string,
assignments map[string]okta.ApplicationGroupAssignment,
assignments []*okta.ApplicationGroupAssignment,
) error {
for groupID := range assignments {
_, err := delete(ctx, appID, groupID)
for i := range assignments {
_, err := delete(ctx, appID, assignments[i].Id)
if err != nil {
return fmt.Errorf(
"could not delete assignment for group %s, to application %s: %w",
groupID,
assignments[i].Id,
appID,
err,
)
Expand Down
2 changes: 1 addition & 1 deletion okta/resource_okta_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, m interface{}
}
// Only sync when it is outlined, an empty list will remove all membership
if _, exists := d.GetOk("group_memberships"); exists {
err = setGroups(ctx, d, client)
err = setGroupUserMemberships(ctx, d, client)
if err != nil {
return diag.Errorf("failed to set user's groups: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion okta/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ func setAllGroups(ctx context.Context, d *schema.ResourceData, c *okta.Client) e
}

// set groups attached to the user that can be changed
func setGroups(ctx context.Context, d *schema.ResourceData, c *okta.Client) error {
func setGroupUserMemberships(ctx context.Context, d *schema.ResourceData, c *okta.Client) error {
groups, response, err := c.User.ListUserGroups(ctx, d.Id())
if err != nil {
return fmt.Errorf("failed to list user groups: %v", err)
Expand Down
2 changes: 1 addition & 1 deletion okta/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,5 @@ func TestUserSetAllGroups(t *testing.T) {
}

func TestUserSetGroups(t *testing.T) {
testUserGroupFetchesAllPages(t, setGroups)
testUserGroupFetchesAllPages(t, setGroupUserMemberships)
}

0 comments on commit 10b42b9

Please sign in to comment.