SCIM: Add "is SCIM controlled" flag to site user, and "SCIM" badge on UI (#48727)

Adds a "SCIMControlled" flag to `SiteUser`, and fetches that via GraphQL, then displays a small "SCIM" badge if the user is SCIM-enabled.
This commit is contained in:
David Veszelovszki 2023-03-07 15:15:39 +01:00 committed by GitHub
parent ae8bcda2fa
commit 012d3470f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 78 additions and 14 deletions

View File

@ -27,6 +27,7 @@ All notable changes to Sourcegraph are documented in this file.
- When encountering GitHub rate limits, Sourcegraph will now wait the recommended amount of time and retry the request. This prevents sync jobs from failing prematurely due to external rate limits. [#48423](https://github.com/sourcegraph/sourcegraph/pull/48423)
- Added a dashboard with information about user and repository background permissions sync jobs. [#46317](https://github.com/sourcegraph/sourcegraph/issues/46317)
- When encountering GitHub or GitLab rate limits, Sourcegraph will now wait the recommended amount of time and retry the request. This prevents sync jobs from failing prematurely due to external rate limits. [#48423](https://github.com/sourcegraph/sourcegraph/pull/48423), [#48616](https://github.com/sourcegraph/sourcegraph/pull/48616)
- Added "SCIM" badges for SCIM-controlled users on the User admin page. [#48727](https://github.com/sourcegraph/sourcegraph/pull/48727)
### Changed

View File

@ -35,6 +35,7 @@ import {
PopoverOpenEvent,
Tooltip,
ErrorAlert,
Badge,
} from '@sourcegraph/wildcard'
import {
@ -491,7 +492,14 @@ export const UsersList: React.FunctionComponent<UsersListProps> = ({ onActionEnd
)
}
function RenderUsernameAndEmail({ username, email, displayName, deletedAt, locked }: SiteUser): JSX.Element {
function RenderUsernameAndEmail({
username,
email,
displayName,
deletedAt,
locked,
scimControlled,
}: SiteUser): JSX.Element {
const [isOpen, setIsOpen] = useState<boolean>(false)
const handleOpenChange = useCallback((event: PopoverOpenEvent): void => {
setIsOpen(event.isOpen)
@ -510,12 +518,36 @@ function RenderUsernameAndEmail({ username, email, displayName, deletedAt, locke
<Icon aria-label="Account locked" svgPath={mdiLock} />
</Tooltip>
)}{' '}
{scimControlled && (
<Tooltip
content={
<Text>
This user is{' '}
<Link to="/help/admin/scim" target="_blank" rel="noopener">
SCIM
</Link>
-controlledan external system controls some of its attributes.
</Text>
}
>
<Badge variant="primary" className="mr-1">
SCIM
</Badge>
</Tooltip>
)}
<Link to={`/users/${username}`} className="text-truncate">
@{username}
</Link>
</>
) : (
<Text className="mb-0 text-truncate">@{username}</Text>
<>
{scimControlled && (
<Badge variant="primary" className="mr-1">
SCIM
</Badge>
)}
<Text className="mb-0 text-truncate">@{username}</Text>
</>
)}
<Popover isOpen={isOpen} onOpenChange={handleOpenChange}>
<PopoverTrigger

View File

@ -55,6 +55,7 @@ export const USERS_MANAGEMENT_USERS_LIST = gql`
lastActiveAt
deletedAt
locked
scimControlled
}
}
}

View File

@ -7782,6 +7782,10 @@ type SiteUser {
"""
siteAdmin: Boolean!
"""
Whether the user is controlled through SCIM.
"""
scimControlled: Boolean!
"""
Total number of user's event_logs.
"""
eventsCount: Float!

View File

@ -106,6 +106,8 @@ func (s *siteUserResolver) DeletedAt(ctx context.Context) *string {
func (s *siteUserResolver) SiteAdmin(ctx context.Context) bool { return s.user.SiteAdmin }
func (s *siteUserResolver) SCIMControlled() bool { return s.user.SCIMControlled }
func (s *siteUserResolver) EventsCount(ctx context.Context) float64 { return s.user.EventsCount }
func (s *siteUserResolver) Locked(ctx context.Context) bool {

View File

@ -92,6 +92,7 @@ PROTECTED_FILES=(
./dev/ci/go-test.sh
./dev/ci/go-backcompat
./dev/ci/asdf-install.sh
./internal/database/migration/definition/read.go
)
# Rewrite the current migrations into a temporary folder that we can force

View File

@ -65,7 +65,7 @@ func findUser(ctx context.Context, db database.DB, id int) (types.UserForSCIM, e
return types.UserForSCIM{}, nil
}
if users[0].SCIMAccountData == "" {
return types.UserForSCIM{}, errors.New("cannot delete user because it doesn't seem to be SCIM-enabled")
return types.UserForSCIM{}, errors.New("cannot delete user because it doesn't seem to be SCIM-controlled")
}
user := *users[0]
return user, nil

View File

@ -22626,6 +22626,16 @@
"ConstraintType": "p",
"ConstraintDefinition": "PRIMARY KEY (id)"
},
{
"Name": "user_external_accounts_user_id_scim_service_type",
"IsPrimaryKey": false,
"IsUnique": true,
"IsExclusion": false,
"IsDeferrable": false,
"IndexDefinition": "CREATE UNIQUE INDEX user_external_accounts_user_id_scim_service_type ON user_external_accounts USING btree (user_id, service_type) WHERE service_type = 'scim'::text",
"ConstraintType": "",
"ConstraintDefinition": ""
},
{
"Name": "user_external_accounts_user_id",
"IsPrimaryKey": false,

View File

@ -3475,6 +3475,7 @@ Foreign-key constraints:
Indexes:
"user_external_accounts_pkey" PRIMARY KEY, btree (id)
"user_external_accounts_account" UNIQUE, btree (service_type, service_id, client_id, account_id) WHERE deleted_at IS NULL
"user_external_accounts_user_id_scim_service_type" UNIQUE, btree (user_id, service_type) WHERE service_type = 'scim'::text
"user_external_accounts_user_id" btree (user_id) WHERE deleted_at IS NULL
Foreign-key constraints:
"user_external_accounts_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)

View File

@ -152,6 +152,7 @@ var (
stats.user_last_active_at AS last_active_at,
users.deleted_at,
users.site_admin,
(SELECT COUNT(user_id) FROM user_external_accounts WHERE user_id=users.id AND service_type = 'scim') >= 1 AS scim_controlled,
COALESCE(stats.user_events_count, 0) AS events_count
FROM users
LEFT JOIN aggregated_user_statistics stats ON stats.user_id = users.id
@ -220,7 +221,7 @@ func (s *UsersStats) ListUsers(ctx context.Context, filters *UsersStatsListUsers
}
query := sqlf.Sprintf(statsCTEQuery, sqlf.Sprintf(`
SELECT id, username, display_name, primary_email, created_at, last_active_at, deleted_at, site_admin, events_count FROM aggregated_stats WHERE %s ORDER BY %s NULLS LAST LIMIT %s OFFSET %s`, sqlf.Join(conds, "AND"), orderBy, limit, offset))
SELECT id, username, display_name, primary_email, created_at, last_active_at, deleted_at, site_admin, scim_controlled, events_count FROM aggregated_stats WHERE %s ORDER BY %s NULLS LAST LIMIT %s OFFSET %s`, sqlf.Join(conds, "AND"), orderBy, limit, offset))
rows, err := s.DB.QueryContext(ctx, query.Query(sqlf.PostgresBindVar), query.Args()...)
@ -234,7 +235,7 @@ func (s *UsersStats) ListUsers(ctx context.Context, filters *UsersStatsListUsers
for rows.Next() {
var node UserStatItem
if err := rows.Scan(&node.Id, &node.Username, &node.DisplayName, &node.PrimaryEmail, &node.CreatedAt, &node.LastActiveAt, &node.DeletedAt, &node.SiteAdmin, &node.EventsCount); err != nil {
if err := rows.Scan(&node.Id, &node.Username, &node.DisplayName, &node.PrimaryEmail, &node.CreatedAt, &node.LastActiveAt, &node.DeletedAt, &node.SiteAdmin, &node.SCIMControlled, &node.EventsCount); err != nil {
return nil, err
}
@ -266,13 +267,14 @@ func toUsersField(orderBy string) (string, error) {
}
type UserStatItem struct {
Id int32
Username string
DisplayName *string
PrimaryEmail *string
CreatedAt time.Time
LastActiveAt *time.Time
DeletedAt *time.Time
SiteAdmin bool
EventsCount float64
Id int32
Username string
DisplayName *string
PrimaryEmail *string
CreatedAt time.Time
LastActiveAt *time.Time
DeletedAt *time.Time
SiteAdmin bool
SCIMControlled bool
EventsCount float64
}

View File

@ -0,0 +1,2 @@
-- Undo the changes made in the up migration
DROP INDEX IF EXISTS user_external_accounts_user_id_scim_service_type;

View File

@ -0,0 +1,3 @@
name: Add index on user_external_accounts for SCIM
parents: [ 1678091683 ]
createIndexConcurrently: true

View File

@ -0,0 +1,3 @@
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS user_external_accounts_user_id_scim_service_type
ON user_external_accounts (user_id, service_type)
WHERE (service_type = 'scim');

View File

@ -5364,6 +5364,8 @@ CREATE UNIQUE INDEX user_external_accounts_account ON user_external_accounts USI
CREATE INDEX user_external_accounts_user_id ON user_external_accounts USING btree (user_id) WHERE (deleted_at IS NULL);
CREATE UNIQUE INDEX user_external_accounts_user_id_scim_service_type ON user_external_accounts USING btree (user_id, service_type) WHERE (service_type = 'scim'::text);
CREATE UNIQUE INDEX user_repo_permissions_perms_unique_idx ON user_repo_permissions USING btree (user_id, user_external_account_id, repo_id);
CREATE INDEX user_repo_permissions_repo_id_idx ON user_repo_permissions USING btree (repo_id);