{cards.map((c, i) => (
diff --git a/docs/site/src/components/CardGrid/styles.module.css b/docs/site/src/components/CardGrid/styles.module.css
index 016e0fadc6d0..cadaa85ddd1d 100644
--- a/docs/site/src/components/CardGrid/styles.module.css
+++ b/docs/site/src/components/CardGrid/styles.module.css
@@ -144,3 +144,85 @@
background: rgba(255, 255, 255, 0.08);
color: var(--mm-color-white);
}
+
+/* Compact variant — secondary destinations that sit under a primary
+ * card grid. Same affordances (surface, hover, arrow) at roughly half
+ * the visual weight, so they read as a second tier rather than a
+ * competing one. */
+.chipRow {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
+ gap: 0.6rem;
+ margin: 0.75rem 0 2.5rem;
+}
+
+.chip {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.6rem 0.85rem;
+ background: var(--mm-bg-surface);
+ border: 1px solid var(--mm-border-subtle);
+ border-radius: 6px;
+ text-decoration: none;
+ color: inherit;
+ transition: background 0.15s ease, border-color 0.15s ease;
+}
+.chip:hover {
+ background: var(--mm-bg-subtle);
+ border-color: var(--mm-border-strong);
+ text-decoration: none;
+ color: inherit;
+}
+.chip:focus-visible {
+ outline: 2px solid var(--mm-color-denim);
+ outline-offset: 2px;
+}
+
+[data-theme='dark'] .chip {
+ background: rgba(255, 255, 255, 0.035);
+ border-color: rgba(255, 255, 255, 0.14);
+}
+[data-theme='dark'] .chip:hover {
+ background: rgba(255, 255, 255, 0.06);
+ border-color: var(--mm-denim-300);
+}
+[data-theme='dark'] .chip:focus-visible {
+ outline-color: var(--mm-denim-300, #8499C5);
+}
+
+.chipBody {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
+.chipTitle {
+ display: block;
+ font-family: var(--mm-font-sans);
+ font-weight: 700;
+ font-size: 0.92rem;
+ letter-spacing: -0.01em;
+ color: var(--mm-text-primary);
+}
+
+.chipDescription {
+ display: block;
+ font-size: 0.8rem;
+ line-height: 1.4;
+ color: var(--mm-text-secondary);
+}
+
+.chipArrow {
+ flex: 0 0 auto;
+ color: var(--mm-text-secondary);
+ font-size: 0.85rem;
+ transition: transform 0.15s ease, color 0.15s ease;
+}
+.chip:hover .chipArrow {
+ color: var(--mm-color-denim);
+ transform: translateX(3px);
+}
+
+[data-theme='dark'] .chip:hover .chipArrow {
+ color: var(--mm-color-white);
+}
diff --git a/e2e-tests/playwright/specs/functional/channels/channel_attributes/channel_attribute_assignment.spec.ts b/e2e-tests/playwright/specs/functional/channels/channel_attributes/channel_attribute_assignment.spec.ts
new file mode 100644
index 000000000000..e5b4baa4ee1d
--- /dev/null
+++ b/e2e-tests/playwright/specs/functional/channels/channel_attributes/channel_attribute_assignment.spec.ts
@@ -0,0 +1,194 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import type {PropertyField} from '@mattermost/types/properties';
+
+import {expect, test} from '@mattermost/playwright-lib';
+
+import {
+ attributeName,
+ createAttribute,
+ deleteAttributes,
+ optionId,
+ purgeAttributes,
+ readChannelValues,
+ valueFor,
+} from './helpers';
+
+test.describe('Channel attribute assignment', {tag: ['@channel_attributes']}, () => {
+ test.describe.configure({mode: 'serial'});
+
+ /**
+ * @objective Verify every supported attribute control can be assigned while creating a channel.
+ */
+ test('assigns select, multiselect, and text attribute values at channel creation', async ({pw}) => {
+ await pw.skipIfNoLicense();
+
+ // The Properties route gate is evaluated when the API router is built, so
+ // the flag has to be in the server config before boot.
+ await pw.skipIfFeatureFlagNotSet('ChannelAttributes', true);
+
+ const {adminClient, user, team} = await pw.initSetup();
+ const suffix = pw.random.id();
+ const created: PropertyField[] = [];
+
+ try {
+ await purgeAttributes(adminClient);
+
+ const program = await createAttribute(adminClient, attributeName('program', suffix), {
+ options: ['AURORA', 'BOREALIS'],
+ });
+ const caveats = await createAttribute(adminClient, attributeName('caveats', suffix), {
+ type: 'multiselect',
+ options: ['NOFORN', 'ORCON'],
+ });
+ const note = await createAttribute(adminClient, attributeName('note', suffix), {type: 'text'});
+ const userScoped = await createAttribute(adminClient, attributeName('clearance', suffix), {
+ objectType: 'user',
+ options: ['SECRET'],
+ });
+ created.push(program, caveats, note, userScoped);
+
+ const {page, channelsPage} = await pw.testBrowser.login(user);
+ await channelsPage.goto(team.name);
+ await channelsPage.toBeVisible();
+
+ const modal = await channelsPage.openNewChannelModal();
+
+ // Only the channel-scoped attributes belong here; a user attribute in
+ // the same group must not leak into channel creation.
+ await expect(page.getByTestId(`channelAttributeRow-${program.name}`)).toBeVisible();
+ await expect(page.getByTestId(`channelAttributeRow-${caveats.name}`)).toBeVisible();
+ await expect(page.getByTestId(`channelAttributeRow-${note.name}`)).toBeVisible();
+ await expect(page.getByTestId(`channelAttributeRow-${userScoped.name}`)).toHaveCount(0);
+
+ const displayName = `Attr Channel ${suffix}`;
+ await modal.fillDisplayName(displayName);
+
+ await page.getByTestId(`channelAttribute-${program.name}`).click();
+ await page.getByText('AURORA', {exact: true}).click();
+
+ // The menu closes on each pick, so a multiselect needs reopening to
+ // add the second value. Two values is what proves the array shape.
+ await page.getByTestId(`channelAttribute-${caveats.name}`).click();
+ await page.getByText('NOFORN', {exact: true}).click();
+ await page.getByTestId(`channelAttribute-${caveats.name}`).click();
+ await page.getByText('ORCON', {exact: true}).click();
+
+ await page.getByLabel(note.name, {exact: true}).fill('handle with care');
+
+ await modal.create();
+
+ // The values are written after the channel exists and the modal only
+ // closes once that resolves, so this is the signal the write finished.
+ // The channel header is already visible from before, so waiting on it
+ // races the write.
+ await expect(modal.container).not.toBeVisible();
+
+ const channel = await adminClient.getChannelByName(team.id, displayName.toLowerCase().replace(/\s+/g, '-'));
+ const values = await readChannelValues(adminClient, channel.id);
+
+ expect(valueFor(values, program)).toBe(optionId(program, 'AURORA'));
+ expect(valueFor(values, caveats)).toEqual([optionId(caveats, 'NOFORN'), optionId(caveats, 'ORCON')]);
+ expect(valueFor(values, note)).toBe('handle with care');
+ } finally {
+ await deleteAttributes(adminClient, created);
+ }
+ });
+
+ /**
+ * @objective Verify attribute assignment works for a private channel, not only a public one.
+ */
+ test('assigns attribute values when creating a private channel', async ({pw}) => {
+ await pw.skipIfNoLicense();
+ await pw.skipIfFeatureFlagNotSet('ChannelAttributes', true);
+
+ const {adminClient, user, team} = await pw.initSetup();
+ const suffix = pw.random.id();
+ const created: PropertyField[] = [];
+
+ try {
+ await purgeAttributes(adminClient);
+
+ const program = await createAttribute(adminClient, attributeName('private_program', suffix), {
+ options: ['AURORA', 'BOREALIS'],
+ });
+ created.push(program);
+
+ const {page, channelsPage} = await pw.testBrowser.login(user);
+ await channelsPage.goto(team.name);
+ await channelsPage.toBeVisible();
+
+ const modal = await channelsPage.openNewChannelModal();
+
+ const displayName = `Attr Private ${suffix}`;
+ await modal.fillDisplayName(displayName);
+ await modal.privateTypeButton.click();
+
+ await page.getByTestId(`channelAttribute-${program.name}`).click();
+ await page.getByText('BOREALIS', {exact: true}).click();
+
+ await modal.create();
+ await expect(modal.container).not.toBeVisible();
+
+ const channel = await adminClient.getChannelByName(team.id, displayName.toLowerCase().replace(/\s+/g, '-'));
+ expect(channel.type).toBe('P');
+
+ const values = await readChannelValues(adminClient, channel.id);
+ expect(valueFor(values, program)).toBe(optionId(program, 'BOREALIS'));
+ } finally {
+ await deleteAttributes(adminClient, created);
+ }
+ });
+
+ /**
+ * @objective Verify a failed value write is surfaced rather than swallowed.
+ */
+ test('surfaces a value write failure and keeps the channel', async ({pw}) => {
+ await pw.skipIfNoLicense();
+ await pw.skipIfFeatureFlagNotSet('ChannelAttributes', true);
+
+ const {adminClient, user, team} = await pw.initSetup();
+ const suffix = pw.random.id();
+ const created: PropertyField[] = [];
+
+ try {
+ await purgeAttributes(adminClient);
+
+ const program = await createAttribute(adminClient, attributeName('failing', suffix), {
+ options: ['AURORA'],
+ });
+ created.push(program);
+
+ const {page, channelsPage} = await pw.testBrowser.login(user);
+ await channelsPage.goto(team.name);
+ await channelsPage.toBeVisible();
+
+ // Fail only the value write. Channel creation must still succeed, so
+ // this proves the error is reported rather than the channel rolled back.
+ await page.route('**/api/v4/properties/groups/access_control/channel/values/**', async (route) => {
+ if (route.request().method() === 'PATCH') {
+ await route.fulfill({status: 500, body: '{"message":"forced failure"}'});
+ return;
+ }
+ await route.continue();
+ });
+
+ const modal = await channelsPage.openNewChannelModal();
+
+ const displayName = `Attr Fail ${suffix}`;
+ await modal.fillDisplayName(displayName);
+ await page.getByTestId(`channelAttribute-${program.name}`).click();
+ await page.getByText('AURORA', {exact: true}).click();
+ await modal.create();
+
+ // Assert the save-failure banner, not the attribute label already visible in the form.
+ await expect(page.getByText(/these attributes were not saved/i)).toBeVisible();
+
+ const channel = await adminClient.getChannelByName(team.id, displayName.toLowerCase().replace(/\s+/g, '-'));
+ expect(channel.delete_at).toBe(0);
+ } finally {
+ await deleteAttributes(adminClient, created);
+ }
+ });
+});
diff --git a/e2e-tests/playwright/specs/functional/channels/channel_attributes/helpers.ts b/e2e-tests/playwright/specs/functional/channels/channel_attributes/helpers.ts
new file mode 100644
index 000000000000..fe937589054f
--- /dev/null
+++ b/e2e-tests/playwright/specs/functional/channels/channel_attributes/helpers.ts
@@ -0,0 +1,86 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import type {Client4} from '@mattermost/client';
+import type {PropertyField, PropertyValue} from '@mattermost/types/properties';
+
+// Channel attributes share the access_control group with Classification Markings.
+export const GROUP = 'access_control';
+export const TARGET_TYPE = 'system';
+
+// Prefix on every field these specs create, so cleanup never touches real ones.
+export const FIELD_PREFIX = 'chanattr_e2e';
+
+type CreateOptions = {
+ objectType?: 'channel' | 'user';
+ type?: 'select' | 'multiselect' | 'text';
+ options?: string[];
+};
+
+export function attributeName(suffix: string, uniqueId: string): string {
+ return `${FIELD_PREFIX}_${suffix}_${uniqueId}`;
+}
+
+export async function createAttribute(
+ adminClient: Client4,
+ name: string,
+ {objectType = 'channel', type, options = []}: CreateOptions = {},
+): Promise
{
+ const field: Record = {
+ name,
+ type: type ?? (options.length ? 'select' : 'text'),
+ target_type: TARGET_TYPE,
+ target_id: '',
+ permission_field: 'admin',
+ permission_values: 'member',
+ permission_options: 'admin',
+ };
+
+ if (options.length) {
+ field.attrs = {options: options.map((optionName) => ({id: '', name: optionName}))};
+ }
+
+ return adminClient.createPropertyField(GROUP, objectType, field as Parameters[2]);
+}
+
+export function optionId(field: PropertyField, name: string): string {
+ const options = (field.attrs?.options ?? []) as Array<{id: string; name: string}>;
+ const match = options.find((option) => option.name === name);
+ if (!match) {
+ throw new Error(`option ${name} not found on ${field.name}`);
+ }
+ return match.id;
+}
+
+// Values are typed loosely because a multiselect stores an array where a select
+// stores a single option id. The endpoint answers a bare null, not an empty
+// array, when a channel has no values at all.
+export async function readChannelValues(client: Client4, channelId: string): Promise>> {
+ const values = await client.getPropertyValues(GROUP, 'channel', channelId);
+ return values ?? [];
+}
+
+export function valueFor(values: Array>, field: PropertyField): unknown {
+ return values.find((value) => value.field_id === field.id)?.value;
+}
+
+export async function deleteAttributes(adminClient: Client4, fields: PropertyField[]): Promise {
+ for (const field of fields) {
+ try {
+ await adminClient.deletePropertyField(GROUP, field.object_type, field.id);
+ } catch {} // eslint-disable-line no-empty
+ }
+}
+
+// Best-effort cleanup of fields left behind by an interrupted run.
+export async function purgeAttributes(adminClient: Client4): Promise {
+ for (const objectType of ['channel', 'user'] as const) {
+ try {
+ const fields = await adminClient.getPropertyFields(GROUP, objectType, TARGET_TYPE);
+ const stale = (fields ?? []).filter(
+ (field) => field.name.startsWith(FIELD_PREFIX) && field.delete_at === 0,
+ );
+ await deleteAttributes(adminClient, stale);
+ } catch {} // eslint-disable-line no-empty
+ }
+}
diff --git a/server/channels/api4/apitestlib.go b/server/channels/api4/apitestlib.go
index 46c9df472562..adce5353aed1 100644
--- a/server/channels/api4/apitestlib.go
+++ b/server/channels/api4/apitestlib.go
@@ -490,31 +490,31 @@ func (th *TestHelper) InitLogin(tb testing.TB) *TestHelper {
systemAdminPassword := th.SystemAdminUser.Password
_, appErr := th.App.UpdateUserRoles(th.Context, th.SystemAdminUser.Id, model.SystemUserRoleId+" "+model.SystemAdminRoleId, false)
require.Nil(tb, appErr)
- th.SystemAdminUser, appErr = th.App.GetUser(th.SystemAdminUser.Id)
+ th.SystemAdminUser, appErr = th.App.GetUser(th.Context, th.SystemAdminUser.Id)
require.Nil(tb, appErr)
th.SystemManagerUser = th.CreateUser(tb)
systemManagerPassword := th.SystemManagerUser.Password
_, appErr = th.App.UpdateUserRoles(th.Context, th.SystemManagerUser.Id, model.SystemUserRoleId+" "+model.SystemManagerRoleId, false)
require.Nil(tb, appErr)
- th.SystemManagerUser, appErr = th.App.GetUser(th.SystemManagerUser.Id)
+ th.SystemManagerUser, appErr = th.App.GetUser(th.Context, th.SystemManagerUser.Id)
require.Nil(tb, appErr)
th.TeamAdminUser = th.CreateUser(tb)
teamAdminPassword := th.TeamAdminUser.Password
_, appErr = th.App.UpdateUserRoles(th.Context, th.TeamAdminUser.Id, model.SystemUserRoleId, false)
require.Nil(tb, appErr)
- th.TeamAdminUser, appErr = th.App.GetUser(th.TeamAdminUser.Id)
+ th.TeamAdminUser, appErr = th.App.GetUser(th.Context, th.TeamAdminUser.Id)
require.Nil(tb, appErr)
th.BasicUser = th.CreateUser(tb)
basicUserPassword := th.BasicUser.Password
- th.BasicUser, appErr = th.App.GetUser(th.BasicUser.Id)
+ th.BasicUser, appErr = th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(tb, appErr)
th.BasicUser2 = th.CreateUser(tb)
basicUser2Password := th.BasicUser2.Password
- th.BasicUser2, appErr = th.App.GetUser(th.BasicUser2.Id)
+ th.BasicUser2, appErr = th.App.GetUser(th.Context, th.BasicUser2.Id)
require.Nil(tb, appErr)
// restore non-hashed password for login
@@ -1429,7 +1429,7 @@ func (th *TestHelper) SetUserRemoteID(tb testing.TB, userID, remoteID string) *m
require.NoError(tb, err)
th.App.InvalidateCacheForUser(userID)
- user, appErr := th.App.GetUser(userID)
+ user, appErr := th.App.GetUser(th.Context, userID)
require.Nil(tb, appErr)
return user
}
diff --git a/server/channels/api4/bot.go b/server/channels/api4/bot.go
index 9ff145cba287..d846625559dc 100644
--- a/server/channels/api4/bot.go
+++ b/server/channels/api4/bot.go
@@ -45,7 +45,7 @@ func createBot(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if user, err := c.App.GetUser(c.AppContext.Session().UserId); err == nil {
+ if user, err := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId); err == nil {
if user.IsBot {
c.SetPermissionError(model.PermissionCreateBot)
return
@@ -249,7 +249,7 @@ func assignBot(c *Context, w http.ResponseWriter, _ *http.Request) {
return
}
- if user, err := c.App.GetUser(userId); err == nil {
+ if user, err := c.App.GetUser(c.AppContext, userId); err == nil {
if user.IsBot {
c.SetPermissionError(model.PermissionAssignBot)
return
diff --git a/server/channels/api4/channel.go b/server/channels/api4/channel.go
index 58117bffd8da..3055922455f5 100644
--- a/server/channels/api4/channel.go
+++ b/server/channels/api4/channel.go
@@ -368,7 +368,7 @@ func updateChannelPrivacy(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(c.AppContext.Session().UserId)
+ user, err := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if err != nil {
c.Err = err
return
@@ -713,7 +713,7 @@ func readAllMessages(c *Context, w http.ResponseWriter, r *http.Request) {
defer c.LogAuditRec(auditRec)
model.AddEventParameterToAuditRec(auditRec, "user_id", c.Params.UserId)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -937,7 +937,7 @@ func discoverableNonMemberView(c *Context, channel *model.Channel) (*model.Chann
if !c.App.Config().FeatureFlags.DiscoverableChannels {
return nil, nil
}
- user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
+ user, userErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if userErr != nil {
return nil, userErr
}
@@ -982,7 +982,7 @@ func getChannelUnread(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -1388,7 +1388,7 @@ func getChannelsForTeamForUser(c *Context, w http.ResponseWriter, r *http.Reques
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -1439,7 +1439,7 @@ func getChannelsForUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -1755,7 +1755,7 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
if *c.App.Config().ServiceSettings.EnableAPIChannelDeletion {
err = c.App.PermanentDeleteChannel(c.AppContext, channel)
} else {
- user, usrErr := c.App.GetUser(c.AppContext.Session().UserId)
+ user, usrErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if usrErr == nil && user != nil && user.IsSystemAdmin() {
// More verbose error message for system admins
err = model.NewAppError("deleteChannel", "api.user.delete_channel.not_enabled.for_admin.app_error", nil, "channelId="+c.Params.ChannelId, http.StatusUnauthorized)
@@ -2015,7 +2015,7 @@ func viewChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -2076,7 +2076,7 @@ func readMultipleChannels(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -2113,7 +2113,7 @@ func readAllInTeam(c *Context, w http.ResponseWriter, r *http.Request) {
model.AddEventParameterToAuditRec(auditRec, "user_id", c.Params.UserId)
model.AddEventParameterToAuditRec(auditRec, "team_id", c.Params.TeamId)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -2247,7 +2247,7 @@ func updateChannelMemberNotifyProps(c *Context, w http.ResponseWriter, r *http.R
model.AddEventParameterToAuditRec(auditRec, "channel_id", c.Params.ChannelId)
model.AddEventParameterToAuditRec(auditRec, "props", props)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -2298,7 +2298,7 @@ func updateChannelMemberAutotranslation(c *Context, w http.ResponseWriter, r *ht
model.AddEventParameterToAuditRec(auditRec, "autotranslation_disabled", props.AutoTranslationDisabled)
model.AddEventParameterToAuditRec(auditRec, "user_id", c.Params.UserId)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -2772,7 +2772,7 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -3114,7 +3114,7 @@ func moveChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(c.AppContext.Session().UserId)
+ user, err := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if err != nil {
c.Err = err
return
@@ -3158,7 +3158,7 @@ func getDirectOrGroupMessageMembersCommonTeams(c *Context, w http.ResponseWriter
return
}
- user, err := c.App.GetUser(c.AppContext.Session().UserId)
+ user, err := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if err != nil {
c.Err = err
return
@@ -3204,7 +3204,7 @@ func convertGroupMessageToChannel(c *Context, w http.ResponseWriter, r *http.Req
return
}
- user, err := c.App.GetUser(c.AppContext.Session().UserId)
+ user, err := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if err != nil {
c.Err = err
return
diff --git a/server/channels/api4/channel_bookmark.go b/server/channels/api4/channel_bookmark.go
index d2ce53f04e98..111028e6031a 100644
--- a/server/channels/api4/channel_bookmark.go
+++ b/server/channels/api4/channel_bookmark.go
@@ -85,7 +85,7 @@ func createChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId)
+ user, gAppErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if gAppErr != nil {
c.Err = gAppErr
return
@@ -178,7 +178,7 @@ func updateChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) {
}
isMember = true
- user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId)
+ user, gAppErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if gAppErr != nil {
c.Err = gAppErr
return
@@ -298,7 +298,7 @@ func updateChannelBookmarkSortOrder(c *Context, w http.ResponseWriter, r *http.R
}
isMember = true
- user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId)
+ user, gAppErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if gAppErr != nil {
c.Err = gAppErr
return
@@ -391,7 +391,7 @@ func deleteChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) {
}
isMember = true
- user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId)
+ user, gAppErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if gAppErr != nil {
c.Err = gAppErr
return
diff --git a/server/channels/api4/channel_category.go b/server/channels/api4/channel_category.go
index e13fd9cf0660..12f82e47cd73 100644
--- a/server/channels/api4/channel_category.go
+++ b/server/channels/api4/channel_category.go
@@ -17,7 +17,7 @@ func getCategoriesForTeamForUser(c *Context, w http.ResponseWriter, r *http.Requ
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -50,7 +50,7 @@ func createCategoryForTeamForUser(c *Context, w http.ResponseWriter, r *http.Req
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -100,7 +100,7 @@ func getCategoryOrderForTeamForUser(c *Context, w http.ResponseWriter, r *http.R
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -128,7 +128,7 @@ func updateCategoryOrderForTeamForUser(c *Context, w http.ResponseWriter, r *htt
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -206,7 +206,7 @@ func updateCategoriesForTeamForUser(c *Context, w http.ResponseWriter, r *http.R
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
diff --git a/server/channels/api4/channel_local.go b/server/channels/api4/channel_local.go
index c0bb5911dcec..225487ab286c 100644
--- a/server/channels/api4/channel_local.go
+++ b/server/channels/api4/channel_local.go
@@ -247,7 +247,7 @@ func localRemoveChannelMember(c *Context, w http.ResponseWriter, r *http.Request
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
diff --git a/server/channels/api4/channel_test.go b/server/channels/api4/channel_test.go
index 04d080431056..574b59910f58 100644
--- a/server/channels/api4/channel_test.go
+++ b/server/channels/api4/channel_test.go
@@ -250,7 +250,7 @@ func TestCreateChannel(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = true })
guestUser := th.CreateUser(t)
- appErr := th.App.VerifyUserEmail(guestUser.Id, guestUser.Email)
+ appErr := th.App.VerifyUserEmail(th.Context, guestUser.Id, guestUser.Email)
require.Nil(t, appErr)
appErr = th.App.DemoteUserToGuest(th.Context, guestUser)
diff --git a/server/channels/api4/cloud.go b/server/channels/api4/cloud.go
index 9b0f6db23b92..cc8b03cebcd9 100644
--- a/server/channels/api4/cloud.go
+++ b/server/channels/api4/cloud.go
@@ -157,7 +157,7 @@ func validateBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, appErr := c.App.GetUser(c.AppContext.Session().UserId)
+ user, appErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if appErr != nil {
c.Err = model.NewAppError("Api4.validateBusinessEmail", "api.cloud.request_error", nil, "", http.StatusForbidden).Wrap(appErr)
return
@@ -207,7 +207,7 @@ func validateWorkspaceBusinessEmail(c *Context, w http.ResponseWriter, r *http.R
return
}
- user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
+ user, userErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if userErr != nil {
c.Err = userErr
return
diff --git a/server/channels/api4/command.go b/server/channels/api4/command.go
index 4588a62c309a..4b82d8722d6e 100644
--- a/server/channels/api4/command.go
+++ b/server/channels/api4/command.go
@@ -53,7 +53,7 @@ func createCommand(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if _, err := c.App.GetUser(cmd.CreatorId); err != nil {
+ if _, err := c.App.GetUser(c.AppContext, cmd.CreatorId); err != nil {
c.Err = err
return
}
diff --git a/server/channels/api4/ldap.go b/server/channels/api4/ldap.go
index 0cb22310638c..78a2b54ad714 100644
--- a/server/channels/api4/ldap.go
+++ b/server/channels/api4/ldap.go
@@ -489,7 +489,7 @@ func addUserToGroupSyncables(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- user, appErr := c.App.GetUser(c.Params.UserId)
+ user, appErr := c.App.GetUser(c.AppContext, c.Params.UserId)
if appErr != nil {
c.Err = appErr
return
diff --git a/server/channels/api4/oauth.go b/server/channels/api4/oauth.go
index ba6960f944c1..5286b3fd9355 100644
--- a/server/channels/api4/oauth.go
+++ b/server/channels/api4/oauth.go
@@ -316,7 +316,7 @@ func getAuthorizedOAuthApps(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
diff --git a/server/channels/api4/post.go b/server/channels/api4/post.go
index d0bfb9e4e2ab..ce0c7cdc6613 100644
--- a/server/channels/api4/post.go
+++ b/server/channels/api4/post.go
@@ -399,7 +399,7 @@ func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *ht
}
userId := c.Params.UserId
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), userId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), userId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -483,7 +483,7 @@ func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -1308,7 +1308,7 @@ func setPostUnread(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapBoolFromJSON(r.Body)
collapsedThreadsSupported := props["collapsed_threads_supported"]
- if c.AppContext.Session().UserId != c.Params.UserId && !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if c.AppContext.Session().UserId != c.Params.UserId && !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -1333,7 +1333,7 @@ func setPostReminder(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if c.AppContext.Session().UserId != c.Params.UserId && !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if c.AppContext.Session().UserId != c.Params.UserId && !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -1443,7 +1443,7 @@ func acknowledgePost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -1482,7 +1482,7 @@ func unacknowledgePost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -1529,7 +1529,7 @@ func moveThread(c *Context, w http.ResponseWriter, r *http.Request) {
model.AddEventParameterToAuditRec(auditRec, "original_post_id", c.Params.PostId)
model.AddEventParameterToAuditRec(auditRec, "to_channel_id", moveThreadParams.ChannelId)
- user, err := c.App.GetUser(c.AppContext.Session().UserId)
+ user, err := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if err != nil {
c.Err = err
return
diff --git a/server/channels/api4/post_test.go b/server/channels/api4/post_test.go
index af7b4bbe2012..7c50ce986b00 100644
--- a/server/channels/api4/post_test.go
+++ b/server/channels/api4/post_test.go
@@ -1687,7 +1687,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
*cfg.ServiceSettings.EnableBotAccountCreation = true
})
bot := th.CreateBotWithSystemAdminClient(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
_, appErr = th.App.UpdateUserRoles(th.Context, bot.UserId, model.TeamUserRoleId+" "+model.SystemUserAccessTokenRoleId, false)
require.Nil(t, appErr)
diff --git a/server/channels/api4/post_utils.go b/server/channels/api4/post_utils.go
index 54e6c5928d93..46dc06f53d60 100644
--- a/server/channels/api4/post_utils.go
+++ b/server/channels/api4/post_utils.go
@@ -36,7 +36,7 @@ func postHardenedModeCheckWithContext(where string, c *Context, props model.Stri
}
func postPriorityCheckWithContext(where string, c *Context, priority *model.PostPriority, rootId string) {
- appErr := app.PostPriorityCheckWithApp(where, c.App, c.AppContext.Session().UserId, priority, rootId)
+ appErr := app.PostPriorityCheckWithApp(where, c.App, c.AppContext, c.AppContext.Session().UserId, priority, rootId)
if appErr != nil {
appErr.Where = where
c.Err = appErr
diff --git a/server/channels/api4/preference.go b/server/channels/api4/preference.go
index 0bd11fd9773e..fea6ca6cee58 100644
--- a/server/channels/api4/preference.go
+++ b/server/channels/api4/preference.go
@@ -27,7 +27,7 @@ func getPreferences(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -49,7 +49,7 @@ func getPreferencesByCategory(c *Context, w http.ResponseWriter, r *http.Request
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -71,7 +71,7 @@ func getPreferenceByCategoryAndName(c *Context, w http.ResponseWriter, r *http.R
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -96,7 +96,7 @@ func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord(model.AuditEventUpdatePreferences, model.AuditStatusFail)
defer c.LogAuditRec(auditRec)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -158,7 +158,7 @@ func deletePreferences(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord(model.AuditEventDeletePreferences, model.AuditStatusFail)
defer c.LogAuditRec(auditRec)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
diff --git a/server/channels/api4/reaction.go b/server/channels/api4/reaction.go
index e3cf38bd891e..05458837a86e 100644
--- a/server/channels/api4/reaction.go
+++ b/server/channels/api4/reaction.go
@@ -16,7 +16,6 @@ func (api *API) InitReaction() {
api.BaseRoutes.Reactions.Handle("", api.APISessionRequired(saveReaction)).Methods(http.MethodPost)
api.BaseRoutes.Post.Handle("/reactions", api.APISessionRequired(getReactions)).Methods(http.MethodGet)
api.BaseRoutes.ReactionByNameForPostForUser.Handle("", api.APISessionRequired(deleteReaction)).Methods(http.MethodDelete)
- api.BaseRoutes.Posts.Handle("/ids/reactions", api.APISessionRequired(getBulkReactions)).Methods(http.MethodPost)
}
func saveReaction(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -114,31 +113,3 @@ func deleteReaction(c *Context, w http.ResponseWriter, r *http.Request) {
ReturnStatusOK(w)
}
-
-func getBulkReactions(c *Context, w http.ResponseWriter, r *http.Request) {
- postIds, err := model.SortedArrayFromJSON(r.Body)
- if err != nil {
- c.Err = model.NewAppError("getBulkReactions", model.PayloadParseError, nil, "", http.StatusBadRequest).Wrap(err)
- return
- }
- for _, postId := range postIds {
- if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), postId); !ok {
- c.SetPermissionError(model.PermissionReadChannelContent)
- return
- }
- }
- reactions, appErr := c.App.GetBulkReactionsForPosts(postIds)
- if appErr != nil {
- c.Err = appErr
- return
- }
-
- js, err := json.Marshal(reactions)
- if err != nil {
- c.Err = model.NewAppError("getBulkReactions", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
- return
- }
- if _, err := w.Write(js); err != nil {
- c.Logger.Warn("Error while writing js response", mlog.Err(err))
- }
-}
diff --git a/server/channels/api4/reaction_test.go b/server/channels/api4/reaction_test.go
index 146dfc754da6..833e9dd6b3a9 100644
--- a/server/channels/api4/reaction_test.go
+++ b/server/channels/api4/reaction_test.go
@@ -517,83 +517,3 @@ func TestDeleteReaction(t *testing.T) {
require.Equal(t, 1, len(reactions), "should have not deleted a reaction")
})
}
-
-func TestGetBulkReactions(t *testing.T) {
- mainHelper.Parallel(t)
- th := Setup(t).InitBasic(t)
- client := th.Client
- userId := th.BasicUser.Id
- user2Id := th.BasicUser2.Id
- post1 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
- post2 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
- post3 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
-
- post4 := &model.Post{UserId: user2Id, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
- post5 := &model.Post{UserId: user2Id, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
-
- post1, _, _ = client.CreatePost(context.Background(), post1)
- post2, _, _ = client.CreatePost(context.Background(), post2)
- post3, _, _ = client.CreatePost(context.Background(), post3)
- post4, _, _ = client.CreatePost(context.Background(), post4)
- post5, _, _ = client.CreatePost(context.Background(), post5)
-
- expectedPostIdsReactionsMap := make(map[string][]*model.Reaction)
- expectedPostIdsReactionsMap[post1.Id] = []*model.Reaction{}
- expectedPostIdsReactionsMap[post2.Id] = []*model.Reaction{}
- expectedPostIdsReactionsMap[post3.Id] = []*model.Reaction{}
- expectedPostIdsReactionsMap[post5.Id] = []*model.Reaction{}
-
- userReactions := []*model.Reaction{
- {
- UserId: userId,
- PostId: post1.Id,
- EmojiName: "happy",
- },
- {
- UserId: userId,
- PostId: post1.Id,
- EmojiName: "sad",
- },
- {
- UserId: userId,
- PostId: post2.Id,
- EmojiName: "smile",
- },
- {
- UserId: user2Id,
- PostId: post4.Id,
- EmojiName: "smile",
- },
- }
-
- for _, userReaction := range userReactions {
- reactions := expectedPostIdsReactionsMap[userReaction.PostId]
- reaction, err := th.App.Srv().Store().Reaction().Save(userReaction)
- require.NoError(t, err)
- reactions = append(reactions, reaction)
- expectedPostIdsReactionsMap[userReaction.PostId] = reactions
- }
-
- postIds := []string{post1.Id, post2.Id, post3.Id, post4.Id, post5.Id}
-
- t.Run("get-reactions", func(t *testing.T) {
- postIdsReactionsMap, _, err := client.GetBulkReactions(context.Background(), postIds)
- require.NoError(t, err)
-
- assert.ElementsMatch(t, expectedPostIdsReactionsMap[post1.Id], postIdsReactionsMap[post1.Id])
- assert.ElementsMatch(t, expectedPostIdsReactionsMap[post2.Id], postIdsReactionsMap[post2.Id])
- assert.ElementsMatch(t, expectedPostIdsReactionsMap[post3.Id], postIdsReactionsMap[post3.Id])
- assert.ElementsMatch(t, expectedPostIdsReactionsMap[post4.Id], postIdsReactionsMap[post4.Id])
- assert.ElementsMatch(t, expectedPostIdsReactionsMap[post5.Id], postIdsReactionsMap[post5.Id])
- assert.Equal(t, expectedPostIdsReactionsMap, postIdsReactionsMap)
- })
-
- t.Run("get-reactions-as-anonymous-user", func(t *testing.T) {
- _, err := client.Logout(context.Background())
- require.NoError(t, err)
-
- _, resp, err := client.GetBulkReactions(context.Background(), postIds)
- require.Error(t, err)
- CheckUnauthorizedStatus(t, resp)
- })
-}
diff --git a/server/channels/api4/recap.go b/server/channels/api4/recap.go
index a5367736d3e0..6ef2bd801f56 100644
--- a/server/channels/api4/recap.go
+++ b/server/channels/api4/recap.go
@@ -51,7 +51,7 @@ func getRecapLimitStatus(c *Context, w http.ResponseWriter, r *http.Request) {
userID := c.AppContext.Session().UserId
- status, appErr := c.App.GetRecapLimitStatus(userID)
+ status, appErr := c.App.GetRecapLimitStatus(c.AppContext, userID)
if appErr != nil {
c.Err = appErr
return
diff --git a/server/channels/api4/remote_cluster.go b/server/channels/api4/remote_cluster.go
index 01aba674274c..47b4a22c2926 100644
--- a/server/channels/api4/remote_cluster.go
+++ b/server/channels/api4/remote_cluster.go
@@ -289,7 +289,7 @@ func remoteSetProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
model.AddEventParameterToAuditRec(auditRec, "filename", imageArray[0].Filename)
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil || !user.IsRemote() {
c.SetInvalidURLParam("user_id")
return
diff --git a/server/channels/api4/shared_channel.go b/server/channels/api4/shared_channel.go
index d0a3b82d868d..6761c852502a 100644
--- a/server/channels/api4/shared_channel.go
+++ b/server/channels/api4/shared_channel.go
@@ -46,7 +46,7 @@ func getSharedChannels(c *Context, w http.ResponseWriter, r *http.Request) {
}
// only return channels the user is a member of, unless they are a shared channels manager.
- if !c.App.HasPermissionTo(c.AppContext.Session().UserId, model.PermissionManageSharedChannels) {
+ if !c.App.HasPermissionTo(c.AppContext, c.AppContext.Session().UserId, model.PermissionManageSharedChannels) {
opts.MemberId = c.AppContext.Session().UserId
}
@@ -329,7 +329,7 @@ func canUserDirectMessage(c *Context, w http.ResponseWriter, r *http.Request) {
// Get shared channel sync service for remote user checks
scs := c.App.Srv().GetSharedChannelSyncService()
if scs != nil {
- otherUser, otherErr := c.App.GetUser(c.Params.OtherUserId)
+ otherUser, otherErr := c.App.GetUser(c.AppContext, c.Params.OtherUserId)
if otherErr != nil {
canDM = false
} else {
diff --git a/server/channels/api4/status.go b/server/channels/api4/status.go
index 00f70ca490f3..d85211298dec 100644
--- a/server/channels/api4/status.go
+++ b/server/channels/api4/status.go
@@ -101,7 +101,7 @@ func updateUserStatus(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -151,7 +151,7 @@ func updateUserCustomStatus(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -177,7 +177,7 @@ func removeUserCustomStatus(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -207,7 +207,7 @@ func removeUserRecentCustomStatus(c *Context, w http.ResponseWriter, r *http.Req
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
diff --git a/server/channels/api4/team.go b/server/channels/api4/team.go
index 1ee579bd07d2..f8b6ec6f10cb 100644
--- a/server/channels/api4/team.go
+++ b/server/channels/api4/team.go
@@ -703,7 +703,7 @@ func deleteTeam(c *Context, w http.ResponseWriter, r *http.Request) {
if *c.App.Config().ServiceSettings.EnableAPITeamDeletion {
err = c.App.PermanentDeleteTeamId(c.AppContext, c.Params.TeamId)
} else {
- user, usrErr := c.App.GetUser(c.AppContext.Session().UserId)
+ user, usrErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if usrErr == nil && user != nil && user.IsSystemAdmin() {
// More verbose error message for system admins
err = model.NewAppError("deleteTeam", "api.user.delete_team.not_enabled.for_admin.app_error", nil, "teamId="+c.Params.TeamId, http.StatusUnauthorized)
@@ -879,7 +879,7 @@ func getTeamMembersForUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) && !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionReadOtherUsersTeams) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) && !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionReadOtherUsersTeams) {
c.SetPermissionError(model.PermissionReadOtherUsersTeams)
return
}
@@ -1028,7 +1028,7 @@ func addTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
canInviteGuests := c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionInviteGuest)
if !canInviteGuests {
- user, err := c.App.GetUser(member.UserId)
+ user, err := c.App.GetUser(c.AppContext, member.UserId)
if err != nil {
c.Err = model.NewAppError("addTeamMembers", "api.team.user.missing_account", nil, "", http.StatusNotFound).Wrap(err)
return
@@ -1203,7 +1203,7 @@ func addTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
// if user cannot invite guests, check if any users are guest users.
if !canInviteGuests {
- user, err := c.App.GetUser(member.UserId)
+ user, err := c.App.GetUser(c.AppContext, member.UserId)
if err != nil {
c.Err = model.NewAppError("addTeamMembers", "api.team.user.missing_account", nil, "", http.StatusNotFound).Wrap(err)
return
@@ -1290,7 +1290,7 @@ func removeTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
}
model.AddEventParameterAuditableToAuditRec(auditRec, "team", team)
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -1317,7 +1317,7 @@ func getTeamUnread(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
diff --git a/server/channels/api4/terms_of_service.go b/server/channels/api4/terms_of_service.go
index 0fc09c3c6a97..cfbfb24e98e9 100644
--- a/server/channels/api4/terms_of_service.go
+++ b/server/channels/api4/terms_of_service.go
@@ -59,7 +59,7 @@ func createTermsOfService(c *Context, w http.ResponseWriter, r *http.Request) {
}
if oldTermsOfService == nil || oldTermsOfService.Text != text {
- termsOfService, err := c.App.CreateTermsOfService(text, userId)
+ termsOfService, err := c.App.CreateTermsOfService(c.AppContext, text, userId)
if err != nil {
c.Err = err
return
diff --git a/server/channels/api4/terms_of_service_test.go b/server/channels/api4/terms_of_service_test.go
index 6a9340a34a3b..cd130bae0782 100644
--- a/server/channels/api4/terms_of_service_test.go
+++ b/server/channels/api4/terms_of_service_test.go
@@ -18,7 +18,7 @@ func TestGetTermsOfService(t *testing.T) {
th := Setup(t).InitBasic(t)
client := th.Client
- _, appErr := th.App.CreateTermsOfService("abc", th.BasicUser.Id)
+ _, appErr := th.App.CreateTermsOfService(th.Context, "abc", th.BasicUser.Id)
require.Nil(t, appErr)
termsOfService, _, err := client.GetTermsOfService(context.Background(), "")
diff --git a/server/channels/api4/user.go b/server/channels/api4/user.go
index 4800e96f9bd2..04de459e6809 100644
--- a/server/channels/api4/user.go
+++ b/server/channels/api4/user.go
@@ -208,7 +208,7 @@ func loginSSOCodeExchange(c *Context, w http.ResponseWriter, r *http.Request) {
}
// Create session for this user
- user, err := c.App.GetUser(userID)
+ user, err := c.App.GetUser(c.AppContext, userID)
if err != nil {
c.Err = err
return
@@ -319,7 +319,7 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -541,7 +541,7 @@ func getDefaultProfileImage(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -577,7 +577,7 @@ func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -657,7 +657,7 @@ func setProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
model.AddEventParameterToAuditRec(auditRec, "filename", imageArray[0].Filename)
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.SetInvalidURLParam("user_id")
return
@@ -711,7 +711,7 @@ func setDefaultProfileImage(c *Context, w http.ResponseWriter, r *http.Request)
model.AddEventParameterToAuditRec(auditRec, "user_id", c.Params.UserId)
defer c.LogAuditRec(auditRec)
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -1126,7 +1126,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
if sort == "display_name" {
var user *model.User
- user, appErr = c.App.GetUser(c.AppContext.Session().UserId)
+ user, appErr = c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if appErr != nil {
c.Err = appErr
return
@@ -1506,7 +1506,7 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- ouser, err := c.App.GetUser(user.Id)
+ ouser, err := c.App.GetUser(c.AppContext, user.Id)
if err != nil {
c.Err = err
return
@@ -1592,7 +1592,7 @@ func patchUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- ouser, err := c.App.GetUser(c.Params.UserId)
+ ouser, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.SetInvalidParam("user_id")
return
@@ -1685,7 +1685,7 @@ func deleteUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(userId)
+ user, err := c.App.GetUser(c.AppContext, userId)
if err != nil {
c.Err = err
return
@@ -1703,7 +1703,7 @@ func deleteUser(c *Context, w http.ResponseWriter, r *http.Request) {
if *c.App.Config().ServiceSettings.EnableAPIUserDeletion {
err = c.App.PermanentDeleteUser(c.AppContext, user)
} else {
- loggedUser, usrErr := c.App.GetUser(c.AppContext.Session().UserId)
+ loggedUser, usrErr := c.App.GetUser(c.AppContext, c.AppContext.Session().UserId)
if usrErr == nil && loggedUser != nil && loggedUser.IsSystemAdmin() {
// More verbose error message for system admins
err = model.NewAppError("deleteUser", "api.user.delete_user.not_enabled.for_admin.app_error", nil, "userId="+c.Params.UserId, http.StatusUnauthorized)
@@ -1804,7 +1804,7 @@ func updateUserActive(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -1887,7 +1887,7 @@ func updateUserAuth(c *Context, w http.ResponseWriter, r *http.Request) {
userAuth.AuthService = ""
}
- if user, err := c.App.GetUser(c.Params.UserId); err == nil {
+ if user, err := c.App.GetUser(c.AppContext, c.Params.UserId); err == nil {
auditRec.AddEventPriorState(user)
}
@@ -1922,7 +1922,7 @@ func updateUserMfa(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -1932,7 +1932,7 @@ func updateUserMfa(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if user, appErr := c.App.GetUser(c.Params.UserId); appErr == nil {
+ if user, appErr := c.App.GetUser(c.AppContext, c.Params.UserId); appErr == nil {
model.AddEventParameterAuditableToAuditRec(auditRec, "user", user)
}
@@ -1978,12 +1978,12 @@ func generateMfaSecret(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
- secret, err := c.App.GenerateMfaSecret(c.Params.UserId)
+ secret, err := c.App.GenerateMfaSecret(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -2011,7 +2011,7 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("attempted")
var canUpdatePassword bool
- if user, err := c.App.GetUser(c.Params.UserId); err == nil {
+ if user, err := c.App.GetUser(c.AppContext, c.Params.UserId); err == nil {
model.AddEventParameterAuditableToAuditRec(auditRec, "user", user)
if user.IsSystemAdmin() {
@@ -2029,7 +2029,7 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) {
// is already hashed or not.
if props["already_hashed"] == "true" {
if canUpdatePassword {
- err = c.App.UpdateHashedPasswordByUserId(c.Params.UserId, newPassword)
+ err = c.App.UpdateHashedPasswordByUserId(c.AppContext, c.Params.UserId, newPassword)
} else if c.Params.UserId == c.AppContext.Session().UserId {
err = model.NewAppError("updatePassword", "api.user.update_password.user_and_hashed.app_error", nil, "", http.StatusUnauthorized)
} else {
@@ -2307,7 +2307,7 @@ func loginWithDesktopToken(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("login_method", "desktop_token")
model.AddEventParameterToAuditRec(auditRec, "device_id", model.RedactDeviceId(deviceId))
- user, err := c.App.ValidateDesktopToken(token, time.Now().Add(-model.DesktopTokenTTL).Unix())
+ user, err := c.App.ValidateDesktopToken(c.AppContext, token, time.Now().Add(-model.DesktopTokenTTL).Unix())
if err != nil {
c.Err = err
return
@@ -2573,7 +2573,7 @@ func getSessions(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -2834,11 +2834,11 @@ func getUserAudits(c *Context, w http.ResponseWriter, r *http.Request) {
model.AddEventParameterToAuditRec(auditRec, "user_id", c.Params.UserId)
defer c.LogAuditRec(auditRec)
- if user, err := c.App.GetUser(c.Params.UserId); err == nil {
+ if user, err := c.App.GetUser(c.AppContext, c.Params.UserId); err == nil {
model.AddEventParameterAuditableToAuditRec(auditRec, "user", user)
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -2977,7 +2977,7 @@ func createUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
model.AddEventParameterToAuditRec(auditRec, "user_id", c.Params.UserId)
defer c.LogAuditRec(auditRec)
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -3242,7 +3242,7 @@ func revokeUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if user, errGet := c.App.GetUser(accessToken.UserId); errGet == nil {
+ if user, errGet := c.App.GetUser(c.AppContext, accessToken.UserId); errGet == nil {
model.AddEventParameterAuditableToAuditRec(auditRec, "user", user)
}
@@ -3293,7 +3293,7 @@ func disableUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- if user, errGet := c.App.GetUser(accessToken.UserId); errGet == nil {
+ if user, errGet := c.App.GetUser(c.AppContext, accessToken.UserId); errGet == nil {
model.AddEventParameterAuditableToAuditRec(auditRec, "user", user)
}
@@ -3344,7 +3344,7 @@ func enableUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if user, errGet := c.App.GetUser(accessToken.UserId); errGet == nil {
+ if user, errGet := c.App.GetUser(c.AppContext, accessToken.UserId); errGet == nil {
model.AddEventParameterAuditableToAuditRec(auditRec, "user", user)
}
@@ -3401,7 +3401,7 @@ func rotateUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, errGet := c.App.GetUser(accessToken.UserId)
+ user, errGet := c.App.GetUser(c.AppContext, accessToken.UserId)
if errGet != nil {
c.Err = errGet
return
@@ -3464,7 +3464,7 @@ func saveUserTermsOfService(c *Context, w http.ResponseWriter, r *http.Request)
}
model.AddEventParameterToAuditRec(auditRec, "accepted", accepted)
- if user, err := c.App.GetUser(userId); err == nil {
+ if user, err := c.App.GetUser(c.AppContext, userId); err == nil {
model.AddEventParameterAuditableToAuditRec(auditRec, "user", user)
}
@@ -3511,7 +3511,7 @@ func promoteGuestToUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -3569,7 +3569,7 @@ func demoteUserToGuest(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -3632,7 +3632,7 @@ func verifyUserEmailWithoutToken(c *Context, w http.ResponseWriter, r *http.Requ
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -3648,7 +3648,7 @@ func verifyUserEmailWithoutToken(c *Context, w http.ResponseWriter, r *http.Requ
return
}
- if err := c.App.VerifyUserEmail(user.Id, user.Email); err != nil {
+ if err := c.App.VerifyUserEmail(c.AppContext, user.Id, user.Email); err != nil {
c.Err = err
return
}
@@ -3668,7 +3668,7 @@ func convertUserToBot(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, appErr := c.App.GetUser(c.Params.UserId)
+ user, appErr := c.App.GetUser(c.AppContext, c.Params.UserId)
if appErr != nil {
c.Err = appErr
return
@@ -3740,7 +3740,7 @@ func getChannelMembersForUser(c *Context, w http.ResponseWriter, r *http.Request
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -3936,7 +3936,7 @@ func getThreadForUser(c *Context, w http.ResponseWriter, r *http.Request) {
if c.Err != nil {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -3979,7 +3979,7 @@ func getThreadsForUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -4062,7 +4062,7 @@ func updateReadStateThreadByUser(c *Context, w http.ResponseWriter, r *http.Requ
model.AddEventParameterToAuditRec(auditRec, "thread_id", c.Params.ThreadId)
model.AddEventParameterToAuditRec(auditRec, "team_id", c.Params.TeamId)
model.AddEventParameterToAuditRec(auditRec, "timestamp", c.Params.Timestamp)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -4102,7 +4102,7 @@ func setUnreadThreadByPostId(c *Context, w http.ResponseWriter, r *http.Request)
model.AddEventParameterToAuditRec(auditRec, "team_id", c.Params.TeamId)
model.AddEventParameterToAuditRec(auditRec, "post_id", c.Params.PostId)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -4149,7 +4149,7 @@ func unfollowThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) {
model.AddEventParameterToAuditRec(auditRec, "thread_id", c.Params.ThreadId)
model.AddEventParameterToAuditRec(auditRec, "team_id", c.Params.TeamId)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -4181,7 +4181,7 @@ func followThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) {
model.AddEventParameterToAuditRec(auditRec, "thread_id", c.Params.ThreadId)
model.AddEventParameterToAuditRec(auditRec, "team_id", c.Params.TeamId)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -4212,7 +4212,7 @@ func updateReadStateAllThreadsByUser(c *Context, w http.ResponseWriter, r *http.
model.AddEventParameterToAuditRec(auditRec, "user_id", c.Params.UserId)
model.AddEventParameterToAuditRec(auditRec, "team_id", c.Params.TeamId)
- if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
+ if !c.App.SessionHasPermissionToUser(c.AppContext, *c.AppContext.Session(), c.Params.UserId) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
@@ -4269,7 +4269,7 @@ func resetPasswordFailedAttempts(c *Context, w http.ResponseWriter, r *http.Requ
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
diff --git a/server/channels/api4/user_local.go b/server/channels/api4/user_local.go
index 2747a92d6194..e157082629ec 100644
--- a/server/channels/api4/user_local.go
+++ b/server/channels/api4/user_local.go
@@ -284,7 +284,7 @@ func localGetUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- user, err := c.App.GetUser(c.Params.UserId)
+ user, err := c.App.GetUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
@@ -325,7 +325,7 @@ func localDeleteUser(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord(model.AuditEventLocalDeleteUser, model.AuditStatusFail)
defer c.LogAuditRec(auditRec)
- user, err := c.App.GetUser(userId)
+ user, err := c.App.GetUser(c.AppContext, userId)
if err != nil {
c.Err = err
return
diff --git a/server/channels/api4/user_test.go b/server/channels/api4/user_test.go
index f23ab512efa3..dd0597cc5664 100644
--- a/server/channels/api4/user_test.go
+++ b/server/channels/api4/user_test.go
@@ -1173,7 +1173,7 @@ func TestGetUserWithAcceptedTermsOfServiceForOtherUser(t *testing.T) {
user := th.CreateUser(t)
- tos, _ := th.App.CreateTermsOfService("Dummy TOS", user.Id)
+ tos, _ := th.App.CreateTermsOfService(th.Context, "Dummy TOS", user.Id)
_, appErr := th.App.UpdateUser(th.Context, user, false)
require.Nil(t, appErr)
@@ -1205,7 +1205,7 @@ func TestGetUserWithAcceptedTermsOfService(t *testing.T) {
user := th.BasicUser
- tos, _ := th.App.CreateTermsOfService("Dummy TOS", user.Id)
+ tos, _ := th.App.CreateTermsOfService(th.Context, "Dummy TOS", user.Id)
ruser, _, err := th.Client.GetUser(context.Background(), user.Id, "")
require.NoError(t, err)
@@ -1235,7 +1235,7 @@ func TestGetUserWithAcceptedTermsOfServiceWithAdminUser(t *testing.T) {
th.LoginSystemAdmin(t)
user := th.BasicUser
- tos, appErr := th.App.CreateTermsOfService("Dummy TOS", user.Id)
+ tos, appErr := th.App.CreateTermsOfService(th.Context, "Dummy TOS", user.Id)
require.Nil(t, appErr)
ruser, _, err := th.SystemAdminClient.GetUser(context.Background(), user.Id, "")
@@ -1357,7 +1357,7 @@ func TestGetUserByUsernameWithAcceptedTermsOfService(t *testing.T) {
require.Equal(t, user.Email, ruser.Email)
- tos, appErr := th.App.CreateTermsOfService("Dummy TOS", user.Id)
+ tos, appErr := th.App.CreateTermsOfService(th.Context, "Dummy TOS", user.Id)
require.Nil(t, appErr)
appErr = th.App.SaveUserTermsOfService(ruser.Id, tos.Id, true)
require.Nil(t, appErr)
@@ -1556,7 +1556,7 @@ func TestGetUserByAuthData(t *testing.T) {
})
t.Run("returns accepted terms of service for system admin", func(t *testing.T) {
- tos, appErr := th.App.CreateTermsOfService("Dummy TOS", user.Id)
+ tos, appErr := th.App.CreateTermsOfService(th.Context, "Dummy TOS", user.Id)
require.Nil(t, appErr)
appErr = th.App.SaveUserTermsOfService(user.Id, tos.Id, true)
require.Nil(t, appErr)
@@ -2533,7 +2533,7 @@ func TestUpdateUserRemoteIdIgnored(t *testing.T) {
require.Equal(t, "updated-nickname", ruser.Nickname)
require.Empty(t, model.SafeDereference(ruser.RemoteId), "remote_id should remain empty")
- dbUser, appErr := th.App.GetUser(user.Id)
+ dbUser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Empty(t, model.SafeDereference(dbUser.RemoteId), "remote_id should not be persisted")
})
@@ -2656,7 +2656,7 @@ func TestPatchUser(t *testing.T) {
require.NotNil(t, appErr, "Password should not match")
currentPassword := user.Password
- user, appErr = th.App.GetUser(ruser.Id)
+ user, appErr = th.App.GetUser(th.Context, ruser.Id)
require.Nil(t, appErr)
appErr = th.App.CheckPasswordAndAllCriteria(th.Context, user.Id, currentPassword, "")
@@ -2737,7 +2737,7 @@ func TestPatchUserRemoteIdIgnored(t *testing.T) {
require.Equal(t, "new-nickname", ruser.Nickname)
require.Empty(t, model.SafeDereference(ruser.RemoteId), "remote_id should remain empty")
- dbUser, appErr := th.App.GetUser(user.Id)
+ dbUser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Empty(t, model.SafeDereference(dbUser.RemoteId), "remote_id should not be persisted")
})
@@ -2754,7 +2754,7 @@ func TestPatchUserRemoteIdIgnored(t *testing.T) {
require.Equal(t, "admin-patched", ruser.Nickname)
require.Empty(t, model.SafeDereference(ruser.RemoteId), "remote_id should remain empty even for admin")
- dbUser, appErr := th.App.GetUser(user.Id)
+ dbUser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Empty(t, model.SafeDereference(dbUser.RemoteId), "remote_id should not be persisted even for admin")
})
@@ -2919,7 +2919,7 @@ func TestUpdateUserAuth(t *testing.T) {
_, resp, err := th.SystemAdminClient.UpdateUserAuth(context.Background(), user.Id, userAuth)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
- storedUser, appErr := th.App.GetUser(user.Id)
+ storedUser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Equal(t, model.UserAuthServiceSaml, storedUser.AuthService)
require.NotNil(t, storedUser.AuthData)
@@ -2930,7 +2930,7 @@ func TestUpdateUserAuth(t *testing.T) {
_, resp, err = th.SystemAdminClient.UpdateUserAuth(context.Background(), user.Id, userAuth)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
- storedUser, appErr = th.App.GetUser(user.Id)
+ storedUser, appErr = th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Equal(t, model.UserAuthServiceSaml, storedUser.AuthService)
require.NotNil(t, storedUser.AuthData)
@@ -2943,7 +2943,7 @@ func TestUpdateUserAuth(t *testing.T) {
CheckOKStatus(t, resp)
require.Nil(t, ruser.AuthData)
require.Empty(t, ruser.AuthService)
- storedUser, appErr = th.App.GetUser(user.Id)
+ storedUser, appErr = th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Nil(t, storedUser.AuthData)
require.Empty(t, storedUser.AuthService)
@@ -4187,7 +4187,7 @@ func TestUserLoginMFAFlow(t *testing.T) {
})
t.Run("WithInvalidMFA", func(t *testing.T) {
- secret, appErr := th.App.GenerateMfaSecret(th.BasicUser.Id)
+ secret, appErr := th.App.GenerateMfaSecret(th.Context, th.BasicUser.Id)
assert.Nil(t, appErr)
// Fake user has MFA enabled
@@ -4212,7 +4212,7 @@ func TestUserLoginMFAFlow(t *testing.T) {
CheckErrorID(t, err, "mfa.validate_token.authenticate.app_error")
assert.Nil(t, user)
- secret2, appErr := th.App.GenerateMfaSecret(th.BasicUser2.Id)
+ secret2, appErr := th.App.GenerateMfaSecret(th.Context, th.BasicUser2.Id)
assert.Nil(t, appErr)
user, _, err = th.Client.LoginWithMFA(context.Background(), th.BasicUser.Email, th.BasicUser.Password, secret2.Secret)
CheckErrorID(t, err, "mfa.validate_token.authenticate.app_error")
@@ -4220,7 +4220,7 @@ func TestUserLoginMFAFlow(t *testing.T) {
})
t.Run("WithCorrectMFA", func(t *testing.T) {
- secret, appErr := th.App.GenerateMfaSecret(th.BasicUser.Id)
+ secret, appErr := th.App.GenerateMfaSecret(th.Context, th.BasicUser.Id)
assert.Nil(t, appErr)
// Fake user has MFA enabled
@@ -5133,13 +5133,13 @@ func TestSetProfileImage(t *testing.T) {
require.Fail(t, "Should have failed either forbidden or unauthorized")
}
- buser, appErr := th.App.GetUser(user.Id)
+ buser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
_, err = th.SystemAdminClient.SetProfileImage(context.Background(), user.Id, data)
require.NoError(t, err)
- ruser, appErr := th.App.GetUser(user.Id)
+ ruser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
assert.True(t, buser.LastPictureUpdate == ruser.LastPictureUpdate, "Same picture should not have updated")
@@ -5149,7 +5149,7 @@ func TestSetProfileImage(t *testing.T) {
_, err = th.SystemAdminClient.SetProfileImage(context.Background(), user.Id, data2)
require.NoError(t, err)
- ruser, appErr = th.App.GetUser(user.Id)
+ ruser, appErr = th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
assert.True(t, buser.LastPictureUpdate < ruser.LastPictureUpdate, "Picture should have updated for user")
@@ -5170,7 +5170,7 @@ func TestSetDefaultProfileImage(t *testing.T) {
_, err := th.Client.SetDefaultProfileImage(context.Background(), user.Id)
require.NoError(t, err)
- iuser, getUserErr := th.App.GetUser(user.Id)
+ iuser, getUserErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, getUserErr)
assert.Less(t, iuser.LastPictureUpdate, -startTime, "LastPictureUpdate should be set to -(current time in milliseconds)")
@@ -5205,7 +5205,7 @@ func TestSetDefaultProfileImage(t *testing.T) {
_, err = th.SystemAdminClient.SetDefaultProfileImage(context.Background(), anotherAdmin.Id)
require.NoError(t, err)
- ruser, appErr := th.App.GetUser(user.Id)
+ ruser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
assert.Less(t, ruser.LastPictureUpdate, iuser.LastPictureUpdate, "LastPictureUpdate should be updated to a lower negative number")
@@ -5273,7 +5273,7 @@ func TestLogin(t *testing.T) {
})
t.Run("login with terms_of_service set", func(t *testing.T) {
- termsOfService, appErr := th.App.CreateTermsOfService("terms of service", th.BasicUser.Id)
+ termsOfService, appErr := th.App.CreateTermsOfService(th.Context, "terms of service", th.BasicUser.Id)
require.Nil(t, appErr)
_, err := th.Client.RegisterTermsOfServiceAction(context.Background(), th.BasicUser.Id, termsOfService.Id, true)
@@ -5563,7 +5563,7 @@ func TestSwitchAccount(t *testing.T) {
_, err := th.App.Srv().Store().User().UpdateAuthData(th.BasicUser.Id, "", nil, "", true)
require.NoError(t, err)
- user, appErr := th.App.GetUser(th.BasicUser.Id)
+ user, appErr := th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
appErr = th.App.UpdatePassword(th.Context, user, th.BasicUser.Password)
require.Nil(t, appErr)
@@ -5718,7 +5718,7 @@ func TestSwitchAccount(t *testing.T) {
// The account must remain attached to its login provider
th.App.InvalidateCacheForUser(th.BasicUser.Id)
- user, appErr := th.App.GetUser(th.BasicUser.Id)
+ user, appErr := th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
require.Equal(t, model.UserAuthServiceGitlab, user.AuthService)
})
@@ -7648,13 +7648,13 @@ func TestRegisterTermsOfServiceAction(t *testing.T) {
_, err := th.Client.RegisterTermsOfServiceAction(context.Background(), th.BasicUser.Id, "st_1", true)
CheckErrorID(t, err, "app.terms_of_service.get.no_rows.app_error")
- termsOfService, appErr := th.App.CreateTermsOfService("terms of service", th.BasicUser.Id)
+ termsOfService, appErr := th.App.CreateTermsOfService(th.Context, "terms of service", th.BasicUser.Id)
require.Nil(t, appErr)
_, err = th.Client.RegisterTermsOfServiceAction(context.Background(), th.BasicUser.Id, termsOfService.Id, true)
require.NoError(t, err)
- _, appErr = th.App.GetUser(th.BasicUser.Id)
+ _, appErr = th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
}
@@ -7666,7 +7666,7 @@ func TestGetUserTermsOfService(t *testing.T) {
_, _, err := th.Client.GetUserTermsOfService(context.Background(), th.BasicUser.Id, "")
CheckErrorID(t, err, "app.user_terms_of_service.get_by_user.no_rows.app_error")
- termsOfService, appErr := th.App.CreateTermsOfService("terms of service", th.BasicUser.Id)
+ termsOfService, appErr := th.App.CreateTermsOfService(th.Context, "terms of service", th.BasicUser.Id)
require.Nil(t, appErr)
_, err = th.Client.RegisterTermsOfServiceAction(context.Background(), th.BasicUser.Id, termsOfService.Id, true)
@@ -7985,13 +7985,13 @@ func TestVerifyUserEmailWithoutToken(t *testing.T) {
require.Nil(t, appErr)
// Set up MFA secret for the user
- secret, appErr := th.App.GenerateMfaSecret(ruser.Id)
+ secret, appErr := th.App.GenerateMfaSecret(th.Context, ruser.Id)
require.Nil(t, appErr)
err = th.Server.Store().User().UpdateMfaSecret(ruser.Id, secret.Secret)
require.NoError(t, err)
// Verify the user has a password hash and MFA secret in the database
- dbUser, appErr := th.App.GetUser(ruser.Id)
+ dbUser, appErr := th.App.GetUser(th.Context, ruser.Id)
require.Nil(t, appErr)
require.NotEmpty(t, dbUser.Password, "User should have a password hash in database")
require.NotEmpty(t, dbUser.MfaSecret, "User should have MFA secret in database")
@@ -10022,7 +10022,7 @@ func TestLockProfileFieldsForEmailUsers(t *testing.T) {
setLock(model.TeamSettingsLockProfileFieldsNameAndUsername)
setNames("First", "Last")
- user, appErr := th.App.GetUser(th.BasicUser.Id)
+ user, appErr := th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
user.Nickname = "UpdatedNick"
_, _, err := th.Client.UpdateUser(context.Background(), user)
@@ -11338,7 +11338,7 @@ func TestSearchUsersWithMfaEnforced(t *testing.T) {
t.Run("user with MFA active can search users", func(t *testing.T) {
userWithMFAOK := th.BasicUser
- secret, appErr := th.App.GenerateMfaSecret(userWithMFAOK.Id)
+ secret, appErr := th.App.GenerateMfaSecret(th.Context, userWithMFAOK.Id)
assert.Nil(t, appErr)
// Fake user has MFA enabled
diff --git a/server/channels/api4/webhook.go b/server/channels/api4/webhook.go
index 37d0b6f0e32e..0caa107212d8 100644
--- a/server/channels/api4/webhook.go
+++ b/server/channels/api4/webhook.go
@@ -65,7 +65,7 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
}
var hookUser *model.User
- if hookUser, err = c.App.GetUser(hook.UserId); err != nil {
+ if hookUser, err = c.App.GetUser(c.AppContext, hook.UserId); err != nil {
c.Err = err
return
}
@@ -484,7 +484,7 @@ func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- _, err := c.App.GetUser(hook.CreatorId)
+ _, err := c.App.GetUser(c.AppContext, hook.CreatorId)
if err != nil {
c.Err = err
return
diff --git a/server/channels/api4/webhook_local.go b/server/channels/api4/webhook_local.go
index de39f90311e4..c668defec955 100644
--- a/server/channels/api4/webhook_local.go
+++ b/server/channels/api4/webhook_local.go
@@ -43,7 +43,7 @@ func localCreateIncomingHook(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- if _, err = c.App.GetUser(hook.UserId); err != nil {
+ if _, err = c.App.GetUser(c.AppContext, hook.UserId); err != nil {
c.Err = err
return
}
@@ -88,7 +88,7 @@ func localCreateOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- _, err := c.App.GetUser(hook.CreatorId)
+ _, err := c.App.GetUser(c.AppContext, hook.CreatorId)
if err != nil {
c.Err = err
return
diff --git a/server/channels/api4/websocket_test.go b/server/channels/api4/websocket_test.go
index ab1418c76524..2d92a759af44 100644
--- a/server/channels/api4/websocket_test.go
+++ b/server/channels/api4/websocket_test.go
@@ -540,7 +540,7 @@ func enableMFAEnforcement(th *TestHelper) {
// Helper function to set up MFA for a user
func setupUserWithMFA(t *testing.T, th *TestHelper, user *model.User) string {
// Setup MFA properly - following authentication_test.go pattern
- secret, appErr := th.App.GenerateMfaSecret(user.Id)
+ secret, appErr := th.App.GenerateMfaSecret(th.Context, user.Id)
require.Nil(t, appErr)
err := th.Server.Store().User().UpdateMfaActive(user.Id, true)
require.NoError(t, err)
diff --git a/server/channels/app/access_control.go b/server/channels/app/access_control.go
index e704a5e41b2e..f95f2bece958 100644
--- a/server/channels/app/access_control.go
+++ b/server/channels/app/access_control.go
@@ -163,7 +163,7 @@ func (a *App) CreateOrUpdateAccessControlPolicy(rctx request.CTX, policy *model.
// callers may intentionally set rules they don't match, mirroring the
// masking self-inclusion exemption below.
if policy.Type == model.AccessControlPolicyTypeTeam {
- if session := rctx.Session(); session != nil && session.UserId != "" && !a.HasPermissionTo(session.UserId, model.PermissionManageSystem) {
+ if session := rctx.Session(); session != nil && session.UserId != "" && !a.HasPermissionTo(rctx, session.UserId, model.PermissionManageSystem) {
for _, rule := range policy.Rules {
if appErr := a.ValidateTeamAdminSelfInclusion(rctx, session.UserId, rule.Expression); appErr != nil {
return nil, appErr
@@ -212,7 +212,7 @@ func (a *App) CreateOrUpdateAccessControlPolicy(rctx request.CTX, policy *model.
// (e.g., creating a "Clearance == Top Secret" rule without holding that
// clearance themselves). Masking and write-path value validation still
// apply to system admins above.
- if !a.HasPermissionTo(callerID, model.PermissionManageSystem) {
+ if !a.HasPermissionTo(rctx, callerID, model.PermissionManageSystem) {
if appErr := a.checkSelfInclusion(rctx, policy, callerID, mergedHidden); appErr != nil {
return nil, appErr
}
@@ -2381,7 +2381,7 @@ type ValidateAccessControlPolicyPermissionOptions struct {
func (a *App) ValidateAccessControlPolicyPermissionWithOptions(rctx request.CTX, userID, policyID string, opts ValidateAccessControlPolicyPermissionOptions) *model.AppError {
// System admins can manage any policy
- if a.HasPermissionTo(userID, model.PermissionManageSystem) {
+ if a.HasPermissionTo(rctx, userID, model.PermissionManageSystem) {
return nil
}
@@ -2460,7 +2460,7 @@ func (a *App) isSystemPolicyAppliedToChannel(rctx request.CTX, policyID, channel
// ValidateChannelAccessControlPolicyCreation validates if a user can create a channel-specific access control policy
func (a *App) ValidateChannelAccessControlPolicyCreation(rctx request.CTX, userID string, policy *model.AccessControlPolicy) *model.AppError {
// System admins can create any type of policy
- if a.HasPermissionTo(userID, model.PermissionManageSystem) {
+ if a.HasPermissionTo(rctx, userID, model.PermissionManageSystem) {
return nil
}
@@ -2614,7 +2614,7 @@ func (a *App) BuildAccessControlSubject(rctx request.CTX, userID string, roles s
// user.isbot / user.createat) for runtime PDP evaluation. a.GetUser is the
// cached user read and already resolves IsBot via the Bots join, so this is
// a single (usually cache-hit) lookup per subject build.
- user, appErr := a.GetUser(userID)
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
// Fail closed: a native-attribute policy must not silently evaluate
// against zero-valued natives if the user read fails. The caller
diff --git a/server/channels/app/admin.go b/server/channels/app/admin.go
index d956a40e23c7..3aef4566d4ec 100644
--- a/server/channels/app/admin.go
+++ b/server/channels/app/admin.go
@@ -186,7 +186,7 @@ func (a *App) TestEmail(rctx request.CTX, userID string, cfg *model.Config) *mod
return model.NewAppError("testEmail", "api.admin.test_email.reenter_password", nil, "", http.StatusBadRequest)
}
}
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
diff --git a/server/channels/app/authentication.go b/server/channels/app/authentication.go
index 73e7668f8566..05c96facedbc 100644
--- a/server/channels/app/authentication.go
+++ b/server/channels/app/authentication.go
@@ -108,7 +108,7 @@ func (a *App) migratePassword(user *model.User, password string) *model.AppError
}
func (a *App) CheckPasswordAndAllCriteria(rctx request.CTX, userID string, password string, mfaToken string) *model.AppError {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
if err.Id != MissingAccountError {
err.StatusCode = http.StatusInternalServerError
@@ -202,7 +202,7 @@ func (a *App) checkLdapUserPasswordAndAllCriteria(rctx request.CTX, user *model.
// We need to get the latest value of the user from the database. user.Id is empty for first-time LDAP users.
if user.Id != "" {
var err *model.AppError
- user, err = a.GetUser(user.Id)
+ user, err = a.GetUser(rctx, user.Id)
if err != nil {
if err.Id != MissingAccountError {
err.StatusCode = http.StatusInternalServerError
@@ -391,7 +391,7 @@ func (a *App) MFARequired(rctx request.CTX) *model.AppError {
return nil
}
- user, err := a.GetUser(session.UserId)
+ user, err := a.GetUser(rctx, session.UserId)
if err != nil {
return model.NewAppError("MfaRequired", "api.context.get_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
diff --git a/server/channels/app/authentication_test.go b/server/channels/app/authentication_test.go
index a581af0e4e97..7a8dbd6467f3 100644
--- a/server/channels/app/authentication_test.go
+++ b/server/channels/app/authentication_test.go
@@ -82,7 +82,7 @@ func TestCheckPasswordAndAllCriteria(t *testing.T) {
require.Nil(t, appErr)
// setup MFA
- secret, appErr := th.App.GenerateMfaSecret(th.BasicUser.Id)
+ secret, appErr := th.App.GenerateMfaSecret(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
err := th.Server.Store().User().UpdateMfaActive(th.BasicUser.Id, true)
require.NoError(t, err)
@@ -98,7 +98,7 @@ func TestCheckPasswordAndAllCriteria(t *testing.T) {
appErr = th.App.CheckPasswordAndAllCriteria(th.Context, th.BasicUser.Id, password, token)
require.Nil(t, appErr)
- updatedUser, appErr := th.App.GetUser(th.BasicUser.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
require.Equal(t, 0, updatedUser.FailedAttempts, "successful login must reset FailedAttempts")
})
@@ -114,7 +114,7 @@ func TestCheckPasswordAndAllCriteria(t *testing.T) {
require.NotNil(t, appErr)
require.Equal(t, "mfa.validate_token.authenticate.app_error", appErr.Id)
- updatedUser, appErr := th.App.GetUser(th.BasicUser.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
require.Equal(t, 0, updatedUser.FailedAttempts, "MFA probe must not consume a slot")
})
@@ -129,7 +129,7 @@ func TestCheckPasswordAndAllCriteria(t *testing.T) {
require.NotNil(t, appErr)
require.Equal(t, "api.user.check_user_mfa.bad_code.app_error", appErr.Id)
- updatedUser, appErr := th.App.GetUser(th.BasicUser.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
require.Equal(t, 1, updatedUser.FailedAttempts, "real MFA failure must consume a slot")
})
@@ -151,7 +151,7 @@ func TestCheckPasswordAndAllCriteria(t *testing.T) {
require.NotNil(t, appErr)
require.Equal(t, "api.user.check_user_password.invalid_hash.app_error", appErr.Id)
- updatedUser, appErr := th.App.GetUser(badHashUser.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, badHashUser.Id)
require.Nil(t, appErr)
require.Equal(t, 0, updatedUser.FailedAttempts, "backend error must not consume a slot")
})
@@ -232,7 +232,7 @@ func TestDoubleCheckPassword(t *testing.T) {
// DoubleCheckPassword does not re-fetch the user; it inspects user.Password
// directly. Pull a fresh struct that reflects the hash we just wrote.
- user, appErr := th.App.GetUser(th.BasicUser.Id)
+ user, appErr := th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
t.Run("correct password succeeds and resets the counter", func(t *testing.T) {
@@ -242,7 +242,7 @@ func TestDoubleCheckPassword(t *testing.T) {
appErr := th.App.DoubleCheckPassword(th.Context, user, password)
require.Nil(t, appErr)
- updatedUser, appErr := th.App.GetUser(user.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Equal(t, 0, updatedUser.FailedAttempts)
})
@@ -264,14 +264,14 @@ func TestDoubleCheckPassword(t *testing.T) {
err = th.App.Srv().Store().User().UpdateFailedPasswordAttempts(badHashUser.Id, 0)
require.NoError(t, err)
- user, appErr := th.App.GetUser(badHashUser.Id)
+ user, appErr := th.App.GetUser(th.Context, badHashUser.Id)
require.Nil(t, appErr)
appErr = th.App.DoubleCheckPassword(th.Context, user, "any-password")
require.NotNil(t, appErr)
require.Equal(t, "api.user.check_user_password.invalid_hash.app_error", appErr.Id)
- updatedUser, appErr := th.App.GetUser(badHashUser.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, badHashUser.Id)
require.Nil(t, appErr)
require.Equal(t, 0, updatedUser.FailedAttempts, "backend error must not consume a slot")
})
@@ -368,7 +368,7 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
}
if tc.expectedErrID == "api.user.check_user_login_attempts.too_many_ldap.app_error" {
- updatedUser, err := th.App.GetUser(ldapUser.Id)
+ updatedUser, err := th.App.GetUser(th.Context, ldapUser.Id)
require.Nil(t, err)
require.Equal(t, maxFailedLoginAttempts, updatedUser.FailedAttempts)
}
@@ -392,12 +392,12 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
EmailVerified: true,
})
require.Nil(t, appErr)
- secret, appErr := th.App.GenerateMfaSecret(created.Id)
+ secret, appErr := th.App.GenerateMfaSecret(th.Context, created.Id)
require.Nil(t, appErr)
require.NoError(t, th.Server.Store().User().UpdateMfaActive(created.Id, true))
require.NoError(t, th.Server.Store().User().UpdateMfaSecret(created.Id, secret.Secret))
require.NoError(t, th.App.Srv().Store().User().UpdateFailedPasswordAttempts(created.Id, 0))
- created, appErr = th.App.GetUser(created.Id)
+ created, appErr = th.App.GetUser(th.Context, created.Id)
require.Nil(t, appErr)
created.AuthData = &userAuthData
return created, &userAuthData
@@ -429,7 +429,7 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
require.NotNil(t, appErr)
require.Equal(t, "ent.ldap.do_login.invalid_password.app_error", appErr.Id)
- updatedUser, appErr := th.App.GetUser(preCreated.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, preCreated.Id)
require.Nil(t, appErr)
require.Equal(t, 1, updatedUser.FailedAttempts, "first-time LDAP wrong password must be counted")
})
@@ -451,7 +451,7 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
require.NotNil(t, appErr)
require.Equal(t, "api.user.check_user_mfa.bad_code.app_error", appErr.Id)
- updatedUser, appErr := th.App.GetUser(preCreated.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, preCreated.Id)
require.Nil(t, appErr)
require.Equal(t, 1, updatedUser.FailedAttempts, "first-time LDAP wrong MFA must be counted")
})
@@ -471,7 +471,7 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
require.NotNil(t, appErr)
require.Equal(t, "ent.ldap.do_login.unable_to_connect.app_error", appErr.Id)
- updatedUser, appErr := th.App.GetUser(user.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Equal(t, 0, updatedUser.FailedAttempts, "LDAP backend error must refund the slot")
})
@@ -490,7 +490,7 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
require.NotNil(t, appErr)
require.Equal(t, "mfa.validate_token.authenticate.app_error", appErr.Id)
- updatedUser, appErr := th.App.GetUser(preCreated.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, preCreated.Id)
require.Nil(t, appErr)
require.Equal(t, 0, updatedUser.FailedAttempts, "MFA probe on existing LDAP user must not consume a slot")
})
@@ -536,7 +536,7 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
close(start)
require.NoError(t, g.Wait())
- updatedUser, appErr := th.App.GetUser(preCreated.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, preCreated.Id)
require.Nil(t, appErr)
require.Equal(t, maxFailedLoginAttempts, updatedUser.FailedAttempts, "concurrent first-time attempts must not lose increments and must cap at maxAttempts")
})
@@ -567,14 +567,14 @@ func TestCheckLdapUserPasswordConcurrency(t *testing.T) {
require.Nil(t, appErr)
// setup MFA
- secret, appErr := th.App.GenerateMfaSecret(user.Id)
+ secret, appErr := th.App.GenerateMfaSecret(th.Context, user.Id)
require.Nil(t, appErr)
err := th.Server.Store().User().UpdateMfaActive(user.Id, true)
require.NoError(t, err)
err = th.Server.Store().User().UpdateMfaSecret(user.Id, secret.Secret)
require.NoError(t, err)
- user, appErr = th.App.GetUser(user.Id)
+ user, appErr = th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
user.AuthData = &authData
@@ -675,7 +675,7 @@ func TestCheckUserPassword(t *testing.T) {
require.NoError(t, err)
th.App.InvalidateCacheForUser(user.Id)
- updatedUser, appErr := th.App.GetUser(user.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Equal(t, hash, updatedUser.Password)
@@ -706,7 +706,7 @@ func TestCheckUserPassword(t *testing.T) {
err := th.App.checkUserPassword(user, pwd)
require.Nil(t, err)
- updatedUser, err := th.App.GetUser(user.Id)
+ updatedUser, err := th.App.GetUser(th.Context, user.Id)
require.Nil(t, err)
require.NotEqual(t, pwdBcrypt, updatedUser.Password)
require.Contains(t, updatedUser.Password, "$pbkdf2")
@@ -727,7 +727,7 @@ func TestCheckUserPassword(t *testing.T) {
t.Run("empty password", func(t *testing.T) {
user := createUserWithHash(pwdPBKDF2)
- user, err := th.App.GetUser(user.Id)
+ user, err := th.App.GetUser(th.Context, user.Id)
require.Nil(t, err)
err = th.App.checkUserPassword(user, "")
@@ -738,7 +738,7 @@ func TestCheckUserPassword(t *testing.T) {
t.Run("user with empty password hash", func(t *testing.T) {
user := createUserWithHash("")
- user, err := th.App.GetUser(user.Id)
+ user, err := th.App.GetUser(th.Context, user.Id)
require.Nil(t, err)
err = th.App.checkUserPassword(user, pwd)
@@ -762,7 +762,7 @@ func TestCheckUserPassword(t *testing.T) {
appErr := th.App.checkUserPassword(user, pwd)
require.Nil(t, appErr)
- updatedUser, appErr := th.App.GetUser(user.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.NotEqual(t, pwdBcrypt, updatedUser.Password)
require.Contains(t, updatedUser.Password, "$pbkdf2")
@@ -793,7 +793,7 @@ func TestMigratePassword(t *testing.T) {
require.NoError(t, err)
th.App.InvalidateCacheForUser(user.Id)
- updatedUser, appErr := th.App.GetUser(user.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Equal(t, hash, updatedUser.Password)
@@ -806,7 +806,7 @@ func TestMigratePassword(t *testing.T) {
err := th.App.migratePassword(user, pwd)
require.Nil(t, err)
- updatedUser, err := th.App.GetUser(user.Id)
+ updatedUser, err := th.App.GetUser(th.Context, user.Id)
require.Nil(t, err)
require.NotEqual(t, pwdBcrypt, updatedUser.Password)
require.Contains(t, updatedUser.Password, "$pbkdf2")
diff --git a/server/channels/app/authorization.go b/server/channels/app/authorization.go
index d0e6b5b6e6b3..506b7148b93c 100644
--- a/server/channels/app/authorization.go
+++ b/server/channels/app/authorization.go
@@ -247,7 +247,7 @@ func (a *App) SessionHasPermissionToCategory(rctx request.CTX, session model.Ses
return err == nil && category != nil && category.UserId == session.UserId && category.UserId == userID && category.TeamId == teamID
}
-func (a *App) SessionHasPermissionToUser(session model.Session, userID string) bool {
+func (a *App) SessionHasPermissionToUser(rctx request.CTX, session model.Session, userID string) bool {
if userID == "" {
return false
}
@@ -263,7 +263,7 @@ func (a *App) SessionHasPermissionToUser(session model.Session, userID string) b
return false
}
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return false
}
@@ -285,15 +285,15 @@ func (a *App) SessionHasPermissionToUserOrBot(rctx request.CTX, session model.Se
return true
}
if err.Id == "store.sql_bot.get.missing.app_error" && err.Where == "SqlBotStore.Get" {
- if a.SessionHasPermissionToUser(session, userID) {
+ if a.SessionHasPermissionToUser(rctx, session, userID) {
return true
}
}
return false
}
-func (a *App) HasPermissionTo(askingUserId string, permission *model.Permission) bool {
- user, err := a.GetUser(askingUserId)
+func (a *App) HasPermissionTo(rctx request.CTX, askingUserId string, permission *model.Permission) bool {
+ user, err := a.GetUser(rctx, askingUserId)
if err != nil {
return false
}
@@ -313,7 +313,7 @@ func (a *App) HasPermissionToTeam(rctx request.CTX, askingUserId string, teamID
return true
}
}
- return a.HasPermissionTo(askingUserId, permission)
+ return a.HasPermissionTo(rctx, askingUserId, permission)
}
// HasPermissionToChannel determines if the specified user has the given permission on the provided channel.
@@ -351,7 +351,7 @@ func (a *App) HasPermissionToChannel(rctx request.CTX, askingUserId string, chan
return a.HasPermissionToTeam(rctx, askingUserId, channel.TeamId, permission), isMember
}
- return a.HasPermissionTo(askingUserId, permission), isMember
+ return a.HasPermissionTo(rctx, askingUserId, permission), isMember
}
func (a *App) HasPermissionToChannelByPost(rctx request.CTX, askingUserId string, postID string, permission *model.Permission) bool {
@@ -365,15 +365,15 @@ func (a *App) HasPermissionToChannelByPost(rctx request.CTX, askingUserId string
return a.HasPermissionToTeam(rctx, askingUserId, channel.TeamId, permission)
}
- return a.HasPermissionTo(askingUserId, permission)
+ return a.HasPermissionTo(rctx, askingUserId, permission)
}
-func (a *App) HasPermissionToUser(askingUserId string, userID string) bool {
+func (a *App) HasPermissionToUser(rctx request.CTX, askingUserId string, userID string) bool {
if askingUserId == userID {
return true
}
- if a.HasPermissionTo(askingUserId, model.PermissionEditOtherUsers) {
+ if a.HasPermissionTo(rctx, askingUserId, model.PermissionEditOtherUsers) {
return true
}
@@ -614,13 +614,13 @@ func (a *App) hasPropertyFieldPermissionLevel(rctx request.CTX, userID string, f
case model.PermissionLevelNone:
return false
case model.PermissionLevelSysadmin:
- return a.HasPermissionTo(userID, model.PermissionManageSystem)
+ return a.HasPermissionTo(rctx, userID, model.PermissionManageSystem)
case model.PermissionLevelMember:
return a.hasPropertyFieldScopeAccess(rctx, userID, field)
case model.PermissionLevelAdmin:
switch field.TargetType {
case string(model.PropertyFieldTargetLevelSystem):
- return a.HasPermissionTo(userID, model.PermissionManageSystem)
+ return a.HasPermissionTo(rctx, userID, model.PermissionManageSystem)
case string(model.PropertyFieldTargetLevelTeam):
return a.HasPermissionToTeam(rctx, userID, field.TargetID, model.PermissionManageTeam)
case string(model.PropertyFieldTargetLevelChannel):
@@ -640,7 +640,7 @@ func (a *App) hasPropertyFieldPermissionLevel(rctx request.CTX, userID string, f
func (a *App) hasPropertyFieldValuePermissionLevel(rctx request.CTX, userID string, field *model.PropertyField, valueTargetID string, level model.PermissionLevel) bool {
switch level {
case model.PermissionLevelSysadmin:
- return a.HasPermissionTo(userID, model.PermissionManageSystem)
+ return a.HasPermissionTo(rctx, userID, model.PermissionManageSystem)
case model.PermissionLevelAdmin:
return a.hasPropertyFieldValueAdmin(rctx, userID, field, valueTargetID)
case model.PermissionLevelMember:
diff --git a/server/channels/app/authorization_test.go b/server/channels/app/authorization_test.go
index 1aaf96e3a30c..52c9b636f5be 100644
--- a/server/channels/app/authorization_test.go
+++ b/server/channels/app/authorization_test.go
@@ -392,9 +392,9 @@ func TestHasPermissionToUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
- assert.True(t, th.App.HasPermissionToUser(th.SystemAdminUser.Id, th.BasicUser.Id))
- assert.True(t, th.App.HasPermissionToUser(th.BasicUser.Id, th.BasicUser.Id))
- assert.False(t, th.App.HasPermissionToUser(th.BasicUser.Id, th.BasicUser2.Id))
+ assert.True(t, th.App.HasPermissionToUser(th.Context, th.SystemAdminUser.Id, th.BasicUser.Id))
+ assert.True(t, th.App.HasPermissionToUser(th.Context, th.BasicUser.Id, th.BasicUser.Id))
+ assert.False(t, th.App.HasPermissionToUser(th.Context, th.BasicUser.Id, th.BasicUser2.Id))
}
func TestSessionHasPermissionToManageBot(t *testing.T) {
@@ -517,8 +517,8 @@ func TestSessionHasPermissionToUser(t *testing.T) {
UserId: th.BasicUser.Id,
Roles: model.SystemUserRoleId,
}
- assert.True(t, th.App.SessionHasPermissionToUser(session, th.BasicUser.Id))
- assert.False(t, th.App.SessionHasPermissionToUser(session, th.BasicUser2.Id))
+ assert.True(t, th.App.SessionHasPermissionToUser(th.Context, session, th.BasicUser.Id))
+ assert.False(t, th.App.SessionHasPermissionToUser(th.Context, session, th.BasicUser2.Id))
})
t.Run("test user manager access", func(t *testing.T) {
@@ -526,11 +526,11 @@ func TestSessionHasPermissionToUser(t *testing.T) {
UserId: th.BasicUser.Id,
Roles: model.SystemUserManagerRoleId,
}
- assert.False(t, th.App.SessionHasPermissionToUser(session, th.BasicUser2.Id))
+ assert.False(t, th.App.SessionHasPermissionToUser(th.Context, session, th.BasicUser2.Id))
th.AddPermissionToRole(t, model.PermissionEditOtherUsers.Id, model.SystemUserManagerRoleId)
- assert.True(t, th.App.SessionHasPermissionToUser(session, th.BasicUser2.Id))
- assert.False(t, th.App.SessionHasPermissionToUser(session, th.SystemAdminUser.Id))
+ assert.True(t, th.App.SessionHasPermissionToUser(th.Context, session, th.BasicUser2.Id))
+ assert.False(t, th.App.SessionHasPermissionToUser(th.Context, session, th.SystemAdminUser.Id))
th.RemovePermissionFromRole(t, model.PermissionEditOtherUsers.Id, model.SystemUserManagerRoleId)
bot, err := th.App.CreateBot(th.Context, &model.Bot{
@@ -545,7 +545,7 @@ func TestSessionHasPermissionToUser(t *testing.T) {
assert.Nil(t, appErr)
}()
- assert.False(t, th.App.SessionHasPermissionToUser(session, bot.UserId))
+ assert.False(t, th.App.SessionHasPermissionToUser(th.Context, session, bot.UserId))
})
t.Run("test admin user access", func(t *testing.T) {
@@ -553,8 +553,8 @@ func TestSessionHasPermissionToUser(t *testing.T) {
UserId: th.SystemAdminUser.Id,
Roles: model.SystemAdminRoleId,
}
- assert.True(t, th.App.SessionHasPermissionToUser(session, th.BasicUser.Id))
- assert.True(t, th.App.SessionHasPermissionToUser(session, th.BasicUser2.Id))
+ assert.True(t, th.App.SessionHasPermissionToUser(th.Context, session, th.BasicUser.Id))
+ assert.True(t, th.App.SessionHasPermissionToUser(th.Context, session, th.BasicUser2.Id))
})
}
diff --git a/server/channels/app/auto_responder.go b/server/channels/app/auto_responder.go
index fd7be6fd29db..e1d709753439 100644
--- a/server/channels/app/auto_responder.go
+++ b/server/channels/app/auto_responder.go
@@ -36,7 +36,7 @@ func (a *App) SendAutoResponseIfNecessary(rctx request.CTX, channel *model.Chann
receiverId = sender.Id
}
- receiver, aErr := a.GetUser(receiverId)
+ receiver, aErr := a.GetUser(rctx, receiverId)
if aErr != nil {
return false, aErr
}
@@ -99,7 +99,7 @@ func (a *App) SetAutoResponderStatus(rctx request.CTX, user *model.User, oldNoti
}
func (a *App) DisableAutoResponder(rctx request.CTX, userID string, asAdmin bool) *model.AppError {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
diff --git a/server/channels/app/auto_responder_test.go b/server/channels/app/auto_responder_test.go
index 471dcaef54f4..19b07652582e 100644
--- a/server/channels/app/auto_responder_test.go
+++ b/server/channels/app/auto_responder_test.go
@@ -77,13 +77,13 @@ func TestDisableAutoResponder(t *testing.T) {
err := th.App.DisableAutoResponder(th.Context, user.Id, true)
require.Nil(t, err)
- userUpdated1, err := th.App.GetUser(user.Id)
+ userUpdated1, err := th.App.GetUser(th.Context, user.Id)
require.Nil(t, err)
assert.Equal(t, userUpdated1.NotifyProps["auto_responder_active"], "false")
err = th.App.DisableAutoResponder(th.Context, user.Id, true)
require.Nil(t, err)
- userUpdated2, err := th.App.GetUser(user.Id)
+ userUpdated2, err := th.App.GetUser(th.Context, user.Id)
require.Nil(t, err)
assert.Equal(t, userUpdated2.NotifyProps["auto_responder_active"], "false")
@@ -191,7 +191,7 @@ func TestSendAutoResponseIfNecessary(t *testing.T) {
})
assert.Nil(t, err)
- botUser, err := th.App.GetUser(bot.UserId)
+ botUser, err := th.App.GetUser(th.Context, bot.UserId)
assert.Nil(t, err)
savedPost, _, _ := th.App.CreatePost(th.Context, &model.Post{
diff --git a/server/channels/app/bot.go b/server/channels/app/bot.go
index fd211ffe34e1..59e97198198e 100644
--- a/server/channels/app/bot.go
+++ b/server/channels/app/bot.go
@@ -607,7 +607,7 @@ func (a *App) notifySysadminsBotOwnerDeactivated(rctx request.CTX, userID string
}
// user being disabled
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
@@ -679,7 +679,7 @@ func (a *App) ConvertUserToBot(rctx request.CTX, user *model.User) (*model.Bot,
}
// Refresh user data
- updatedUser, err := a.GetUser(user.Id)
+ updatedUser, err := a.GetUser(rctx, user.Id)
if err != nil {
return nil, err
}
diff --git a/server/channels/app/bot_test.go b/server/channels/app/bot_test.go
index 9a6b618f4fd0..bb9dce805355 100644
--- a/server/channels/app/bot_test.go
+++ b/server/channels/app/bot_test.go
@@ -86,7 +86,7 @@ func TestCreateBot(t *testing.T) {
assert.Equal(t, "a bot", bot.Description)
assert.Equal(t, th.BasicUser.Id, bot.OwnerId)
- user, err := th.App.GetUser(bot.UserId)
+ user, err := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, err)
// Check that a post was created to add bot to team and channels
@@ -658,7 +658,7 @@ func TestUpdateBotActive(t *testing.T) {
require.Nil(t, err)
require.Zero(t, refetched.DeleteAt)
- botUser, err := th.App.GetUser(protectedBot.UserId)
+ botUser, err := th.App.GetUser(th.Context, protectedBot.UserId)
require.Nil(t, err)
require.Zero(t, botUser.DeleteAt)
})
@@ -833,7 +833,7 @@ func TestDisableUserBots(t *testing.T) {
require.Nil(t, err)
require.Zero(t, bot.DeleteAt)
- user, err := th.App.GetUser(u2bot1.UserId)
+ user, err := th.App.GetUser(th.Context, u2bot1.UserId)
require.Nil(t, err)
require.Zero(t, user.DeleteAt)
@@ -1059,7 +1059,7 @@ func TestConvertUserToBot(t *testing.T) {
require.Nil(t, err)
// Verify OAuth credentials are set
- oauthUser, appErr := th.App.GetUser(oauthUser.Id)
+ oauthUser, appErr := th.App.GetUser(th.Context, oauthUser.Id)
require.Nil(t, appErr)
require.Equal(t, "google", oauthUser.AuthService)
require.NotNil(t, oauthUser.AuthData)
@@ -1073,7 +1073,7 @@ func TestConvertUserToBot(t *testing.T) {
}()
// Get updated user and verify OAuth credentials are cleared
- updatedUser, err := th.App.GetUser(oauthUser.Id)
+ updatedUser, err := th.App.GetUser(th.Context, oauthUser.Id)
require.Nil(t, err)
assert.Empty(t, updatedUser.AuthService)
// AuthData may be empty string instead of nil in the database
@@ -1127,7 +1127,7 @@ func TestGetSystemBot(t *testing.T) {
// before the protection guard existed: deactivate the underlying user
// and mark the bot record deleted directly in the store (bypassing
// UpdateBotActive's guard).
- botUser, err := th.App.GetUser(bot.UserId)
+ botUser, err := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, err)
_, err = th.App.UpdateActive(th.Context, botUser, false)
require.Nil(t, err)
@@ -1148,7 +1148,7 @@ func TestGetSystemBot(t *testing.T) {
require.Nil(t, err)
require.Zero(t, healed.DeleteAt)
- healedUser, err := th.App.GetUser(bot.UserId)
+ healedUser, err := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, err)
require.Zero(t, healedUser.DeleteAt)
})
@@ -1167,7 +1167,7 @@ func TestSystemBotProtectedFromOwnerDeactivation(t *testing.T) {
require.Nil(t, err)
require.Equal(t, model.BotSystemBotUsername, systemBot.Username)
- owner, err := th.App.GetUser(systemBot.OwnerId)
+ owner, err := th.App.GetUser(th.Context, systemBot.OwnerId)
require.Nil(t, err)
// A regular bot owned by the same user, to confirm the guard is scoped to
diff --git a/server/channels/app/channel.go b/server/channels/app/channel.go
index 30b754bd7b18..d2f20bed4290 100644
--- a/server/channels/app/channel.go
+++ b/server/channels/app/channel.go
@@ -192,7 +192,7 @@ func (a *App) CreateChannelWithUser(rctx request.CTX, channel *model.Channel, us
a.addChannelToDefaultCategory(rctx, userID, channel)
var user *model.User
- if user, err = a.GetUser(userID); err != nil {
+ if user, err = a.GetUser(rctx, userID); err != nil {
return nil, err
}
@@ -2014,7 +2014,7 @@ func (a *App) AddChannelMember(rctx request.CTX, userID string, channel *model.C
var user *model.User
var err *model.AppError
- if user, err = a.GetUser(userID); err != nil {
+ if user, err = a.GetUser(rctx, userID); err != nil {
return nil, err
}
@@ -2024,7 +2024,7 @@ func (a *App) AddChannelMember(rctx request.CTX, userID string, channel *model.C
var userRequestor *model.User
if opts.UserRequestorID != "" {
- if userRequestor, err = a.GetUser(opts.UserRequestorID); err != nil {
+ if userRequestor, err = a.GetUser(rctx, opts.UserRequestorID); err != nil {
return nil, err
}
}
@@ -3078,7 +3078,7 @@ func (a *App) removeUserFromChannel(rctx request.CTX, userIDToRemove string, rem
var actorUser *model.User
if removerUserId != "" {
- actorUser, _ = a.GetUser(removerUserId)
+ actorUser, _ = a.GetUser(rctx, removerUserId)
}
a.Srv().Go(func() {
@@ -3123,7 +3123,7 @@ func (a *App) RemoveUserFromChannel(rctx request.CTX, userIDToRemove string, rem
}
var user *model.User
- if user, err = a.GetUser(userIDToRemove); err != nil {
+ if user, err = a.GetUser(rctx, userIDToRemove); err != nil {
return err
}
@@ -3236,7 +3236,7 @@ func (a *App) MarkChannelAsUnreadFromPost(rctx request.CTX, postID string, userI
return nil, err
}
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return nil, err
}
@@ -3263,7 +3263,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(rctx request.CTX, postID
return nil, appErr
}
- user, appErr := a.GetUser(userID)
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return nil, appErr
}
@@ -3380,7 +3380,7 @@ func (a *App) AutocompleteChannels(rctx request.CTX, userID, term string) (model
includeDeleted := true
term = strings.TrimSpace(term)
- user, appErr := a.GetUser(userID)
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return nil, appErr
}
@@ -3401,7 +3401,7 @@ func (a *App) AutocompleteChannelsForTeam(rctx request.CTX, teamID, userID, term
includeDeleted := true
term = strings.TrimSpace(term)
- user, appErr := a.GetUser(userID)
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return nil, appErr
}
@@ -3418,7 +3418,7 @@ func (a *App) AutocompleteChannelsForTeamFiltered(rctx request.CTX, teamID, user
includeDeleted := true
term = strings.TrimSpace(term)
- user, appErr := a.GetUser(userID)
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return nil, appErr
}
@@ -4431,7 +4431,7 @@ func (a *App) validateForConvertGroupMessageToChannel(rctx request.CTX, converte
}
func (a *App) postMessageForConvertGroupMessageToChannel(rctx request.CTX, channelID, convertedByUserId string, channelUsers []*model.User) *model.AppError {
- convertedByUser, appErr := a.GetUser(convertedByUserId)
+ convertedByUser, appErr := a.GetUser(rctx, convertedByUserId)
if appErr != nil {
return appErr
}
@@ -4633,7 +4633,7 @@ func (a *App) GetRecommendedPublicChannelsForUser(rctx request.CTX, userID, team
return model.ChannelList{}, nil
}
- user, appErr := a.GetUser(userID)
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return nil, appErr
}
diff --git a/server/channels/app/channel_discoverable_visibility.go b/server/channels/app/channel_discoverable_visibility.go
index 95cf7a2c012a..aa7ad006fac4 100644
--- a/server/channels/app/channel_discoverable_visibility.go
+++ b/server/channels/app/channel_discoverable_visibility.go
@@ -108,7 +108,7 @@ func (a *App) FilterDiscoverableChannelsByPolicy(rctx request.CTX, channels []*m
}
userOnce.Do(func() {
- user, userErr = a.GetUser(userID)
+ user, userErr = a.GetUser(rctx, userID)
})
if userErr != nil {
return nil, userErr
@@ -211,7 +211,7 @@ func (a *App) FilterChannelListWithTeamDataForUserVisibility(rctx request.CTX, c
}
userOnce.Do(func() {
- user, userErr = a.GetUser(userID)
+ user, userErr = a.GetUser(rctx, userID)
})
if userErr != nil {
return nil, 0, userErr
diff --git a/server/channels/app/channel_join_request.go b/server/channels/app/channel_join_request.go
index 05b8c98e5034..c9b5a2e8126a 100644
--- a/server/channels/app/channel_join_request.go
+++ b/server/channels/app/channel_join_request.go
@@ -65,7 +65,7 @@ func (a *App) requestJoinChannelGuard(rctx request.CTX, user *model.User, channe
// admin review, or nil when the user was added directly to the channel (the
// caller can detect this via the `joined` return value).
func (a *App) RequestJoinChannel(rctx request.CTX, userID, channelID, message string) (joined bool, req *model.ChannelJoinRequest, appErr *model.AppError) {
- user, appErr := a.GetUser(userID)
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return false, nil, appErr
}
diff --git a/server/channels/app/channel_test.go b/server/channels/app/channel_test.go
index 3b9e26a862f4..83f111c58db8 100644
--- a/server/channels/app/channel_test.go
+++ b/server/channels/app/channel_test.go
@@ -2164,7 +2164,7 @@ func TestAddUserToChannel(t *testing.T) {
require.Nil(t, appErr)
}()
bot := th.CreateBot(t)
- botUser, _ := th.App.GetUser(bot.UserId)
+ botUser, _ := th.App.GetUser(th.Context, bot.UserId)
defer func() {
appErr := th.App.PermanentDeleteBot(th.Context, botUser.Id)
require.Nil(t, appErr)
@@ -2259,7 +2259,7 @@ func TestRemoveUserFromChannel(t *testing.T) {
}()
bot := th.CreateBot(t)
- botUser, _ := th.App.GetUser(bot.UserId)
+ botUser, _ := th.App.GetUser(th.Context, bot.UserId)
defer func() {
appErr := th.App.PermanentDeleteBot(th.Context, botUser.Id)
require.Nil(t, appErr)
diff --git a/server/channels/app/content_flagging.go b/server/channels/app/content_flagging.go
index babfb50990de..d56434af346e 100644
--- a/server/channels/app/content_flagging.go
+++ b/server/channels/app/content_flagging.go
@@ -96,7 +96,7 @@ func (a *App) FlagPost(rctx request.CTX, post *model.Post, teamId, reportingUser
return model.NewAppError("FlagPost", "app.data_spillage.get_group.error", nil, "", http.StatusInternalServerError).Wrap(appErr)
}
- reportingUser, appErr := a.GetUser(reportingUserId)
+ reportingUser, appErr := a.GetUser(rctx, reportingUserId)
if appErr != nil {
return appErr
}
@@ -338,7 +338,7 @@ func (a *App) createContentReviewPost(rctx request.CTX, flaggedPostId, teamId, r
return appErr
}
- reportingUser, appErr := a.GetUser(reportingUserId)
+ reportingUser, appErr := a.GetUser(rctx, reportingUserId)
if appErr != nil {
return appErr
}
@@ -353,7 +353,7 @@ func (a *App) createContentReviewPost(rctx request.CTX, flaggedPostId, teamId, r
return appErr
}
- flaggedPostAuthor, appErr := a.GetUser(flaggedPostAuthorId)
+ flaggedPostAuthor, appErr := a.GetUser(rctx, flaggedPostAuthorId)
if appErr != nil {
return appErr
}
@@ -523,7 +523,7 @@ func (a *App) getAllUsersInTeamForRoles(teamId string, systemRoles, teamRoles []
}
func (a *App) sendContentFlaggingConfirmationMessage(rctx request.CTX, flaggingUserId, flaggedPostAuthorId, channelID string) *model.AppError {
- flaggedPostAuthor, appErr := a.GetUser(flaggedPostAuthorId)
+ flaggedPostAuthor, appErr := a.GetUser(rctx, flaggedPostAuthorId)
if appErr != nil {
return appErr
}
@@ -1216,7 +1216,7 @@ func (a *App) postAssignReviewerMessage(rctx request.CTX, contentFlaggingGroupId
return nil, nil
}
- reviewerUser, appErr := a.GetUser(reviewerId)
+ reviewerUser, appErr := a.GetUser(rctx, reviewerId)
if appErr != nil {
return nil, appErr
}
@@ -1225,7 +1225,7 @@ func (a *App) postAssignReviewerMessage(rctx request.CTX, contentFlaggingGroupId
if reviewerId == assignedById {
assignedByUser = reviewerUser
} else {
- assignedByUser, appErr = a.GetUser(assignedById)
+ assignedByUser, appErr = a.GetUser(rctx, assignedById)
if appErr != nil {
return nil, appErr
}
@@ -1236,7 +1236,7 @@ func (a *App) postAssignReviewerMessage(rctx request.CTX, contentFlaggingGroupId
}
func (a *App) postDeletePostReviewerMessage(rctx request.CTX, flaggedPostId, actorUserId, comment, contentFlaggingGroupId string) ([]*model.Post, *model.AppError) {
- actorUser, appErr := a.GetUser(actorUserId)
+ actorUser, appErr := a.GetUser(rctx, actorUserId)
if appErr != nil {
return nil, appErr
}
@@ -1250,7 +1250,7 @@ func (a *App) postDeletePostReviewerMessage(rctx request.CTX, flaggedPostId, act
}
func (a *App) postKeepPostReviewerMessage(rctx request.CTX, flaggedPostId, actorUserId, comment, contentFlaggingGroupId string) ([]*model.Post, *model.AppError) {
- actorUser, appErr := a.GetUser(actorUserId)
+ actorUser, appErr := a.GetUser(rctx, actorUserId)
if appErr != nil {
return nil, appErr
}
@@ -1373,7 +1373,7 @@ func (a *App) postReviewerMessage(rctx request.CTX, message, contentFlaggingGrou
T := i18n.GetUserTranslations("")
// Fetch reviewer user to get their locale
reviewerUserId := channel.GetOtherUserIdForDM(reviewerPost.UserId)
- reviewer, userErr := a.GetUser(reviewerUserId)
+ reviewer, userErr := a.GetUser(rctx, reviewerUserId)
if userErr != nil {
rctx.Logger().Error("Failed to get reviewer user for localization, falling back to default locale", mlog.Err(userErr), mlog.String("user_id", reviewerPost.UserId))
} else {
diff --git a/server/channels/app/content_flagging_exposure_report_test.go b/server/channels/app/content_flagging_exposure_report_test.go
index 8ae88e5b74c2..7ba5a7eef8c0 100644
--- a/server/channels/app/content_flagging_exposure_report_test.go
+++ b/server/channels/app/content_flagging_exposure_report_test.go
@@ -277,7 +277,7 @@ func TestComputePostExposure(t *testing.T) {
t.Run("excludes bots", func(t *testing.T) {
channel := th.CreateChannel(t, th.BasicTeam)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
th.AddUserToChannel(t, botUser, channel)
diff --git a/server/channels/app/content_flagging_report.go b/server/channels/app/content_flagging_report.go
index 4b4db2fab977..dcfd0b223581 100644
--- a/server/channels/app/content_flagging_report.go
+++ b/server/channels/app/content_flagging_report.go
@@ -92,7 +92,7 @@ func (a *App) writeFlaggedPostReport(rctx request.CTX, zw *zip.Writer, postID, g
if appErr := a.writeExposureReportEntry(rctx, zw, rc.Post.Id); appErr != nil {
return appErr
}
- if appErr := a.writeReportMetadataEntry(zw, generatedByUserID); appErr != nil {
+ if appErr := a.writeReportMetadataEntry(rctx, zw, generatedByUserID); appErr != nil {
return appErr
}
@@ -136,7 +136,7 @@ func (a *App) loadFlaggedPostReportContext(rctx request.CTX, postID string) (*mo
}
}
- author, appErr := a.GetUser(post.UserId)
+ author, appErr := a.GetUser(rctx, post.UserId)
if appErr != nil {
return nil, appErr
}
@@ -212,8 +212,8 @@ func (a *App) writeContentReviewEntry(rctx request.CTX, zw *zip.Writer, post *mo
return nil
}
-func (a *App) writeReportMetadataEntry(zw *zip.Writer, generatedByUserID string) *model.AppError {
- generator, appErr := a.GetUser(generatedByUserID)
+func (a *App) writeReportMetadataEntry(rctx request.CTX, zw *zip.Writer, generatedByUserID string) *model.AppError {
+ generator, appErr := a.GetUser(rctx, generatedByUserID)
if appErr != nil {
return appErr
}
@@ -298,7 +298,7 @@ func (a *App) buildContentReviewYAML(rctx request.CTX, post *model.Post, generat
out.Hidden = postHiddenByContentFlagging
if reporterID := out.ReporterUserID; reporterID != "" {
- if u, uErr := a.GetUser(reporterID); uErr == nil {
+ if u, uErr := a.GetUser(rctx, reporterID); uErr == nil {
out.ReporterUsername = u.Username
} else {
rctx.Logger().Warn("Failed to fetch reporter user for flagged post report", mlog.String("user_id", reporterID), mlog.Err(uErr))
@@ -325,7 +325,7 @@ func (a *App) buildContentReviewYAML(rctx request.CTX, post *model.Post, generat
}
if actorUserId != "" {
- if u, uErr := a.GetUser(actorUserId); uErr == nil {
+ if u, uErr := a.GetUser(rctx, actorUserId); uErr == nil {
out.ActorUsername = u.Username
out.ActorUserId = u.Id
} else {
@@ -345,7 +345,7 @@ func (a *App) buildContentReviewYAML(rctx request.CTX, post *model.Post, generat
}
if reviewerID != "" {
- if u, uErr := a.GetUser(reviewerID); uErr == nil {
+ if u, uErr := a.GetUser(rctx, reviewerID); uErr == nil {
out.ReviewerUsername = u.Username
} else {
rctx.Logger().Warn("Failed to fetch reviewer user for flagged post report", mlog.String("user_id", reviewerID), mlog.Err(uErr))
@@ -464,7 +464,7 @@ func (a *App) notifyReviewersOfReportGeneration(rctx request.CTX, flaggedPostID,
return
}
- generator, appErr := a.GetUser(generatedByUserID)
+ generator, appErr := a.GetUser(rctx, generatedByUserID)
if appErr != nil {
rctx.Logger().Warn("Failed to fetch generating user for report generation notification", mlog.Err(appErr))
return
diff --git a/server/channels/app/desktop_login.go b/server/channels/app/desktop_login.go
index aff30ca3f1e7..d23f7d4f6ae0 100644
--- a/server/channels/app/desktop_login.go
+++ b/server/channels/app/desktop_login.go
@@ -8,6 +8,7 @@ import (
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
+ "github.com/mattermost/mattermost/server/public/shared/request"
)
func (a *App) GenerateAndSaveDesktopToken(createAt int64, user *model.User) (*string, *model.AppError) {
@@ -24,7 +25,7 @@ func (a *App) GenerateAndSaveDesktopToken(createAt int64, user *model.User) (*st
return &token, nil
}
-func (a *App) ValidateDesktopToken(token string, expiryTime int64) (*model.User, *model.AppError) {
+func (a *App) ValidateDesktopToken(rctx request.CTX, token string, expiryTime int64) (*model.User, *model.AppError) {
// Check if token is valid
userId, err := a.Srv().Store().DesktopTokens().GetUserId(token, expiryTime)
if err != nil {
@@ -36,7 +37,7 @@ func (a *App) ValidateDesktopToken(token string, expiryTime int64) (*model.User,
}
// Get the user profile
- user, userErr := a.GetUser(*userId)
+ user, userErr := a.GetUser(rctx, *userId)
if userErr != nil {
// Delete the token if the user is invalid somehow
if deleteErr := a.Srv().Store().DesktopTokens().Delete(token); deleteErr != nil {
diff --git a/server/channels/app/desktop_login_test.go b/server/channels/app/desktop_login_test.go
index 06af63acaca6..f9bb181cb979 100644
--- a/server/channels/app/desktop_login_test.go
+++ b/server/channels/app/desktop_login_test.go
@@ -46,28 +46,28 @@ func TestValidateDesktopToken(t *testing.T) {
require.NotNil(t, badUserServerToken)
t.Run("validate token", func(t *testing.T) {
- user, err := th.App.ValidateDesktopToken(*authenticatedServerToken, time.Now().Add(-TTL).Unix())
+ user, err := th.App.ValidateDesktopToken(th.Context, *authenticatedServerToken, time.Now().Add(-TTL).Unix())
assert.Nil(t, err)
assert.NotNil(t, user)
assert.Equal(t, th.BasicUser.Id, user.Id)
})
t.Run("validate token - expired", func(t *testing.T) {
- user, err := th.App.ValidateDesktopToken(*expiredServerToken, time.Now().Add(-TTL).Unix())
+ user, err := th.App.ValidateDesktopToken(th.Context, *expiredServerToken, time.Now().Add(-TTL).Unix())
assert.NotNil(t, err)
assert.Nil(t, user)
assert.Equal(t, "app.desktop_token.validate.invalid", err.Id)
})
t.Run("validate token - not authenticated", func(t *testing.T) {
- user, err := th.App.ValidateDesktopToken("not_real_token", time.Now().Add(-TTL).Unix())
+ user, err := th.App.ValidateDesktopToken(th.Context, "not_real_token", time.Now().Add(-TTL).Unix())
assert.NotNil(t, err)
assert.Nil(t, user)
assert.Equal(t, "app.desktop_token.validate.invalid", err.Id)
})
t.Run("validate token - bad user id", func(t *testing.T) {
- user, err := th.App.ValidateDesktopToken(*badUserServerToken, time.Now().Add(-TTL).Unix())
+ user, err := th.App.ValidateDesktopToken(th.Context, *badUserServerToken, time.Now().Add(-TTL).Unix())
assert.NotNil(t, err)
assert.Nil(t, user)
assert.Equal(t, "app.desktop_token.validate.no_user", err.Id)
diff --git a/server/channels/app/expired_access_token_notify.go b/server/channels/app/expired_access_token_notify.go
index 569babe6b843..5115f1e47e53 100644
--- a/server/channels/app/expired_access_token_notify.go
+++ b/server/channels/app/expired_access_token_notify.go
@@ -35,7 +35,7 @@ func (a *App) NotifyExpiredAccessTokensDeleted(rctx request.CTX, tokens []*model
}
for _, token := range tokens {
- user, appErr := a.GetUser(token.UserId)
+ user, appErr := a.GetUser(rctx, token.UserId)
if appErr != nil {
rctx.Logger().Warn("Failed to get user for expired personal access token notification",
mlog.String("user_id", token.UserId),
diff --git a/server/channels/app/expirynotify.go b/server/channels/app/expirynotify.go
index 93fc7cc5dd7d..2cb4f0ad85de 100644
--- a/server/channels/app/expirynotify.go
+++ b/server/channels/app/expirynotify.go
@@ -44,7 +44,6 @@ func (a *App) NotifySessionsExpired() error {
tmpMessage := msg.DeepCopy()
tmpMessage.SetDeviceIdAndPlatform(session.DeviceId)
tmpMessage.AckId = model.NewId()
- tmpMessage.Message = a.getSessionExpiredPushMessage(session)
rctx := request.EmptyContext(a.Log().With(
mlog.String("type", model.NotificationTypePush),
@@ -56,6 +55,8 @@ func (a *App) NotifySessionsExpired() error {
mlog.String("post_id", msg.PostId),
))
+ tmpMessage.Message = a.getSessionExpiredPushMessage(rctx, session)
+
errPush := a.sendToPushProxy(rctx, tmpMessage, session)
if errPush != nil {
reason := model.NotificationReasonPushProxySendError
@@ -91,9 +92,9 @@ func (a *App) NotifySessionsExpired() error {
return nil
}
-func (a *App) getSessionExpiredPushMessage(session *model.Session) string {
+func (a *App) getSessionExpiredPushMessage(rctx request.CTX, session *model.Session) string {
locale := model.DefaultLocale
- user, err := a.GetUser(session.UserId)
+ user, err := a.GetUser(rctx, session.UserId)
if err == nil {
locale = user.Locale
}
diff --git a/server/channels/app/file.go b/server/channels/app/file.go
index faa170216813..0a5f5910a66c 100644
--- a/server/channels/app/file.go
+++ b/server/channels/app/file.go
@@ -1619,7 +1619,7 @@ func (a *App) buildFileDownloadSubject(rctx request.CTX, userID string) (*model.
if rctx.Session().UserId == userID {
subject, appErr = a.BuildAccessControlSubjectForSession(rctx, "")
} else {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
rctx.Logger().Warn("Failed to get user for file download permission filtering",
mlog.String("user_id", userID),
diff --git a/server/channels/app/helper_test.go b/server/channels/app/helper_test.go
index 7a1b5316aba0..681603340317 100644
--- a/server/channels/app/helper_test.go
+++ b/server/channels/app/helper_test.go
@@ -328,15 +328,15 @@ func (th *TestHelper) InitBasic(tb testing.TB) *TestHelper {
th.SystemAdminUser = th.CreateUser(tb)
_, appErr := th.App.UpdateUserRoles(th.Context, th.SystemAdminUser.Id, model.SystemUserRoleId+" "+model.SystemAdminRoleId, false)
require.Nil(tb, appErr)
- th.SystemAdminUser, appErr = th.App.GetUser(th.SystemAdminUser.Id)
+ th.SystemAdminUser, appErr = th.App.GetUser(th.Context, th.SystemAdminUser.Id)
require.Nil(tb, appErr)
th.BasicUser = th.CreateUser(tb)
- th.BasicUser, appErr = th.App.GetUser(th.BasicUser.Id)
+ th.BasicUser, appErr = th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(tb, appErr)
th.BasicUser2 = th.CreateUser(tb)
- th.BasicUser2, appErr = th.App.GetUser(th.BasicUser2.Id)
+ th.BasicUser2, appErr = th.App.GetUser(th.Context, th.BasicUser2.Id)
require.Nil(tb, appErr)
th.BasicTeam = th.CreateTeam(tb)
@@ -846,7 +846,7 @@ func (th *TestHelper) SetUserRemoteID(tb testing.TB, userID, remoteID string) *m
require.NoError(tb, err)
th.App.InvalidateCacheForUser(userID)
- user, appErr := th.App.GetUser(userID)
+ user, appErr := th.App.GetUser(th.Context, userID)
require.Nil(tb, appErr)
return user
}
diff --git a/server/channels/app/import_functions.go b/server/channels/app/import_functions.go
index 243c9bc139f1..cf088cf1fc50 100644
--- a/server/channels/app/import_functions.go
+++ b/server/channels/app/import_functions.go
@@ -593,7 +593,7 @@ func (a *App) importUser(rctx request.CTX, data *imports.UserImportData, dryRun
if appErr = a.updateUserNotifyProps(user.Id, user.NotifyProps); appErr != nil {
return appErr
}
- if savedUser, appErr = a.GetUser(user.Id); appErr != nil {
+ if savedUser, appErr = a.GetUser(rctx, user.Id); appErr != nil {
return appErr
}
}
@@ -616,7 +616,7 @@ func (a *App) importUser(rctx request.CTX, data *imports.UserImportData, dryRun
}
if emailVerified {
if hasUserEmailVerifiedChanged {
- if err := a.VerifyUserEmail(user.Id, user.Email); err != nil {
+ if err := a.VerifyUserEmail(rctx, user.Id, user.Email); err != nil {
return err
}
}
diff --git a/server/channels/app/integration_action_test.go b/server/channels/app/integration_action_test.go
index dca9568740db..6c2ffae21b22 100644
--- a/server/channels/app/integration_action_test.go
+++ b/server/channels/app/integration_action_test.go
@@ -2531,7 +2531,7 @@ func buildMmBlocksActionsProp(id, url string, context map[string]any) map[string
func setupBotInChannel(t *testing.T, th *TestHelper) *model.User {
t.Helper()
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, botUser.Id, "")
require.Nil(t, appErr)
diff --git a/server/channels/app/login.go b/server/channels/app/login.go
index fe03113667f2..0f3df6779563 100644
--- a/server/channels/app/login.go
+++ b/server/channels/app/login.go
@@ -101,7 +101,7 @@ func (a *App) GetUserForLogin(rctx request.CTX, id, loginId string) (*model.User
if enableEmail || enableUsername {
// If we are given a userID then fail if we can't find a user with that ID
if id != "" {
- user, err := a.GetUser(id)
+ user, err := a.GetUser(rctx, id)
if err != nil {
if err.Id != MissingAccountError {
err.StatusCode = http.StatusInternalServerError
diff --git a/server/channels/app/notification_test.go b/server/channels/app/notification_test.go
index 999c6267c14c..20604bc3b3ee 100644
--- a/server/channels/app/notification_test.go
+++ b/server/channels/app/notification_test.go
@@ -442,7 +442,7 @@ func TestSendNotifications_SilentPostBroadcastsPosted(t *testing.T) {
th.AddUserToChannel(t, th.BasicUser2, th.BasicChannel)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
@@ -481,7 +481,7 @@ func TestCreatePostSilentBroadcastsPostedWithProps(t *testing.T) {
th.AddUserToChannel(t, th.BasicUser2, th.BasicChannel)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
@@ -521,7 +521,7 @@ func TestSendNotifications_SilentSkipsGroupMention(t *testing.T) {
th.AddUserToChannel(t, th.BasicUser2, th.BasicChannel)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
@@ -566,7 +566,7 @@ func TestSendNotifications_SilentSkipsCRTFollowers(t *testing.T) {
th.AddUserToChannel(t, th.BasicUser2, th.BasicChannel)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
diff --git a/server/channels/app/notify_expiring_access_tokens.go b/server/channels/app/notify_expiring_access_tokens.go
index 0648fccc0612..a4fd85d37088 100644
--- a/server/channels/app/notify_expiring_access_tokens.go
+++ b/server/channels/app/notify_expiring_access_tokens.go
@@ -117,7 +117,7 @@ func (a *App) sendAccessTokenExpiryNotification(rctx request.CTX, systemBot *mod
return appErr
}
- user, appErr := a.GetUser(token.UserId)
+ user, appErr := a.GetUser(rctx, token.UserId)
if appErr != nil {
return appErr
}
diff --git a/server/channels/app/oauth.go b/server/channels/app/oauth.go
index bf0a5fdb9692..d85c6e982f3e 100644
--- a/server/channels/app/oauth.go
+++ b/server/channels/app/oauth.go
@@ -279,7 +279,7 @@ func (a *App) GetOAuthAccessTokenForImplicitFlow(rctx request.CTX, userID string
return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "", http.StatusNotFound).Wrap(err)
}
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return nil, err
}
diff --git a/server/channels/app/oauth_test.go b/server/channels/app/oauth_test.go
index 24b82257eaa5..5bcfc25d6090 100644
--- a/server/channels/app/oauth_test.go
+++ b/server/channels/app/oauth_test.go
@@ -1633,7 +1633,7 @@ func TestSwitchOAuthToEmail(t *testing.T) {
require.NoError(t, err)
th.App.InvalidateCacheForUser(th.BasicUser.Id)
- user, appErr := th.App.GetUser(th.BasicUser.Id)
+ user, appErr := th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(t, appErr)
require.Equal(t, model.UserAuthServiceGitlab, user.AuthService)
@@ -1650,7 +1650,7 @@ func TestSwitchOAuthToEmail(t *testing.T) {
require.Equal(t, "api.user.oauth_to_email.integration_session.app_error", appErr.Id)
require.Equal(t, http.StatusForbidden, appErr.StatusCode)
- user, appErr = th.App.GetUser(user.Id)
+ user, appErr = th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Equal(t, model.UserAuthServiceGitlab, user.AuthService)
})
@@ -1664,7 +1664,7 @@ func TestSwitchOAuthToEmail(t *testing.T) {
require.Nil(t, appErr)
require.Equal(t, "/login?extra=signin_change", link)
- user, appErr = th.App.GetUser(user.Id)
+ user, appErr = th.App.GetUser(th.Context, user.Id)
require.Nil(t, appErr)
require.Empty(t, user.AuthService)
})
@@ -1982,7 +1982,7 @@ func TestLoginByIntune_BotAccountBlocked(t *testing.T) {
// Create bot account
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
// Create mock Intune interface that returns bot user
@@ -2028,7 +2028,7 @@ func TestLoginByIntune_AccountLocked(t *testing.T) {
require.Nil(t, appErr)
// Reload user to get updated DeleteAt
- deletedUser, appErr = th.App.GetUser(deletedUser.Id)
+ deletedUser, appErr = th.App.GetUser(th.Context, deletedUser.Id)
require.Nil(t, appErr)
// Create mock Intune interface that returns deleted user
diff --git a/server/channels/app/plugin_access_control.go b/server/channels/app/plugin_access_control.go
index e186f99d15e2..2d5d046b65ef 100644
--- a/server/channels/app/plugin_access_control.go
+++ b/server/channels/app/plugin_access_control.go
@@ -47,11 +47,11 @@ func (a *App) pluginAccessControlAvailable() bool {
// validatePluginActingUser validates actingUserID is a well-formed ID of an
// existing user. Permission checks are the calling plugin's responsibility.
-func (a *App) validatePluginActingUser(where, actingUserID string) *model.AppError {
+func (a *App) validatePluginActingUser(rctx request.CTX, where, actingUserID string) *model.AppError {
if !model.IsValidId(actingUserID) {
return model.NewAppError(where, "app.access_control.plugin.invalid_acting_user.app_error", nil, "", http.StatusBadRequest)
}
- if _, appErr := a.GetUser(actingUserID); appErr != nil {
+ if _, appErr := a.GetUser(rctx, actingUserID); appErr != nil {
return model.NewAppError(where, "app.access_control.plugin.invalid_acting_user.app_error", nil, "", http.StatusBadRequest).Wrap(appErr)
}
return nil
@@ -121,7 +121,7 @@ func (a *App) EvaluatePluginAccessRequest(rctx request.CTX, pluginID, userID, re
return a.resolvePluginPolicyExistence(rctx, where, pluginID, resourceType, resourceID, "abac_unavailable")
}
- user, appErr := a.GetUser(userID)
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
rctx.Logger().Warn("Plugin access evaluation: failed to load user; resolving policy existence",
mlog.String("plugin_id", pluginID),
@@ -185,7 +185,7 @@ func (a *App) SavePluginAccessControlPolicy(rctx request.CTX, pluginID, actingUs
if appErr := a.pluginAccessControlScopeCheck("SavePluginAccessControlPolicy", pluginID, policy.Type); appErr != nil {
return nil, appErr
}
- if appErr := a.validatePluginActingUser("SavePluginAccessControlPolicy", actingUserID); appErr != nil {
+ if appErr := a.validatePluginActingUser(rctx, "SavePluginAccessControlPolicy", actingUserID); appErr != nil {
return nil, appErr
}
@@ -331,7 +331,7 @@ func (a *App) DeletePluginAccessControlPolicy(rctx request.CTX, pluginID, acting
if appErr := a.pluginAccessControlScopeCheck(where, pluginID, resourceType); appErr != nil {
return appErr
}
- if appErr := a.validatePluginActingUser(where, actingUserID); appErr != nil {
+ if appErr := a.validatePluginActingUser(rctx, where, actingUserID); appErr != nil {
return appErr
}
if !model.IsValidId(id) {
@@ -366,7 +366,7 @@ func (a *App) CheckPluginAccessControlExpression(rctx request.CTX, pluginID, act
if appErr := a.pluginAccessControlScopeCheck("CheckPluginAccessControlExpression", pluginID, resourceType); appErr != nil {
return nil, appErr
}
- if appErr := a.validatePluginActingUser("CheckPluginAccessControlExpression", actingUserID); appErr != nil {
+ if appErr := a.validatePluginActingUser(rctx, "CheckPluginAccessControlExpression", actingUserID); appErr != nil {
return nil, appErr
}
@@ -380,7 +380,7 @@ func (a *App) QueryUsersForPluginAccessControlExpression(rctx request.CTX, plugi
if appErr := a.pluginAccessControlScopeCheck("QueryUsersForPluginAccessControlExpression", pluginID, resourceType); appErr != nil {
return nil, appErr
}
- if appErr := a.validatePluginActingUser("QueryUsersForPluginAccessControlExpression", actingUserID); appErr != nil {
+ if appErr := a.validatePluginActingUser(rctx, "QueryUsersForPluginAccessControlExpression", actingUserID); appErr != nil {
return nil, appErr
}
@@ -411,7 +411,7 @@ func (a *App) GetPluginAccessControlFieldsAutocomplete(rctx request.CTX, pluginI
if a.Srv().ch.AccessControl == nil {
return nil, model.NewAppError("GetPluginAccessControlFieldsAutocomplete", "app.pap.get_access_control_auto_complete.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
}
- if appErr := a.validatePluginActingUser("GetPluginAccessControlFieldsAutocomplete", actingUserID); appErr != nil {
+ if appErr := a.validatePluginActingUser(rctx, "GetPluginAccessControlFieldsAutocomplete", actingUserID); appErr != nil {
return nil, appErr
}
@@ -438,7 +438,7 @@ func (a *App) GetPluginAccessControlVisualAST(rctx request.CTX, pluginID, acting
if appErr := a.pluginAccessControlScopeCheck("GetPluginAccessControlVisualAST", pluginID, resourceType); appErr != nil {
return nil, appErr
}
- if appErr := a.validatePluginActingUser("GetPluginAccessControlVisualAST", actingUserID); appErr != nil {
+ if appErr := a.validatePluginActingUser(rctx, "GetPluginAccessControlVisualAST", actingUserID); appErr != nil {
return nil, appErr
}
diff --git a/server/channels/app/plugin_api.go b/server/channels/app/plugin_api.go
index fb61838dd605..5b6f34c5a090 100644
--- a/server/channels/app/plugin_api.go
+++ b/server/channels/app/plugin_api.go
@@ -89,7 +89,7 @@ func (api *PluginAPI) UnregisterCommand(teamID, trigger string) error {
}
func (api *PluginAPI) ExecuteSlashCommand(commandArgs *model.CommandArgs) (*model.CommandResponse, error) {
- user, appErr := api.app.GetUser(commandArgs.UserId)
+ user, appErr := api.app.GetUser(api.ctx, commandArgs.UserId)
if appErr != nil {
return nil, appErr
}
@@ -266,7 +266,7 @@ func (api *PluginAPI) CreateUser(user *model.User) (*model.User, *model.AppError
}
func (api *PluginAPI) DeleteUser(userID string) *model.AppError {
- user, err := api.app.GetUser(userID)
+ user, err := api.app.GetUser(api.ctx, userID)
if err != nil {
return err
}
@@ -283,7 +283,7 @@ func (api *PluginAPI) GetUsersByIds(usersID []string) ([]*model.User, *model.App
}
func (api *PluginAPI) GetUser(userID string) (*model.User, *model.AppError) {
- return api.app.GetUser(userID)
+ return api.app.GetUser(api.ctx, userID)
}
func (api *PluginAPI) GetUserByEmail(email string) (*model.User, *model.AppError) {
@@ -420,7 +420,7 @@ func (api *PluginAPI) RemoveUserCustomStatus(userID string) *model.AppError {
}
func (api *PluginAPI) GetUserCustomStatus(userID string) (*model.CustomStatus, *model.AppError) {
- return api.app.GetCustomStatus(userID)
+ return api.app.GetCustomStatus(api.ctx, userID)
}
func (api *PluginAPI) GetUsersInChannel(channelID, sortBy string, page, perPage int) ([]*model.User, *model.AppError) {
@@ -447,7 +447,7 @@ func (api *PluginAPI) GetLDAPUserAttributes(userID string, attributes []string)
return nil, model.NewAppError("GetLdapUserAttributes", "ent.ldap.disabled.app_error", nil, "", http.StatusNotImplemented)
}
- user, err := api.app.GetUser(userID)
+ user, err := api.app.GetUser(api.ctx, userID)
if err != nil {
return nil, err
}
@@ -1014,7 +1014,7 @@ func (api *PluginAPI) UpdatePost(post *model.Post) (*model.Post, *model.AppError
}
func (api *PluginAPI) GetProfileImage(userID string) ([]byte, *model.AppError) {
- user, err := api.app.GetUser(userID)
+ user, err := api.app.GetUser(api.ctx, userID)
if err != nil {
return nil, err
}
@@ -1024,7 +1024,7 @@ func (api *PluginAPI) GetProfileImage(userID string) ([]byte, *model.AppError) {
}
func (api *PluginAPI) SetProfileImage(userID string, data []byte) *model.AppError {
- if _, err := api.app.GetUser(userID); err != nil {
+ if _, err := api.app.GetUser(api.ctx, userID); err != nil {
return err
}
@@ -1248,7 +1248,7 @@ func (api *PluginAPI) SendToastMessage(userID, connectionID, message string, opt
}
func (api *PluginAPI) HasPermissionTo(userID string, permission *model.Permission) bool {
- return api.app.HasPermissionTo(userID, permission)
+ return api.app.HasPermissionTo(api.ctx, userID, permission)
}
func (api *PluginAPI) HasPermissionToTeam(userID, teamID string, permission *model.Permission) bool {
@@ -1291,7 +1291,7 @@ func (api *PluginAPI) CreateBot(bot *model.Bot) (*model.Bot, *model.AppError) {
bot.OwnerId = api.id
}
// Bots cannot be owners of other bots
- if user, err := api.app.GetUser(bot.OwnerId); err == nil {
+ if user, err := api.app.GetUser(api.ctx, bot.OwnerId); err == nil {
if user.IsBot {
return nil, model.NewAppError("CreateBot", "plugin_api.bot_cant_create_bot", nil, "", http.StatusBadRequest)
}
diff --git a/server/channels/app/plugin_api_test.go b/server/channels/app/plugin_api_test.go
index 9ce084fd623a..85e94dddb762 100644
--- a/server/channels/app/plugin_api_test.go
+++ b/server/channels/app/plugin_api_test.go
@@ -697,14 +697,14 @@ func TestPluginAPIUserCustomStatus(t *testing.T) {
err = api.UpdateUserCustomStatus(user1.Id, custom)
assert.Nil(t, err)
- userCs, err := th.App.GetCustomStatus(user1.Id)
+ userCs, err := th.App.GetCustomStatus(th.Context, user1.Id)
assert.Nil(t, err)
assert.Equal(t, custom, userCs)
custom.Text = ""
err = api.UpdateUserCustomStatus(user1.Id, custom)
assert.Nil(t, err)
- userCs, err = th.App.GetCustomStatus(user1.Id)
+ userCs, err = th.App.GetCustomStatus(th.Context, user1.Id)
assert.Nil(t, err)
assert.Equal(t, custom, userCs)
@@ -712,7 +712,7 @@ func TestPluginAPIUserCustomStatus(t *testing.T) {
custom.Emoji = ""
err = api.UpdateUserCustomStatus(user1.Id, custom)
assert.Nil(t, err)
- userCs, err = th.App.GetCustomStatus(user1.Id)
+ userCs, err = th.App.GetCustomStatus(th.Context, user1.Id)
assert.Nil(t, err)
assert.Equal(t, custom, userCs)
@@ -725,7 +725,7 @@ func TestPluginAPIUserCustomStatus(t *testing.T) {
err = api.RemoveUserCustomStatus(user1.Id)
assert.Nil(t, err)
var csClear *model.CustomStatus
- userCs, err = th.App.GetCustomStatus(user1.Id)
+ userCs, err = th.App.GetCustomStatus(th.Context, user1.Id)
assert.Nil(t, err)
assert.Equal(t, csClear, userCs)
}
@@ -2395,7 +2395,7 @@ func TestAPIMetrics(t *testing.T) {
_, appErr := th.App.CreateUser(th.Context, user1)
require.Nil(t, appErr)
time.Sleep(1 * time.Second)
- user1, appErr = th.App.GetUser(user1.Id)
+ user1, appErr = th.App.GetUser(th.Context, user1.Id)
require.Nil(t, appErr)
require.Equal(t, "plugin-callback-success", user1.Nickname)
diff --git a/server/channels/app/plugin_hooks_test.go b/server/channels/app/plugin_hooks_test.go
index 89510b32e88e..5a02ceecced6 100644
--- a/server/channels/app/plugin_hooks_test.go
+++ b/server/channels/app/plugin_hooks_test.go
@@ -889,7 +889,7 @@ func TestUserHasLoggedIn(t *testing.T) {
assert.NotNil(t, session)
require.EventuallyWithT(t, func(c *assert.CollectT) {
- user, _ := th.App.GetUser(th.BasicUser.Id)
+ user, _ := th.App.GetUser(th.Context, th.BasicUser.Id)
assert.Equal(c, user.FirstName, "plugin-callback-success", "Expected firstname overwrite, got default")
}, 2*time.Second, 100*time.Millisecond)
}
@@ -938,7 +938,7 @@ func TestUserHasBeenDeactivated(t *testing.T) {
require.Nil(t, err)
time.Sleep(2 * time.Second)
- user, err = th.App.GetUser(user.Id)
+ user, err = th.App.GetUser(th.Context, user.Id)
require.Nil(t, err)
require.Equal(t, "plugin-callback-success", user.Nickname)
}
@@ -983,7 +983,7 @@ func TestUserHasBeenCreated(t *testing.T) {
require.Nil(t, err)
time.Sleep(2 * time.Second)
- user, err = th.App.GetUser(user.Id)
+ user, err = th.App.GetUser(th.Context, user.Id)
require.Nil(t, err)
require.Equal(t, "plugin-callback-success", user.Nickname)
}
@@ -1169,7 +1169,7 @@ func TestActiveHooks(t *testing.T) {
_, appErr := th.App.CreateUser(th.Context, user1)
require.Nil(t, appErr)
time.Sleep(2 * time.Second)
- user1, appErr = th.App.GetUser(user1.Id)
+ user1, appErr = th.App.GetUser(th.Context, user1.Id)
require.Nil(t, appErr)
require.Equal(t, "plugin-callback-success", user1.Nickname)
@@ -1275,7 +1275,7 @@ func TestHookMetrics(t *testing.T) {
_, appErr := th.App.CreateUser(th.Context, user1)
require.Nil(t, appErr)
time.Sleep(2 * time.Second)
- user1, appErr = th.App.GetUser(user1.Id)
+ user1, appErr = th.App.GetUser(th.Context, user1.Id)
require.Nil(t, appErr)
require.Equal(t, "plugin-callback-success", user1.Nickname)
diff --git a/server/channels/app/post.go b/server/channels/app/post.go
index 7fb5d56ac572..950f5476d929 100644
--- a/server/channels/app/post.go
+++ b/server/channels/app/post.go
@@ -635,7 +635,7 @@ func (a *App) FillInPostProps(rctx request.CTX, post *model.Post, channel *model
// Populate AI-generated username from provided user ID
if aiGenUserID, ok := post.GetProp(model.PostPropsAIGeneratedByUserID).(string); ok && aiGenUserID != "" {
- user, err := a.GetUser(aiGenUserID)
+ user, err := a.GetUser(rctx, aiGenUserID)
if err != nil {
// If user doesn't exist, remove the ai_generated_by prop to avoid storing invalid data
rctx.Logger().Warn("Failed to get user for AI-generated post, removing ai_generated_by prop", mlog.String("user_id", aiGenUserID), mlog.Err(err))
@@ -3473,7 +3473,7 @@ func (a *App) SendTestMessage(rctx request.CTX, userID string) (*model.Post, *mo
return nil, model.NewAppError("SendTestMessage", "app.notifications.send_test_message.errors.no_channel", nil, "", http.StatusInternalServerError).Wrap(err)
}
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return nil, model.NewAppError("SendTestMessage", "app.notifications.send_test_message.errors.no_user", nil, "", http.StatusInternalServerError).Wrap(err)
}
@@ -3534,7 +3534,7 @@ func (a *App) RewriteMessage(
userLocale := ""
if session := rctx.Session(); session != nil && session.UserId != "" {
- user, appErr := a.GetUser(session.UserId)
+ user, appErr := a.GetUser(rctx, session.UserId)
if appErr == nil {
userLocale = user.Locale
} else {
diff --git a/server/channels/app/post_metadata.go b/server/channels/app/post_metadata.go
index 5b4f4e0cb70a..a0bbd601b712 100644
--- a/server/channels/app/post_metadata.go
+++ b/server/channels/app/post_metadata.go
@@ -448,7 +448,7 @@ func (a *App) sanitizeFileAttachmentsForUser(rctx request.CTX, post *model.Post,
return
}
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
rctx.Logger().Warn("Failed to get user for file attachment sanitization, stripping attachments",
mlog.String("user_id", userID),
diff --git a/server/channels/app/post_permission_utils.go b/server/channels/app/post_permission_utils.go
index dda89f81117a..224f63faa9f9 100644
--- a/server/channels/app/post_permission_utils.go
+++ b/server/channels/app/post_permission_utils.go
@@ -11,8 +11,8 @@ import (
"github.com/mattermost/mattermost/server/public/shared/request"
)
-func PostPriorityCheckWithApp(where string, a *App, userId string, priority *model.PostPriority, rootId string) *model.AppError {
- user, appErr := a.GetUser(userId)
+func PostPriorityCheckWithApp(where string, a *App, rctx request.CTX, userId string, priority *model.PostPriority, rootId string) *model.AppError {
+ user, appErr := a.GetUser(rctx, userId)
if appErr != nil {
return appErr
}
@@ -157,7 +157,7 @@ func PostBurnOnReadCheckWithApp(where string, a *App, rctx request.CTX, userId,
// Check if the DM is with a bot (AI agents, plugins, etc.)
otherUserId := channel.GetOtherUserIdForDM(userId)
if otherUserId != "" && otherUserId != userId {
- otherUser, err := a.GetUser(otherUserId)
+ otherUser, err := a.GetUser(rctx, otherUserId)
if err != nil {
// Failed to retrieve the other user (user not found, DB error, etc.)
// Block burn-on-read post as we cannot validate the recipient
diff --git a/server/channels/app/post_persistent_notification_test.go b/server/channels/app/post_persistent_notification_test.go
index 99133d1eeda6..896c4b8e1410 100644
--- a/server/channels/app/post_persistent_notification_test.go
+++ b/server/channels/app/post_persistent_notification_test.go
@@ -449,7 +449,7 @@ func TestSendPersistentNotificationsBotSender(t *testing.T) {
})
require.Nil(t, appErr)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, botUser.Id, "")
@@ -501,7 +501,7 @@ func TestSendPersistentNotificationsBotSenderNotInChannel(t *testing.T) {
})
require.Nil(t, appErr)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
// Make the bot a system admin so it can post to channels it's not a member of
diff --git a/server/channels/app/post_test.go b/server/channels/app/post_test.go
index d3a43a532539..41e34d099ceb 100644
--- a/server/channels/app/post_test.go
+++ b/server/channels/app/post_test.go
@@ -1526,7 +1526,7 @@ func TestCreatePost(t *testing.T) {
th := Setup(t).InitBasic(t)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
@@ -1571,7 +1571,7 @@ func TestCreatePost(t *testing.T) {
th := Setup(t).InitBasic(t)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
@@ -1709,7 +1709,7 @@ func TestCreatePost(t *testing.T) {
th := Setup(t).InitBasic(t)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
@@ -1730,7 +1730,7 @@ func TestCreatePost(t *testing.T) {
th := Setup(t).InitBasic(t)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
@@ -1757,7 +1757,7 @@ func TestCreatePost(t *testing.T) {
})
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
@@ -1785,7 +1785,7 @@ func TestCreatePost(t *testing.T) {
th := Setup(t).InitBasic(t)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
dm, appErr := th.App.createDirectChannel(th.Context, botUser.Id, th.BasicUser2.Id)
@@ -1814,7 +1814,7 @@ func TestCreatePost(t *testing.T) {
th := Setup(t).InitBasic(t)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, botUser.Id, "")
require.Nil(t, appErr)
@@ -1846,7 +1846,7 @@ func TestCreatePost(t *testing.T) {
th.AddUserToChannel(t, th.BasicUser2, th.BasicChannel)
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
@@ -2182,7 +2182,7 @@ func TestCreatePostAsUser(t *testing.T) {
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
@@ -2213,7 +2213,7 @@ func TestCreatePostAsUser(t *testing.T) {
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
th.LinkUserToTeam(t, botUser, th.BasicTeam)
@@ -6410,7 +6410,7 @@ func TestBurnOnReadRestrictionsForDMsAndBots(t *testing.T) {
require.Nil(t, appErr)
// Get the bot user
- botUser, appErr := th.App.GetUser(createdBot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, createdBot.UserId)
require.Nil(t, appErr)
require.True(t, botUser.IsBot)
@@ -6459,7 +6459,7 @@ func TestBurnOnReadRestrictionsForDMsAndBots(t *testing.T) {
require.Nil(t, appErr)
// Get the bot user
- botUser, appErr := th.App.GetUser(createdBot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, createdBot.UserId)
require.Nil(t, appErr)
require.True(t, botUser.IsBot)
diff --git a/server/channels/app/properties/access_control_attribute_validation.go b/server/channels/app/properties/access_control_attribute_validation.go
index bdf1775da869..c958b001d709 100644
--- a/server/channels/app/properties/access_control_attribute_validation.go
+++ b/server/channels/app/properties/access_control_attribute_validation.go
@@ -25,7 +25,7 @@ var (
// PermissionChecker checks whether a user has a specific permission.
// This avoids a circular dependency between the properties and app packages.
-type PermissionChecker func(userID string, permission *model.Permission) bool
+type PermissionChecker func(rctx request.CTX, userID string, permission *model.Permission) bool
// AccessControlAttributeValidationHook validates and sanitizes property field attributes
// and values for managed property groups. It owns the full attr pipeline
@@ -458,7 +458,7 @@ func (h *AccessControlAttributeValidationHook) enforceGroupPermissions(rctx requ
return nil, fmt.Errorf("missing permission to set managed=admin: no permission checker configured: %w", ErrAdminRequired)
}
callerID := h.propertyService.extractCallerID(rctx)
- if callerID == "" || !h.permissionChecker(callerID, model.PermissionManageSystem) {
+ if callerID == "" || !h.permissionChecker(rctx, callerID, model.PermissionManageSystem) {
return nil, fmt.Errorf("missing permission to set managed=admin: only system admins can set managed=admin: %w", ErrAdminRequired)
}
field.PermissionValues = &sysadmin
diff --git a/server/channels/app/properties/access_control_attribute_validation_test.go b/server/channels/app/properties/access_control_attribute_validation_test.go
index b492a4bbc387..a9661298da90 100644
--- a/server/channels/app/properties/access_control_attribute_validation_test.go
+++ b/server/channels/app/properties/access_control_attribute_validation_test.go
@@ -10,6 +10,7 @@ import (
"testing"
"github.com/mattermost/mattermost/server/public/model"
+ "github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -1086,7 +1087,7 @@ func TestAccessControlAttributeValidationHookManagedAuthorization(t *testing.T)
adminUserID := model.NewId()
regularUserID := model.NewId()
- permChecker := func(userID string, perm *model.Permission) bool {
+ permChecker := func(_ request.CTX, userID string, perm *model.Permission) bool {
return userID == adminUserID && perm.Id == model.PermissionManageSystem.Id
}
@@ -1571,7 +1572,7 @@ func TestAccessControlAttributeValidationHookSync(t *testing.T) {
require.NoError(t, err)
adminUserID := model.NewId()
- permChecker := func(userID string, perm *model.Permission) bool {
+ permChecker := func(_ request.CTX, userID string, perm *model.Permission) bool {
return userID == adminUserID && perm.Id == model.PermissionManageSystem.Id
}
@@ -1762,7 +1763,7 @@ func TestAccessControlAttributeValidationHook_Owners(t *testing.T) {
group, err := th.service.RegisterPropertyGroup(&model.PropertyGroup{Name: "test_owner_validation", Version: model.PropertyGroupVersionV2})
require.NoError(t, err)
- hook := NewAccessControlAttributeValidationHook(th.service, func(userID string, _ *model.Permission) bool {
+ hook := NewAccessControlAttributeValidationHook(th.service, func(_ request.CTX, userID string, _ *model.Permission) bool {
return userID == "admin-user"
}, group.ID)
th.service.AddHook(hook)
diff --git a/server/channels/app/reaction.go b/server/channels/app/reaction.go
index ad471b4beaa8..0aafbe4f4c85 100644
--- a/server/channels/app/reaction.go
+++ b/server/channels/app/reaction.go
@@ -126,34 +126,6 @@ func (a *App) GetReactionsForPost(postID string) ([]*model.Reaction, *model.AppE
return reactions, nil
}
-func (a *App) GetBulkReactionsForPosts(postIDs []string) (map[string][]*model.Reaction, *model.AppError) {
- reactions := make(map[string][]*model.Reaction)
-
- allReactions, err := a.Srv().Store().Reaction().BulkGetForPosts(postIDs)
- if err != nil {
- return nil, model.NewAppError("GetBulkReactionsForPosts", "app.reaction.bulk_get_for_post_ids.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
- }
-
- for _, reaction := range allReactions {
- reactionsForPost := reactions[reaction.PostId]
- reactionsForPost = append(reactionsForPost, reaction)
-
- reactions[reaction.PostId] = reactionsForPost
- }
-
- reactions = populateEmptyReactions(postIDs, reactions)
- return reactions, nil
-}
-
-func populateEmptyReactions(postIDs []string, reactions map[string][]*model.Reaction) map[string][]*model.Reaction {
- for _, postID := range postIDs {
- if _, present := reactions[postID]; !present {
- reactions[postID] = []*model.Reaction{}
- }
- }
- return reactions
-}
-
func (a *App) DeleteReactionForPost(rctx request.CTX, reaction *model.Reaction) *model.AppError {
reaction.EmojiName = strings.ToLower(reaction.EmojiName)
post, err := a.GetSinglePost(rctx, reaction.PostId, false)
diff --git a/server/channels/app/recap.go b/server/channels/app/recap.go
index aae5866f4c1a..76eca554ef86 100644
--- a/server/channels/app/recap.go
+++ b/server/channels/app/recap.go
@@ -63,7 +63,7 @@ func (a *App) CreateRecap(rctx request.CTX, title string, channelIDs []string, a
storeErr error
)
if model.IsLimitEnabled(limits.MaxRecapsPerDay) {
- startOfDayMillis, dayErr := a.getStartOfUserDayMillis(userID)
+ startOfDayMillis, dayErr := a.getStartOfUserDayMillis(rctx, userID)
if dayErr != nil {
return nil, dayErr
}
@@ -215,7 +215,7 @@ func (a *App) RegenerateRecap(rctx request.CTX, userID string, recap *model.Reca
}
if model.IsLimitEnabled(limits.MaxRecapsPerDay) {
- startOfDayMillis, dayErr := a.getStartOfUserDayMillis(userID)
+ startOfDayMillis, dayErr := a.getStartOfUserDayMillis(rctx, userID)
if dayErr != nil {
return nil, dayErr
}
@@ -314,7 +314,7 @@ func (a *App) ProcessRecapChannelWithOptions(rctx request.CTX, recapID, channelI
}
fetchSince, allowRecentFallback := recapFetchStartAt(options.TimePeriod, lastViewedAt, time.Now())
- remainingPosts, limitErr := a.getRemainingPostsForRecap(userID, recapID)
+ remainingPosts, limitErr := a.getRemainingPostsForRecap(rctx, userID, recapID)
if limitErr != nil {
return result, limitErr
}
@@ -435,7 +435,7 @@ func (a *App) fetchPostsForRecapWithFallback(rctx request.CTX, channelID string,
// Enrich with usernames
for _, post := range posts {
- user, _ := a.GetUser(post.UserId)
+ user, _ := a.GetUser(rctx, post.UserId)
if user != nil {
if post.Props == nil {
post.Props = make(model.StringInterface)
@@ -518,7 +518,7 @@ func (a *App) checkManualRecapCooldown(userID string, limits *model.EffectiveRec
"", http.StatusTooManyRequests)
}
-func (a *App) getRemainingPostsForRecap(userID string, recapID string) (int, *model.AppError) {
+func (a *App) getRemainingPostsForRecap(rctx request.CTX, userID string, recapID string) (int, *model.AppError) {
const defaultFetchLimit = 100
limits, limitsErr := a.GetEffectiveLimits()
@@ -537,7 +537,7 @@ func (a *App) getRemainingPostsForRecap(userID string, recapID string) (int, *mo
}
if model.IsLimitEnabled(limits.MaxPostsPerDay) {
- startOfDayMillis, dayErr := a.getStartOfUserDayMillis(userID)
+ startOfDayMillis, dayErr := a.getStartOfUserDayMillis(rctx, userID)
if dayErr != nil {
return 0, dayErr
}
diff --git a/server/channels/app/recap_limits.go b/server/channels/app/recap_limits.go
index 874d0fe06d10..a9caef058e0e 100644
--- a/server/channels/app/recap_limits.go
+++ b/server/channels/app/recap_limits.go
@@ -8,10 +8,11 @@ import (
"time"
"github.com/mattermost/mattermost/server/public/model"
+ "github.com/mattermost/mattermost/server/public/shared/request"
)
-func (a *App) getStartOfUserDay(userID string) (time.Time, *model.AppError) {
- user, appErr := a.GetUser(userID)
+func (a *App) getStartOfUserDay(rctx request.CTX, userID string) (time.Time, *model.AppError) {
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return time.Time{}, appErr
}
@@ -22,8 +23,8 @@ func (a *App) getStartOfUserDay(userID string) (time.Time, *model.AppError) {
return startOfDay, nil
}
-func (a *App) getStartOfUserDayMillis(userID string) (int64, *model.AppError) {
- startOfDay, appErr := a.getStartOfUserDay(userID)
+func (a *App) getStartOfUserDayMillis(rctx request.CTX, userID string) (int64, *model.AppError) {
+ startOfDay, appErr := a.getStartOfUserDay(rctx, userID)
if appErr != nil {
return 0, appErr
}
@@ -43,14 +44,14 @@ func (a *App) requireAIRecapsEnabled(where string) *model.AppError {
}
// GetRecapLimitStatus returns the current user's limit status for UI display
-func (a *App) GetRecapLimitStatus(userID string) (*model.RecapLimitStatus, *model.AppError) {
+func (a *App) GetRecapLimitStatus(rctx request.CTX, userID string) (*model.RecapLimitStatus, *model.AppError) {
// Get effective limits
limits, appErr := a.GetEffectiveLimits()
if appErr != nil {
return nil, appErr
}
- startOfDay, appErr := a.getStartOfUserDay(userID)
+ startOfDay, appErr := a.getStartOfUserDay(rctx, userID)
if appErr != nil {
return nil, appErr
}
diff --git a/server/channels/app/report.go b/server/channels/app/report.go
index 89b5de0c3065..949b26d37e5b 100644
--- a/server/channels/app/report.go
+++ b/server/channels/app/report.go
@@ -123,7 +123,7 @@ func (a *App) SendReportToUser(rctx request.CTX, job *model.Job, format string)
return err
}
- user, err := a.GetUser(requestingUserId)
+ user, err := a.GetUser(rctx, requestingUserId)
if err != nil {
return err
}
@@ -240,7 +240,7 @@ func (a *App) StartUsersBatchExport(rctx request.CTX, ro *model.UserReportOption
return
}
- user, err := a.GetUser(rctx.Session().UserId)
+ user, err := a.GetUser(rctx, rctx.Session().UserId)
if err != nil {
rctx.Logger().Error("Failed to get the user", mlog.Err(err))
return
diff --git a/server/channels/app/scheduled_post_job.go b/server/channels/app/scheduled_post_job.go
index c561ef9569d2..6515e8f64174 100644
--- a/server/channels/app/scheduled_post_job.go
+++ b/server/channels/app/scheduled_post_job.go
@@ -266,7 +266,7 @@ func (a *App) postScheduledPost(rctx request.CTX, scheduledPost *model.Scheduled
// canPostScheduledPost checks whether the scheduled post be created based on permissions and other checks.
func (a *App) canPostScheduledPost(rctx request.CTX, scheduledPost *model.ScheduledPost, channel *model.Channel) (string, error) {
- user, appErr := a.GetUser(scheduledPost.UserId)
+ user, appErr := a.GetUser(rctx, scheduledPost.UserId)
if appErr != nil {
if appErr.Id == MissingAccountError {
rctx.Logger().Debug("canPostScheduledPost user not found for scheduled post", mlog.String("scheduled_post_id", scheduledPost.Id), mlog.String("user_id", scheduledPost.UserId), mlog.String("error_code", model.ScheduledPostErrorCodeUserDoesNotExist))
@@ -364,7 +364,7 @@ func (a *App) canPostScheduledPost(rctx request.CTX, scheduledPost *model.Schedu
return model.ScheduledPostErrorInvalidPost, nil
}
- if appErr := PostPriorityCheckWithApp("ScheduledPostJob.postChecks", a, scheduledPost.UserId, scheduledPost.GetPriority(), scheduledPost.RootId); appErr != nil {
+ if appErr := PostPriorityCheckWithApp("ScheduledPostJob.postChecks", a, rctx, scheduledPost.UserId, scheduledPost.GetPriority(), scheduledPost.RootId); appErr != nil {
rctx.Logger().Debug(
"canPostScheduledPost post priority check failed",
mlog.String("scheduled_post_id", scheduledPost.Id),
@@ -503,7 +503,7 @@ func (a *App) notifyUser(rctx request.CTX, userId string, userFailedMessages []*
return
}
- user, err := a.GetUser(userId)
+ user, err := a.GetUser(rctx, userId)
if err != nil {
rctx.Logger().Error("Failed to get the user", mlog.Err(err))
return
diff --git a/server/channels/app/scheduled_recap.go b/server/channels/app/scheduled_recap.go
index 4ccb41b9bf7a..48975a892ea1 100644
--- a/server/channels/app/scheduled_recap.go
+++ b/server/channels/app/scheduled_recap.go
@@ -233,7 +233,7 @@ func (a *App) CreateRecapFromSchedule(rctx request.CTX, sr *model.ScheduledRecap
err error
)
if model.IsLimitEnabled(limits.MaxRecapsPerDay) {
- startOfDayMillis, dayErr := a.getStartOfUserDayMillis(sr.UserId)
+ startOfDayMillis, dayErr := a.getStartOfUserDayMillis(rctx, sr.UserId)
if dayErr != nil {
return nil, dayErr
}
diff --git a/server/channels/app/server.go b/server/channels/app/server.go
index 6687f7d8c5d9..7fa0077d154c 100644
--- a/server/channels/app/server.go
+++ b/server/channels/app/server.go
@@ -337,14 +337,14 @@ func NewServer(options ...Option) (*Server, error) {
// Attribute validation hook — validates visibility, sort_order on fields,
// field-type constraints on values (options, user IDs, value_type), and
// managed-flag authorization + permission level enforcement.
- permChecker := func(userID string, perm *model.Permission) bool {
+ permChecker := func(rctx request.CTX, userID string, perm *model.Permission) bool {
// Local-mode (unrestricted) sessions are tagged with
// CallerIDLocalAdmin by the HTTP layer; grant them admin
// permissions without a user lookup.
if userID == model.CallerIDLocalAdmin {
return true
}
- return app.HasPermissionTo(userID, perm)
+ return app.HasPermissionTo(rctx, userID, perm)
}
attrValidationHook := properties.NewAccessControlAttributeValidationHook(s.propertyService, permChecker, cpaGroup.ID)
s.propertyService.AddHook(attrValidationHook)
diff --git a/server/channels/app/session.go b/server/channels/app/session.go
index f433136d1f9e..9c1592ea6f05 100644
--- a/server/channels/app/session.go
+++ b/server/channels/app/session.go
@@ -31,7 +31,7 @@ func (a *App) CreateSession(rctx request.CTX, session *model.Session) (*model.Se
// remote/synthetic users cannot create sessions. This lookup will already be cached.
// Some unit tests rely on sessions being created for users that don't exist, therefore
// missing users are allowed.
- user, appErr := a.GetUser(session.UserId)
+ user, appErr := a.GetUser(rctx, session.UserId)
if appErr != nil && appErr.StatusCode != http.StatusNotFound {
return nil, appErr
}
diff --git a/server/channels/app/shared_channel_global_user_sync_self_referential_test.go b/server/channels/app/shared_channel_global_user_sync_self_referential_test.go
index 7976ffd4b168..ab14a345df85 100644
--- a/server/channels/app/shared_channel_global_user_sync_self_referential_test.go
+++ b/server/channels/app/shared_channel_global_user_sync_self_referential_test.go
@@ -204,7 +204,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
// Create bot
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
botUser.UpdateAt = baseTime + 300
_, err = ss.User().Update(th.Context, botUser, true)
@@ -1222,7 +1222,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
}, 2*time.Second, 100*time.Millisecond, "Synced user should NEVER be synced back to its originating cluster")
// Verify that the synced user still exists locally but wasn't synced
- user, appErr := th.App.GetUser(syncedUserOnB.Id)
+ user, appErr := th.App.GetUser(th.Context, syncedUserOnB.Id)
require.Nil(t, appErr)
assert.NotNil(t, user.RemoteId, "Synced user should still have RemoteId")
assert.Equal(t, clusterB.RemoteId, *user.RemoteId, "RemoteId should point to origin cluster")
diff --git a/server/channels/app/shared_channel_membership_sync_self_referential_test.go b/server/channels/app/shared_channel_membership_sync_self_referential_test.go
index fd16ef73b3c9..c85cc862f3ec 100644
--- a/server/channels/app/shared_channel_membership_sync_self_referential_test.go
+++ b/server/channels/app/shared_channel_membership_sync_self_referential_test.go
@@ -236,7 +236,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
// Add users that should be synced (including bots and system admins)
// Add a bot
bot := th.CreateBot(t)
- botUser, appErr := th.App.GetUser(bot.UserId)
+ botUser, appErr := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, appErr)
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, bot.UserId, th.BasicUser.Id)
require.Nil(t, appErr)
diff --git a/server/channels/app/shared_channel_test.go b/server/channels/app/shared_channel_test.go
index dee7db01433e..6f0c46c27042 100644
--- a/server/channels/app/shared_channel_test.go
+++ b/server/channels/app/shared_channel_test.go
@@ -1236,7 +1236,7 @@ func TestPluginAPIReceiveSharedChannelSyncMsg(t *testing.T) {
assert.Empty(t, resp.UserErrors)
// Verify the user was actually created in the database
- user, appErr := th.App.GetUser(userID)
+ user, appErr := th.App.GetUser(th.Context, userID)
require.Nil(t, appErr)
assert.Contains(t, user.Username, username) // username gets ":remotename" appended by sync
assert.Equal(t, rc.RemoteId, user.GetRemoteID())
@@ -1825,7 +1825,7 @@ func TestPluginAPIReceiveSharedChannelProfileImageSyncMsg(t *testing.T) {
require.NoError(t, err)
// Verify the user's LastPictureUpdate was bumped
- updatedUser, appErr := th.App.GetUser(remoteUser.Id)
+ updatedUser, appErr := th.App.GetUser(th.Context, remoteUser.Id)
require.Nil(t, appErr)
assert.Greater(t, updatedUser.LastPictureUpdate, lastPictureBefore)
@@ -1917,7 +1917,7 @@ func TestPluginRPCSharedChannelSync(t *testing.T) {
// --- Post-activation verification: check everything landed in the DB ---
// Verify synced user
- user, appErr := th.App.GetUser(syncUserID)
+ user, appErr := th.App.GetUser(th.Context, syncUserID)
require.Nil(t, appErr, "synced user should exist in DB")
assert.Contains(t, user.Username, "rpc-synced-user")
assert.Equal(t, rc.RemoteId, user.GetRemoteID())
diff --git a/server/channels/app/slashcommands/command_groupmsg.go b/server/channels/app/slashcommands/command_groupmsg.go
index ddfe207a3b8a..08ec363ad10a 100644
--- a/server/channels/app/slashcommands/command_groupmsg.go
+++ b/server/channels/app/slashcommands/command_groupmsg.go
@@ -108,7 +108,7 @@ func (*groupmsgProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Com
var groupChannel *model.Channel
var channelErr *model.AppError
- if a.HasPermissionTo(args.UserId, model.PermissionCreateGroupChannel) {
+ if a.HasPermissionTo(rctx, args.UserId, model.PermissionCreateGroupChannel) {
groupChannel, channelErr = a.CreateGroupChannel(rctx, targetUsersSlice, args.UserId)
if channelErr != nil {
rctx.Logger().Error(channelErr.Error())
diff --git a/server/channels/app/slashcommands/command_leave.go b/server/channels/app/slashcommands/command_leave.go
index 85f2bfbc4f70..6b2fd0410c6a 100644
--- a/server/channels/app/slashcommands/command_leave.go
+++ b/server/channels/app/slashcommands/command_leave.go
@@ -59,7 +59,7 @@ func (*LeaveProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Comman
return &model.CommandResponse{GotoLocation: args.SiteURL + "/"}
}
- user, err := a.GetUser(args.UserId)
+ user, err := a.GetUser(rctx, args.UserId)
if err != nil {
return &model.CommandResponse{Text: args.T("api.command_leave.fail.app_error"), ResponseType: model.CommandResponseTypeEphemeral}
}
diff --git a/server/channels/app/slashcommands/command_mobile_logs.go b/server/channels/app/slashcommands/command_mobile_logs.go
index 76f6ae6d5c4d..1487aff4dbe9 100644
--- a/server/channels/app/slashcommands/command_mobile_logs.go
+++ b/server/channels/app/slashcommands/command_mobile_logs.go
@@ -108,7 +108,7 @@ func (*MobileLogsProvider) DoCommand(a *app.App, rctx request.CTX, args *model.C
}
}
- caller, appErr := a.GetUser(args.UserId)
+ caller, appErr := a.GetUser(rctx, args.UserId)
if appErr != nil {
rctx.Logger().Error("Failed to get caller for mobile-logs command", mlog.String("user_id", args.UserId), mlog.Err(appErr))
return &model.CommandResponse{
@@ -125,11 +125,11 @@ func (*MobileLogsProvider) DoCommand(a *app.App, rctx request.CTX, args *model.C
// Cross-user: callers without system admin get one neutral outcome for any failure
// (unknown user, deactivated, disallowed target, or missing role) to avoid username
// enumeration. System admins still get explicit not-found messages for support workflows.
- if !a.HasPermissionTo(args.UserId, model.PermissionManageSystem) && !a.HasPermissionTo(args.UserId, model.PermissionEditOtherUsers) {
+ if !a.HasPermissionTo(rctx, args.UserId, model.PermissionManageSystem) && !a.HasPermissionTo(rctx, args.UserId, model.PermissionEditOtherUsers) {
return mobileLogsCrossUserUnavailableResponse(args)
}
- callerHasManageSystem := a.HasPermissionTo(args.UserId, model.PermissionManageSystem)
+ callerHasManageSystem := a.HasPermissionTo(rctx, args.UserId, model.PermissionManageSystem)
targetUser, lookupErr := a.GetUserByUsername(username)
if lookupErr != nil {
diff --git a/server/channels/app/slashcommands/command_msg.go b/server/channels/app/slashcommands/command_msg.go
index 0739cbbad4b4..8b32cabdb67b 100644
--- a/server/channels/app/slashcommands/command_msg.go
+++ b/server/channels/app/slashcommands/command_msg.go
@@ -77,7 +77,7 @@ func (*msgProvider) DoCommand(a *app.App, rctx request.CTX, args *model.CommandA
if channel, channelErr := a.Srv().Store().Channel().GetByName(args.TeamId, channelName, true); channelErr != nil {
var nfErr *store.ErrNotFound
if errors.As(channelErr, &nfErr) {
- if !a.HasPermissionTo(args.UserId, model.PermissionCreateDirectChannel) {
+ if !a.HasPermissionTo(rctx, args.UserId, model.PermissionCreateDirectChannel) {
return &model.CommandResponse{Text: args.T("api.command_msg.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral}
}
diff --git a/server/channels/app/slashcommands/command_remote.go b/server/channels/app/slashcommands/command_remote.go
index 583591b14c83..351f360e2204 100644
--- a/server/channels/app/slashcommands/command_remote.go
+++ b/server/channels/app/slashcommands/command_remote.go
@@ -72,7 +72,7 @@ func (rp *RemoteProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Co
}
func (rp *RemoteProvider) DoCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) *model.CommandResponse {
- if !a.HasPermissionTo(args.UserId, model.PermissionManageSecureConnections) {
+ if !a.HasPermissionTo(rctx, args.UserId, model.PermissionManageSecureConnections) {
return response(args.T("api.command_remote.permission_required", map[string]any{"Permission": "manage_secure_connections"}))
}
@@ -97,7 +97,7 @@ func (rp *RemoteProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Co
}
func (rp *RemoteProvider) GetAutoCompleteListItems(rctx request.CTX, a *app.App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) ([]model.AutocompleteListItem, error) {
- if !a.HasPermissionTo(commandArgs.UserId, model.PermissionManageSecureConnections) {
+ if !a.HasPermissionTo(rctx, commandArgs.UserId, model.PermissionManageSecureConnections) {
return nil, errors.New("You require `manage_secure_connections` permission to manage secure connections.")
}
diff --git a/server/channels/app/slashcommands/command_share.go b/server/channels/app/slashcommands/command_share.go
index c71c3d3f5a7b..8f6c133d3792 100644
--- a/server/channels/app/slashcommands/command_share.go
+++ b/server/channels/app/slashcommands/command_share.go
@@ -63,7 +63,7 @@ func (sp *ShareProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Com
}
func (sp *ShareProvider) GetAutoCompleteListItems(rctx request.CTX, a *app.App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) ([]model.AutocompleteListItem, error) {
- if !a.HasPermissionTo(commandArgs.UserId, model.PermissionManageSharedChannels) {
+ if !a.HasPermissionTo(rctx, commandArgs.UserId, model.PermissionManageSharedChannels) {
return []model.AutocompleteListItem{}, nil
}
@@ -127,7 +127,7 @@ func (sp *ShareProvider) getAutoCompleteUnInviteRemote(a *app.App, _ *model.Comm
}
func (sp *ShareProvider) DoCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) *model.CommandResponse {
- if !a.HasPermissionTo(args.UserId, model.PermissionManageSharedChannels) {
+ if !a.HasPermissionTo(rctx, args.UserId, model.PermissionManageSharedChannels) {
return response(args.T("api.command_share.permission_required", map[string]any{"Permission": "manage_shared_channels"}))
}
diff --git a/server/channels/app/slashcommands/command_share_test.go b/server/channels/app/slashcommands/command_share_test.go
index 1ccfecd68c72..1d9e4e97d7c9 100644
--- a/server/channels/app/slashcommands/command_share_test.go
+++ b/server/channels/app/slashcommands/command_share_test.go
@@ -243,7 +243,7 @@ func TestShareProviderGetAutoCompleteListItemsPermission(t *testing.T) {
t.Run("invite without manage_shared_channels permission returns no remote cluster data", func(t *testing.T) {
th := setupForSharedChannels(t).initBasic(t)
- require.False(t, th.App.HasPermissionTo(th.BasicUser.Id, model.PermissionManageSharedChannels),
+ require.False(t, th.App.HasPermissionTo(th.Context, th.BasicUser.Id, model.PermissionManageSharedChannels),
"precondition: BasicUser must not have manage_shared_channels for this subtest")
rc := seedRemote(t, th)
@@ -270,7 +270,7 @@ func TestShareProviderGetAutoCompleteListItemsPermission(t *testing.T) {
t.Run("uninvite without manage_shared_channels permission returns no remote cluster data", func(t *testing.T) {
th := setupForSharedChannels(t).initBasic(t)
- require.False(t, th.App.HasPermissionTo(th.BasicUser.Id, model.PermissionManageSharedChannels),
+ require.False(t, th.App.HasPermissionTo(th.Context, th.BasicUser.Id, model.PermissionManageSharedChannels),
"precondition: BasicUser must not have manage_shared_channels for this subtest")
rc := seedRemote(t, th)
@@ -393,7 +393,7 @@ func TestShareProviderGetAutoCompleteListItemsAdjacentRoles(t *testing.T) {
th := setupForSharedChannels(t).initBasic(t)
guest := th.createGuest(t)
- require.False(t, th.App.HasPermissionTo(guest.Id, model.PermissionManageSharedChannels),
+ require.False(t, th.App.HasPermissionTo(th.Context, guest.Id, model.PermissionManageSharedChannels),
"precondition: a freshly-created guest must not have manage_shared_channels")
rc := seedRemote(t, th)
@@ -420,7 +420,7 @@ func TestShareProviderGetAutoCompleteListItemsAdjacentRoles(t *testing.T) {
th := setupForSharedChannels(t).initBasic(t)
guest := th.createGuest(t)
- require.False(t, th.App.HasPermissionTo(guest.Id, model.PermissionManageSharedChannels),
+ require.False(t, th.App.HasPermissionTo(th.Context, guest.Id, model.PermissionManageSharedChannels),
"precondition: a freshly-created guest must not have manage_shared_channels")
rc := seedRemote(t, th)
@@ -446,7 +446,7 @@ func TestShareProviderGetAutoCompleteListItemsAdjacentRoles(t *testing.T) {
t.Run("system admin receives remote cluster data on invite", func(t *testing.T) {
th := setupForSharedChannels(t).initBasic(t)
- require.True(t, th.App.HasPermissionTo(th.SystemAdminUser.Id, model.PermissionManageSharedChannels),
+ require.True(t, th.App.HasPermissionTo(th.Context, th.SystemAdminUser.Id, model.PermissionManageSharedChannels),
"precondition: SystemAdminUser must have manage_shared_channels via inherited permissions")
rc := seedRemote(t, th)
@@ -479,7 +479,7 @@ func TestShareProviderGetAutoCompleteListItemsAdjacentRoles(t *testing.T) {
t.Run("system admin receives remote cluster data on uninvite", func(t *testing.T) {
th := setupForSharedChannels(t).initBasic(t)
- require.True(t, th.App.HasPermissionTo(th.SystemAdminUser.Id, model.PermissionManageSharedChannels),
+ require.True(t, th.App.HasPermissionTo(th.Context, th.SystemAdminUser.Id, model.PermissionManageSharedChannels),
"precondition: SystemAdminUser must have manage_shared_channels via inherited permissions")
rc := seedRemote(t, th)
diff --git a/server/channels/app/status.go b/server/channels/app/status.go
index ebd904ac3643..d99baaa19829 100644
--- a/server/channels/app/status.go
+++ b/server/channels/app/status.go
@@ -93,7 +93,7 @@ func (a *App) SetCustomStatus(rctx request.CTX, userID string, cs *model.CustomS
}
}
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
@@ -114,7 +114,7 @@ func (a *App) SetCustomStatus(rctx request.CTX, userID string, cs *model.CustomS
}
func (a *App) RemoveCustomStatus(rctx request.CTX, userID string) *model.AppError {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
@@ -128,8 +128,8 @@ func (a *App) RemoveCustomStatus(rctx request.CTX, userID string) *model.AppErro
return nil
}
-func (a *App) GetCustomStatus(userID string) (*model.CustomStatus, *model.AppError) {
- user, err := a.GetUser(userID)
+func (a *App) GetCustomStatus(rctx request.CTX, userID string) (*model.CustomStatus, *model.AppError) {
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return &model.CustomStatus{}, err
}
diff --git a/server/channels/app/status_test.go b/server/channels/app/status_test.go
index e37819679917..7cacc87553c7 100644
--- a/server/channels/app/status_test.go
+++ b/server/channels/app/status_test.go
@@ -30,7 +30,7 @@ func TestCustomStatus(t *testing.T) {
err := th.App.SetCustomStatus(th.Context, user.Id, cs)
require.Nil(t, err, "failed to set custom status %v", err)
- csSaved, err := th.App.GetCustomStatus(user.Id)
+ csSaved, err := th.App.GetCustomStatus(th.Context, user.Id)
require.Nil(t, err, "failed to get custom status after save %v", err)
require.Equal(t, cs, csSaved)
@@ -38,7 +38,7 @@ func TestCustomStatus(t *testing.T) {
require.Nil(t, err, "failed to clear custom status %v", err)
var csClear *model.CustomStatus
- csSaved, err = th.App.GetCustomStatus(user.Id)
+ csSaved, err = th.App.GetCustomStatus(th.Context, user.Id)
require.Nil(t, err, "failed to get custom status after clear %v", err)
require.Equal(t, csClear, csSaved)
}
@@ -191,7 +191,7 @@ func TestSetCustomStatus(t *testing.T) {
require.Nil(t, err)
}
- customStatus, err := th.App.GetCustomStatus(th.BasicUser.Id)
+ customStatus, err := th.App.GetCustomStatus(th.Context, th.BasicUser.Id)
require.Nil(t, err)
diff --git a/server/channels/app/team.go b/server/channels/app/team.go
index 0b90fbb543c0..2ed1a9e72579 100644
--- a/server/channels/app/team.go
+++ b/server/channels/app/team.go
@@ -138,7 +138,7 @@ func (a *App) CreateTeam(rctx request.CTX, team *model.Team) (*model.Team, *mode
}
func (a *App) CreateTeamWithUser(rctx request.CTX, team *model.Team, userID string) (*model.Team, *model.AppError) {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return nil, err
}
@@ -875,7 +875,7 @@ func (a *App) JoinUserToTeam(rctx request.CTX, team *model.Team, user *model.Use
var actor *model.User
if userRequestorId != "" {
- actor, _ = a.GetUser(userRequestorId)
+ actor, _ = a.GetUser(rctx, userRequestorId)
}
a.Srv().Go(func() {
@@ -1286,7 +1286,7 @@ func (a *App) RemoveUserFromTeam(rctx request.CTX, teamID string, userID string,
func (a *App) postProcessTeamMemberLeave(rctx request.CTX, teamMember *model.TeamMember, requestorId string) *model.AppError {
var actor *model.User
if requestorId != "" {
- actor, _ = a.GetUser(requestorId)
+ actor, _ = a.GetUser(rctx, requestorId)
}
a.Srv().Go(func() {
diff --git a/server/channels/app/team_access_control.go b/server/channels/app/team_access_control.go
index 8666c9abd7c1..e93b79a4765d 100644
--- a/server/channels/app/team_access_control.go
+++ b/server/channels/app/team_access_control.go
@@ -358,7 +358,7 @@ func (a *App) ValidateTeamAdminSelfInclusion(rctx request.CTX, userID, expressio
// nil to have it resolved here.
func (a *App) SendTeamAccessControlRemovalNotification(rctx request.CTX, systemBot *model.Bot, userID string, team *model.Team) *model.AppError {
locale := ""
- if user, err := a.GetUser(userID); err == nil {
+ if user, err := a.GetUser(rctx, userID); err == nil {
locale = user.Locale
}
T := i18n.GetUserTranslations(locale)
@@ -377,7 +377,7 @@ func (a *App) SendTeamAccessControlAdditionNotification(rctx request.CTX, system
a.LogAuditRec(rctx, rec, nil)
locale := ""
- if user, err := a.GetUser(userID); err == nil {
+ if user, err := a.GetUser(rctx, userID); err == nil {
locale = user.Locale
}
T := i18n.GetUserTranslations(locale)
diff --git a/server/channels/app/team_directory_visibility.go b/server/channels/app/team_directory_visibility.go
index 93ea84fe8d0e..d8080de7b7e6 100644
--- a/server/channels/app/team_directory_visibility.go
+++ b/server/channels/app/team_directory_visibility.go
@@ -169,7 +169,7 @@ func (a *App) FilterNonQualifyingTeamsForUser(rctx request.CTX, teams []*model.T
}
userOnce.Do(func() {
- user, userErr = a.GetUser(userID)
+ user, userErr = a.GetUser(rctx, userID)
})
if userErr != nil {
return nil, 0, userErr
@@ -261,7 +261,7 @@ func (a *App) AnnotateRecommendedTeamsForUser(rctx request.CTX, teams []*model.T
}
userOnce.Do(func() {
- user, userErr = a.GetUser(userID)
+ user, userErr = a.GetUser(rctx, userID)
})
if userErr != nil {
return
diff --git a/server/channels/app/terms_of_service.go b/server/channels/app/terms_of_service.go
index e3f4a8447183..c3cdab139679 100644
--- a/server/channels/app/terms_of_service.go
+++ b/server/channels/app/terms_of_service.go
@@ -8,16 +8,17 @@ import (
"net/http"
"github.com/mattermost/mattermost/server/public/model"
+ "github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
)
-func (a *App) CreateTermsOfService(text, userID string) (*model.TermsOfService, *model.AppError) {
+func (a *App) CreateTermsOfService(rctx request.CTX, text, userID string) (*model.TermsOfService, *model.AppError) {
termsOfService := &model.TermsOfService{
Text: text,
UserId: userID,
}
- if _, appErr := a.GetUser(userID); appErr != nil {
+ if _, appErr := a.GetUser(rctx, userID); appErr != nil {
return nil, appErr
}
diff --git a/server/channels/app/user.go b/server/channels/app/user.go
index 284e5fe01f05..cd1c850eab73 100644
--- a/server/channels/app/user.go
+++ b/server/channels/app/user.go
@@ -539,9 +539,8 @@ func (a *App) AddUserToTeamByInviteIfNeeded(rctx request.CTX, user *model.User,
return nil
}
-// TODO: Migrate this compatibility wrapper to accept request.CTX.
-func (a *App) GetUser(userID string) (*model.User, *model.AppError) {
- user, err := a.ch.srv.userService.GetUser(request.EmptyContext(a.Log()), userID)
+func (a *App) GetUser(rctx request.CTX, userID string) (*model.User, *model.AppError) {
+ user, err := a.ch.srv.userService.GetUser(rctx, userID)
if err != nil {
var nfErr *store.ErrNotFound
switch {
@@ -934,8 +933,8 @@ func (a *App) sanitizeProfiles(users []*model.User, asAdmin bool) []*model.User
return users
}
-func (a *App) GenerateMfaSecret(userID string) (*model.MfaSecret, *model.AppError) {
- user, appErr := a.GetUser(userID)
+func (a *App) GenerateMfaSecret(rctx request.CTX, userID string) (*model.MfaSecret, *model.AppError) {
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return nil, appErr
}
@@ -952,8 +951,8 @@ func (a *App) GenerateMfaSecret(userID string) (*model.MfaSecret, *model.AppErro
return mfaSecret, nil
}
-func (a *App) ActivateMfa(userID, token string) *model.AppError {
- user, appErr := a.GetUser(userID)
+func (a *App) ActivateMfa(rctx request.CTX, userID, token string) *model.AppError {
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return appErr
}
@@ -981,8 +980,8 @@ func (a *App) ActivateMfa(userID, token string) *model.AppError {
return nil
}
-func (a *App) DeactivateMfa(userID string) *model.AppError {
- user, appErr := a.GetUser(userID)
+func (a *App) DeactivateMfa(rctx request.CTX, userID string) *model.AppError {
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return appErr
}
@@ -1050,7 +1049,7 @@ func (a *App) SetDefaultProfileImage(rctx request.CTX, user *model.User) *model.
return err
}
- updatedUser, appErr := a.GetUser(user.Id)
+ updatedUser, appErr := a.GetUser(rctx, user.Id)
if appErr != nil {
rctx.Logger().Warn("Error in getting users profile forcing logout", mlog.String("user_id", user.Id), mlog.Err(appErr))
return nil
@@ -1140,7 +1139,7 @@ func (a *App) SetProfileImageFromFile(rctx request.CTX, userID string, file io.R
}
func (a *App) UpdatePasswordAsUser(rctx request.CTX, userID, currentPassword, newPassword string) *model.AppError {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
@@ -1172,7 +1171,7 @@ func (a *App) UpdatePasswordAsUser(rctx request.CTX, userID, currentPassword, ne
func (a *App) userDeactivated(rctx request.CTX, userID string) *model.AppError {
a.SetStatusOffline(userID, false, true)
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
@@ -1470,7 +1469,7 @@ func (a *App) IsProfileImageLockedForUser(session model.Session, user *model.Use
}
func (a *App) PatchUser(rctx request.CTX, userID string, patch *model.UserPatch, asAdmin bool) (*model.User, *model.AppError) {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return nil, err
}
@@ -1642,7 +1641,7 @@ func (a *App) UpdateUser(rctx request.CTX, user *model.User, sendNotifications b
rctx.Logger().Warn("Error with updating default profile image", mlog.Err(err))
}
- tempUser, getUserErr := a.GetUser(user.Id)
+ tempUser, getUserErr := a.GetUser(rctx, user.Id)
if getUserErr != nil {
rctx.Logger().Warn("Error when retrieving user after profile picture update, avatar may fail to update automatically on client applications.", mlog.Err(getUserErr))
} else {
@@ -1691,7 +1690,7 @@ func (a *App) UpdateUser(rctx request.CTX, user *model.User, sendNotifications b
}
func (a *App) UpdateUserActive(rctx request.CTX, userID string, active bool) *model.AppError {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
@@ -1722,17 +1721,17 @@ func (a *App) updateUserNotifyProps(userID string, props map[string]string) *mod
func (a *App) UpdateMfa(rctx request.CTX, activate bool, userID, token string) *model.AppError {
if activate {
- if err := a.ActivateMfa(userID, token); err != nil {
+ if err := a.ActivateMfa(rctx, userID, token); err != nil {
return err
}
} else {
- if err := a.DeactivateMfa(userID); err != nil {
+ if err := a.DeactivateMfa(rctx, userID); err != nil {
return err
}
}
a.Srv().Go(func() {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
rctx.Logger().Error("Failed to get user", mlog.Err(err))
return
@@ -1747,7 +1746,7 @@ func (a *App) UpdateMfa(rctx request.CTX, activate bool, userID, token string) *
}
func (a *App) UpdatePasswordByUserIdSendEmail(rctx request.CTX, userID, newPassword, method string) *model.AppError {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
@@ -1823,8 +1822,8 @@ func (a *App) UpdatePasswordSendEmail(rctx request.CTX, user *model.User, newPas
return nil
}
-func (a *App) UpdateHashedPasswordByUserId(userID, newHashedPassword string) *model.AppError {
- user, err := a.GetUser(userID)
+func (a *App) UpdateHashedPasswordByUserId(rctx request.CTX, userID, newHashedPassword string) *model.AppError {
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
@@ -1873,7 +1872,7 @@ func (a *App) resetPasswordFromToken(rctx request.CTX, userSuppliedTokenString,
return model.NewAppError("resetPassword", "api.user.reset_password.token_parse.error", nil, "", http.StatusInternalServerError)
}
- user, err := a.GetUser(tokenData.UserId)
+ user, err := a.GetUser(rctx, tokenData.UserId)
if err != nil {
return err
}
@@ -2054,7 +2053,7 @@ func (a *App) DeleteToken(token *model.Token) *model.AppError {
}
func (a *App) UpdateUserRoles(rctx request.CTX, userID string, newRoles string, sendWebSocketEvent bool) (*model.User, *model.AppError) {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
err.StatusCode = http.StatusBadRequest
return nil, err
@@ -2329,13 +2328,13 @@ func (a *App) VerifyEmailFromToken(rctx request.CTX, userSuppliedTokenString str
return model.NewAppError("VerifyEmailFromToken", "api.user.verify_email.token_parse.error", nil, "", http.StatusInternalServerError)
}
- user, err := a.GetUser(tokenData.UserId)
+ user, err := a.GetUser(rctx, tokenData.UserId)
if err != nil {
return err
}
tokenData.Email = strings.ToLower(tokenData.Email)
- if err := a.VerifyUserEmail(tokenData.UserId, tokenData.Email); err != nil {
+ if err := a.VerifyUserEmail(rctx, tokenData.UserId, tokenData.Email); err != nil {
return err
}
@@ -2392,14 +2391,14 @@ func (a *App) GetFilteredUsersStats(options *model.UserCountOptions) (*model.Use
return stats, nil
}
-func (a *App) VerifyUserEmail(userID, email string) *model.AppError {
+func (a *App) VerifyUserEmail(rctx request.CTX, userID, email string) *model.AppError {
if _, err := a.Srv().Store().User().VerifyEmail(userID, email); err != nil {
return model.NewAppError("VerifyUserEmail", "app.user.verify_email.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
a.InvalidateCacheForUser(userID)
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return err
}
@@ -2754,7 +2753,7 @@ func (a *App) userBelongsToChannels(userID string, channelIDs []string) (bool, *
}
func (a *App) GetViewUsersRestrictions(rctx request.CTX, userID string) (*model.ViewUsersRestrictions, *model.AppError) {
- if a.HasPermissionTo(userID, model.PermissionViewMembers) {
+ if a.HasPermissionTo(rctx, userID, model.PermissionViewMembers) {
return nil, nil
}
@@ -2803,7 +2802,7 @@ func (a *App) PromoteGuestToUser(rctx request.CTX, user *model.User, requestorId
}
}
- promotedUser, err := a.GetUser(user.Id)
+ promotedUser, err := a.GetUser(rctx, user.Id)
if err != nil {
rctx.Logger().Warn("Failed to get user on promote guest to user", mlog.Err(err))
} else {
@@ -2912,7 +2911,7 @@ func (a *App) PublishUserTyping(userID, channelID, parentId string) *model.AppEr
func (a *App) invalidateUserCacheAndPublish(rctx request.CTX, userID string) {
a.InvalidateCacheForUser(userID)
- user, userErr := a.GetUser(userID)
+ user, userErr := a.GetUser(rctx, userID)
if userErr != nil {
rctx.Logger().Error("Error in getting users profile", mlog.String("user_id", userID), mlog.Err(userErr))
return
@@ -3166,7 +3165,7 @@ func (a *App) UpdateThreadFollowForUserFromChannelAdd(rctx request.CTX, userID,
if appErr != nil {
return appErr
}
- user, appErr := a.GetUser(userID)
+ user, appErr := a.GetUser(rctx, userID)
if appErr != nil {
return appErr
}
@@ -3232,7 +3231,7 @@ func (a *App) UpdateThreadReadForUserByPost(rctx request.CTX, currentSessionId,
}
func (a *App) UpdateThreadReadForUser(rctx request.CTX, currentSessionId, userID, teamID, threadID string, timestamp int64) (*model.ThreadResponse, *model.AppError) {
- user, err := a.GetUser(userID)
+ user, err := a.GetUser(rctx, userID)
if err != nil {
return nil, err
}
diff --git a/server/channels/app/user_terms_of_service_test.go b/server/channels/app/user_terms_of_service_test.go
index 2b8cbe236aae..265d9af950d3 100644
--- a/server/channels/app/user_terms_of_service_test.go
+++ b/server/channels/app/user_terms_of_service_test.go
@@ -18,7 +18,7 @@ func TestUserTermsOfService(t *testing.T) {
assert.Nil(t, userTermsOfService)
assert.Equal(t, "app.user_terms_of_service.get_by_user.no_rows.app_error", err.Id)
- termsOfService, err := th.App.CreateTermsOfService("terms of service", th.BasicUser.Id)
+ termsOfService, err := th.App.CreateTermsOfService(th.Context, "terms of service", th.BasicUser.Id)
checkNoError(t, err)
err = th.App.SaveUserTermsOfService(th.BasicUser.Id, termsOfService.Id, true)
diff --git a/server/channels/app/user_test.go b/server/channels/app/user_test.go
index 8d64c13bb3c7..9f2b02c233ae 100644
--- a/server/channels/app/user_test.go
+++ b/server/channels/app/user_test.go
@@ -121,7 +121,7 @@ func TestUpdateDefaultProfileImage(t *testing.T) {
err = th.App.UpdateDefaultProfileImage(th.Context, user)
require.Nil(t, err)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
assert.Less(t, user.LastPictureUpdate, -startTime, "LastPictureUpdate should be set to -(current time in milliseconds)")
}
@@ -256,7 +256,7 @@ func TestUpdateUser(t *testing.T) {
// Give the user a LastPictureUpdate to mimic having a custom profile picture
err := th.App.Srv().Store().User().UpdateLastPictureUpdate(user.Id)
require.NoError(t, err)
- iUser, errGetUser := th.App.GetUser(user.Id)
+ iUser, errGetUser := th.App.GetUser(th.Context, user.Id)
require.Nil(t, errGetUser)
iUser.Username = "updatedUsername"
iLastPictureUpdate := iUser.LastPictureUpdate
@@ -402,7 +402,7 @@ func TestCreateUser(t *testing.T) {
time.Sleep(1 * time.Second)
- user, err = th.App.GetUser(user.Id)
+ user, err = th.App.GetUser(th.Context, user.Id)
require.Nil(t, err)
require.Equal(t, "sanitized", user.Nickname)
})
@@ -452,7 +452,7 @@ func TestUpdateActiveBotsSideEffect(t *testing.T) {
retbot1, err := th.App.GetBot(th.Context, bot.UserId, true)
require.Nil(t, err)
require.Zero(t, retbot1.DeleteAt)
- user1, err := th.App.GetUser(bot.UserId)
+ user1, err := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, err)
require.Zero(t, user1.DeleteAt)
@@ -470,7 +470,7 @@ func TestUpdateActiveBotsSideEffect(t *testing.T) {
retbot2, err := th.App.GetBot(th.Context, bot.UserId, true)
require.Nil(t, err)
require.NotZero(t, retbot2.DeleteAt)
- user2, err := th.App.GetUser(bot.UserId)
+ user2, err := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, err)
require.NotZero(t, user2.DeleteAt)
@@ -573,10 +573,10 @@ func TestUpdateOAuthUserAttrs(t *testing.T) {
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
data := bytes.NewReader(gitlabUser)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
require.Nil(t, appErr)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
require.Equal(t, gitlabUserObj.Username, user.Username, "user's username is not updated")
})
@@ -587,10 +587,10 @@ func TestUpdateOAuthUserAttrs(t *testing.T) {
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
data := bytes.NewReader(gitlabUser)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
require.Nil(t, appErr)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
require.NotEqual(t, gitlabUserObj.Username, user.Username, "user's username is updated though there already exists another user with the same username")
})
@@ -602,10 +602,10 @@ func TestUpdateOAuthUserAttrs(t *testing.T) {
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
data := bytes.NewReader(gitlabUser)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
require.Nil(t, appErr)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
require.Equal(t, gitlabUserObj.Email, user.Email, "user's email is not updated")
@@ -618,10 +618,10 @@ func TestUpdateOAuthUserAttrs(t *testing.T) {
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
data := bytes.NewReader(gitlabUser)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
require.Nil(t, appErr)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
require.NotEqual(t, gitlabUserObj.Email, user.Email, "user's email is updated though there already exists another user with the same email")
})
@@ -632,10 +632,10 @@ func TestUpdateOAuthUserAttrs(t *testing.T) {
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
data := bytes.NewReader(gitlabUser)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
require.Nil(t, appErr)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
require.Equal(t, "Updated", user.FirstName, "user's first name is not updated")
})
@@ -645,10 +645,10 @@ func TestUpdateOAuthUserAttrs(t *testing.T) {
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
data := bytes.NewReader(gitlabUser)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
require.Nil(t, appErr)
- user = getUserFromDB(th.App, user.Id, t)
+ user = getUserFromDB(th.Context, th.App, user.Id, t)
require.Equal(t, "Lastname", user.LastName, "user's last name is not updated")
})
@@ -720,7 +720,7 @@ func TestUpdateUserEmail(t *testing.T) {
appErr = th.App.VerifyEmailFromToken(th.Context, token.Token)
assert.Nil(t, appErr)
- user2, appErr = th.App.GetUser(user2.Id)
+ user2, appErr = th.App.GetUser(th.Context, user2.Id)
assert.Nil(t, appErr)
assert.Equal(t, newEmail, user2.Email)
assert.True(t, user2.EmailVerified)
@@ -846,8 +846,8 @@ func TestUpdateUserEmail(t *testing.T) {
})
}
-func getUserFromDB(a *App, id string, t *testing.T) *model.User {
- user, err := a.GetUser(id)
+func getUserFromDB(rctx request.CTX, a *App, id string, t *testing.T) *model.User {
+ user, err := a.GetUser(rctx, id)
require.Nil(t, err, "user is not found", err)
return user
}
@@ -1563,7 +1563,7 @@ func TestPermanentDeleteUser(t *testing.T) {
assert.Equal(t, 1, botCount1)
// test that bot is deleted from bots table
- retUser1, err := th.App.GetUser(bot.UserId)
+ retUser1, err := th.App.GetUser(th.Context, bot.UserId)
assert.Nil(t, err)
err = th.App.PermanentDeleteUser(th.Context, retUser1)
@@ -2043,7 +2043,7 @@ func TestPromoteGuestToUser(t *testing.T) {
err := th.App.PromoteGuestToUser(th.Context, th.BasicUser, th.BasicUser.Id)
require.Nil(t, err)
- user, err := th.App.GetUser(th.BasicUser.Id)
+ user, err := th.App.GetUser(th.Context, th.BasicUser.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", user.Roles)
})
@@ -2054,7 +2054,7 @@ func TestPromoteGuestToUser(t *testing.T) {
err := th.App.PromoteGuestToUser(th.Context, guest, th.BasicUser.Id)
require.Nil(t, err)
- guest, err = th.App.GetUser(guest.Id)
+ guest, err = th.App.GetUser(th.Context, guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", guest.Roles)
})
@@ -2070,7 +2070,7 @@ func TestPromoteGuestToUser(t *testing.T) {
err = th.App.PromoteGuestToUser(th.Context, guest, th.BasicUser.Id)
require.Nil(t, err)
- guest, err = th.App.GetUser(guest.Id)
+ guest, err = th.App.GetUser(th.Context, guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", guest.Roles)
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
@@ -2094,7 +2094,7 @@ func TestPromoteGuestToUser(t *testing.T) {
err = th.App.PromoteGuestToUser(th.Context, guest, th.BasicUser.Id)
require.Nil(t, err)
- guest, err = th.App.GetUser(guest.Id)
+ guest, err = th.App.GetUser(th.Context, guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", guest.Roles)
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
@@ -2126,7 +2126,7 @@ func TestPromoteGuestToUser(t *testing.T) {
err = th.App.PromoteGuestToUser(th.Context, guest, th.BasicUser.Id)
require.Nil(t, err)
- guest, err = th.App.GetUser(guest.Id)
+ guest, err = th.App.GetUser(th.Context, guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", guest.Roles)
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
@@ -2176,7 +2176,7 @@ func TestDemoteUserToGuest(t *testing.T) {
t.Run("Must reject bot user", func(t *testing.T) {
bot := th.CreateBot(t)
- user, err := th.App.GetUser(bot.UserId)
+ user, err := th.App.GetUser(th.Context, bot.UserId)
require.Nil(t, err)
require.True(t, user.IsBot)
@@ -2218,7 +2218,7 @@ func TestDemoteUserToGuest(t *testing.T) {
err := th.App.DemoteUserToGuest(th.Context, guest)
require.Nil(t, err)
- user, err := th.App.GetUser(guest.Id)
+ user, err := th.App.GetUser(th.Context, guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
})
@@ -2229,7 +2229,7 @@ func TestDemoteUserToGuest(t *testing.T) {
err := th.App.DemoteUserToGuest(th.Context, user)
require.Nil(t, err)
- user, err = th.App.GetUser(user.Id)
+ user, err = th.App.GetUser(th.Context, user.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
})
@@ -2245,7 +2245,7 @@ func TestDemoteUserToGuest(t *testing.T) {
err = th.App.DemoteUserToGuest(th.Context, user)
require.Nil(t, err)
- user, err = th.App.GetUser(user.Id)
+ user, err = th.App.GetUser(th.Context, user.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
@@ -2269,7 +2269,7 @@ func TestDemoteUserToGuest(t *testing.T) {
err = th.App.DemoteUserToGuest(th.Context, user)
require.Nil(t, err)
- user, err = th.App.GetUser(user.Id)
+ user, err = th.App.GetUser(th.Context, user.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
@@ -2301,7 +2301,7 @@ func TestDemoteUserToGuest(t *testing.T) {
err = th.App.DemoteUserToGuest(th.Context, user)
require.Nil(t, err)
- user, err = th.App.GetUser(user.Id)
+ user, err = th.App.GetUser(th.Context, user.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
@@ -2349,7 +2349,7 @@ func TestDemoteUserToGuest(t *testing.T) {
err = th.App.DemoteUserToGuest(th.Context, user)
require.Nil(t, err)
- user, err = th.App.GetUser(user.Id)
+ user, err = th.App.GetUser(th.Context, user.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
@@ -2378,15 +2378,15 @@ func TestDeactivateGuests(t *testing.T) {
err := th.App.DeactivateGuests(th.Context)
require.Nil(t, err)
- guest1, err = th.App.GetUser(guest1.Id)
+ guest1, err = th.App.GetUser(th.Context, guest1.Id)
assert.Nil(t, err)
assert.NotEqual(t, int64(0), guest1.DeleteAt)
- guest2, err = th.App.GetUser(guest2.Id)
+ guest2, err = th.App.GetUser(th.Context, guest2.Id)
assert.Nil(t, err)
assert.NotEqual(t, int64(0), guest2.DeleteAt)
- user, err = th.App.GetUser(user.Id)
+ user, err = th.App.GetUser(th.Context, user.Id)
assert.Nil(t, err)
assert.Equal(t, int64(0), user.DeleteAt)
}
@@ -2464,7 +2464,7 @@ func TestDeactivateMfa(t *testing.T) {
})
user := th.BasicUser
- err := th.App.DeactivateMfa(user.Id)
+ err := th.App.DeactivateMfa(th.Context, user.Id)
require.Nil(t, err)
})
}
diff --git a/server/channels/jobs/helper_test.go b/server/channels/jobs/helper_test.go
index b16fcb351b30..4d19c0153145 100644
--- a/server/channels/jobs/helper_test.go
+++ b/server/channels/jobs/helper_test.go
@@ -180,15 +180,15 @@ func (th *TestHelper) InitBasic(tb testing.TB) *TestHelper {
th.SystemAdminUser = th.CreateUser(tb)
_, appErr := th.App.UpdateUserRoles(th.Context, th.SystemAdminUser.Id, model.SystemUserRoleId+" "+model.SystemAdminRoleId, false)
require.Nil(tb, appErr)
- th.SystemAdminUser, appErr = th.App.GetUser(th.SystemAdminUser.Id)
+ th.SystemAdminUser, appErr = th.App.GetUser(th.Context, th.SystemAdminUser.Id)
require.Nil(tb, appErr)
th.BasicUser = th.CreateUser(tb)
- th.BasicUser, appErr = th.App.GetUser(th.BasicUser.Id)
+ th.BasicUser, appErr = th.App.GetUser(th.Context, th.BasicUser.Id)
require.Nil(tb, appErr)
th.BasicUser2 = th.CreateUser(tb)
- th.BasicUser2, appErr = th.App.GetUser(th.BasicUser2.Id)
+ th.BasicUser2, appErr = th.App.GetUser(th.Context, th.BasicUser2.Id)
require.Nil(tb, appErr)
th.BasicTeam = th.CreateTeam(tb)
diff --git a/server/channels/store/retrylayer/retrylayer.go b/server/channels/store/retrylayer/retrylayer.go
index 5d019a3e522a..f44f21312a10 100644
--- a/server/channels/store/retrylayer/retrylayer.go
+++ b/server/channels/store/retrylayer/retrylayer.go
@@ -11109,27 +11109,6 @@ func (s *RetryLayerPropertyValueStore) Upsert(values []*model.PropertyValue) ([]
}
-func (s *RetryLayerReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
-
- tries := 0
- for {
- result, err := s.ReactionStore.BulkGetForPosts(postIds)
- if err == nil {
- return result, nil
- }
- if !isRepeatableError(err) {
- return result, err
- }
- tries++
- if tries >= 3 {
- err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
- return result, err
- }
- timepkg.Sleep(100 * timepkg.Millisecond)
- }
-
-}
-
func (s *RetryLayerReactionStore) Delete(reaction *model.Reaction) (*model.Reaction, error) {
tries := 0
diff --git a/server/channels/store/sqlstore/reaction_store.go b/server/channels/store/sqlstore/reaction_store.go
index d2e3e1f3b06b..8ef989f6031c 100644
--- a/server/channels/store/sqlstore/reaction_store.go
+++ b/server/channels/store/sqlstore/reaction_store.go
@@ -162,31 +162,6 @@ func (s *SqlReactionStore) GetUniqueCountForPost(postId string) (int, error) {
return int(count), nil
}
-func (s *SqlReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
- placeholder, values := constructArrayArgs(postIds)
- var reactions []*model.Reaction
-
- if err := s.GetReplica().Select(&reactions,
- `SELECT
- UserId,
- PostId,
- EmojiName,
- CreateAt,
- COALESCE(UpdateAt, CreateAt) As UpdateAt,
- COALESCE(DeleteAt, 0) As DeleteAt,
- RemoteId,
- ChannelId
- FROM
- Reactions
- WHERE
- PostId IN `+placeholder+` AND COALESCE(DeleteAt, 0) = 0
- ORDER BY
- CreateAt`, values...); err != nil {
- return nil, errors.Wrap(err, "failed to get Reactions")
- }
- return reactions, nil
-}
-
func (s *SqlReactionStore) GetSingle(userID, postID, remoteID, emojiName string) (*model.Reaction, error) {
query := s.getQueryBuilder().
Select("UserId", "PostId", "EmojiName", "CreateAt",
diff --git a/server/channels/store/store.go b/server/channels/store/store.go
index 17f22dfc935b..2c233f0625b3 100644
--- a/server/channels/store/store.go
+++ b/server/channels/store/store.go
@@ -816,7 +816,6 @@ type ReactionStore interface {
GetUniqueCountForPost(postID string) (int, error)
ExistsOnPost(postID string, emojiName string) (bool, error)
DeleteAllWithEmojiName(rctx request.CTX, emojiName string) error
- BulkGetForPosts(postIds []string) ([]*model.Reaction, error)
GetSingle(userID, postID, remoteID, emojiName string) (*model.Reaction, error)
DeleteOrphanedRowsByIds(r *model.RetentionIdsForDeletion) (int64, error)
PermanentDeleteBatch(endTime int64, limit int64) (int64, error)
diff --git a/server/channels/store/storetest/mocks/ReactionStore.go b/server/channels/store/storetest/mocks/ReactionStore.go
index ec99d895a404..6b53539b760a 100644
--- a/server/channels/store/storetest/mocks/ReactionStore.go
+++ b/server/channels/store/storetest/mocks/ReactionStore.go
@@ -15,36 +15,6 @@ type ReactionStore struct {
mock.Mock
}
-// BulkGetForPosts provides a mock function with given fields: postIds
-func (_m *ReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
- ret := _m.Called(postIds)
-
- if len(ret) == 0 {
- panic("no return value specified for BulkGetForPosts")
- }
-
- var r0 []*model.Reaction
- var r1 error
- if rf, ok := ret.Get(0).(func([]string) ([]*model.Reaction, error)); ok {
- return rf(postIds)
- }
- if rf, ok := ret.Get(0).(func([]string) []*model.Reaction); ok {
- r0 = rf(postIds)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).([]*model.Reaction)
- }
- }
-
- if rf, ok := ret.Get(1).(func([]string) error); ok {
- r1 = rf(postIds)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
// Delete provides a mock function with given fields: reaction
func (_m *ReactionStore) Delete(reaction *model.Reaction) (*model.Reaction, error) {
ret := _m.Called(reaction)
diff --git a/server/channels/store/storetest/reaction_store.go b/server/channels/store/storetest/reaction_store.go
index 4c78fb0ae32d..171d1300d74c 100644
--- a/server/channels/store/storetest/reaction_store.go
+++ b/server/channels/store/storetest/reaction_store.go
@@ -26,7 +26,6 @@ func TestReactionStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStor
t.Run("ReactionDeleteAllWithEmojiName", func(t *testing.T) { testReactionDeleteAllWithEmojiName(t, rctx, ss, s) })
t.Run("PermanentDeleteByUser", func(t *testing.T) { testPermanentDeleteByUser(t, rctx, ss) })
t.Run("PermanentDeleteBatch", func(t *testing.T) { testReactionStorePermanentDeleteBatch(t, rctx, ss) })
- t.Run("ReactionBulkGetForPosts", func(t *testing.T) { testReactionBulkGetForPosts(t, rctx, ss) })
t.Run("ReactionDeadlock", func(t *testing.T) { testReactionDeadlock(t, rctx, ss) })
t.Run("ExistsOnPost", func(t *testing.T) { testExistsOnPost(t, rctx, ss) })
t.Run("GetUniqueCountForPost", func(t *testing.T) { testGetUniqueCountForPost(t, rctx, ss) })
@@ -742,83 +741,6 @@ func testReactionStorePermanentDeleteBatch(t *testing.T, rctx request.CTX, ss st
require.Len(t, returned, 1, "reactions for newer post should not have been deleted")
}
-func testReactionBulkGetForPosts(t *testing.T, rctx request.CTX, ss store.Store) {
- userId := model.NewId()
- post, _ := ss.Post().Save(rctx, &model.Post{
- ChannelId: model.NewId(),
- UserId: userId,
- })
- postId := post.Id
- post, _ = ss.Post().Save(rctx, &model.Post{
- ChannelId: model.NewId(),
- UserId: userId,
- })
- post2Id := post.Id
- post, _ = ss.Post().Save(rctx, &model.Post{
- ChannelId: model.NewId(),
- UserId: userId,
- })
- post3Id := post.Id
- post, _ = ss.Post().Save(rctx, &model.Post{
- ChannelId: model.NewId(),
- UserId: userId,
- })
- post4Id := post.Id
-
- reactions := []*model.Reaction{
- {
- UserId: userId,
- PostId: postId,
- EmojiName: "smile",
- },
- {
- UserId: model.NewId(),
- PostId: post2Id,
- EmojiName: "smile",
- },
- {
- UserId: userId,
- PostId: post3Id,
- EmojiName: "sad",
- },
- {
- UserId: userId,
- PostId: postId,
- EmojiName: "angry",
- },
- {
- UserId: userId,
- PostId: post2Id,
- EmojiName: "angry",
- },
- {
- UserId: userId,
- PostId: post4Id,
- EmojiName: "angry",
- },
- }
-
- for _, reaction := range reactions {
- _, err := ss.Reaction().Save(reaction)
- require.NoError(t, err)
- }
-
- postIds := []string{postId, post2Id, post3Id}
- returned, err := ss.Reaction().BulkGetForPosts(postIds)
- require.NoError(t, err)
- require.Len(t, returned, 5, "should've returned 5 reactions")
-
- post4IdFound := false
- for _, reaction := range returned {
- if reaction.PostId == post4Id {
- post4IdFound = true
- break
- }
- }
-
- require.False(t, post4IdFound, "Wrong reaction returned")
-}
-
// testReactionDeadlock is a best-case attempt to recreate the deadlock scenario.
// It at least deadlocks 2 times out of 5.
func testReactionDeadlock(t *testing.T, rctx request.CTX, ss store.Store) {
diff --git a/server/channels/store/timerlayer/timerlayer.go b/server/channels/store/timerlayer/timerlayer.go
index 47d9001668ae..bfaac90da924 100644
--- a/server/channels/store/timerlayer/timerlayer.go
+++ b/server/channels/store/timerlayer/timerlayer.go
@@ -8879,22 +8879,6 @@ func (s *TimerLayerPropertyValueStore) Upsert(values []*model.PropertyValue) ([]
return result, err
}
-func (s *TimerLayerReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
- start := time.Now()
-
- result, err := s.ReactionStore.BulkGetForPosts(postIds)
-
- elapsed := float64(time.Since(start)) / float64(time.Second)
- if s.Root.Metrics != nil {
- success := "false"
- if err == nil {
- success = "true"
- }
- s.Root.Metrics.ObserveStoreMethodDuration("ReactionStore.BulkGetForPosts", success, elapsed)
- }
- return result, err
-}
-
func (s *TimerLayerReactionStore) Delete(reaction *model.Reaction) (*model.Reaction, error) {
start := time.Now()
diff --git a/server/cmd/mmctl/commands/ldap_e2e_test.go b/server/cmd/mmctl/commands/ldap_e2e_test.go
index 0e4497c9b3ee..12fa9c34bd8b 100644
--- a/server/cmd/mmctl/commands/ldap_e2e_test.go
+++ b/server/cmd/mmctl/commands/ldap_e2e_test.go
@@ -122,7 +122,7 @@ func (s *MmctlE2ETestSuite) TestLdapIDMigrateCmd() {
s.Require().Equal(printer.GetLines()[0], "AD/LDAP IdAttribute migration complete. You can now change your IdAttribute to: "+"cn")
s.Require().Len(printer.GetErrorLines(), 0)
- updatedUser, appErr := s.th.App.GetUser(ldapUser.Id)
+ updatedUser, appErr := s.th.App.GetUser(s.th.Context, ldapUser.Id)
s.Require().Nil(appErr)
s.Require().Equal("Dev1", *updatedUser.AuthData)
})
diff --git a/server/cmd/mmctl/commands/permissions_role_e2e_test.go b/server/cmd/mmctl/commands/permissions_role_e2e_test.go
index 4461972e634d..11b122ee7cd8 100644
--- a/server/cmd/mmctl/commands/permissions_role_e2e_test.go
+++ b/server/cmd/mmctl/commands/permissions_role_e2e_test.go
@@ -56,7 +56,7 @@ func (s *MmctlE2ETestSuite) TestAssignUsersCmd() {
roles := user.Roles
- u, err2 := s.th.App.GetUser(user.Id)
+ u, err2 := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(err2)
s.Require().True(u.IsInRole(model.SystemManagerRoleId))
@@ -97,7 +97,7 @@ func (s *MmctlE2ETestSuite) TestUnassignUsersCmd() {
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
- u, err2 := s.th.App.GetUser(user.Id)
+ u, err2 := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(err2)
s.Require().False(u.IsInRole(model.SystemManagerRoleId))
})
diff --git a/server/cmd/mmctl/commands/user_e2e_test.go b/server/cmd/mmctl/commands/user_e2e_test.go
index 37874631065e..f416618b2956 100644
--- a/server/cmd/mmctl/commands/user_e2e_test.go
+++ b/server/cmd/mmctl/commands/user_e2e_test.go
@@ -34,7 +34,7 @@ func (s *MmctlE2ETestSuite) TestUserActivateCmd() {
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
- ruser, err := s.th.App.GetUser(user.Id)
+ ruser, err := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(err)
s.Require().Zero(ruser.DeleteAt)
})
@@ -51,7 +51,7 @@ func (s *MmctlE2ETestSuite) TestUserActivateCmd() {
s.Require().Len(printer.GetErrorLines(), 1)
s.Require().Equal(printer.GetErrorLines()[0], "unable to change activation status of user "+user.Id+": You do not have the appropriate permissions.")
- ruser, err := s.th.App.GetUser(user.Id)
+ ruser, err := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(err)
s.Require().NotZero(ruser.DeleteAt)
})
@@ -84,7 +84,7 @@ func (s *MmctlE2ETestSuite) TestUserDeactivateCmd() {
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
- ruser, err := s.th.App.GetUser(user.Id)
+ ruser, err := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(err)
s.Require().NotZero(ruser.DeleteAt)
})
@@ -101,7 +101,7 @@ func (s *MmctlE2ETestSuite) TestUserDeactivateCmd() {
s.Require().Len(printer.GetErrorLines(), 1)
s.Require().Equal(printer.GetErrorLines()[0], "unable to change activation status of user "+user.Id+": You do not have the appropriate permissions.")
- ruser, err := s.th.App.GetUser(user.Id)
+ ruser, err := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(err)
s.Require().Zero(ruser.DeleteAt)
})
@@ -487,7 +487,7 @@ func (s *MmctlE2ETestSuite) TestResetUserMfaCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// make sure user is updated after reset mfa
- ruser, err := s.th.App.GetUser(user.Id)
+ ruser, err := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(err)
s.Require().NotEqual(ruser.UpdateAt, user.UpdateAt)
})
@@ -730,7 +730,7 @@ func (s *MmctlE2ETestSuite) TestDeleteUsersCmd() {
s.Require().Equal(newUser.Username, deletedUser.Username)
// expect user deleted
- _, err = s.th.App.GetUser(newUser.Id)
+ _, err = s.th.App.GetUser(s.th.Context, newUser.Id)
s.Require().NotNil(err)
s.Require().Equal("GetUser: Unable to find the user., resource \"User\" not found, id: "+newUser.Id, err.Error())
})
@@ -781,7 +781,7 @@ func (s *MmctlE2ETestSuite) TestDeleteUsersCmd() {
s.Require().EqualError(err, expectedErr.Error())
// expect user not deleted
- user, err := s.th.App.GetUser(newUser.Id)
+ user, err := s.th.App.GetUser(s.th.Context, newUser.Id)
s.Require().Nil(err)
s.Require().Equal(newUser.Username, user.Username)
})
@@ -810,7 +810,7 @@ func (s *MmctlE2ETestSuite) TestDeleteUsersCmd() {
s.Require().EqualError(err, expectedErr.Error())
// expect user not deleted
- user, err := s.th.App.GetUser(newUser.Id)
+ user, err := s.th.App.GetUser(s.th.Context, newUser.Id)
s.Require().Nil(err)
s.Require().Equal(newUser.Username, user.Username)
})
@@ -838,7 +838,7 @@ func (s *MmctlE2ETestSuite) TestDeleteUsersCmd() {
s.Require().Equal(newUser.Username, deletedUser.Username)
// expect user deleted
- _, err = s.th.App.GetUser(newUser.Id)
+ _, err = s.th.App.GetUser(s.th.Context, newUser.Id)
s.Require().NotNil(err)
s.Require().EqualError(err, "GetUser: Unable to find the user., resource \"User\" not found, id: "+newUser.Id)
})
@@ -1061,7 +1061,7 @@ func (s *MmctlE2ETestSuite) TestMigrateAuthCmd() {
s.Require().Equal("Successfully migrated accounts.", printer.GetLines()[0])
s.Require().Empty(printer.GetErrorLines())
- updatedUser, appErr := s.th.App.GetUser(ldapUser.Id)
+ updatedUser, appErr := s.th.App.GetUser(s.th.Context, ldapUser.Id)
s.Require().Nil(appErr)
s.Require().Equal(model.UserAuthServiceSaml, updatedUser.AuthService)
})
@@ -1090,7 +1090,7 @@ func (s *MmctlE2ETestSuite) TestMigrateAuthCmd() {
s.Require().Equal("Successfully migrated accounts.", printer.GetLines()[0])
s.Require().Empty(printer.GetErrorLines())
- updatedUser, appErr := s.th.App.GetUser(samlUser.Id)
+ updatedUser, appErr := s.th.App.GetUser(s.th.Context, samlUser.Id)
s.Require().Nil(appErr)
s.Require().Equal(model.UserAuthServiceLdap, updatedUser.AuthService)
})
@@ -1605,7 +1605,7 @@ func (s *MmctlE2ETestSuite) TestUserEditUsernameCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify username was updated
- updatedUser, appErr := s.th.App.GetUser(user.Id)
+ updatedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().Equal(newUsername, updatedUser.Username)
@@ -1626,7 +1626,7 @@ func (s *MmctlE2ETestSuite) TestUserEditUsernameCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify username was not changed
- unchangedUser, appErr := s.th.App.GetUser(user.Id)
+ unchangedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().Equal(user.Username, unchangedUser.Username)
})
@@ -1643,7 +1643,7 @@ func (s *MmctlE2ETestSuite) TestUserEditUsernameCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify username was not changed
- unchangedUser, appErr := s.th.App.GetUser(user.Id)
+ unchangedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().Equal(user.Username, unchangedUser.Username)
})
@@ -1684,7 +1684,7 @@ func (s *MmctlE2ETestSuite) TestUserEditEmailCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify email was updated
- updatedUser, appErr := s.th.App.GetUser(user.Id)
+ updatedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().Equal(newEmail, updatedUser.Email)
@@ -1705,7 +1705,7 @@ func (s *MmctlE2ETestSuite) TestUserEditEmailCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify email was not changed
- unchangedUser, appErr := s.th.App.GetUser(user.Id)
+ unchangedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().Equal(user.Email, unchangedUser.Email)
})
@@ -1722,7 +1722,7 @@ func (s *MmctlE2ETestSuite) TestUserEditEmailCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify email was not changed
- unchangedUser, appErr := s.th.App.GetUser(user.Id)
+ unchangedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().Equal(user.Email, unchangedUser.Email)
})
@@ -1763,7 +1763,7 @@ func (s *MmctlE2ETestSuite) TestUserEditAuthdataCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify authdata was not changed
- unchangedUser, appErr := s.th.App.GetUser(user.Id)
+ unchangedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().NotNil(unchangedUser.AuthData)
s.Require().Equal("existingauthdata", *unchangedUser.AuthData)
@@ -1779,7 +1779,7 @@ func (s *MmctlE2ETestSuite) TestUserEditAuthdataCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify authdata was not changed
- unchangedUser, appErr := s.th.App.GetUser(user.Id)
+ unchangedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().NotNil(unchangedUser.AuthData)
s.Require().Equal("existingauthdata", *unchangedUser.AuthData)
@@ -1801,7 +1801,7 @@ func (s *MmctlE2ETestSuite) TestUserEditAuthdataCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify authdata was not changed
- unchangedUser, appErr := s.th.App.GetUser(user.Id)
+ unchangedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().NotNil(unchangedUser.AuthData)
s.Require().Equal("existingauthdata", *unchangedUser.AuthData)
@@ -1827,7 +1827,7 @@ func (s *MmctlE2ETestSuite) TestUserEditAuthdataCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
// Verify authdata was updated
- updatedUser, appErr := s.th.App.GetUser(user.Id)
+ updatedUser, appErr := s.th.App.GetUser(s.th.Context, user.Id)
s.Require().Nil(appErr)
s.Require().NotNil(updatedUser.AuthData)
s.Require().Equal(newAuthdata, *updatedUser.AuthData)
diff --git a/server/i18n/en.json b/server/i18n/en.json
index 67f2caee008e..ce29581ec103 100644
--- a/server/i18n/en.json
+++ b/server/i18n/en.json
@@ -8824,10 +8824,6 @@
"id": "app.property_value.validate.app_error",
"translation": "Property value failed validation."
},
- {
- "id": "app.reaction.bulk_get_for_post_ids.app_error",
- "translation": "Unable to get reactions for post."
- },
{
"id": "app.reaction.delete_all_with_emoji_name.get_reactions.app_error",
"translation": "Unable to get all reactions with this emoji name."
diff --git a/server/public/model/client4.go b/server/public/model/client4.go
index 9e91cc1c3cec..05ada3486985 100644
--- a/server/public/model/client4.go
+++ b/server/public/model/client4.go
@@ -6337,16 +6337,6 @@ func (c *Client4) DeleteReaction(ctx context.Context, reaction *Reaction) (*Resp
return BuildResponse(r), nil
}
-// FetchBulkReactions returns a map of postIds and corresponding reactions
-func (c *Client4) GetBulkReactions(ctx context.Context, postIds []string) (map[string][]*Reaction, *Response, error) {
- r, err := c.doAPIPostJSON(ctx, c.postsRoute().Join("ids", "reactions"), postIds)
- if err != nil {
- return nil, BuildResponse(r), err
- }
- defer closeBody(r)
- return DecodeJSONFromResponse[map[string][]*Reaction](r)
-}
-
// Timezone Section
// GetSupportedTimezone returns a page of supported timezones on the system.
diff --git a/tools/mattermost-govet/apiAuditLogs/whitelist.go b/tools/mattermost-govet/apiAuditLogs/whitelist.go
index 9ec9502a30a9..4a54fb548e92 100644
--- a/tools/mattermost-govet/apiAuditLogs/whitelist.go
+++ b/tools/mattermost-govet/apiAuditLogs/whitelist.go
@@ -22,7 +22,6 @@ var whiteList = map[string]bool{
"getBotIconImage": true,
"getBots": true,
"getBrandImage": true,
- "getBulkReactions": true,
"getChannel": true,
"getChannelByName": true,
"getChannelByNameForTeamName": true,
diff --git a/webapp/channels/src/actions/property_values_websocket.test.ts b/webapp/channels/src/actions/property_values_websocket.test.ts
new file mode 100644
index 000000000000..52b4e0e6912b
--- /dev/null
+++ b/webapp/channels/src/actions/property_values_websocket.test.ts
@@ -0,0 +1,139 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import type {PropertyValue} from '@mattermost/types/properties';
+
+import {PropertyTypes} from 'mattermost-redux/action_types';
+
+import {handlePropertyValuesUpdated} from 'actions/websocket_actions';
+
+import mockStore from 'tests/test_store';
+
+// The server emits four payloads under one property_values_updated event,
+// distinguished only by which keys are present. See the Go-side contract in
+// TestPropertyValuesUpdatedPayloadShapes; these assert the client half.
+
+const CHANNEL_ID = 'channel_id_1';
+const FIELD_ID = 'field_id_1';
+const GROUP_ID = 'group_id_1';
+
+function storedValue(overrides: Partial> = {}): PropertyValue {
+ return {
+ id: 'value_id_1',
+ target_id: CHANNEL_ID,
+ target_type: 'channel',
+ group_id: GROUP_ID,
+ field_id: FIELD_ID,
+ value: 'AURORA',
+ create_at: 1,
+ update_at: 1,
+ delete_at: 0,
+ created_by: '',
+ updated_by: '',
+ ...overrides,
+ };
+}
+
+function dispatchEvent(data: Record) {
+ const store = mockStore({
+ entities: {
+ general: {config: {}, license: {}},
+ properties: {
+ fields: {byId: {}, byObjectType: {}},
+ values: {byTargetId: {}, byFieldId: {}},
+ groups: {byId: {}, byName: {}},
+ },
+ channelCategories: {byId: {}, orderByTeam: {}},
+ },
+ });
+
+ const msg = {
+ event: 'property_values_updated',
+ data,
+ broadcast: {omit_users: null, user_id: '', channel_id: CHANNEL_ID, team_id: ''},
+ seq: 1,
+ };
+
+ store.dispatch(handlePropertyValuesUpdated(msg as any) as any);
+ return store.getActions().flatMap((action: any) => (Array.isArray(action) ? action : [action]));
+}
+
+function typesOf(actions: any[]): string[] {
+ return actions.map((action) => action.type);
+}
+
+describe('property_values_updated payload shapes', () => {
+ test('shape 1: upsert stores the values', () => {
+ const actions = dispatchEvent({
+ object_type: 'channel',
+ target_id: CHANNEL_ID,
+ values: JSON.stringify([storedValue()]),
+ });
+
+ expect(typesOf(actions)).toContain(PropertyTypes.RECEIVED_PROPERTY_VALUES);
+ const received = actions.find((a) => a.type === PropertyTypes.RECEIVED_PROPERTY_VALUES);
+ expect(received.data.values[0].value).toBe('AURORA');
+ });
+
+ test('shape 1b: a null-valued row is stored, not deleted', () => {
+ // The user-initiated clear keeps the row. No delete event ever arrives,
+ // so removing it here would leave the client waiting for one.
+ const actions = dispatchEvent({
+ object_type: 'channel',
+ target_id: CHANNEL_ID,
+ values: JSON.stringify([storedValue({value: null})]),
+ });
+
+ expect(typesOf(actions)).toContain(PropertyTypes.RECEIVED_PROPERTY_VALUES);
+ expect(typesOf(actions)).not.toContain(PropertyTypes.PROPERTY_VALUE_DELETED);
+ });
+
+ test('shape 2: a tombstone deletes the row rather than storing a blank one', () => {
+ // Identical to shape 1b on the wire apart from the empty id.
+ const actions = dispatchEvent({
+ object_type: 'channel',
+ target_id: CHANNEL_ID,
+ values: JSON.stringify([storedValue({id: '', value: null})]),
+ });
+
+ expect(typesOf(actions)).toContain(PropertyTypes.PROPERTY_VALUE_DELETED);
+ expect(typesOf(actions)).not.toContain(PropertyTypes.RECEIVED_PROPERTY_VALUES);
+
+ const deleted = actions.find((a) => a.type === PropertyTypes.PROPERTY_VALUE_DELETED);
+ expect(deleted.data).toEqual({targetId: CHANNEL_ID, fieldId: FIELD_ID});
+ });
+
+ test('shape 3: an empty array clears only that target', () => {
+ const actions = dispatchEvent({
+ object_type: 'channel',
+ target_id: CHANNEL_ID,
+ values: '[]',
+ });
+
+ expect(typesOf(actions)).toContain(PropertyTypes.PROPERTY_VALUES_DELETED_FOR_TARGET);
+ const cleared = actions.find((a) => a.type === PropertyTypes.PROPERTY_VALUES_DELETED_FOR_TARGET);
+ expect(cleared.data).toEqual({targetId: CHANNEL_ID});
+ });
+
+ test('shape 4: field_id with no target clears that field everywhere', () => {
+ const actions = dispatchEvent({
+ field_id: FIELD_ID,
+ values: '[]',
+ });
+
+ expect(typesOf(actions)).toContain(PropertyTypes.PROPERTY_VALUES_DELETED_FOR_FIELD);
+ const cleared = actions.find((a) => a.type === PropertyTypes.PROPERTY_VALUES_DELETED_FOR_FIELD);
+ expect(cleared.data).toEqual({fieldId: FIELD_ID});
+
+ // Must not be mistaken for a target-scoped clear, which would wipe an
+ // unrelated channel instead of one attribute.
+ expect(typesOf(actions)).not.toContain(PropertyTypes.PROPERTY_VALUES_DELETED_FOR_TARGET);
+ });
+
+ test('malformed JSON is ignored', () => {
+ const actions = dispatchEvent({object_type: 'channel', target_id: CHANNEL_ID, values: 'not json'});
+
+ expect(typesOf(actions)).not.toContain(PropertyTypes.RECEIVED_PROPERTY_VALUES);
+ expect(typesOf(actions)).not.toContain(PropertyTypes.PROPERTY_VALUE_DELETED);
+ });
+});
diff --git a/webapp/channels/src/actions/websocket_actions.ts b/webapp/channels/src/actions/websocket_actions.ts
index b0c7a571214d..f3f0e87985c2 100644
--- a/webapp/channels/src/actions/websocket_actions.ts
+++ b/webapp/channels/src/actions/websocket_actions.ts
@@ -19,6 +19,7 @@ import type {OpenDialogRequest} from '@mattermost/types/integrations';
import type {Job} from '@mattermost/types/jobs';
import type {Post, PostAcknowledgement} from '@mattermost/types/posts';
import type {PreferenceType} from '@mattermost/types/preferences';
+import type {PropertyValue} from '@mattermost/types/properties';
import {SESSION_ATTRIBUTES_OBJECT_TYPE} from '@mattermost/types/properties_user';
import type {Reaction} from '@mattermost/types/reactions';
import type {Role} from '@mattermost/types/roles';
@@ -107,6 +108,7 @@ import {
import {removeNotVisibleUsers} from 'mattermost-redux/actions/websocket';
import {Client4} from 'mattermost-redux/client';
import {General, Permissions} from 'mattermost-redux/constants';
+import {ACCESS_CONTROL_PROPERTY_GROUP} from 'mattermost-redux/constants/properties';
import {appsEnabled} from 'mattermost-redux/selectors/entities/apps';
import {
getChannel,
@@ -161,7 +163,6 @@ import {isThreadOpen, isThreadManuallyUnread} from 'selectors/views/threads';
import store from 'stores/redux_store';
import {
- CLASSIFICATIONS_GROUP_NAME,
CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE,
CLASSIFICATIONS_SYSTEM_OBJECT_TYPE,
CLASSIFICATIONS_FIELD_TARGET_TYPE,
@@ -353,7 +354,7 @@ export function reconnect() {
if (getFeatureFlagValue(state, 'ClassificationMarkings') === 'true') {
dispatch(
fetchPropertyFields(
- CLASSIFICATIONS_GROUP_NAME,
+ ACCESS_CONTROL_PROPERTY_GROUP,
CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE,
CLASSIFICATIONS_FIELD_TARGET_TYPE,
CLASSIFICATIONS_FIELD_TARGET_ID,
@@ -361,13 +362,13 @@ export function reconnect() {
);
dispatch(
fetchPropertyFields(
- CLASSIFICATIONS_GROUP_NAME,
+ ACCESS_CONTROL_PROPERTY_GROUP,
CLASSIFICATIONS_SYSTEM_OBJECT_TYPE,
CLASSIFICATIONS_FIELD_TARGET_TYPE,
CLASSIFICATIONS_FIELD_TARGET_ID,
),
);
- dispatch(fetchSystemPropertyValues(CLASSIFICATIONS_GROUP_NAME));
+ dispatch(fetchSystemPropertyValues(ACCESS_CONTROL_PROPERTY_GROUP));
}
if (state.websocket.lastDisconnectAt) {
@@ -1503,7 +1504,20 @@ function handlePropertyFieldDeleted(
};
}
-function handlePropertyValuesUpdated(msg: WebSocketMessages.PropertyValuesUpdated): ThunkActionFunc {
+// The server emits four distinct payloads under one event, distinguished only by
+// which keys are present (see TestPropertyValuesUpdatedPayloadShapes):
+//
+// upsert object_type + target_id + the target's full values array
+// single delete same keys, but one synthesized row with an empty id
+// delete for target same keys, values "[]"
+// delete for field field_id, no object_type/target_id, values "[]"
+//
+// A delete tombstone carries no value, and a nil RawMessage marshals to null, so
+// it is byte-identical to a user-initiated `PATCH value: null` apart from the
+// empty id. Treating either of the empty-array shapes as an upsert is a no-op,
+// and treating a tombstone as one leaves a blank row in place of the deleted
+// value — so each shape has to be routed to its own reducer action.
+export function handlePropertyValuesUpdated(msg: WebSocketMessages.PropertyValuesUpdated): ThunkActionFunc {
return (doDispatch) => {
let values;
try {
@@ -1520,13 +1534,35 @@ function handlePropertyValuesUpdated(msg: WebSocketMessages.PropertyValuesUpdate
values,
};
- // Populate the Redux property values store so any component that reads
- // from entities.properties.values (e.g. GlobalClassificationBanner) gets
- // real-time updates without an extra network round-trip.
- doDispatch({
- type: PropertyTypes.RECEIVED_PROPERTY_VALUES,
- data: {values},
- });
+ const {object_type: objectType, target_id: targetId, field_id: fieldId} = msg.data;
+ const isTargetScoped = objectType !== undefined && targetId !== undefined;
+
+ if (!isTargetScoped && fieldId) {
+ doDispatch({
+ type: PropertyTypes.PROPERTY_VALUES_DELETED_FOR_FIELD,
+ data: {fieldId},
+ });
+ } else if (isTargetScoped && values.length === 0) {
+ doDispatch({
+ type: PropertyTypes.PROPERTY_VALUES_DELETED_FOR_TARGET,
+ data: {targetId},
+ });
+ } else if (isTargetScoped && values.every((value: PropertyValue) => !value.id)) {
+ for (const value of values) {
+ doDispatch({
+ type: PropertyTypes.PROPERTY_VALUE_DELETED,
+ data: {targetId: value.target_id ?? targetId, fieldId: value.field_id},
+ });
+ }
+ } else {
+ // Populate the Redux property values store so any component that reads
+ // from entities.properties.values (e.g. GlobalClassificationBanner) gets
+ // real-time updates without an extra network round-trip.
+ doDispatch({
+ type: PropertyTypes.RECEIVED_PROPERTY_VALUES,
+ data: {values},
+ });
+ }
doDispatch(handleManagedCategoryPropertyValuesUpdated(parsedPropertyValuesUpdated));
};
diff --git a/webapp/channels/src/components/admin_console/classification_markings/classification_markings.test.tsx b/webapp/channels/src/components/admin_console/classification_markings/classification_markings.test.tsx
index cdbd8edd57d2..3581a8e6e46b 100644
--- a/webapp/channels/src/components/admin_console/classification_markings/classification_markings.test.tsx
+++ b/webapp/channels/src/components/admin_console/classification_markings/classification_markings.test.tsx
@@ -6,6 +6,7 @@ import React from 'react';
import type {PropertyField, PropertyFieldOption, PropertyValue} from '@mattermost/types/properties';
import {Client4} from 'mattermost-redux/client';
+import {ACCESS_CONTROL_PROPERTY_GROUP, DISPLAY_BANNER_BOTTOM, DISPLAY_BANNER_TOP} from 'mattermost-redux/constants/properties';
import {act, renderWithContext, screen, userEvent, waitFor} from 'tests/react_testing_utils';
@@ -22,17 +23,15 @@ import {
CLASSIFICATIONS_CHANNEL_OBJECT_TYPE,
CLASSIFICATIONS_FIELD_TARGET_ID,
CLASSIFICATIONS_FIELD_TARGET_TYPE,
- CLASSIFICATIONS_GROUP_NAME,
CLASSIFICATIONS_SYSTEM_FIELD_NAME,
CLASSIFICATIONS_SYSTEM_OBJECT_TYPE,
CLASSIFICATIONS_SYSTEM_VALUE_TARGET_ID,
CLASSIFICATIONS_TEMPLATE_FIELD_NAME,
CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE,
+ CLASSIFICATIONS_GROUP_NAME,
CLASSIFICATIONS_USER_OBJECT_TYPE,
CLEARANCE_FIELD_DISPLAY_NAME,
CLEARANCE_FIELD_NAME,
- DISPLAY_BANNER_BOTTOM,
- DISPLAY_BANNER_TOP,
} from './utils';
import type {ClassificationLevel} from './utils/presets';
import {PRESET_CUSTOM, presets} from './utils/presets';
@@ -50,7 +49,7 @@ jest.mock('utils/browser_history', () => ({
function makePropertyField(overrides: Partial = {}): PropertyField {
return {
id: 'field1',
- group_id: CLASSIFICATIONS_GROUP_NAME,
+ group_id: ACCESS_CONTROL_PROPERTY_GROUP,
name: CLASSIFICATIONS_TEMPLATE_FIELD_NAME,
type: 'rank',
attrs: {options: []},
@@ -69,7 +68,7 @@ function makePropertyField(overrides: Partial = {}): PropertyFiel
function makeLinkedField(overrides: Partial = {}): PropertyField {
return {
id: 'linked_field1',
- group_id: CLASSIFICATIONS_GROUP_NAME,
+ group_id: ACCESS_CONTROL_PROPERTY_GROUP,
name: CLASSIFICATIONS_SYSTEM_FIELD_NAME,
type: 'rank',
attrs: {actions: []},
@@ -89,7 +88,7 @@ function makeLinkedField(overrides: Partial = {}): PropertyField
function makeChannelLinkedField(overrides: Partial = {}): PropertyField {
return {
id: 'channel_field1',
- group_id: CLASSIFICATIONS_GROUP_NAME,
+ group_id: ACCESS_CONTROL_PROPERTY_GROUP,
name: CLASSIFICATIONS_CHANNEL_FIELD_NAME,
type: 'rank',
attrs: {},
@@ -140,7 +139,7 @@ function makeSystemValue(fieldId: string, optionId: string): PropertyValue {
expect(result).toEqual(expected);
expect(Client4.getPropertyFields).toHaveBeenCalledTimes(1);
expect(Client4.getPropertyFields).toHaveBeenCalledWith(
- CLASSIFICATIONS_GROUP_NAME,
+ ACCESS_CONTROL_PROPERTY_GROUP,
CLASSIFICATIONS_CHANNEL_OBJECT_TYPE,
CLASSIFICATIONS_FIELD_TARGET_TYPE,
'',
@@ -792,7 +791,7 @@ describe('ClassificationMarkings component', () => {
await waitFor(() => {
expect(Client4.patchPropertyField).toHaveBeenCalledWith(
- CLASSIFICATIONS_GROUP_NAME,
+ ACCESS_CONTROL_PROPERTY_GROUP,
CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE,
'field1',
expect.objectContaining({
@@ -1044,7 +1043,7 @@ describe('GlobalClassificationIndicators section', () => {
await waitFor(() => {
// Template field patched without global_banner in attrs.
expect(Client4.patchPropertyField).toHaveBeenCalledWith(
- CLASSIFICATIONS_GROUP_NAME,
+ ACCESS_CONTROL_PROPERTY_GROUP,
CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE,
'field1',
expect.objectContaining({
@@ -1062,7 +1061,7 @@ describe('GlobalClassificationIndicators section', () => {
// Linked field patched with updated actions (top_and_bottom).
expect(Client4.patchPropertyField).toHaveBeenCalledWith(
- CLASSIFICATIONS_GROUP_NAME,
+ ACCESS_CONTROL_PROPERTY_GROUP,
CLASSIFICATIONS_SYSTEM_OBJECT_TYPE,
'linked_field1',
expect.objectContaining({
@@ -1113,7 +1112,7 @@ describe('GlobalClassificationIndicators section', () => {
await waitFor(() => {
// Template field saved without global_banner.
expect(Client4.patchPropertyField).toHaveBeenCalledWith(
- CLASSIFICATIONS_GROUP_NAME,
+ ACCESS_CONTROL_PROPERTY_GROUP,
CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE,
'field1',
expect.not.objectContaining({
@@ -1123,7 +1122,7 @@ describe('GlobalClassificationIndicators section', () => {
// Linked field patched with empty actions (banner disabled).
expect(Client4.patchPropertyField).toHaveBeenCalledWith(
- CLASSIFICATIONS_GROUP_NAME,
+ ACCESS_CONTROL_PROPERTY_GROUP,
CLASSIFICATIONS_SYSTEM_OBJECT_TYPE,
'linked_field1',
expect.objectContaining({
@@ -1232,7 +1231,7 @@ describe('Channel classification linked field branches', () => {
await waitFor(() => {
expect(createSpy).toHaveBeenCalledWith(
- CLASSIFICATIONS_GROUP_NAME,
+ ACCESS_CONTROL_PROPERTY_GROUP,
CLASSIFICATIONS_CHANNEL_OBJECT_TYPE,
expect.objectContaining({
name: CLASSIFICATIONS_CHANNEL_FIELD_NAME,
diff --git a/webapp/channels/src/components/admin_console/classification_markings/classification_markings.tsx b/webapp/channels/src/components/admin_console/classification_markings/classification_markings.tsx
index 559c36ba6c77..2ff303d6fde8 100644
--- a/webapp/channels/src/components/admin_console/classification_markings/classification_markings.tsx
+++ b/webapp/channels/src/components/admin_console/classification_markings/classification_markings.tsx
@@ -10,6 +10,7 @@ import {PlusIcon} from '@mattermost/compass-icons/components';
import type {PropertyField} from '@mattermost/types/properties';
import PropertyTypes from 'mattermost-redux/action_types/properties';
+import {DISPLAY_BANNER_TOP} from 'mattermost-redux/constants/properties';
import {getAccessControlSettings} from 'mattermost-redux/selectors/entities/access_control';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
@@ -40,7 +41,6 @@ import {
CLEARANCE_FIELD_DISPLAY_NAME,
CLEARANCE_FIELD_NAME,
DEFAULT_GLOBAL_BANNER,
- DISPLAY_BANNER_TOP,
actionsToGlobalBanner,
fetchChannelClassificationField,
fetchClassificationField,
diff --git a/webapp/channels/src/components/admin_console/classification_markings/utils/index.ts b/webapp/channels/src/components/admin_console/classification_markings/utils/index.ts
index 9ba60ab65036..5488a6a58445 100644
--- a/webapp/channels/src/components/admin_console/classification_markings/utils/index.ts
+++ b/webapp/channels/src/components/admin_console/classification_markings/utils/index.ts
@@ -4,6 +4,7 @@
import type {PropertyField, PropertyFieldOption, PropertyValue} from '@mattermost/types/properties';
import {Client4} from 'mattermost-redux/client';
+import {ACCESS_CONTROL_PROPERTY_GROUP, DISPLAY_BANNER_BOTTOM, DISPLAY_BANNER_TOP} from 'mattermost-redux/constants/properties';
import type {ClassificationLevel} from './presets';
import {PRESET_CUSTOM, presets} from './presets';
@@ -53,10 +54,6 @@ export const CLASSIFICATIONS_SYSTEM_VALUE_TARGET_ID = 'system';
export const CLASSIFICATIONS_CHANNEL_OBJECT_TYPE = 'channel';
export const CLASSIFICATIONS_CHANNEL_FIELD_NAME = 'classification';
-// Actions stored on the linked fields' attrs.actions to control banner placement.
-export const DISPLAY_BANNER_TOP = 'display_banner_top';
-export const DISPLAY_BANNER_BOTTOM = 'display_banner_bottom';
-
export type GlobalBannerPlacement = 'top' | 'top_and_bottom';
export type GlobalBannerConfig = {
@@ -166,7 +163,7 @@ export async function fetchClassificationField(): Promise {
const options = levelsToOptions(levels);
- return Client4.createPropertyField(CLASSIFICATIONS_GROUP_NAME, CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE, {
+ return Client4.createPropertyField(ACCESS_CONTROL_PROPERTY_GROUP, CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE, {
name: CLASSIFICATIONS_TEMPLATE_FIELD_NAME,
type: 'rank' as PropertyField['type'],
target_type: CLASSIFICATIONS_FIELD_TARGET_TYPE,
@@ -201,12 +198,12 @@ export async function saveCreateField(levels: ClassificationLevel[]): Promise {
- await Client4.deletePropertyField(CLASSIFICATIONS_GROUP_NAME, CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE, fieldId);
+ await Client4.deletePropertyField(ACCESS_CONTROL_PROPERTY_GROUP, CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE, fieldId);
}
export async function savePatchField(fieldId: string, levels: ClassificationLevel[]): Promise {
const options = levelsToOptions(levels);
- return Client4.patchPropertyField(CLASSIFICATIONS_GROUP_NAME, CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE, fieldId, {
+ return Client4.patchPropertyField(ACCESS_CONTROL_PROPERTY_GROUP, CLASSIFICATIONS_TEMPLATE_OBJECT_TYPE, fieldId, {
attrs: {options},
} as Partial);
}
@@ -221,7 +218,7 @@ export async function fetchLinkedClassificationField(): Promise {
- return Client4.createPropertyField(CLASSIFICATIONS_GROUP_NAME, CLASSIFICATIONS_SYSTEM_OBJECT_TYPE, {
+ return Client4.createPropertyField(ACCESS_CONTROL_PROPERTY_GROUP, CLASSIFICATIONS_SYSTEM_OBJECT_TYPE, {
name: CLASSIFICATIONS_SYSTEM_FIELD_NAME,
type: 'rank' as PropertyField['type'],
target_type: CLASSIFICATIONS_FIELD_TARGET_TYPE,
@@ -258,7 +255,7 @@ export async function saveCreateLinkedField(templateFieldId: string, config: Glo
}
export async function savePatchLinkedField(linkedFieldId: string, config: GlobalBannerConfig): Promise {
- return Client4.patchPropertyField(CLASSIFICATIONS_GROUP_NAME, CLASSIFICATIONS_SYSTEM_OBJECT_TYPE, linkedFieldId, {
+ return Client4.patchPropertyField(ACCESS_CONTROL_PROPERTY_GROUP, CLASSIFICATIONS_SYSTEM_OBJECT_TYPE, linkedFieldId, {
attrs: {
actions: placementToActions(config),
},
@@ -266,7 +263,7 @@ export async function savePatchLinkedField(linkedFieldId: string, config: Global
}
export async function saveDeleteLinkedField(fieldId: string): Promise {
- await Client4.deletePropertyField(CLASSIFICATIONS_GROUP_NAME, CLASSIFICATIONS_SYSTEM_OBJECT_TYPE, fieldId);
+ await Client4.deletePropertyField(ACCESS_CONTROL_PROPERTY_GROUP, CLASSIFICATIONS_SYSTEM_OBJECT_TYPE, fieldId);
}
// --- System classification property value API ---
@@ -276,7 +273,7 @@ export async function saveDeleteLinkedField(fieldId: string): Promise {
* Uses the dedicated system values endpoint (no target_id in URL).
*/
export async function fetchSystemClassificationValue(linkedFieldId: string): Promise {
- const values = await Client4.getSystemPropertyValues(CLASSIFICATIONS_GROUP_NAME);
+ const values = await Client4.getSystemPropertyValues(ACCESS_CONTROL_PROPERTY_GROUP);
const match = ((values as Array>) ?? []).find((v) => v.field_id === linkedFieldId);
return match?.value;
}
@@ -287,7 +284,7 @@ export async function fetchSystemClassificationValue(linkedFieldId: string): Pro
* Returns the saved property values so callers can eagerly update the store.
*/
export async function saveUpsertSystemValue(linkedFieldId: string, optionId: string): Promise>> {
- return Client4.patchSystemPropertyValues(CLASSIFICATIONS_GROUP_NAME, [
+ return Client4.patchSystemPropertyValues(ACCESS_CONTROL_PROPERTY_GROUP, [
{field_id: linkedFieldId, value: optionId},
]);
}
@@ -302,7 +299,7 @@ export async function fetchChannelClassificationField(): Promise {
- return Client4.createPropertyField(CLASSIFICATIONS_GROUP_NAME, CLASSIFICATIONS_CHANNEL_OBJECT_TYPE, {
+ return Client4.createPropertyField(ACCESS_CONTROL_PROPERTY_GROUP, CLASSIFICATIONS_CHANNEL_OBJECT_TYPE, {
name: CLASSIFICATIONS_CHANNEL_FIELD_NAME,
type: 'rank' as PropertyField['type'],
target_type: CLASSIFICATIONS_FIELD_TARGET_TYPE,
@@ -336,7 +333,7 @@ export async function saveCreateChannelLinkedField(templateFieldId: string): Pro
}
export async function saveDeleteChannelLinkedField(fieldId: string): Promise {
- await Client4.deletePropertyField(CLASSIFICATIONS_GROUP_NAME, CLASSIFICATIONS_CHANNEL_OBJECT_TYPE, fieldId);
+ await Client4.deletePropertyField(ACCESS_CONTROL_PROPERTY_GROUP, CLASSIFICATIONS_CHANNEL_OBJECT_TYPE, fieldId);
}
// --- User field API (clearance attribute for classification enforcement) ---
diff --git a/webapp/channels/src/components/channel_attributes/channel_attributes_form.scss b/webapp/channels/src/components/channel_attributes/channel_attributes_form.scss
new file mode 100644
index 000000000000..7a4d0b6f6be5
--- /dev/null
+++ b/webapp/channels/src/components/channel_attributes/channel_attributes_form.scss
@@ -0,0 +1,55 @@
+.channel-attributes-form {
+ padding-top: 24px;
+ border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
+
+ &__title {
+ margin: 0 0 4px;
+ color: var(--center-channel-color);
+ font-family: Metropolis, sans-serif;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 24px;
+ }
+
+ &__description {
+ margin: 0 0 16px;
+ color: rgba(var(--center-channel-color-rgb), 0.75);
+ font-size: 12px;
+ line-height: 16px;
+ }
+
+ &__row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 16px;
+ gap: 16px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ // Matches the classification rows above: a long attribute name wraps rather
+ // than being clipped, since the name is the only thing identifying the row.
+ &__label {
+ width: 140px;
+ flex-shrink: 0;
+ margin: 0;
+ color: var(--center-channel-color);
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 20px;
+ overflow-wrap: anywhere;
+ }
+
+ &__control {
+ min-width: 0;
+ flex: 1 1 auto;
+
+ // Bootstrap's .form-control carries its own border, which paints a second
+ // box inside the fieldset's. Same override the purpose textarea needs.
+ .Input_wrapper input.form-control {
+ border: none;
+ }
+ }
+}
diff --git a/webapp/channels/src/components/channel_attributes/channel_attributes_form.test.tsx b/webapp/channels/src/components/channel_attributes/channel_attributes_form.test.tsx
new file mode 100644
index 000000000000..6c23c21f2846
--- /dev/null
+++ b/webapp/channels/src/components/channel_attributes/channel_attributes_form.test.tsx
@@ -0,0 +1,131 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {screen} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+
+import type {PropertyField} from '@mattermost/types/properties';
+
+import {renderWithContext} from 'tests/react_testing_utils';
+
+import ChannelAttributesForm from './channel_attributes_form';
+
+function field(overrides: Partial & {id: string; name: string}): PropertyField {
+ return {
+ group_id: 'group1',
+ type: 'select',
+ target_id: '',
+ target_type: 'system',
+ object_type: 'channel',
+ create_at: 1,
+ update_at: 1,
+ delete_at: 0,
+ created_by: '',
+ updated_by: '',
+ ...overrides,
+ };
+}
+
+const program = field({
+ id: 'f_program',
+ name: 'program',
+ attrs: {display_name: 'Program', options: [{id: 'opt_a', name: 'AURORA'}, {id: 'opt_b', name: 'BOREALIS'}]},
+});
+
+describe('ChannelAttributesForm', () => {
+ test('renders nothing when there is no field with a control', () => {
+ const {container} = renderWithContext(
+ ,
+ );
+
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ test('renders nothing at all when there are no fields', () => {
+ const {container} = renderWithContext(
+ ,
+ );
+
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ test('prefers display_name over the machine name', () => {
+ renderWithContext(
+ ,
+ );
+
+ expect(screen.getByText('Program')).toBeInTheDocument();
+ expect(screen.queryByText('program')).not.toBeInTheDocument();
+ });
+
+ test('reports the selected option id, not its label', async () => {
+ const onChange = jest.fn();
+ renderWithContext(
+ ,
+ );
+
+ await userEvent.click(screen.getByText('Select a value'));
+ await userEvent.click(screen.getByText('AURORA'));
+
+ expect(onChange).toHaveBeenCalledWith('f_program', 'opt_a');
+ });
+
+ test('reports a multiselect as an array of option ids', async () => {
+ const onChange = jest.fn();
+ const caveats = field({
+ id: 'f_caveats',
+ name: 'caveats',
+ type: 'multiselect',
+ attrs: {options: [{id: 'opt_a', name: 'NOFORN'}, {id: 'opt_b', name: 'ORCON'}]},
+ });
+
+ renderWithContext(
+ ,
+ );
+
+ // The placeholder is gone once a chip is present, so the menu is opened
+ // through the combobox itself.
+ await userEvent.click(screen.getByRole('combobox'));
+ await userEvent.click(screen.getByText('ORCON'));
+
+ expect(onChange).toHaveBeenCalledWith('f_caveats', ['opt_a', 'opt_b']);
+ });
+
+ test('reports undefined when a text value is emptied, so no row is written', async () => {
+ const onChange = jest.fn();
+ const note = field({id: 'f_note', name: 'note', type: 'text'});
+
+ renderWithContext(
+ ,
+ );
+
+ await userEvent.clear(screen.getByLabelText('note'));
+
+ expect(onChange).toHaveBeenCalledWith('f_note', undefined);
+ });
+});
diff --git a/webapp/channels/src/components/channel_attributes/channel_attributes_form.tsx b/webapp/channels/src/components/channel_attributes/channel_attributes_form.tsx
new file mode 100644
index 000000000000..5a33e451d9f5
--- /dev/null
+++ b/webapp/channels/src/components/channel_attributes/channel_attributes_form.tsx
@@ -0,0 +1,157 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {useCallback, useMemo} from 'react';
+import {FormattedMessage, useIntl} from 'react-intl';
+import type {OnChangeValue} from 'react-select';
+
+import type {PropertyField, PropertyFieldOption} from '@mattermost/types/properties';
+import {supportsOptions} from '@mattermost/types/properties';
+
+import DropdownInput from 'components/dropdown_input';
+import type {ValueType as Option} from 'components/dropdown_input';
+import Input from 'components/widgets/inputs/input/input';
+
+import './channel_attributes_form.scss';
+
+export type ChannelAttributeSelection = Record;
+
+type Props = {
+ fields: PropertyField[];
+ values: ChannelAttributeSelection;
+ onChange: (fieldId: string, value: string | string[] | undefined) => void;
+ disabled?: boolean;
+};
+
+// The menu is portalled to the body to escape the modal's overflow, which drops
+// it out of the modal's stacking context — without these it paints behind the
+// modal and swallows clicks.
+const dropdownStyles = {
+ menu: (provided: Record) => ({...provided, zIndex: 100}),
+ menuPortal: (provided: Record) => ({...provided, zIndex: 1100}),
+};
+
+function isText(field: PropertyField): boolean {
+ return field.type === 'text';
+}
+
+function fieldLabel(field: PropertyField): string {
+ const displayName = field.attrs?.display_name;
+ return typeof displayName === 'string' && displayName ? displayName : field.name;
+}
+
+function toOptions(field: PropertyField): Option[] {
+ const options = (field.attrs?.options as PropertyFieldOption[] | undefined) ?? [];
+ return options.map((option) => ({label: option.name, value: option.id}));
+}
+
+const ChannelAttributesForm = ({fields, values, onChange, disabled}: Props) => {
+ const {formatMessage} = useIntl();
+
+ // Date and user-valued attributes are storable through the API but have no
+ // assignment UI in this release, so they are skipped rather than rendered as
+ // something the user cannot fill in.
+ const supported = useMemo(() => fields.filter((field) => supportsOptions(field) || isText(field)), [fields]);
+
+ // react-select hands back an array for isMulti and a single option otherwise,
+ // so the shape is narrowed here rather than trusted from the field type.
+ const handleSelect = useCallback((fieldId: string, selected: OnChangeValue
)}
+ {isBuiltInType(type) && (
+