mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Auth: Display id Provider label in orgs/users view (#58033)
* Add frontend test * Add frontend label component * Adjust backend tests * Retrieve auth IDP labels for users at org/users. Co-authored-by: Misi <mgyongyosi@users.noreply.github.com> Co-authored-by: Kalle <kalleep@users.noreply.github.com> Co-authored-by: Jo <Jguer@users.noreply.github.com>
This commit is contained in:
parent
5b7ef92399
commit
6cc56311d9
@ -41,6 +41,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@ -354,6 +355,9 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer {
|
||||
License: &licensing.OSSLicensingService{},
|
||||
AccessControl: acimpl.ProvideAccessControl(cfg),
|
||||
annotationsRepo: annotationstest.NewFakeAnnotationsRepo(),
|
||||
authInfoService: &logintest.AuthInfoServiceFake{
|
||||
ExpectedLabels: map[int64]string{int64(1): login.GetAuthProviderLabel(login.LDAPAuthModule)},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,6 +435,9 @@ func setupHTTPServerWithCfgDb(
|
||||
orgService: orgMock,
|
||||
teamService: teamService,
|
||||
annotationsRepo: annotationstest.NewFakeAnnotationsRepo(),
|
||||
authInfoService: &logintest.AuthInfoServiceFake{
|
||||
ExpectedLabels: map[int64]string{int64(1): login.GetAuthProviderLabel(login.LDAPAuthModule)},
|
||||
},
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -211,6 +212,7 @@ func (hs *HTTPServer) getOrgUsersHelper(c *models.ReqContext, query *org.GetOrgU
|
||||
|
||||
filteredUsers := make([]*org.OrgUserDTO, 0, len(result))
|
||||
userIDs := map[string]bool{}
|
||||
authLabelsUserIDs := make([]int64, 0, len(result))
|
||||
for _, user := range result {
|
||||
if dtos.IsHiddenUser(user.Login, signedInUser, hs.Cfg) {
|
||||
continue
|
||||
@ -218,14 +220,24 @@ func (hs *HTTPServer) getOrgUsersHelper(c *models.ReqContext, query *org.GetOrgU
|
||||
user.AvatarURL = dtos.GetGravatarUrl(user.Email)
|
||||
|
||||
userIDs[fmt.Sprint(user.UserID)] = true
|
||||
authLabelsUserIDs = append(authLabelsUserIDs, user.UserID)
|
||||
filteredUsers = append(filteredUsers, user)
|
||||
}
|
||||
|
||||
// Get accesscontrol metadata for users in the target org
|
||||
modules, err := hs.authInfoService.GetUserLabels(c.Req.Context(), models.GetUserLabelsQuery{
|
||||
UserIDs: authLabelsUserIDs,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
hs.log.Warn("failed to retrieve users IDP label", err)
|
||||
}
|
||||
|
||||
// Get accesscontrol metadata and IPD labels for users in the target org
|
||||
accessControlMetadata := hs.getMultiAccessControlMetadata(c, query.OrgID, "users:id:", userIDs)
|
||||
if len(accessControlMetadata) > 0 {
|
||||
for i := range filteredUsers {
|
||||
filteredUsers[i].AccessControl = accessControlMetadata[fmt.Sprint(filteredUsers[i].UserID)]
|
||||
for i := range filteredUsers {
|
||||
filteredUsers[i].AccessControl = accessControlMetadata[fmt.Sprint(filteredUsers[i].UserID)]
|
||||
if module, ok := modules[filteredUsers[i].UserID]; ok {
|
||||
filteredUsers[i].AuthLabels = []string{login.GetAuthProviderLabel(module)}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,6 +125,10 @@ type GetAuthInfoQuery struct {
|
||||
Result *UserAuth
|
||||
}
|
||||
|
||||
type GetUserLabelsQuery struct {
|
||||
UserIDs []int64
|
||||
}
|
||||
|
||||
type TeamOrgGroupDTO struct {
|
||||
TeamName string `json:"teamName"`
|
||||
OrgName string `json:"orgName"`
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
type AuthInfoService interface {
|
||||
LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*user.User, error)
|
||||
GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) error
|
||||
GetUserLabels(ctx context.Context, query models.GetUserLabelsQuery) (map[int64]string, error)
|
||||
GetExternalUserInfoByLogin(ctx context.Context, query *models.GetExternalUserInfoByLoginQuery) error
|
||||
SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error
|
||||
UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error
|
||||
|
@ -108,6 +108,30 @@ func (s *AuthInfoStore) GetAuthInfo(ctx context.Context, query *models.GetAuthIn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AuthInfoStore) GetUserLabels(ctx context.Context, query models.GetUserLabelsQuery) (map[int64]string, error) {
|
||||
userAuths := []models.UserAuth{}
|
||||
params := make([]interface{}, 0, len(query.UserIDs))
|
||||
for _, id := range query.UserIDs {
|
||||
params = append(params, id)
|
||||
}
|
||||
|
||||
err := s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
return sess.Table("user_auth").In("user_id", params).OrderBy("created").Find(&userAuths)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labelMap := make(map[int64]string, len(userAuths))
|
||||
|
||||
for i := range userAuths {
|
||||
labelMap[userAuths[i].UserId] = userAuths[i].AuthModule
|
||||
}
|
||||
|
||||
return labelMap, nil
|
||||
}
|
||||
|
||||
func (s *AuthInfoStore) SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error {
|
||||
authUser := &models.UserAuth{
|
||||
UserId: cmd.UserId,
|
||||
|
@ -185,6 +185,13 @@ func (s *Implementation) GetAuthInfo(ctx context.Context, query *models.GetAuthI
|
||||
return s.authInfoStore.GetAuthInfo(ctx, query)
|
||||
}
|
||||
|
||||
func (s *Implementation) GetUserLabels(ctx context.Context, query models.GetUserLabelsQuery) (map[int64]string, error) {
|
||||
if len(query.UserIDs) == 0 {
|
||||
return map[int64]string{}, nil
|
||||
}
|
||||
return s.authInfoStore.GetUserLabels(ctx, query)
|
||||
}
|
||||
|
||||
func (s *Implementation) UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error {
|
||||
return s.authInfoStore.UpdateAuthInfo(ctx, cmd)
|
||||
}
|
||||
|
@ -478,6 +478,7 @@ func TestUserAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
type FakeAuthInfoStore struct {
|
||||
login.AuthInfoService
|
||||
ExpectedError error
|
||||
ExpectedUser *user.User
|
||||
ExpectedOAuth *models.UserAuth
|
||||
|
@ -19,11 +19,13 @@ func (l *LoginServiceFake) DisableExternalUser(ctx context.Context, username str
|
||||
func (l *LoginServiceFake) SetTeamSyncFunc(login.TeamSyncFunc) {}
|
||||
|
||||
type AuthInfoServiceFake struct {
|
||||
login.AuthInfoService
|
||||
LatestUserID int64
|
||||
ExpectedUserAuth *models.UserAuth
|
||||
ExpectedUser *user.User
|
||||
ExpectedExternalUser *models.ExternalUserInfo
|
||||
ExpectedError error
|
||||
ExpectedLabels map[int64]string
|
||||
}
|
||||
|
||||
func (a *AuthInfoServiceFake) LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*user.User, error) {
|
||||
@ -41,6 +43,10 @@ func (a *AuthInfoServiceFake) GetAuthInfo(ctx context.Context, query *models.Get
|
||||
return a.ExpectedError
|
||||
}
|
||||
|
||||
func (a *AuthInfoServiceFake) GetUserLabels(ctx context.Context, query models.GetUserLabelsQuery) (map[int64]string, error) {
|
||||
return a.ExpectedLabels, a.ExpectedError
|
||||
}
|
||||
|
||||
func (a *AuthInfoServiceFake) SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error {
|
||||
return a.ExpectedError
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ type UserProtectionService interface {
|
||||
type Store interface {
|
||||
GetExternalUserInfoByLogin(ctx context.Context, query *models.GetExternalUserInfoByLoginQuery) error
|
||||
GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) error
|
||||
GetUserLabels(ctx context.Context, query models.GetUserLabelsQuery) (map[int64]string, error)
|
||||
SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error
|
||||
UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error
|
||||
UpdateAuthInfoDate(ctx context.Context, authInfo *models.UserAuth) error
|
||||
|
@ -304,6 +304,7 @@ func (m *MockSocialConnector) TokenSource(ctx context.Context, t *oauth2.Token)
|
||||
}
|
||||
|
||||
type FakeAuthInfoStore struct {
|
||||
login.Store
|
||||
ExpectedError error
|
||||
ExpectedUser *user.User
|
||||
ExpectedOAuth *models.UserAuth
|
||||
|
@ -148,6 +148,7 @@ type OrgUserDTO struct {
|
||||
LastSeenAtAge string `json:"lastSeenAtAge"`
|
||||
AccessControl map[string]bool `json:"accessControl,omitempty"`
|
||||
IsDisabled bool `json:"isDisabled"`
|
||||
AuthLabels []string `json:"authLabels" xorm:"-"`
|
||||
}
|
||||
|
||||
type RemoveOrgUserCommand struct {
|
||||
|
@ -48,6 +48,12 @@ describe('Render', () => {
|
||||
|
||||
expect(screen.getByText('Disabled')).toBeInTheDocument();
|
||||
});
|
||||
it('should render LDAP label', () => {
|
||||
const usersData = getMockUsers(5);
|
||||
usersData[0].authLabels = ['LDAP'];
|
||||
setup({ users: usersData });
|
||||
expect(screen.getByText(usersData[0].authLabels[0])).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Remove modal', () => {
|
||||
|
@ -4,6 +4,7 @@ import { OrgRole } from '@grafana/data';
|
||||
import { Button, ConfirmModal } from '@grafana/ui';
|
||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { AccessControlAction, OrgUser, Role } from 'app/types';
|
||||
|
||||
@ -50,6 +51,7 @@ const UsersTable: FC<Props> = (props) => {
|
||||
<th>Role</th>
|
||||
<th style={{ width: '34px' }} />
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -101,6 +103,12 @@ const UsersTable: FC<Props> = (props) => {
|
||||
{user.isDisabled && <span className="label label-tag label-tag--gray">Disabled</span>}
|
||||
</td>
|
||||
|
||||
<td className="width-1">
|
||||
{Array.isArray(user.authLabels) && user.authLabels.length > 0 && (
|
||||
<TagBadge label={user.authLabels[0]} removeIcon={false} count={0} />
|
||||
)}
|
||||
</td>
|
||||
|
||||
{contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersRemove, user) && (
|
||||
<td className="text-right">
|
||||
<Button
|
||||
|
@ -15,6 +15,7 @@ export const getMockUsers = (amount: number) => {
|
||||
role: 'Admin',
|
||||
userId: i,
|
||||
isDisabled: false,
|
||||
authLabels: [],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ export interface OrgUser extends WithAccessControlMetadata {
|
||||
role: OrgRole;
|
||||
userId: number;
|
||||
isDisabled: boolean;
|
||||
authLabels?: string[];
|
||||
}
|
||||
|
||||
export interface User {
|
||||
|
Loading…
Reference in New Issue
Block a user