Skip to content

Commit 76408d5

Browse files
sapktechknowlogick
authored andcommitted
org/members: display 2FA members states + optimize sql requests (#7621)
* org/members: display 2FA state * fix comment typo * lay down UserList bases * add basic test for previous methods * add comment for UserList type * add valid two-fa account * test new UserList methods * optimize MembersIsPublic by side loading info on GetMembers + fix integrations tests * respect fmt rules * use map for data * Optimize GetTwoFaStatus * rewrite by using existing sub func * Optimize IsUserOrgOwner * remove un-used code * tests: cover empty org + fix import order * tests: add ErrTeamNotExist path * tests: fix wrong expected result
1 parent 3566d2c commit 76408d5

File tree

13 files changed

+346
-25
lines changed

13 files changed

+346
-25
lines changed

models/fixtures/org_user.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,9 @@
3939
uid: 20
4040
org_id: 19
4141
is_public: true
42+
43+
-
44+
id: 8
45+
uid: 24
46+
org_id: 25
47+
is_public: true

models/fixtures/team.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,13 @@
7777
name: review_team
7878
authorize: 1 # read
7979
num_repos: 1
80-
num_members: 1
80+
num_members: 1
81+
82+
-
83+
id: 10
84+
org_id: 25
85+
lower_name: notowners
86+
name: NotOwners
87+
authorize: 1 # owner
88+
num_repos: 0
89+
num_members: 1

models/fixtures/team_user.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,10 @@
6262
id: 11
6363
org_id: 17
6464
team_id: 9
65-
uid: 20
65+
uid: 20
66+
67+
-
68+
id: 12
69+
org_id: 25
70+
team_id: 10
71+
uid: 24

models/fixtures/two_factor.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-
2+
id: 1
3+
uid: 24
4+
secret: KlDporn6Ile4vFcKI8z7Z6sqK1Scj2Qp0ovtUzCZO6jVbRW2lAoT7UDxDPtrab8d2B9zKOocBRdBJnS8orsrUNrsyETY+jJHb79M82uZRioKbRUz15sfOpmJmEzkFeSg6S4LicUBQos=
5+
scratch_salt: Qb5bq2DyR2
6+
scratch_hash: 068eb9b8746e0bcfe332fac4457693df1bda55800eb0f6894d14ebb736ae6a24e0fc8fc5333c19f57f81599788f0b8e51ec1
7+
last_used_passcode:
8+
created_unix: 1564253724
9+
updated_unix: 1564253724

models/fixtures/user.yml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,4 +365,39 @@
365365
is_active: true
366366
num_members: 0
367367
num_teams: 0
368-
visibility: 2
368+
visibility: 2
369+
370+
-
371+
id: 24
372+
lower_name: user24
373+
name: user24
374+
full_name: "user24"
375+
376+
keep_email_private: true
377+
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
378+
type: 0 # individual
379+
salt: ZogKvWdyEx
380+
is_admin: false
381+
avatar: avatar24
382+
avatar_email: [email protected]
383+
num_repos: 0
384+
num_stars: 0
385+
num_followers: 0
386+
num_following: 0
387+
is_active: true
388+
389+
-
390+
id: 25
391+
lower_name: org25
392+
name: org25
393+
full_name: "org25"
394+
395+
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
396+
type: 1 # organization
397+
salt: ZogKvWdyEx
398+
is_admin: false
399+
avatar: avatar25
400+
avatar_email: [email protected]
401+
num_repos: 0
402+
num_members: 1
403+
num_teams: 1

models/org.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,12 @@ func (org *User) GetMembers() error {
7272
}
7373

7474
var ids = make([]int64, len(ous))
75+
var idsIsPublic = make(map[int64]bool, len(ous))
7576
for i, ou := range ous {
7677
ids[i] = ou.UID
78+
idsIsPublic[ou.UID] = ou.IsPublic
7779
}
80+
org.MembersIsPublic = idsIsPublic
7881
org.Members, err = GetUsersByIDs(ids)
7982
return err
8083
}
@@ -298,15 +301,13 @@ type OrgUser struct {
298301
}
299302

300303
func isOrganizationOwner(e Engine, orgID, uid int64) (bool, error) {
301-
ownerTeam := &Team{
302-
OrgID: orgID,
303-
Name: ownerTeamName,
304-
}
305-
if has, err := e.Get(ownerTeam); err != nil {
304+
ownerTeam, err := getOwnerTeam(e, orgID)
305+
if err != nil {
306+
if err == ErrTeamNotExist {
307+
log.Error("Organization does not have owner team: %d", orgID)
308+
return false, nil
309+
}
306310
return false, err
307-
} else if !has {
308-
log.Error("Organization does not have owner team: %d", orgID)
309-
return false, nil
310311
}
311312
return isTeamMember(e, orgID, ownerTeam.ID, uid)
312313
}

models/org_team.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@ func GetTeam(orgID int64, name string) (*Team, error) {
362362
return getTeam(x, orgID, name)
363363
}
364364

365+
// getOwnerTeam returns team by given team name and organization.
366+
func getOwnerTeam(e Engine, orgID int64) (*Team, error) {
367+
return getTeam(e, orgID, ownerTeamName)
368+
}
369+
365370
func getTeamByID(e Engine, teamID int64) (*Team, error) {
366371
t := new(Team)
367372
has, err := e.ID(teamID).Get(t)

models/user.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,12 @@ type User struct {
139139
NumRepos int
140140

141141
// For organization
142-
NumTeams int
143-
NumMembers int
144-
Teams []*Team `xorm:"-"`
145-
Members []*User `xorm:"-"`
146-
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
142+
NumTeams int
143+
NumMembers int
144+
Teams []*Team `xorm:"-"`
145+
Members UserList `xorm:"-"`
146+
MembersIsPublic map[int64]bool `xorm:"-"`
147+
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
147148

148149
// Preferences
149150
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`

models/user_test.go

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package models
66

77
import (
8+
"fmt"
89
"math/rand"
910
"strings"
1011
"testing"
@@ -15,6 +16,58 @@ import (
1516
"github.com/stretchr/testify/assert"
1617
)
1718

19+
func TestUserIsPublicMember(t *testing.T) {
20+
assert.NoError(t, PrepareTestDatabase())
21+
22+
tt := []struct {
23+
uid int64
24+
orgid int64
25+
expected bool
26+
}{
27+
{2, 3, true},
28+
{4, 3, false},
29+
{5, 6, true},
30+
{5, 7, false},
31+
}
32+
for _, v := range tt {
33+
t.Run(fmt.Sprintf("UserId%dIsPublicMemberOf%d", v.uid, v.orgid), func(t *testing.T) {
34+
testUserIsPublicMember(t, v.uid, v.orgid, v.expected)
35+
})
36+
}
37+
}
38+
39+
func testUserIsPublicMember(t *testing.T, uid int64, orgID int64, expected bool) {
40+
user, err := GetUserByID(uid)
41+
assert.NoError(t, err)
42+
assert.Equal(t, expected, user.IsPublicMember(orgID))
43+
}
44+
45+
func TestIsUserOrgOwner(t *testing.T) {
46+
assert.NoError(t, PrepareTestDatabase())
47+
48+
tt := []struct {
49+
uid int64
50+
orgid int64
51+
expected bool
52+
}{
53+
{2, 3, true},
54+
{4, 3, false},
55+
{5, 6, true},
56+
{5, 7, true},
57+
}
58+
for _, v := range tt {
59+
t.Run(fmt.Sprintf("UserId%dIsOrgOwnerOf%d", v.uid, v.orgid), func(t *testing.T) {
60+
testIsUserOrgOwner(t, v.uid, v.orgid, v.expected)
61+
})
62+
}
63+
}
64+
65+
func testIsUserOrgOwner(t *testing.T, uid int64, orgID int64, expected bool) {
66+
user, err := GetUserByID(uid)
67+
assert.NoError(t, err)
68+
assert.Equal(t, expected, user.IsUserOrgOwner(orgID))
69+
}
70+
1871
func TestGetUserEmailsByNames(t *testing.T) {
1972
assert.NoError(t, PrepareTestDatabase())
2073

@@ -83,7 +136,7 @@ func TestSearchUsers(t *testing.T) {
83136
[]int64{7, 17})
84137

85138
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 3, PageSize: 2},
86-
[]int64{19})
139+
[]int64{19, 25})
87140

88141
testOrgSuccess(&SearchUserOptions{Page: 4, PageSize: 2},
89142
[]int64{})
@@ -95,13 +148,13 @@ func TestSearchUsers(t *testing.T) {
95148
}
96149

97150
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
98-
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
151+
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24})
99152

100153
testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
101154
[]int64{9})
102155

103156
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
104-
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
157+
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24})
105158

106159
testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
107160
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})

models/userlist.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import (
8+
"fmt"
9+
10+
"code.gitea.io/gitea/modules/log"
11+
)
12+
13+
//UserList is a list of user.
14+
// This type provide valuable methods to retrieve information for a group of users efficiently.
15+
type UserList []*User
16+
17+
func (users UserList) getUserIDs() []int64 {
18+
userIDs := make([]int64, len(users))
19+
for _, user := range users {
20+
userIDs = append(userIDs, user.ID) //Considering that user id are unique in the list
21+
}
22+
return userIDs
23+
}
24+
25+
// IsUserOrgOwner returns true if user is in the owner team of given organization.
26+
func (users UserList) IsUserOrgOwner(orgID int64) map[int64]bool {
27+
results := make(map[int64]bool, len(users))
28+
for _, user := range users {
29+
results[user.ID] = false //Set default to false
30+
}
31+
ownerMaps, err := users.loadOrganizationOwners(x, orgID)
32+
if err == nil {
33+
for _, owner := range ownerMaps {
34+
results[owner.UID] = true
35+
}
36+
}
37+
return results
38+
}
39+
40+
func (users UserList) loadOrganizationOwners(e Engine, orgID int64) (map[int64]*TeamUser, error) {
41+
if len(users) == 0 {
42+
return nil, nil
43+
}
44+
ownerTeam, err := getOwnerTeam(e, orgID)
45+
if err != nil {
46+
if err == ErrTeamNotExist {
47+
log.Error("Organization does not have owner team: %d", orgID)
48+
return nil, nil
49+
}
50+
return nil, err
51+
}
52+
53+
userIDs := users.getUserIDs()
54+
ownerMaps := make(map[int64]*TeamUser)
55+
err = e.In("uid", userIDs).
56+
And("org_id=?", orgID).
57+
And("team_id=?", ownerTeam.ID).
58+
Find(&ownerMaps)
59+
if err != nil {
60+
return nil, fmt.Errorf("find team users: %v", err)
61+
}
62+
return ownerMaps, nil
63+
}
64+
65+
// GetTwoFaStatus return state of 2FA enrollement
66+
func (users UserList) GetTwoFaStatus() map[int64]bool {
67+
results := make(map[int64]bool, len(users))
68+
for _, user := range users {
69+
results[user.ID] = false //Set default to false
70+
}
71+
tokenMaps, err := users.loadTwoFactorStatus(x)
72+
if err == nil {
73+
for _, token := range tokenMaps {
74+
results[token.UID] = true
75+
}
76+
}
77+
78+
return results
79+
}
80+
81+
func (users UserList) loadTwoFactorStatus(e Engine) (map[int64]*TwoFactor, error) {
82+
if len(users) == 0 {
83+
return nil, nil
84+
}
85+
86+
userIDs := users.getUserIDs()
87+
tokenMaps := make(map[int64]*TwoFactor, len(userIDs))
88+
err := e.
89+
In("uid", userIDs).
90+
Find(&tokenMaps)
91+
if err != nil {
92+
return nil, fmt.Errorf("find two factor: %v", err)
93+
}
94+
return tokenMaps, nil
95+
}

0 commit comments

Comments
 (0)