Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Disable profile controls if the HS doesn't allow them to be set #12652

Merged
merged 8 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/views/settings/AvatarSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ const AvatarSetting: React.FC<IProps> = ({
aria-labelledby={disabled ? undefined : a11yId}
// Inhibit tab stop as we have explicit upload/remove buttons
tabIndex={-1}
disabled={disabled}
>
<BaseAvatar idName={placeholderId} name={placeholderName} size="90px" />
</AccessibleButton>
Expand All @@ -184,6 +185,7 @@ const AvatarSetting: React.FC<IProps> = ({
onClick={uploadAvatar}
// Inhibit tab stop as we have explicit upload/remove buttons
tabIndex={-1}
disabled={disabled}
/>
);
}
Expand Down
19 changes: 17 additions & 2 deletions src/components/views/settings/UserProfileSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,17 @@ const UsernameBox: React.FC<UsernameBoxProps> = ({ username }) => {
);
};

interface UserProfileSettingsProps {
// Whether the homeserver allows the user to set their display name.
canSetDisplayName: boolean;
// Whether the homeserver allows the user to set their avatar.
canSetAvatar: boolean;
}

/**
* A group of settings views to allow the user to set their profile information.
*/
const UserProfileSettings: React.FC = () => {
const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ canSetDisplayName, canSetAvatar }) => {
const [avatarURL, setAvatarURL] = useState(OwnProfileStore.instance.avatarMxc);
const [displayName, setDisplayName] = useState(OwnProfileStore.instance.displayName ?? "");
const [initialDisplayName, setInitialDisplayName] = useState(OwnProfileStore.instance.displayName ?? "");
Expand Down Expand Up @@ -143,10 +150,16 @@ const UserProfileSettings: React.FC = () => {
[client],
);

const someFieldsDisabled = !canSetDisplayName || !canSetAvatar;

return (
<div className="mx_UserProfileSettings">
<h2>{_t("common|profile")}</h2>
<div>{_t("settings|general|profile_subtitle")}</div>
<div>
{someFieldsDisabled
? _t("settings|general|profile_subtitle_oidc")
: _t("settings|general|profile_subtitle")}
</div>
<div className="mx_UserProfileSettings_profile">
<AvatarSetting
avatar={avatarURL ?? undefined}
Expand All @@ -155,6 +168,7 @@ const UserProfileSettings: React.FC = () => {
removeAvatar={avatarURL ? onAvatarRemove : undefined}
placeholderName={displayName}
placeholderId={client.getUserId() ?? ""}
disabled={!canSetAvatar}
/>
<EditInPlace
className="mx_UserProfileSettings_profile_displayName"
Expand All @@ -169,6 +183,7 @@ const UserProfileSettings: React.FC = () => {
onCancel={onDisplayNameCancel}
onSave={onDisplayNameSave}
error={displayNameError ? _t("settings|general|display_name_error") : undefined}
disabled={!canSetDisplayName}
/>
</div>
{avatarError && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ interface IState {
idServerName?: string;
externalAccountManagementUrl?: string;
canMake3pidChanges: boolean;
canSetDisplayName: boolean;
canSetAvatar: boolean;
}

export default class GeneralUserSettingsTab extends React.Component<IProps, IState> {
Expand Down Expand Up @@ -122,6 +124,8 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
loading3pids: true, // whether or not the emails and msisdns have been loaded
canChangePassword: false,
canMake3pidChanges: false,
canSetDisplayName: false,
canSetAvatar: false,
};

this.dispatcherRef = dis.register(this.onAction);
Expand Down Expand Up @@ -167,7 +171,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
private async getCapabilities(): Promise<void> {
const cli = this.context.client!;

const capabilities = await cli.getCapabilities(); // this is cached
const capabilities = cli.getCachedCapabilities() ?? {};
const changePasswordCap = capabilities["m.change_password"];

// You can change your password so long as the capability isn't explicitly disabled. The implicit
Expand All @@ -182,7 +186,17 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
// so the behaviour for when it is missing has to be assume true
const canMake3pidChanges = !capabilities["m.3pid_changes"] || capabilities["m.3pid_changes"].enabled === true;

this.setState({ canChangePassword, externalAccountManagementUrl, canMake3pidChanges });
const canSetDisplayName =
!capabilities["m.set_displayname"] || capabilities["m.set_displayname"].enabled === true;
const canSetAvatar = !capabilities["m.set_avatar_url"] || capabilities["m.set_avatar_url"].enabled === true;

this.setState({
canChangePassword,
externalAccountManagementUrl,
canMake3pidChanges,
canSetDisplayName,
canSetAvatar,
});
}

private async getThreepidState(): Promise<void> {
Expand Down Expand Up @@ -561,7 +575,10 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
return (
<SettingsTab data-testid="mx_GeneralUserSettingsTab">
<SettingsSection>
<UserProfileSettings />
<UserProfileSettings
canSetDisplayName={this.state.canSetDisplayName}
canSetAvatar={this.state.canSetAvatar}
/>
{this.renderAccountSection()}
{this.renderLanguageSection()}
{supportsMultiLanguageSpellCheck ? this.renderSpellCheckSection() : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ const SessionManagerTab: React.FC<{
const userId = matrixClient?.getUserId();
const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined;
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
const capabilities = useAsyncMemo(async () => matrixClient?.getCapabilities(), [matrixClient]);
const capabilities = matrixClient.getCachedCapabilities();
const wellKnown = useMemo(() => matrixClient?.getClientWellKnown(), [matrixClient]);
const oidcClientConfig = useAsyncMemo(async () => {
try {
Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2529,6 +2529,7 @@
"password_change_section": "Set a new account password…",
"password_change_success": "Your password was successfully changed.",
"profile_subtitle": "This is how you appear to others on the app.",
"profile_subtitle_oidc": "Your account is managed separately by an identity provider and so some of your personal information can’t be changed here.",
"remove_email_prompt": "Remove %(email)s?",
"remove_msisdn_prompt": "Remove %(phone)s?",
"spell_check_locale_placeholder": "Choose a locale",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const renderProfileSettings = (toastRack: Partial<ToastRack>, client: MatrixClie
return render(
<MatrixClientContext.Provider value={client}>
<ToastContext.Provider value={toastRack}>
<UserProfileSettings />
<UserProfileSettings canSetAvatar={true} canSetDisplayName={true} />
</ToastContext.Provider>
</MatrixClientContext.Provider>,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe("<GeneralUserSettingsTab />", () => {
const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsServer(),
getCapabilities: jest.fn(),
getCachedCapabilities: jest.fn(),
getThreePids: jest.fn(),
getIdentityServerUrl: jest.fn(),
deleteThreePid: jest.fn(),
Expand All @@ -63,7 +63,7 @@ describe("<GeneralUserSettingsTab />", () => {
jest.spyOn(SettingsStore, "getValue").mockRestore();
jest.spyOn(logger, "error").mockRestore();

mockClient.getCapabilities.mockResolvedValue({});
mockClient.getCachedCapabilities.mockReturnValue({});
mockClient.getThreePids.mockResolvedValue({
threepids: [],
});
Expand Down Expand Up @@ -198,7 +198,7 @@ describe("<GeneralUserSettingsTab />", () => {

describe("3pids", () => {
beforeEach(() => {
mockClient.getCapabilities.mockResolvedValue({
mockClient.getCachedCapabilities.mockReturnValue({
"m.3pid_changes": {
enabled: true,
},
Expand Down Expand Up @@ -300,7 +300,7 @@ describe("<GeneralUserSettingsTab />", () => {
it("should allow 3pid changes when capabilities does not have 3pid_changes", async () => {
// We support as far back as v1.1 which doesn't have m.3pid_changes
// so the behaviour for when it is missing has to be assume true
mockClient.getCapabilities.mockResolvedValue({});
mockClient.getCachedCapabilities.mockReturnValue({});

render(getComponent());

Expand All @@ -315,7 +315,7 @@ describe("<GeneralUserSettingsTab />", () => {

describe("when 3pid changes capability is disabled", () => {
beforeEach(() => {
mockClient.getCapabilities.mockResolvedValue({
mockClient.getCachedCapabilities.mockReturnValue({
"m.3pid_changes": {
enabled: false,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1684,7 +1684,7 @@ describe("<SessionManagerTab />", () => {
"org.matrix.msc3886": true,
},
});
mockClient.getCapabilities.mockResolvedValue({
mockClient.getCachedCapabilities.mockReturnValue({
[GET_LOGIN_TOKEN_CAPABILITY.name]: {
enabled: true,
},
Expand Down Expand Up @@ -1726,7 +1726,7 @@ describe("<SessionManagerTab />", () => {
"org.matrix.msc4108": true,
},
});
mockClient.getCapabilities.mockResolvedValue({
mockClient.getCachedCapabilities.mockReturnValue({
[GET_LOGIN_TOKEN_CAPABILITY.name]: {
enabled: true,
},
Expand Down
2 changes: 1 addition & 1 deletion test/test-utils/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const mockClientMethodsEvents = () => ({
export const mockClientMethodsServer = (): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
getIdentityServerUrl: jest.fn(),
getHomeserverUrl: jest.fn(),
getCapabilities: jest.fn().mockReturnValue({}),
getCachedCapabilities: jest.fn().mockReturnValue({}),
getClientWellKnown: jest.fn().mockReturnValue({}),
waitForClientWellKnown: jest.fn().mockResolvedValue({}),
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(false),
Expand Down
Loading