Skip to content

Commit 21586bc

Browse files
authored
Merge pull request #103 from onelogin/fix-custom-attributes
Fix custom attribute API handling
2 parents e51b117 + 8cde410 commit 21586bc

File tree

5 files changed

+206
-0
lines changed

5 files changed

+206
-0
lines changed

changelog.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## [4.3.0]
4+
5+
### Added
6+
- Helper methods for creating and updating custom attributes with proper payload structure:
7+
- Added `CreateCustomAttribute(name, shortname string)` method
8+
- Added `UpdateCustomAttribute(id int, name, shortname string)` method
9+
- These methods properly wrap parameters in the required "user_field" object
10+
- Added example code for custom attributes with create, read, update, delete examples
11+
12+
### Fixed
13+
- Fixed custom attribute creation/update by properly wrapping parameters in the "user_field" object
14+
- Fixed handling of HTTP 204 No Content responses for operations like delete that return no content on success
15+
316
## [4.2.0]
417

518
### Fixed

pkg/onelogin/users.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,34 @@ func (sdk *OneloginSDK) CreateCustomAttributes(requestBody interface{}) (interfa
218218
return utl.CheckHTTPResponse(resp)
219219
}
220220

221+
// CreateCustomAttribute creates a new custom attribute with the specified name and shortname.
222+
// This helper method properly wraps the name and shortname in the required "user_field" object
223+
// as expected by the OneLogin API.
224+
func (sdk *OneloginSDK) CreateCustomAttribute(name, shortname string) (interface{}, error) {
225+
return sdk.CreateCustomAttributeWithContext(context.Background(), name, shortname)
226+
}
227+
228+
// CreateCustomAttributeWithContext creates a new custom attribute with the provided context
229+
func (sdk *OneloginSDK) CreateCustomAttributeWithContext(ctx context.Context, name, shortname string) (interface{}, error) {
230+
// Use map[string]string for the inner map to ensure consistent types
231+
payload := map[string]interface{}{
232+
"user_field": map[string]string{
233+
"name": name,
234+
"shortname": shortname,
235+
},
236+
}
237+
238+
p, err := utl.BuildAPIPath(UserPathV2, "custom_attributes")
239+
if err != nil {
240+
return nil, err
241+
}
242+
resp, err := sdk.Client.PostWithContext(ctx, &p, payload)
243+
if err != nil {
244+
return nil, err
245+
}
246+
return utl.CheckHTTPResponse(resp)
247+
}
248+
221249
func (sdk *OneloginSDK) UpdateCustomAttributes(id int, requestBody interface{}) (interface{}, error) {
222250
p, err := utl.BuildAPIPath(UserPathV2, "custom_attributes", id)
223251
if err != nil {
@@ -230,6 +258,34 @@ func (sdk *OneloginSDK) UpdateCustomAttributes(id int, requestBody interface{})
230258
return utl.CheckHTTPResponse(resp)
231259
}
232260

261+
// UpdateCustomAttribute updates an existing custom attribute with the specified name and shortname.
262+
// This helper method properly wraps the name and shortname in the required "user_field" object
263+
// as expected by the OneLogin API.
264+
func (sdk *OneloginSDK) UpdateCustomAttribute(id int, name, shortname string) (interface{}, error) {
265+
return sdk.UpdateCustomAttributeWithContext(context.Background(), id, name, shortname)
266+
}
267+
268+
// UpdateCustomAttributeWithContext updates an existing custom attribute with the provided context
269+
func (sdk *OneloginSDK) UpdateCustomAttributeWithContext(ctx context.Context, id int, name, shortname string) (interface{}, error) {
270+
// Use map[string]string for the inner map to ensure consistent types
271+
payload := map[string]interface{}{
272+
"user_field": map[string]string{
273+
"name": name,
274+
"shortname": shortname,
275+
},
276+
}
277+
278+
p, err := utl.BuildAPIPath(UserPathV2, "custom_attributes", id)
279+
if err != nil {
280+
return nil, err
281+
}
282+
resp, err := sdk.Client.PutWithContext(ctx, &p, payload)
283+
if err != nil {
284+
return nil, err
285+
}
286+
return utl.CheckHTTPResponse(resp)
287+
}
288+
233289
func (sdk *OneloginSDK) DeleteCustomAttributes(id int) (interface{}, error) {
234290
p, err := utl.BuildAPIPath(UserPathV2, "custom_attributes", id)
235291
if err != nil {

pkg/onelogin/utilities/web.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import (
1414
// receive http response, check error code status, if good return json of resp.Body
1515
// else return error
1616
func CheckHTTPResponse(resp *http.Response) (interface{}, error) {
17+
// Handle 204 No Content responses - this is a success but with no content
18+
if resp.StatusCode == http.StatusNoContent {
19+
return map[string]interface{}{"status": "success"}, nil
20+
}
21+
1722
// Check if the request was successful
1823
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
1924
return nil, fmt.Errorf("request failed with status: %d", resp.StatusCode)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin"
8+
)
9+
10+
func main() {
11+
// Get credentials from environment variables
12+
clientID := os.Getenv("ONELOGIN_CLIENT_ID")
13+
clientSecret := os.Getenv("ONELOGIN_CLIENT_SECRET")
14+
subdomain := os.Getenv("ONELOGIN_SUBDOMAIN")
15+
16+
if clientID == "" || clientSecret == "" || subdomain == "" {
17+
fmt.Println("Please set ONELOGIN_CLIENT_ID, ONELOGIN_CLIENT_SECRET, and ONELOGIN_SUBDOMAIN environment variables")
18+
os.Exit(1)
19+
}
20+
21+
// Set environment variables for SDK initialization
22+
os.Setenv("ONELOGIN_CLIENT_ID", clientID)
23+
os.Setenv("ONELOGIN_CLIENT_SECRET", clientSecret)
24+
os.Setenv("ONELOGIN_SUBDOMAIN", subdomain)
25+
26+
// Initialize the SDK
27+
sdk, err := onelogin.NewOneloginSDK()
28+
if err != nil {
29+
fmt.Printf("Error initializing SDK: %v\n", err)
30+
os.Exit(1)
31+
}
32+
33+
// Example 1: Create a custom attribute using the new helper function
34+
fmt.Println("Example 1: Creating a custom attribute")
35+
36+
// Create with the new helper that properly structures the payload
37+
createResp, err := sdk.CreateCustomAttribute("Employee ID", "employee_id")
38+
if err != nil {
39+
fmt.Printf("Error creating custom attribute: %v\n", err)
40+
os.Exit(1)
41+
}
42+
fmt.Printf("Custom attribute created successfully: %+v\n", createResp)
43+
44+
// Extract the ID from the response for later use
45+
attr := createResp.(map[string]interface{})
46+
attrID := int(attr["id"].(float64))
47+
fmt.Printf("Created attribute ID: %d\n", attrID)
48+
49+
// Example 2: Get all custom attributes
50+
fmt.Println("\nExample 2: Get custom attributes")
51+
customAttrs, err := sdk.GetCustomAttributes()
52+
if err != nil {
53+
fmt.Printf("Error getting custom attributes: %v\n", err)
54+
} else {
55+
fmt.Printf("Custom attributes: %+v\n", customAttrs)
56+
}
57+
58+
// Example 3: Get custom attribute by ID
59+
fmt.Println("\nExample 3: Get custom attribute by ID")
60+
attrByID, err := sdk.GetCustomAttributeByID(attrID)
61+
if err != nil {
62+
fmt.Printf("Error getting custom attribute by ID: %v\n", err)
63+
} else {
64+
fmt.Printf("Custom attribute by ID: %+v\n", attrByID)
65+
}
66+
67+
// Example 4: Update the custom attribute
68+
fmt.Println("\nExample 4: Update custom attribute")
69+
updateResp, err := sdk.UpdateCustomAttribute(attrID, "Employee Number", "employee_id")
70+
if err != nil {
71+
fmt.Printf("Error updating custom attribute: %v\n", err)
72+
} else {
73+
fmt.Printf("Custom attribute updated successfully: %+v\n", updateResp)
74+
}
75+
76+
// Verify the update
77+
updatedAttr, err := sdk.GetCustomAttributeByID(attrID)
78+
if err != nil {
79+
fmt.Printf("Error getting updated custom attribute: %v\n", err)
80+
} else {
81+
fmt.Printf("Updated custom attribute: %+v\n", updatedAttr)
82+
}
83+
84+
// Example 5: Delete the custom attribute
85+
fmt.Println("\nExample 5: Delete custom attribute")
86+
deleteResp, err := sdk.DeleteCustomAttributes(attrID)
87+
if err != nil {
88+
fmt.Printf("Error deleting custom attribute: %v\n", err)
89+
} else {
90+
fmt.Printf("Custom attribute deleted successfully: %+v\n", deleteResp)
91+
}
92+
93+
// Verify the deletion
94+
fmt.Println("\nVerifying deletion - get all custom attributes")
95+
remainingAttrs, err := sdk.GetCustomAttributes()
96+
if err != nil {
97+
fmt.Printf("Error getting remaining custom attributes: %v\n", err)
98+
} else {
99+
fmt.Printf("Remaining custom attributes: %+v\n", remainingAttrs)
100+
101+
// Check if our deleted attribute is still in the list
102+
deleted := true
103+
attrs := remainingAttrs.([]interface{})
104+
for _, a := range attrs {
105+
attr, ok := a.(map[string]interface{})
106+
if !ok {
107+
continue
108+
}
109+
110+
if id, exists := attr["id"]; exists && int(id.(float64)) == attrID {
111+
deleted = false
112+
break
113+
}
114+
}
115+
116+
if deleted {
117+
fmt.Println("✅ Attribute was successfully deleted!")
118+
} else {
119+
fmt.Println("❌ Attribute was NOT successfully deleted!")
120+
}
121+
}
122+
123+
fmt.Println("\nExamples completed")
124+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
# Set this to your test credentials
4+
# Or use env variables: ONELOGIN_CLIENT_ID, ONELOGIN_CLIENT_SECRET, ONELOGIN_SUBDOMAIN
5+
6+
cd "$(dirname "$0")"
7+
echo "Running custom attributes example..."
8+
go run custom_attributes_example.go

0 commit comments

Comments
 (0)