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/guardian"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
"github.com/grafana/grafana/pkg/services/licensing"
|
"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/loginservice"
|
||||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
@ -354,6 +355,9 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer {
|
|||||||
License: &licensing.OSSLicensingService{},
|
License: &licensing.OSSLicensingService{},
|
||||||
AccessControl: acimpl.ProvideAccessControl(cfg),
|
AccessControl: acimpl.ProvideAccessControl(cfg),
|
||||||
annotationsRepo: annotationstest.NewFakeAnnotationsRepo(),
|
annotationsRepo: annotationstest.NewFakeAnnotationsRepo(),
|
||||||
|
authInfoService: &logintest.AuthInfoServiceFake{
|
||||||
|
ExpectedLabels: map[int64]string{int64(1): login.GetAuthProviderLabel(login.LDAPAuthModule)},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,6 +435,9 @@ func setupHTTPServerWithCfgDb(
|
|||||||
orgService: orgMock,
|
orgService: orgMock,
|
||||||
teamService: teamService,
|
teamService: teamService,
|
||||||
annotationsRepo: annotationstest.NewFakeAnnotationsRepo(),
|
annotationsRepo: annotationstest.NewFakeAnnotationsRepo(),
|
||||||
|
authInfoService: &logintest.AuthInfoServiceFake{
|
||||||
|
ExpectedLabels: map[int64]string{int64(1): login.GetAuthProviderLabel(login.LDAPAuthModule)},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, o := range options {
|
for _, o := range options {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"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/org"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"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))
|
filteredUsers := make([]*org.OrgUserDTO, 0, len(result))
|
||||||
userIDs := map[string]bool{}
|
userIDs := map[string]bool{}
|
||||||
|
authLabelsUserIDs := make([]int64, 0, len(result))
|
||||||
for _, user := range result {
|
for _, user := range result {
|
||||||
if dtos.IsHiddenUser(user.Login, signedInUser, hs.Cfg) {
|
if dtos.IsHiddenUser(user.Login, signedInUser, hs.Cfg) {
|
||||||
continue
|
continue
|
||||||
@ -218,14 +220,24 @@ func (hs *HTTPServer) getOrgUsersHelper(c *models.ReqContext, query *org.GetOrgU
|
|||||||
user.AvatarURL = dtos.GetGravatarUrl(user.Email)
|
user.AvatarURL = dtos.GetGravatarUrl(user.Email)
|
||||||
|
|
||||||
userIDs[fmt.Sprint(user.UserID)] = true
|
userIDs[fmt.Sprint(user.UserID)] = true
|
||||||
|
authLabelsUserIDs = append(authLabelsUserIDs, user.UserID)
|
||||||
filteredUsers = append(filteredUsers, user)
|
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)
|
accessControlMetadata := hs.getMultiAccessControlMetadata(c, query.OrgID, "users:id:", userIDs)
|
||||||
if len(accessControlMetadata) > 0 {
|
for i := range filteredUsers {
|
||||||
for i := range filteredUsers {
|
filteredUsers[i].AccessControl = accessControlMetadata[fmt.Sprint(filteredUsers[i].UserID)]
|
||||||
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
|
Result *UserAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetUserLabelsQuery struct {
|
||||||
|
UserIDs []int64
|
||||||
|
}
|
||||||
|
|
||||||
type TeamOrgGroupDTO struct {
|
type TeamOrgGroupDTO struct {
|
||||||
TeamName string `json:"teamName"`
|
TeamName string `json:"teamName"`
|
||||||
OrgName string `json:"orgName"`
|
OrgName string `json:"orgName"`
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
type AuthInfoService interface {
|
type AuthInfoService interface {
|
||||||
LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*user.User, error)
|
LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*user.User, error)
|
||||||
GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) 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
|
GetExternalUserInfoByLogin(ctx context.Context, query *models.GetExternalUserInfoByLoginQuery) error
|
||||||
SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error
|
SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error
|
||||||
UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) 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
|
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 {
|
func (s *AuthInfoStore) SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error {
|
||||||
authUser := &models.UserAuth{
|
authUser := &models.UserAuth{
|
||||||
UserId: cmd.UserId,
|
UserId: cmd.UserId,
|
||||||
|
@ -185,6 +185,13 @@ func (s *Implementation) GetAuthInfo(ctx context.Context, query *models.GetAuthI
|
|||||||
return s.authInfoStore.GetAuthInfo(ctx, query)
|
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 {
|
func (s *Implementation) UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error {
|
||||||
return s.authInfoStore.UpdateAuthInfo(ctx, cmd)
|
return s.authInfoStore.UpdateAuthInfo(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
@ -478,6 +478,7 @@ func TestUserAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FakeAuthInfoStore struct {
|
type FakeAuthInfoStore struct {
|
||||||
|
login.AuthInfoService
|
||||||
ExpectedError error
|
ExpectedError error
|
||||||
ExpectedUser *user.User
|
ExpectedUser *user.User
|
||||||
ExpectedOAuth *models.UserAuth
|
ExpectedOAuth *models.UserAuth
|
||||||
|
@ -19,11 +19,13 @@ func (l *LoginServiceFake) DisableExternalUser(ctx context.Context, username str
|
|||||||
func (l *LoginServiceFake) SetTeamSyncFunc(login.TeamSyncFunc) {}
|
func (l *LoginServiceFake) SetTeamSyncFunc(login.TeamSyncFunc) {}
|
||||||
|
|
||||||
type AuthInfoServiceFake struct {
|
type AuthInfoServiceFake struct {
|
||||||
|
login.AuthInfoService
|
||||||
LatestUserID int64
|
LatestUserID int64
|
||||||
ExpectedUserAuth *models.UserAuth
|
ExpectedUserAuth *models.UserAuth
|
||||||
ExpectedUser *user.User
|
ExpectedUser *user.User
|
||||||
ExpectedExternalUser *models.ExternalUserInfo
|
ExpectedExternalUser *models.ExternalUserInfo
|
||||||
ExpectedError error
|
ExpectedError error
|
||||||
|
ExpectedLabels map[int64]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthInfoServiceFake) LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*user.User, error) {
|
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
|
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 {
|
func (a *AuthInfoServiceFake) SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error {
|
||||||
return a.ExpectedError
|
return a.ExpectedError
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ type UserProtectionService interface {
|
|||||||
type Store interface {
|
type Store interface {
|
||||||
GetExternalUserInfoByLogin(ctx context.Context, query *models.GetExternalUserInfoByLoginQuery) error
|
GetExternalUserInfoByLogin(ctx context.Context, query *models.GetExternalUserInfoByLoginQuery) error
|
||||||
GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) 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
|
SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error
|
||||||
UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error
|
UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error
|
||||||
UpdateAuthInfoDate(ctx context.Context, authInfo *models.UserAuth) 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 {
|
type FakeAuthInfoStore struct {
|
||||||
|
login.Store
|
||||||
ExpectedError error
|
ExpectedError error
|
||||||
ExpectedUser *user.User
|
ExpectedUser *user.User
|
||||||
ExpectedOAuth *models.UserAuth
|
ExpectedOAuth *models.UserAuth
|
||||||
|
@ -148,6 +148,7 @@ type OrgUserDTO struct {
|
|||||||
LastSeenAtAge string `json:"lastSeenAtAge"`
|
LastSeenAtAge string `json:"lastSeenAtAge"`
|
||||||
AccessControl map[string]bool `json:"accessControl,omitempty"`
|
AccessControl map[string]bool `json:"accessControl,omitempty"`
|
||||||
IsDisabled bool `json:"isDisabled"`
|
IsDisabled bool `json:"isDisabled"`
|
||||||
|
AuthLabels []string `json:"authLabels" xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoveOrgUserCommand struct {
|
type RemoveOrgUserCommand struct {
|
||||||
|
@ -48,6 +48,12 @@ describe('Render', () => {
|
|||||||
|
|
||||||
expect(screen.getByText('Disabled')).toBeInTheDocument();
|
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', () => {
|
describe('Remove modal', () => {
|
||||||
|
@ -4,6 +4,7 @@ import { OrgRole } from '@grafana/data';
|
|||||||
import { Button, ConfirmModal } from '@grafana/ui';
|
import { Button, ConfirmModal } from '@grafana/ui';
|
||||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||||
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
||||||
|
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||||
import { contextSrv } from 'app/core/core';
|
import { contextSrv } from 'app/core/core';
|
||||||
import { AccessControlAction, OrgUser, Role } from 'app/types';
|
import { AccessControlAction, OrgUser, Role } from 'app/types';
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ const UsersTable: FC<Props> = (props) => {
|
|||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
<th style={{ width: '34px' }} />
|
<th style={{ width: '34px' }} />
|
||||||
<th></th>
|
<th></th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -101,6 +103,12 @@ const UsersTable: FC<Props> = (props) => {
|
|||||||
{user.isDisabled && <span className="label label-tag label-tag--gray">Disabled</span>}
|
{user.isDisabled && <span className="label label-tag label-tag--gray">Disabled</span>}
|
||||||
</td>
|
</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) && (
|
{contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersRemove, user) && (
|
||||||
<td className="text-right">
|
<td className="text-right">
|
||||||
<Button
|
<Button
|
||||||
|
@ -15,6 +15,7 @@ export const getMockUsers = (amount: number) => {
|
|||||||
role: 'Admin',
|
role: 'Admin',
|
||||||
userId: i,
|
userId: i,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
|
authLabels: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ export interface OrgUser extends WithAccessControlMetadata {
|
|||||||
role: OrgRole;
|
role: OrgRole;
|
||||||
userId: number;
|
userId: number;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
|
authLabels?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
|
Loading…
Reference in New Issue
Block a user