mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 20:31:48 +00:00
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:
parent
ae8bcda2fa
commit
012d3470f4
@ -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
|
||||
|
||||
|
||||
@ -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>
|
||||
-controlled—an 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
|
||||
|
||||
@ -55,6 +55,7 @@ export const USERS_MANAGEMENT_USERS_LIST = gql`
|
||||
lastActiveAt
|
||||
deletedAt
|
||||
locked
|
||||
scimControlled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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!
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
-- Undo the changes made in the up migration
|
||||
DROP INDEX IF EXISTS user_external_accounts_user_id_scim_service_type;
|
||||
@ -0,0 +1,3 @@
|
||||
name: Add index on user_external_accounts for SCIM
|
||||
parents: [ 1678091683 ]
|
||||
createIndexConcurrently: true
|
||||
@ -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');
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user