diff --git a/pkg/services/org/model.go b/pkg/services/org/model.go
index 9e2691f0340..7873e899eb3 100644
--- a/pkg/services/org/model.go
+++ b/pkg/services/org/model.go
@@ -157,6 +157,7 @@ type OrgUserDTO struct {
IsDisabled bool `json:"isDisabled"`
AuthLabels []string `json:"authLabels" xorm:"-"`
IsExternallySynced bool `json:"isExternallySynced"`
+ IsProvisioned bool `json:"isProvisioned"`
}
type RemoveOrgUserCommand struct {
diff --git a/pkg/services/org/orgimpl/store.go b/pkg/services/org/orgimpl/store.go
index 04edfb1e8f0..be838861c6a 100644
--- a/pkg/services/org/orgimpl/store.go
+++ b/pkg/services/org/orgimpl/store.go
@@ -611,6 +611,7 @@ func (ss *sqlStore) SearchOrgUsers(ctx context.Context, query *org.SearchOrgUser
"u.created",
"u.updated",
"u.is_disabled",
+ "u.is_provisioned",
)
if len(query.SortOpts) > 0 {
diff --git a/pkg/services/user/model.go b/pkg/services/user/model.go
index 78d9f492e36..6e2377052a9 100644
--- a/pkg/services/user/model.go
+++ b/pkg/services/user/model.go
@@ -137,11 +137,11 @@ type UserSearchHitDTO struct {
AvatarURL string `json:"avatarUrl" xorm:"avatar_url"`
IsAdmin bool `json:"isAdmin"`
IsDisabled bool `json:"isDisabled"`
+ IsProvisioned bool `json:"isProvisioned"`
LastSeenAt time.Time `json:"lastSeenAt"`
LastSeenAtAge string `json:"lastSeenAtAge"`
AuthLabels []string `json:"authLabels"`
AuthModule AuthModuleConversion `json:"-"`
- IsProvisioned bool `json:"-" xorm:"is_provisioned"`
}
type GetUserProfileQuery struct {
@@ -166,7 +166,7 @@ type UserProfileDTO struct {
CreatedAt time.Time `json:"createdAt"`
AvatarURL string `json:"avatarUrl"`
AccessControl map[string]bool `json:"accessControl,omitempty"`
- IsProvisioned bool `json:"-"`
+ IsProvisioned bool `json:"isProvisioned"`
}
// implement Conversion interface to define custom field mapping (xorm feature)
diff --git a/pkg/services/user/userimpl/store.go b/pkg/services/user/userimpl/store.go
index f740949942e..eb6b5a68227 100644
--- a/pkg/services/user/userimpl/store.go
+++ b/pkg/services/user/userimpl/store.go
@@ -374,6 +374,7 @@ func (ss *sqlStore) GetProfile(ctx context.Context, query *user.GetUserProfileQu
Theme: usr.Theme,
IsGrafanaAdmin: usr.IsAdmin,
IsDisabled: usr.IsDisabled,
+ IsProvisioned: usr.IsProvisioned,
OrgID: usr.OrgID,
UpdatedAt: usr.Updated,
CreatedAt: usr.Created,
@@ -526,7 +527,7 @@ func (ss *sqlStore) Search(ctx context.Context, query *user.SearchUsersQuery) (*
sess.Limit(query.Limit, offset)
}
- sess.Cols("u.id", "u.uid", "u.email", "u.name", "u.login", "u.is_admin", "u.is_disabled", "u.last_seen_at", "user_auth.auth_module")
+ sess.Cols("u.id", "u.uid", "u.email", "u.name", "u.login", "u.is_admin", "u.is_disabled", "u.last_seen_at", "user_auth.auth_module", "u.is_provisioned")
if len(query.SortOpts) > 0 {
for i := range query.SortOpts {
diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json
index a036e242e30..cc8046467ec 100644
--- a/public/api-enterprise-spec.json
+++ b/public/api-enterprise-spec.json
@@ -5913,6 +5913,9 @@
"isExternallySynced": {
"type": "boolean"
},
+ "isProvisioned": {
+ "type": "boolean"
+ },
"lastSeenAt": {
"type": "string",
"format": "date-time"
@@ -8605,6 +8608,9 @@
"isGrafanaAdminExternallySynced": {
"type": "boolean"
},
+ "isProvisioned": {
+ "type": "boolean"
+ },
"login": {
"type": "string"
},
@@ -8652,6 +8658,9 @@
"isDisabled": {
"type": "boolean"
},
+ "isProvisioned": {
+ "type": "boolean"
+ },
"lastSeenAt": {
"type": "string",
"format": "date-time"
diff --git a/public/api-merged.json b/public/api-merged.json
index 3ca4e15b213..cba17c0c8ac 100644
--- a/public/api-merged.json
+++ b/public/api-merged.json
@@ -17760,6 +17760,9 @@
"isExternallySynced": {
"type": "boolean"
},
+ "isProvisioned": {
+ "type": "boolean"
+ },
"lastSeenAt": {
"type": "string",
"format": "date-time"
@@ -22367,6 +22370,9 @@
"isGrafanaAdminExternallySynced": {
"type": "boolean"
},
+ "isProvisioned": {
+ "type": "boolean"
+ },
"login": {
"type": "string"
},
@@ -22414,6 +22420,9 @@
"isDisabled": {
"type": "boolean"
},
+ "isProvisioned": {
+ "type": "boolean"
+ },
"lastSeenAt": {
"type": "string",
"format": "date-time"
diff --git a/public/app/features/admin/UserAdminPage.tsx b/public/app/features/admin/UserAdminPage.tsx
index 46d6fa41f03..9af0af250b3 100644
--- a/public/app/features/admin/UserAdminPage.tsx
+++ b/public/app/features/admin/UserAdminPage.tsx
@@ -115,7 +115,10 @@ export const UserAdminPage = ({
const isLDAPUser = user?.isExternal && user?.authLabels?.includes('LDAP');
const canReadSessions = contextSrv.hasPermission(AccessControlAction.UsersAuthTokenList);
const canReadLDAPStatus = contextSrv.hasPermission(AccessControlAction.LDAPStatusRead);
- const authSource = user?.authLabels?.[0];
+ let authSource = user?.authLabels?.[0];
+ if (user?.isProvisioned) {
+ authSource = 'SCIM';
+ }
const lockMessage = authSource ? `Synced via ${authSource}` : '';
const pageNav: NavModelItem = {
text: user?.login ?? '',
diff --git a/public/app/features/admin/UserProfile.tsx b/public/app/features/admin/UserProfile.tsx
index 01f4256d523..8fa8e1f0bc4 100644
--- a/public/app/features/admin/UserProfile.tsx
+++ b/public/app/features/admin/UserProfile.tsx
@@ -71,12 +71,18 @@ export function UserProfile({
});
};
- const authSource = user.authLabels?.length && user.authLabels[0];
+ let authSource = user.authLabels?.length && user.authLabels[0];
+ if (user.isProvisioned) {
+ authSource = 'SCIM';
+ }
const lockMessage = authSource ? `Synced via ${authSource}` : '';
- const editLocked = user.isExternal || !contextSrv.hasPermissionInMetadata(AccessControlAction.UsersWrite, user);
+ const editLocked =
+ user.isExternal || user.isProvisioned || !contextSrv.hasPermissionInMetadata(AccessControlAction.UsersWrite, user);
const passwordChangeLocked =
- user.isExternal || !contextSrv.hasPermissionInMetadata(AccessControlAction.UsersPasswordUpdate, user);
+ user.isExternal ||
+ user.isProvisioned ||
+ !contextSrv.hasPermissionInMetadata(AccessControlAction.UsersPasswordUpdate, user);
const canDelete = contextSrv.hasPermissionInMetadata(AccessControlAction.UsersDelete, user);
const canDisable = contextSrv.hasPermissionInMetadata(AccessControlAction.UsersDisable, user);
const canEnable = contextSrv.hasPermissionInMetadata(AccessControlAction.UsersEnable, user);
diff --git a/public/app/features/admin/Users/OrgUsersTable.tsx b/public/app/features/admin/Users/OrgUsersTable.tsx
index 861181db16c..9e27806a05c 100644
--- a/public/app/features/admin/Users/OrgUsersTable.tsx
+++ b/public/app/features/admin/Users/OrgUsersTable.tsx
@@ -216,6 +216,13 @@ export const OrgUsersTable = ({
<>{Array.isArray(value) && value.length > 0 && }>
),
},
+ {
+ id: 'isProvisioned',
+ header: 'Provisioned',
+ cell: ({ cell: { value } }: Cell<'isProvisioned'>) => (
+ <>{value && }>
+ ),
+ },
{
id: 'isDisabled',
header: '',
diff --git a/public/app/features/admin/Users/UsersTable.tsx b/public/app/features/admin/Users/UsersTable.tsx
index 23fbf4b763f..405186e229b 100644
--- a/public/app/features/admin/Users/UsersTable.tsx
+++ b/public/app/features/admin/Users/UsersTable.tsx
@@ -152,6 +152,13 @@ export const UsersTable = ({
<>{Array.isArray(value) && value.length > 0 && }>
),
},
+ {
+ id: 'isProvisioned',
+ header: 'Provisioned',
+ cell: ({ cell: { value } }: Cell<'isProvisioned'>) => (
+ <>{value && }>
+ ),
+ },
{
id: 'isDisabled',
header: '',
diff --git a/public/app/features/profile/UserProfileEditForm.tsx b/public/app/features/profile/UserProfileEditForm.tsx
index f942b6876fc..0c788577731 100644
--- a/public/app/features/profile/UserProfileEditForm.tsx
+++ b/public/app/features/profile/UserProfileEditForm.tsx
@@ -22,7 +22,10 @@ export const UserProfileEditForm = ({ user, isSavingUser, updateProfile }: Props
// check if authLabels is longer than 0 otherwise false
const isExternalUser: boolean = (user && user.isExternal) ?? false;
- const authSource = isExternalUser && user && user.authLabels ? user.authLabels[0] : '';
+ let authSource = isExternalUser && user && user.authLabels ? user.authLabels[0] : '';
+ if (user?.isProvisioned) {
+ authSource = 'SCIM';
+ }
const lockMessage = authSource ? ` (Synced via ${authSource})` : '';
const disabledEdit = disableLoginForm || isExternalUser;
diff --git a/public/app/types/user.ts b/public/app/types/user.ts
index 0d6a231692c..67264b0f298 100644
--- a/public/app/types/user.ts
+++ b/public/app/types/user.ts
@@ -19,6 +19,8 @@ export interface OrgUser extends WithAccessControlMetadata {
isDisabled: boolean;
authLabels?: string[];
isExternallySynced?: boolean;
+ // Externally provisioned
+ isProvisioned?: boolean;
}
export interface User {
@@ -56,6 +58,7 @@ export interface UserDTO extends WithAccessControlMetadata {
orgs?: Unit[];
isExternallySynced?: boolean;
isGrafanaAdminExternallySynced?: boolean;
+ isProvisioned?: boolean;
}
export interface Invitee {
diff --git a/public/openapi3.json b/public/openapi3.json
index d89f792df71..78d904c2847 100644
--- a/public/openapi3.json
+++ b/public/openapi3.json
@@ -7822,6 +7822,9 @@
"isExternallySynced": {
"type": "boolean"
},
+ "isProvisioned": {
+ "type": "boolean"
+ },
"lastSeenAt": {
"format": "date-time",
"type": "string"
@@ -12428,6 +12431,9 @@
"isGrafanaAdminExternallySynced": {
"type": "boolean"
},
+ "isProvisioned": {
+ "type": "boolean"
+ },
"login": {
"type": "string"
},
@@ -12475,6 +12481,9 @@
"isDisabled": {
"type": "boolean"
},
+ "isProvisioned": {
+ "type": "boolean"
+ },
"lastSeenAt": {
"format": "date-time",
"type": "string"