SCIM: Disable auto assign organization if the user has been provisioned (#101307)

* Add isProvisioned field to model

* Add new isProvisioned column to migration

* Disable auto assignment to organization if the user is provisioned

* add annotation to user model

* add annotation to user models

* Remove IsProvisioned field from Identity

* Move new field assignenment and add default value

* Update annotations for user query results

* Remove isProvisioned from identity

* Add new column to test

* Resolve user from identity at SyncOrgHook
This commit is contained in:
linoman
2025-03-03 17:51:23 +01:00
committed by GitHub
parent 165bca6417
commit b7a0aeeb0d
6 changed files with 27 additions and 2 deletions
@@ -50,6 +50,16 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a
return nil
}
// ignore org syncing if the user is provisioned
usr, err := s.userService.GetByID(ctx, &user.GetUserByIDQuery{ID: userID})
if err != nil {
ctxLogger.Error("Failed to get user from provided identity", "error", err)
return nil
}
if usr.IsProvisioned {
return nil
}
ctxLogger.Debug("Syncing organization roles", "extOrgRoles", id.OrgRoles)
// don't sync org roles if none is specified
if len(id.OrgRoles) == 0 {
@@ -155,6 +155,12 @@ func addUserMigrations(mg *Migrator) {
Cols: []string{"uid"}, Type: UniqueIndex,
}))
// Modifies the user table to add a new column is_provisioned to indicate if the user is provisioned
// by SCIM or not.
mg.AddMigration("Add is_provisioned column to user", NewAddColumnMigration(userV2, &Column{
Name: "is_provisioned", Type: DB_Bool, Nullable: false, Default: "0",
}))
// Service accounts login were not unique per org. this migration is part of making it unique per org
// to be able to create service accounts that are unique per org
mg.AddMigration(usermig.AllowSameLoginCrossOrgs, &usermig.ServiceAccountsSameLoginCrossOrgs{})
+7 -1
View File
@@ -47,6 +47,8 @@ type User struct {
Created time.Time
Updated time.Time
LastSeenAt time.Time
IsProvisioned bool `xorm:"is_provisioned"`
}
type CreateUserCommand struct {
@@ -64,6 +66,7 @@ type CreateUserCommand struct {
SkipOrgSetup bool
DefaultOrgRole string
IsServiceAccount bool
IsProvisioned bool
}
type GetUserByLoginQuery struct {
@@ -114,7 +117,8 @@ type SearchUsersQuery struct {
SortOpts []model.SortOption
Filters []Filter
IsDisabled *bool
IsDisabled *bool
IsProvisioned *bool
}
type SearchUserQueryResult struct {
@@ -137,6 +141,7 @@ type UserSearchHitDTO struct {
LastSeenAtAge string `json:"lastSeenAtAge"`
AuthLabels []string `json:"authLabels"`
AuthModule AuthModuleConversion `json:"-"`
IsProvisioned bool `json:"-" xorm:"is_provisioned"`
}
type GetUserProfileQuery struct {
@@ -161,6 +166,7 @@ type UserProfileDTO struct {
CreatedAt time.Time `json:"createdAt"`
AvatarURL string `json:"avatarUrl"`
AccessControl map[string]bool `json:"accessControl,omitempty"`
IsProvisioned bool `json:"-"`
}
// implement Conversion interface to define custom field mapping (xorm feature)
+2 -1
View File
@@ -134,6 +134,7 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use
Updated: timeNow(),
LastSeenAt: timeNow().AddDate(-10, 0, 0),
IsServiceAccount: cmd.IsServiceAccount,
IsProvisioned: cmd.IsProvisioned,
}
salt, err := util.GetRandomString(10)
@@ -164,7 +165,7 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use
}
// create org user link
if !cmd.SkipOrgSetup {
if !cmd.SkipOrgSetup && !usr.IsProvisioned {
orgUser := org.OrgUser{
OrgID: orgID,
UserID: usr.ID,
+1
View File
@@ -57,6 +57,7 @@ func TestUserService(t *testing.T) {
require.Equal(t, "login", u.Login)
require.Equal(t, "name", u.Name)
require.Equal(t, "email", u.Email)
require.False(t, u.IsProvisioned)
})
t.Run("delete user store returns error", func(t *testing.T) {
@@ -69,6 +69,7 @@ func setupDBForGrafana(t *testing.T, ctx context.Context, m cfgMap) {
last_seen_at DATETIME NULL,
is_disabled INTEGER NOT NULL DEFAULT 0,
is_service_account BOOLEAN DEFAULT 0,
is_provisioned BOOLEAN DEFAULT 0,
uid TEXT NULL
);
CREATE TABLE org (