Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f6476b6

Browse files
kenperkinsChristoph Glaubitz
andauthored
Added Email of Keystone to Identity (dexidp#1681)
* Added Email of Keystone to Identity After the successful login to keystone, the Email of the logged in user is fetch from keystone and provided to `identity.Email`. This is useful for upstream software that uses the Email as the primary identification. * Removed unnecessary code from getUsers * Changed creation of userResponse in keystone * Fixing linter error Co-authored-by: Christoph Glaubitz <[email protected]>
1 parent ebef257 commit f6476b6

2 files changed

Lines changed: 124 additions & 32 deletions

File tree

connector/keystone/keystone.go

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ type groupsResponse struct {
9595
Groups []group `json:"groups"`
9696
}
9797

98+
type userResponse struct {
99+
User struct {
100+
Name string `json:"name"`
101+
Email string `json:"email"`
102+
ID string `json:"id"`
103+
} `json:"user"`
104+
}
105+
98106
var (
99107
_ connector.PasswordConnector = &conn{}
100108
_ connector.RefreshConnector = &conn{}
@@ -143,6 +151,16 @@ func (p *conn) Login(ctx context.Context, scopes connector.Scopes, username, pas
143151
}
144152
identity.Username = username
145153
identity.UserID = tokenResp.Token.User.ID
154+
155+
user, err := p.getUser(ctx, tokenResp.Token.User.ID, token)
156+
if err != nil {
157+
return identity, false, err
158+
}
159+
if user.User.Email != "" {
160+
identity.Email = user.User.Email
161+
identity.EmailVerified = true
162+
}
163+
146164
return identity, true, nil
147165
}
148166

@@ -216,26 +234,43 @@ func (p *conn) getAdminToken(ctx context.Context) (string, error) {
216234
}
217235

218236
func (p *conn) checkIfUserExists(ctx context.Context, userID string, token string) (bool, error) {
237+
user, err := p.getUser(ctx, userID, token)
238+
return user != nil, err
239+
}
240+
241+
func (p *conn) getUser(ctx context.Context, userID string, token string) (*userResponse, error) {
219242
// https://developer.openstack.org/api-ref/identity/v3/#show-user-details
220243
userURL := p.Host + "/v3/users/" + userID
221244
client := &http.Client{}
222245
req, err := http.NewRequest("GET", userURL, nil)
223246
if err != nil {
224-
return false, err
247+
return nil, err
225248
}
226249

227250
req.Header.Set("X-Auth-Token", token)
228251
req = req.WithContext(ctx)
229252
resp, err := client.Do(req)
230253
if err != nil {
231-
return false, err
254+
return nil, err
232255
}
233256
defer resp.Body.Close()
234257

235-
if resp.StatusCode == 200 {
236-
return true, nil
258+
if resp.StatusCode != 200 {
259+
return nil, err
260+
}
261+
262+
data, err := ioutil.ReadAll(resp.Body)
263+
if err != nil {
264+
return nil, err
237265
}
238-
return false, err
266+
267+
user := userResponse{}
268+
err = json.Unmarshal(data, &user)
269+
if err != nil {
270+
return nil, err
271+
}
272+
273+
return &user, nil
239274
}
240275

241276
func (p *conn) getUserGroups(ctx context.Context, userID string, token string) ([]string, error) {

connector/keystone/keystone_test.go

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,6 @@ var (
3535
groupsURL = ""
3636
)
3737

38-
type userResponse struct {
39-
User struct {
40-
ID string `json:"id"`
41-
} `json:"user"`
42-
}
43-
4438
type groupResponse struct {
4539
Group struct {
4640
ID string `json:"id"`
@@ -144,7 +138,7 @@ func createUser(t *testing.T, token, userName, userEmail, userPass string) strin
144138
}
145139

146140
// delete group or user
147-
func delete(t *testing.T, token, id, uri string) {
141+
func deleteResource(t *testing.T, token, id, uri string) {
148142
t.Helper()
149143
client := &http.Client{}
150144

@@ -246,27 +240,94 @@ func TestIncorrectCredentialsLogin(t *testing.T) {
246240
func TestValidUserLogin(t *testing.T) {
247241
setupVariables(t)
248242
token, _ := getAdminToken(t, adminUser, adminPass)
249-
userID := createUser(t, token, testUser, testEmail, testPass)
250-
c := conn{Host: keystoneURL, Domain: testDomain,
251-
AdminUsername: adminUser, AdminPassword: adminPass}
252-
s := connector.Scopes{OfflineAccess: true, Groups: true}
253-
identity, validPW, err := c.Login(context.Background(), s, testUser, testPass)
254-
if err != nil {
255-
t.Fatal(err.Error())
243+
244+
type tUser struct {
245+
username string
246+
domain string
247+
email string
248+
password string
249+
}
250+
251+
type expect struct {
252+
username string
253+
email string
254+
verifiedEmail bool
255+
}
256+
257+
var tests = []struct {
258+
name string
259+
input tUser
260+
expected expect
261+
}{
262+
{
263+
name: "test with email address",
264+
input: tUser{
265+
username: testUser,
266+
domain: testDomain,
267+
email: testEmail,
268+
password: testPass,
269+
},
270+
expected: expect{
271+
username: testUser,
272+
email: testEmail,
273+
verifiedEmail: true,
274+
},
275+
},
276+
{
277+
name: "test without email address",
278+
input: tUser{
279+
username: testUser,
280+
domain: testDomain,
281+
email: "",
282+
password: testPass,
283+
},
284+
expected: expect{
285+
username: testUser,
286+
email: "",
287+
verifiedEmail: false,
288+
},
289+
},
256290
}
257-
t.Log(identity)
258291

259-
if !validPW {
260-
t.Fatal("Valid password was not accepted")
292+
for _, tt := range tests {
293+
t.Run(tt.name, func(t *testing.T) {
294+
userID := createUser(t, token, tt.input.username, tt.input.email, tt.input.password)
295+
defer deleteResource(t, token, userID, usersURL)
296+
297+
c := conn{Host: keystoneURL, Domain: tt.input.domain,
298+
AdminUsername: adminUser, AdminPassword: adminPass}
299+
s := connector.Scopes{OfflineAccess: true, Groups: true}
300+
identity, validPW, err := c.Login(context.Background(), s, tt.input.username, tt.input.password)
301+
if err != nil {
302+
t.Fatal(err.Error())
303+
}
304+
t.Log(identity)
305+
if identity.Username != tt.expected.username {
306+
t.Fatalf("Invalid user. Got: %v. Wanted: %v", identity.Username, tt.expected.username)
307+
}
308+
if identity.UserID == "" {
309+
t.Fatalf("Didn't get any UserID back")
310+
}
311+
if identity.Email != tt.expected.email {
312+
t.Fatalf("Invalid email. Got: %v. Wanted: %v", identity.Email, tt.expected.email)
313+
}
314+
if identity.EmailVerified != tt.expected.verifiedEmail {
315+
t.Fatalf("Invalid verifiedEmail. Got: %v. Wanted: %v", identity.EmailVerified, tt.expected.verifiedEmail)
316+
}
317+
318+
if !validPW {
319+
t.Fatal("Valid password was not accepted")
320+
}
321+
})
261322
}
262-
delete(t, token, userID, usersURL)
263323
}
264324

265325
func TestUseRefreshToken(t *testing.T) {
266326
setupVariables(t)
267327
token, adminID := getAdminToken(t, adminUser, adminPass)
268328
groupID := createGroup(t, token, "Test group description", testGroup)
269329
addUserToGroup(t, token, groupID, adminID)
330+
defer deleteResource(t, token, groupID, groupsURL)
270331

271332
c := conn{Host: keystoneURL, Domain: testDomain,
272333
AdminUsername: adminUser, AdminPassword: adminPass}
@@ -282,8 +343,6 @@ func TestUseRefreshToken(t *testing.T) {
282343
t.Fatal(err.Error())
283344
}
284345

285-
delete(t, token, groupID, groupsURL)
286-
287346
expectEquals(t, 1, len(identityRefresh.Groups))
288347
expectEquals(t, testGroup, identityRefresh.Groups[0])
289348
}
@@ -307,7 +366,7 @@ func TestUseRefreshTokenUserDeleted(t *testing.T) {
307366
t.Fatal(err.Error())
308367
}
309368

310-
delete(t, token, userID, usersURL)
369+
deleteResource(t, token, userID, usersURL)
311370
_, err = c.Refresh(context.Background(), s, identityLogin)
312371

313372
if !strings.Contains(err.Error(), "does not exist") {
@@ -319,6 +378,7 @@ func TestUseRefreshTokenGroupsChanged(t *testing.T) {
319378
setupVariables(t)
320379
token, _ := getAdminToken(t, adminUser, adminPass)
321380
userID := createUser(t, token, testUser, testEmail, testPass)
381+
defer deleteResource(t, token, userID, usersURL)
322382

323383
c := conn{Host: keystoneURL, Domain: testDomain,
324384
AdminUsername: adminUser, AdminPassword: adminPass}
@@ -338,29 +398,29 @@ func TestUseRefreshTokenGroupsChanged(t *testing.T) {
338398

339399
groupID := createGroup(t, token, "Test group", testGroup)
340400
addUserToGroup(t, token, groupID, userID)
401+
defer deleteResource(t, token, groupID, groupsURL)
341402

342403
identityRefresh, err = c.Refresh(context.Background(), s, identityLogin)
343404
if err != nil {
344405
t.Fatal(err.Error())
345406
}
346407

347-
delete(t, token, groupID, groupsURL)
348-
delete(t, token, userID, usersURL)
349-
350408
expectEquals(t, 1, len(identityRefresh.Groups))
351409
}
352410

353411
func TestNoGroupsInScope(t *testing.T) {
354412
setupVariables(t)
355413
token, _ := getAdminToken(t, adminUser, adminPass)
356414
userID := createUser(t, token, testUser, testEmail, testPass)
415+
defer deleteResource(t, token, userID, usersURL)
357416

358417
c := conn{Host: keystoneURL, Domain: testDomain,
359418
AdminUsername: adminUser, AdminPassword: adminPass}
360419
s := connector.Scopes{OfflineAccess: true, Groups: false}
361420

362421
groupID := createGroup(t, token, "Test group", testGroup)
363422
addUserToGroup(t, token, groupID, userID)
423+
defer deleteResource(t, token, groupID, groupsURL)
364424

365425
identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass)
366426
if err != nil {
@@ -373,9 +433,6 @@ func TestNoGroupsInScope(t *testing.T) {
373433
t.Fatal(err.Error())
374434
}
375435
expectEquals(t, 0, len(identityRefresh.Groups))
376-
377-
delete(t, token, groupID, groupsURL)
378-
delete(t, token, userID, usersURL)
379436
}
380437

381438
func setupVariables(t *testing.T) {

0 commit comments

Comments
 (0)