Skip to content
This repository was archived by the owner on Jan 29, 2026. It is now read-only.

Commit a919be1

Browse files
committed
Move user find API to v2
This brings a rich new set of user properties we Can read out. Have a look at the new `User` struct
1 parent 4de6fbe commit a919be1

File tree

3 files changed

+130
-106
lines changed

3 files changed

+130
-106
lines changed

iam/user.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,42 @@
11
package iam
22

3-
// User represents an IAM user resource
3+
import "time"
4+
5+
// User represents a user profile in IAM
6+
type User struct {
7+
PreferredLanguage string `json:"preferredLanguage"`
8+
EmailAddress string `json:"emailAddress"`
9+
ID string `json:"id"`
10+
LoginID string `json:"loginId"`
11+
Name struct {
12+
Given string `json:"given"`
13+
Family string `json:"family"`
14+
} `json:"name"`
15+
ManagingOrganization string `json:"managingOrganization"`
16+
PasswordStatus struct {
17+
PasswordExpiresOn time.Time `json:"passwordExpiresOn"`
18+
PasswordChangedOn time.Time `json:"passwordChangedOn"`
19+
} `json:"passwordStatus"`
20+
Memberships []struct {
21+
OrganizationID string `json:"organizationId"`
22+
OrganizationName string `json:"organizationName"`
23+
Roles []string `json:"roles"`
24+
Groups []string `json:"groups"`
25+
} `json:"memberships"`
26+
AccountStatus struct {
27+
LastLoginTime time.Time `json:"lastLoginTime"`
28+
MfaStatus string `json:"mfaStatus"`
29+
EmailVerified bool `json:"emailVerified"`
30+
Disabled bool `json:"disabled"`
31+
AccountLockedOn time.Time `json:"accountLockedOn"`
32+
AccountLockedUntil time.Time `json:"accountLockedUntil"`
33+
NumberOfInvalidAttempt int `json:"numberOfInvalidAttempt"`
34+
LastInvalidAttemptedOn time.Time `json:"lastInvalidAttemptedOn"`
35+
} `json:"accountStatus"`
36+
ConsentedApps []string `json:"consentedApps"`
37+
}
38+
39+
// Person represents an IAM user resource
440
type Person struct {
541
ID string `json:"id,omitempty" validate:"omitempty"`
642
// Pattern: ^((?![~`!#%^&*()+={}[\\]|/\\\\<>,;:\"'?])[\\S])*$

iam/users_service.go

Lines changed: 22 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package iam
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"net/http"
76

@@ -22,6 +21,8 @@ type GetUserOptions struct {
2221
GroupID *string `url:"groupId,omitempty"`
2322
PageSize *string `url:"pageSize,omitempty"`
2423
PageNumber *string `url:"pageNumber,omitempty"`
24+
UserID *string `url:"userId,omitempty"`
25+
ProfileType *string `url:"profileType,omitempty" enum:"membership|accountStatus|passwordStatus|consentedApps|all"`
2526
}
2627

2728
// UsersService provides operations on IAM User resources
@@ -260,44 +261,27 @@ func (u *UsersService) GetUsers(opts *GetUserOptions, options ...OptionFunc) (*U
260261
}
261262

262263
// GetUserByID looks up a user by UUID
263-
func (u *UsersService) GetUserByID(uuid string) (*Person, *Response, error) {
264-
req, _ := u.client.NewRequest(IDM, "GET", "security/users/"+uuid, nil, nil)
265-
var user interface{}
264+
func (u *UsersService) GetUserByID(uuid string) (*User, *Response, error) {
265+
opt := &GetUserOptions{
266+
UserID: &uuid,
267+
ProfileType: String("all"),
268+
}
269+
req, _ := u.client.NewRequest(IDM, "GET", "authorize/identity/User", opt, nil)
270+
req.Header.Set("api-version", userAPIVersion)
271+
272+
var responseStruct struct {
273+
Total int `json:"total"`
274+
Entry []User `json:"entry"`
275+
}
266276

267-
resp, err := u.client.Do(req, &user)
277+
resp, err := u.client.Do(req, &responseStruct)
268278
if err != nil {
269279
return nil, resp, err
270280
}
271-
m, err := json.Marshal(user)
272-
if err != nil {
273-
return nil, resp, fmt.Errorf("error parsing json response: %w", err)
281+
if responseStruct.Total == 0 {
282+
return nil, resp, ErrEmptyResults
274283
}
275-
276-
jsonParsed, err := gabs.ParseJSON(m)
277-
if err != nil {
278-
return nil, resp, fmt.Errorf("Eror decoding JSON: %w", err)
279-
}
280-
if err = checkResponseCode200(jsonParsed); err != nil {
281-
return nil, resp, &UserError{User: uuid, Err: err}
282-
}
283-
email, ok := jsonParsed.Path("exchange.loginId").Data().(string)
284-
if !ok {
285-
return nil, resp, fmt.Errorf("Invalid response")
286-
}
287-
r := jsonParsed.Path("exchange.profile")
288-
first := r.Path("givenName").Data().(string)
289-
last := r.Path("familyName").Data().(string)
290-
disabled := r.Path("disabled").Data().(bool)
291-
// TODO use Profile here
292-
var foundUser Person
293-
foundUser.Name.Family = last
294-
foundUser.Name.Given = first
295-
foundUser.Disabled = disabled
296-
foundUser.Telecom = append(foundUser.Telecom, TelecomEntry{
297-
System: "email",
298-
Value: email,
299-
})
300-
return &foundUser, resp, nil
284+
return &responseStruct.Entry[0], resp, nil
301285
}
302286

303287
func checkResponseCode200(json *gabs.Container) error {
@@ -317,32 +301,14 @@ func checkResponseCode200(json *gabs.Container) error {
317301

318302
// GetUserIDByLoginID looks up the UUID of a user by LoginID (email address)
319303
func (u *UsersService) GetUserIDByLoginID(loginID string) (string, *Response, error) {
320-
req, _ := u.client.NewRequest(IDM, "GET", "security/users?loginId="+loginID, nil, nil)
321-
var d interface{}
322-
323-
resp, err := u.client.Do(req, &d)
304+
user, resp, err := u.GetUserByID(loginID)
324305
if err != nil {
325306
return "", resp, err
326307
}
327-
m, err := json.Marshal(d)
328-
if err != nil {
329-
return "", resp, fmt.Errorf("error parsing json response: %w", err)
330-
}
331-
jsonParsed, err := gabs.ParseJSON(m)
332-
if err != nil {
333-
return "", resp, fmt.Errorf("Eror decoding JSON: %w", err)
334-
}
335-
if err = checkResponseCode200(jsonParsed); err != nil {
336-
return "", resp, &UserError{User: loginID, Err: err}
308+
if user == nil {
309+
return "", resp, ErrEmptyResults
337310
}
338-
339-
r := jsonParsed.Path("exchange.users").Index(0)
340-
userUUID, ok := r.Path("userUUID").Data().(string)
341-
if !ok {
342-
return "", resp, fmt.Errorf("lookup failed")
343-
}
344-
return userUUID, resp, nil
345-
311+
return user.ID, resp, nil
346312
}
347313

348314
// SetMFA activate Multi-Factor-Authentication for the given UUID. See also SetMFAByLoginID.

iam/users_service_test.go

Lines changed: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -190,32 +190,77 @@ func TestGetUsers(t *testing.T) {
190190
assert.True(t, list.HasNextPage)
191191
}
192192

193-
func userIDByLoginIDHandler(t *testing.T, loginID, userUUID string) func(http.ResponseWriter, *http.Request) {
193+
func userIDByLoginIDHandler(t *testing.T, loginID, email, userUUID string) func(http.ResponseWriter, *http.Request) {
194194
return func(w http.ResponseWriter, r *http.Request) {
195195
if r.Method != "GET" {
196196
t.Errorf("Expected ‘GET’ request, got ‘%s’", r.Method)
197197
}
198198
w.Header().Set("Content-Type", "application/json")
199199
w.WriteHeader(http.StatusOK)
200+
userId := r.URL.Query().Get("userId")
200201

201-
if r.URL.Query().Get("loginId") != loginID {
202+
if !(userId == loginID || userId == userUUID) {
202203
_, _ = io.WriteString(w, `{
203-
"responseCode": "4010",
204-
"responseMessage": "User does not exist"
204+
"total": 0,
205+
"entry": []
205206
}`)
206207
return
207208
}
208209
_, _ = io.WriteString(w, `{
209-
"exchange": {
210-
"users": [
211-
{
212-
"userUUID": "`+userUUID+`"
213-
}
214-
]
215-
},
216-
"responseCode": "200",
217-
"responseMessage": "Success"
218-
}`)
210+
"total": 1,
211+
"entry": [
212+
{
213+
"preferredLanguage": "en-US",
214+
"loginId": "`+loginID+`",
215+
"emailAddress": "`+email+`",
216+
"id": "`+userUUID+`",
217+
"managingOrganization": "c29cdb88-7cda-4fc1-af8b-ee5947659958",
218+
"name": {
219+
"given": "Ron",
220+
"family": "Swanson"
221+
},
222+
"memberships": [
223+
{
224+
"organizationId": "c29cdb88-7cda-4fc1-af8b-ee5947659958",
225+
"organizationName": "Pawnee",
226+
"roles": [
227+
"ANALYZE",
228+
"S3CREDSADMINROLE",
229+
"ADMIN"
230+
],
231+
"groups": [
232+
"S3CredsAdminGroup",
233+
"AdminGroup"
234+
]
235+
},
236+
{
237+
"organizationId": "d4be75cc-e81b-4d7d-b034-baf6f3f10792",
238+
"organizationName": "Eagleton",
239+
"roles": [
240+
"LOGUSER"
241+
],
242+
"groups": [
243+
"LogUserGroup"
244+
]
245+
}
246+
],
247+
"passwordStatus": {
248+
"passwordExpiresOn": "2022-02-04T10:07:55Z",
249+
"passwordChangedOn": "2020-02-15T10:07:55Z"
250+
},
251+
"accountStatus": {
252+
"mfaStatus": "NOTREQUIRED",
253+
"lastLoginTime": "2020-05-09T12:27:41Z",
254+
"emailVerified": true,
255+
"numberOfInvalidAttempt": 0,
256+
"disabled": false
257+
},
258+
"consentedApps": [
259+
"default default default"
260+
]
261+
}
262+
]
263+
}`)
219264
}
220265
}
221266

@@ -224,8 +269,9 @@ func TestGetUserIDByLoginID(t *testing.T) {
224269
defer teardown()
225270

226271
userUUID := "f5fe538f-c3b5-4454-8774-cd3789f59b9f"
227-
loginID := "foo@bar.com"
228-
muxIDM.HandleFunc("/security/users", userIDByLoginIDHandler(t, loginID, userUUID))
272+
loginID := "ron"
273+
email := "foo@bar.com"
274+
muxIDM.HandleFunc("/authorize/identity/User", userIDByLoginIDHandler(t, loginID, email, userUUID))
229275

230276
uuid, resp, err := client.Users.GetUserIDByLoginID(loginID)
231277
if !assert.NotNil(t, resp) {
@@ -241,31 +287,10 @@ func TestGetUserByID(t *testing.T) {
241287
defer teardown()
242288

243289
userUUID := "44d20214-7879-4e35-923d-f9d4e01c9746"
290+
loginID := "ron"
291+
email := "foo@bar.com"
244292

245-
muxIDM.HandleFunc("/security/users/"+userUUID, func(w http.ResponseWriter, r *http.Request) {
246-
if r.Method != "GET" {
247-
t.Errorf("Expected ‘GET’ request, got ‘%s’", r.Method)
248-
}
249-
250-
w.Header().Set("Content-Type", "application/json")
251-
w.WriteHeader(http.StatusOK)
252-
_, _ = io.WriteString(w, `{
253-
"exchange": {
254-
"loginId": "john.doe@domain.com",
255-
"profile": {
256-
"contact": {
257-
"emailAddress": "john.doe@domain.com"
258-
},
259-
"givenName": "John",
260-
"familyName": "Doe",
261-
"addresses": [],
262-
"disabled": false
263-
}
264-
},
265-
"responseCode": "200",
266-
"responseMessage": "Success"
267-
}`)
268-
})
293+
muxIDM.HandleFunc("/authorize/identity/User", userIDByLoginIDHandler(t, loginID, email, userUUID))
269294

270295
foundUser, resp, err := client.Users.GetUserByID(userUUID)
271296
if !assert.NotNil(t, resp) {
@@ -276,11 +301,8 @@ func TestGetUserByID(t *testing.T) {
276301
}
277302
assert.Nil(t, err)
278303
assert.Equal(t, http.StatusOK, resp.StatusCode)
279-
if !assert.NotEqual(t, 0, len(foundUser.Telecom)) {
280-
return
281-
}
282-
assert.Equal(t, "john.doe@domain.com", foundUser.Telecom[0].Value)
283-
assert.Equal(t, "Doe", foundUser.Name.Family)
304+
assert.Equal(t, email, foundUser.EmailAddress)
305+
assert.Equal(t, "Swanson", foundUser.Name.Family)
284306
}
285307

286308
func TestUserActions(t *testing.T) {
@@ -296,8 +318,9 @@ func TestUserActions(t *testing.T) {
296318
muxIDM.HandleFunc("/authorize/identity/User/$recover-password",
297319
actionRequestHandler(t, "recoverPassword", "TODO: fix", http.StatusOK))
298320
userUUID := "f5fe538f-c3b5-4454-8774-cd3789f59b9f"
299-
loginID := "foo@bar.com"
300-
muxIDM.HandleFunc("/security/users", userIDByLoginIDHandler(t, loginID, userUUID))
321+
loginID := "ron"
322+
email := "foo@bar.com"
323+
muxIDM.HandleFunc("/authorize/identity/User", userIDByLoginIDHandler(t, loginID, email, userUUID))
301324
muxIDM.HandleFunc("/authorize/identity/User/"+userUUID+"/$mfa",
302325
actionRequestHandler(t, "setMFA", "TODO: fix", http.StatusAccepted))
303326
muxIDM.HandleFunc("/authorize/identity/User/"+userUUID+"/$unlock",
@@ -340,9 +363,8 @@ func TestUserActions(t *testing.T) {
340363
return
341364
}
342365
assert.Nil(t, err)
343-
assert.True(t, ok)
344-
assert.Equal(t, http.StatusOK, resp.StatusCode)
345366
assert.Equal(t, userUUID, uuid)
367+
assert.Equal(t, http.StatusOK, resp.StatusCode)
346368

347369
ok, resp, err = client.Users.SetMFAByLoginID(loginID, true)
348370
if !assert.NotNil(t, resp) {

0 commit comments

Comments
 (0)