Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

org/members: display 2FA members states + optimize sql requests #7621

Merged
merged 23 commits into from
Aug 2, 2019
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions models/fixtures/org_user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@
uid: 20
org_id: 19
is_public: true

-
id: 8
uid: 24
org_id: 25
is_public: true
11 changes: 10 additions & 1 deletion models/fixtures/team.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,13 @@
name: review_team
authorize: 1 # read
num_repos: 1
num_members: 1
num_members: 1

-
id: 10
org_id: 25
lower_name: notowners
name: NotOwners
authorize: 1 # owner
num_repos: 0
num_members: 1
8 changes: 7 additions & 1 deletion models/fixtures/team_user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,10 @@
id: 11
org_id: 17
team_id: 9
uid: 20
uid: 20

-
id: 12
org_id: 25
team_id: 10
uid: 24
9 changes: 9 additions & 0 deletions models/fixtures/two_factor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-
id: 1
uid: 24
secret: KlDporn6Ile4vFcKI8z7Z6sqK1Scj2Qp0ovtUzCZO6jVbRW2lAoT7UDxDPtrab8d2B9zKOocBRdBJnS8orsrUNrsyETY+jJHb79M82uZRioKbRUz15sfOpmJmEzkFeSg6S4LicUBQos=
scratch_salt: Qb5bq2DyR2
scratch_hash: 068eb9b8746e0bcfe332fac4457693df1bda55800eb0f6894d14ebb736ae6a24e0fc8fc5333c19f57f81599788f0b8e51ec1
last_used_passcode:
created_unix: 1564253724
updated_unix: 1564253724
37 changes: 36 additions & 1 deletion models/fixtures/user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,39 @@
is_active: true
num_members: 0
num_teams: 0
visibility: 2
visibility: 2

-
id: 24
lower_name: user24
name: user24
full_name: "user24"
email: user24@example.com
keep_email_private: true
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
type: 0 # individual
salt: ZogKvWdyEx
is_admin: false
avatar: avatar24
avatar_email: user24@example.com
num_repos: 0
num_stars: 0
num_followers: 0
num_following: 0
is_active: true

-
id: 25
lower_name: org25
name: org25
full_name: "org25"
email: org25@example.com
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
type: 1 # organization
salt: ZogKvWdyEx
is_admin: false
avatar: avatar25
avatar_email: org25@example.com
num_repos: 0
num_members: 1
num_teams: 1
17 changes: 9 additions & 8 deletions models/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,12 @@ func (org *User) GetMembers() error {
}

var ids = make([]int64, len(ous))
var idsIsPublic = make(map[int64]bool, len(ous))
for i, ou := range ous {
ids[i] = ou.UID
idsIsPublic[ou.UID] = ou.IsPublic
}
org.MembersIsPublic = idsIsPublic
org.Members, err = GetUsersByIDs(ids)
return err
}
Expand Down Expand Up @@ -298,15 +301,13 @@ type OrgUser struct {
}

func isOrganizationOwner(e Engine, orgID, uid int64) (bool, error) {
ownerTeam := &Team{
OrgID: orgID,
Name: ownerTeamName,
}
if has, err := e.Get(ownerTeam); err != nil {
ownerTeam, err := getOwnerTeam(e, orgID)
if err != nil {
if err == ErrTeamNotExist {
log.Error("Organization does not have owner team: %d", orgID)
return false, nil
}
return false, err
} else if !has {
log.Error("Organization does not have owner team: %d", orgID)
return false, nil
}
return isTeamMember(e, orgID, ownerTeam.ID, uid)
}
Expand Down
5 changes: 5 additions & 0 deletions models/org_team.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,11 @@ func GetTeam(orgID int64, name string) (*Team, error) {
return getTeam(x, orgID, name)
}

// getOwnerTeam returns team by given team name and organization.
func getOwnerTeam(e Engine, orgID int64) (*Team, error) {
return getTeam(e, orgID, ownerTeamName)
}

func getTeamByID(e Engine, teamID int64) (*Team, error) {
t := new(Team)
has, err := e.ID(teamID).Get(t)
Expand Down
11 changes: 6 additions & 5 deletions models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ type User struct {
NumRepos int

// For organization
NumTeams int
NumMembers int
Teams []*Team `xorm:"-"`
Members []*User `xorm:"-"`
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
NumTeams int
NumMembers int
Teams []*Team `xorm:"-"`
Members UserList `xorm:"-"`
MembersIsPublic map[int64]bool `xorm:"-"`
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`

// Preferences
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
Expand Down
59 changes: 56 additions & 3 deletions models/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package models

import (
"fmt"
"math/rand"
"strings"
"testing"
Expand All @@ -15,6 +16,58 @@ import (
"github.com/stretchr/testify/assert"
)

func TestUserIsPublicMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())

tt := []struct {
uid int64
orgid int64
expected bool
}{
{2, 3, true},
{4, 3, false},
{5, 6, true},
{5, 7, false},
}
for _, v := range tt {
t.Run(fmt.Sprintf("UserId%dIsPublicMemberOf%d", v.uid, v.orgid), func(t *testing.T) {
testUserIsPublicMember(t, v.uid, v.orgid, v.expected)
})
}
}

func testUserIsPublicMember(t *testing.T, uid int64, orgID int64, expected bool) {
user, err := GetUserByID(uid)
assert.NoError(t, err)
assert.Equal(t, expected, user.IsPublicMember(orgID))
}

func TestIsUserOrgOwner(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())

tt := []struct {
uid int64
orgid int64
expected bool
}{
{2, 3, true},
{4, 3, false},
{5, 6, true},
{5, 7, true},
}
for _, v := range tt {
t.Run(fmt.Sprintf("UserId%dIsOrgOwnerOf%d", v.uid, v.orgid), func(t *testing.T) {
testIsUserOrgOwner(t, v.uid, v.orgid, v.expected)
})
}
}

func testIsUserOrgOwner(t *testing.T, uid int64, orgID int64, expected bool) {
user, err := GetUserByID(uid)
assert.NoError(t, err)
assert.Equal(t, expected, user.IsUserOrgOwner(orgID))
}

func TestGetUserEmailsByNames(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())

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

testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 3, PageSize: 2},
[]int64{19})
[]int64{19, 25})

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

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

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

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

testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
Expand Down
95 changes: 95 additions & 0 deletions models/userlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package models

import (
"fmt"

"code.gitea.io/gitea/modules/log"
)

//UserList is a list of user.
// This type provide valuable methods to retrieve information for a group of users efficiently.
type UserList []*User

func (users UserList) getUserIDs() []int64 {
userIDs := make([]int64, len(users))
for _, user := range users {
userIDs = append(userIDs, user.ID) //Considering that user id are unique in the list
}
return userIDs
}

// IsUserOrgOwner returns true if user is in the owner team of given organization.
func (users UserList) IsUserOrgOwner(orgID int64) map[int64]bool {
results := make(map[int64]bool, len(users))
for _, user := range users {
results[user.ID] = false //Set default to false
}
ownerMaps, err := users.loadOrganizationOwners(x, orgID)
if err == nil {
for _, owner := range ownerMaps {
results[owner.UID] = true
}
}
return results
}

func (users UserList) loadOrganizationOwners(e Engine, orgID int64) (map[int64]*TeamUser, error) {
if len(users) == 0 {
return nil, nil
}
ownerTeam, err := getOwnerTeam(e, orgID)
if err != nil {
if err == ErrTeamNotExist {
log.Error("Organization does not have owner team: %d", orgID)
return nil, nil
}
return nil, err
}

userIDs := users.getUserIDs()
ownerMaps := make(map[int64]*TeamUser)
err = e.In("uid", userIDs).
And("org_id=?", orgID).
And("team_id=?", ownerTeam.ID).
Find(&ownerMaps)
if err != nil {
return nil, fmt.Errorf("find team users: %v", err)
}
return ownerMaps, nil
}

// GetTwoFaStatus return state of 2FA enrollement
func (users UserList) GetTwoFaStatus() map[int64]bool {
results := make(map[int64]bool, len(users))
for _, user := range users {
results[user.ID] = false //Set default to false
}
tokenMaps, err := users.loadTwoFactorStatus(x)
if err == nil {
for _, token := range tokenMaps {
results[token.UID] = true
}
}

return results
}

func (users UserList) loadTwoFactorStatus(e Engine) (map[int64]*TwoFactor, error) {
if len(users) == 0 {
return nil, nil
}

userIDs := users.getUserIDs()
tokenMaps := make(map[int64]*TwoFactor, len(userIDs))
err := e.
In("uid", userIDs).
Find(&tokenMaps)
if err != nil {
return nil, fmt.Errorf("find two factor: %v", err)
}
return tokenMaps, nil
}
Loading