From be537e2f8cd3e8c0dd5a34cd6cfbdf3c1f8e329a Mon Sep 17 00:00:00 2001 From: John Hopper Date: Mon, 18 Nov 2024 14:01:31 -0800 Subject: [PATCH 1/3] fix: BED-5047 - prefer 14 register non-sparse hyperloglog to reduce the cost of merging sketches --- packages/go/dawgs/cardinality/hyperloglog64.go | 4 ++-- packages/go/dawgs/cardinality/hyperloglog64_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/go/dawgs/cardinality/hyperloglog64.go b/packages/go/dawgs/cardinality/hyperloglog64.go index 067de75d2d..3c6ffb83d6 100644 --- a/packages/go/dawgs/cardinality/hyperloglog64.go +++ b/packages/go/dawgs/cardinality/hyperloglog64.go @@ -38,7 +38,7 @@ type hyperLogLog64 struct { func NewHyperLogLog64() Simplex[uint64] { return &hyperLogLog64{ - sketch: hyperloglog.New16(), + sketch: hyperloglog.NewNoSparse(), } } @@ -49,7 +49,7 @@ func (s *hyperLogLog64) Clone() Simplex[uint64] { } func (s *hyperLogLog64) Clear() { - s.sketch = hyperloglog.New16() + s.sketch = hyperloglog.NewNoSparse() } func (s *hyperLogLog64) Add(values ...uint64) { diff --git a/packages/go/dawgs/cardinality/hyperloglog64_test.go b/packages/go/dawgs/cardinality/hyperloglog64_test.go index 0399d6de3f..1f1aaba363 100644 --- a/packages/go/dawgs/cardinality/hyperloglog64_test.go +++ b/packages/go/dawgs/cardinality/hyperloglog64_test.go @@ -40,8 +40,8 @@ func TestHyperLogLog64(t *testing.T) { deviation = 100 - cardinalityMax/float64(estimatedCardinality)*100 ) - // We expect the HLL sketch to have a cardinality that does not deviate more than 0.58% from reality - require.Truef(t, deviation < 0.58, "Expected a cardinality less than 0.58%% but got %.2f%%", deviation) + // We expect the HLL sketch to have a cardinality that does not deviate more than 0.66% from reality + require.Truef(t, deviation < 0.66, "Expected a cardinality less than 0.66%% but got %.2f%%", deviation) for i := 0; i < 100; i++ { previous := sketch.Cardinality() From e4456c60965bea5f4bad4a6c2914504c6e20b68a Mon Sep 17 00:00:00 2001 From: Mistah J <26472282+mistahj67@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:28:46 -0700 Subject: [PATCH 2/3] fix: removing authSecret with SSO assignment --- cmd/api/src/api/v2/auth/auth.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/api/src/api/v2/auth/auth.go b/cmd/api/src/api/v2/auth/auth.go index 34db75f867..e6a7c680bc 100644 --- a/cmd/api/src/api/v2/auth/auth.go +++ b/cmd/api/src/api/v2/auth/auth.go @@ -528,7 +528,7 @@ func (s ManagementResource) UpdateUser(response http.ResponseWriter, request *ht updateUserRequest v2.UpdateUserRequest pathVars = mux.Vars(request) rawUserID = pathVars[api.URIPathVariableUserID] - context = *ctx.FromRequest(request) + authCtx = *ctx.FromRequest(request) ) if userID, err := uuid.FromString(rawUserID); err != nil { @@ -550,7 +550,7 @@ func (s ManagementResource) UpdateUser(response http.ResponseWriter, request *ht user.IsDisabled = updateUserRequest.IsDisabled if user.IsDisabled { - if loggedInUser, _ := auth.GetUserFromAuthCtx(context.AuthCtx); user.ID == loggedInUser.ID { + if loggedInUser, _ := auth.GetUserFromAuthCtx(authCtx.AuthCtx); user.ID == loggedInUser.ID { api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, api.ErrorResponseUserSelfDisable, request), response) return } else if userSessions, err := s.db.LookupActiveSessionsByUser(request.Context(), user); err != nil { @@ -576,6 +576,7 @@ func (s ManagementResource) UpdateUser(response http.ResponseWriter, request *ht return } else { // Ensure that the AuthSecret reference is nil and that the SAML provider is set + user.AuthSecret = nil // Required or the below updateUser will re-add the authSecret user.SAMLProviderID = null.Int32From(samlProviderID) user.SSOProviderID = provider.SSOProviderID } @@ -587,6 +588,7 @@ func (s ManagementResource) UpdateUser(response http.ResponseWriter, request *ht api.HandleDatabaseError(request, response, err) return } else { + user.AuthSecret = nil // Required or the below updateUser will re-add the authSecret user.SSOProviderID = updateUserRequest.SSOProviderID if ssoProvider.Type == model.SessionAuthProviderSAML { if ssoProvider.SAMLProvider != nil { @@ -600,6 +602,7 @@ func (s ManagementResource) UpdateUser(response http.ResponseWriter, request *ht } else { // Default SAMLProviderID and SSOProviderID to null if the update request contains no SAMLProviderID and SSOProviderID user.SAMLProvider = nil + user.SSOProvider = nil user.SAMLProviderID = null.NewInt32(0, false) user.SSOProviderID = null.NewInt32(0, false) } From d59c2b6d8b4fb6728bfbc4a570468ce27e91a123 Mon Sep 17 00:00:00 2001 From: mistahj67 <26472282+mistahj67@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:36:33 -0700 Subject: [PATCH 3/3] BED-5051 fix: disable user when not assigned to SSO provider (#968) --- .../bh-shared-ui/src/hooks/useUsers.tsx | 153 ------------------ .../bh-shared-ui/src/views/Users/Users.tsx | 51 ++---- .../js-client-library/src/client.ts | 3 +- .../javascript/js-client-library/src/types.ts | 30 ++++ 4 files changed, 47 insertions(+), 190 deletions(-) delete mode 100644 packages/javascript/bh-shared-ui/src/hooks/useUsers.tsx diff --git a/packages/javascript/bh-shared-ui/src/hooks/useUsers.tsx b/packages/javascript/bh-shared-ui/src/hooks/useUsers.tsx deleted file mode 100644 index 4476ea1dd1..0000000000 --- a/packages/javascript/bh-shared-ui/src/hooks/useUsers.tsx +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2023 Specter Ops, Inc. -// -// Licensed under the Apache License, Version 2.0 -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -import { PutUserAuthSecretRequest, RequestOptions } from 'js-client-library'; -import { useMutation, useQuery, useQueryClient } from 'react-query'; -import { apiClient } from '../utils'; -import { addNotification } from '../providers/NotificationProvider/actions'; - -export type User = { - id: string; - sso_provider_id: number | null; - AuthSecret: any; - roles: Role[]; - first_name: string | null; - last_name: string | null; - email_address: string | null; - principal_name: string; - last_login: string; -}; - -export type Permission = { - id: number; - name: string; - authority: string; -}; - -export type Role = { - name: string; - description: string; - permissions: Permission[]; -}; - -export type CreateUserRequest = { - firstName: string; - lastName: string; - emailAddress: string; - principal: string; - roles: number[]; - SAMLProviderId?: string; - password?: string; - needsPasswordReset?: boolean; -}; - -export type UpdateUserRequest = { - firstName: string; - lastName: string; - emailAddress: string; - principal: string; - roles: number[]; -}; - -export const userKeys = { - all: ['users'] as const, - detail: (userId: number) => [...userKeys.all, userId] as const, -}; - -export const getUsers = (options?: RequestOptions): Promise => - apiClient.listUsers(options).then((res) => res.data.data.users); - -export const createUser = ({ newUser }: { newUser: CreateUserRequest }, options?: RequestOptions) => - apiClient.createUser(newUser, options).then((res) => res.data.data); - -export const updateUser = ( - { userId, updatedUser }: { userId: string; updatedUser: UpdateUserRequest }, - options?: RequestOptions -) => apiClient.updateUser(userId, updatedUser, options).then((res) => res.data.data); - -export const deleteUser = ({ userId }: { userId: string }, options?: RequestOptions) => - apiClient.deleteUser(userId, options).then((res) => res.data); - -export const expireUserPassword = ({ userId }: { userId: string }, options?: RequestOptions) => - apiClient.expireUserAuthSecret(userId, options).then((res) => res.data); - -export const updateUserPassword = ( - { - userId, - payload, - }: { - userId: string; - payload: PutUserAuthSecretRequest; - }, - options?: RequestOptions -) => apiClient.putUserAuthSecret(userId, payload, options).then((res) => res.data); - -export const useGetUsers = () => useQuery(userKeys.all, ({ signal }) => getUsers({ signal })); - -export const useCreateUser = () => { - const queryClient = useQueryClient(); - - return useMutation(createUser, { - onSuccess: () => { - addNotification('User created successfully!', 'createUserSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; - -export const useUpdateUser = () => { - const queryClient = useQueryClient(); - - return useMutation(updateUser, { - onSuccess: () => { - addNotification('User updated successfully!', 'updateUserSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; - -export const useDeleteUser = () => { - const queryClient = useQueryClient(); - - return useMutation(deleteUser, { - onSuccess: () => { - addNotification('User deleted successfully!', 'deleteUserSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; - -export const useExpireUserPassword = () => { - const queryClient = useQueryClient(); - - return useMutation(expireUserPassword, { - onSuccess: () => { - addNotification('User password expired successfully!', 'expireUserPasswordSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; - -export const useUpdateUserPassword = () => { - const queryClient = useQueryClient(); - - return useMutation(updateUserPassword, { - onSuccess: () => { - addNotification('User password updated successfully!', 'updateUserPasswordSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; diff --git a/packages/javascript/bh-shared-ui/src/views/Users/Users.tsx b/packages/javascript/bh-shared-ui/src/views/Users/Users.tsx index 31caf71d62..12809920b0 100644 --- a/packages/javascript/bh-shared-ui/src/views/Users/Users.tsx +++ b/packages/javascript/bh-shared-ui/src/views/Users/Users.tsx @@ -32,10 +32,9 @@ import { CreateUserDialog, } from '../../components'; import { apiClient, LuxonFormat } from '../../utils'; -import { CreateUserRequest, PutUserAuthSecretRequest, UpdateUserRequest } from 'js-client-library'; +import { CreateUserRequest, PutUserAuthSecretRequest, UpdateUserRequest, User } from 'js-client-library'; import find from 'lodash/find'; import { useToggle } from '../../hooks'; -import { User } from '../../hooks/useUsers'; import UserActionsMenu from '../../components/UserActionsMenu'; import { useNotifications } from '../../providers'; @@ -93,51 +92,31 @@ const Users = () => { } ); - const disableUserMutation = useMutation( - async (userId: string) => { - const user = listUsersQuery.data.find((user: User) => { + const disableEnableUserMutation = useMutation( + async ({ userId, disable }: { userId: string; disable: boolean }) => { + const user = listUsersQuery.data?.find((user: User) => { return user.id === userId; }); - const updatedUser = { - emailAddress: user.email_address || '', - principal: user.principal_name || '', - firstName: user.first_name || '', - lastName: user.last_name || '', - SSOProviderId: user.sso_provider_id?.toString() || '', - roles: user.roles?.map((role: any) => role.id) || [], - is_disabled: true, - }; - return apiClient.updateUser(selectedUserId!, updatedUser); - }, - { - onSuccess: () => { - addNotification('User disabled successfully!', 'disableUserSuccess'); - listUsersQuery.refetch(); - }, - } - ); + if (!user) { + return; + } - const enableUserMutation = useMutation( - async (userId: string) => { - const user = listUsersQuery.data.find((user: User) => { - return user.id === userId; - }); - - const updatedUser = { + const updatedUser: UpdateUserRequest = { emailAddress: user.email_address || '', principal: user.principal_name || '', firstName: user.first_name || '', lastName: user.last_name || '', - SSOProviderId: user.sso_provider_id?.toString() || '', + ...(user.sso_provider_id && { SSOProviderId: user.sso_provider_id }), roles: user.roles?.map((role: any) => role.id) || [], - is_disabled: false, + is_disabled: disable, }; + return apiClient.updateUser(selectedUserId!, updatedUser); }, { - onSuccess: () => { - addNotification('User enabled successfully!', 'enableUserSuccess'); + onSuccess: (_, { disable }) => { + addNotification(`User ${disable ? 'disabled' : 'enabled'} successfully!`, 'disableEnableUserSuccess'); listUsersQuery.refetch(); }, } @@ -301,7 +280,7 @@ const Users = () => { title={'Enable User'} onClose={(response) => { if (response) { - enableUserMutation.mutate(selectedUserId!); + disableEnableUserMutation.mutate({ userId: selectedUserId!, disable: false }); } toggleEnableUserDialog(); }} @@ -312,7 +291,7 @@ const Users = () => { title={'Disable User'} onClose={(response) => { if (response) { - disableUserMutation.mutate(selectedUserId!); + disableEnableUserMutation.mutate({ userId: selectedUserId!, disable: true }); } toggleDisableUserDialog(); }} diff --git a/packages/javascript/js-client-library/src/client.ts b/packages/javascript/js-client-library/src/client.ts index df53d08192..6548478cf3 100644 --- a/packages/javascript/js-client-library/src/client.ts +++ b/packages/javascript/js-client-library/src/client.ts @@ -740,7 +740,8 @@ class BHEAPIClient { deleteUserToken = (tokenId: string, options?: types.RequestOptions) => this.baseClient.delete(`/api/v2/tokens/${tokenId}`, options); - listUsers = (options?: types.RequestOptions) => this.baseClient.get('/api/v2/bloodhound-users', options); + listUsers = (options?: types.RequestOptions) => + this.baseClient.get('/api/v2/bloodhound-users', options); getUser = (userId: string, options?: types.RequestOptions) => this.baseClient.get(`/api/v2/bloodhound-users/${userId}`, options); diff --git a/packages/javascript/js-client-library/src/types.ts b/packages/javascript/js-client-library/src/types.ts index 9e47d0910d..1e15874460 100644 --- a/packages/javascript/js-client-library/src/types.ts +++ b/packages/javascript/js-client-library/src/types.ts @@ -185,6 +185,36 @@ export interface ListSSOProvidersResponse { data: SSOProvider[]; } +export interface User { + id: string; + sso_provider_id: number | null; + AuthSecret: any; + roles: Role[]; + first_name: string | null; + last_name: string | null; + email_address: string | null; + principal_name: string; + last_login: string; +} + +interface Permission { + id: number; + name: string; + authority: string; +} + +interface Role { + name: string; + description: string; + permissions: Permission[]; +} + +export interface ListUsersResponse { + data: { + users: User[]; + }; +} + export interface LoginRequest { login_method: string; secret: string;