Skip to content
This repository has been 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 #12634

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"@sentry/browser": "^8.0.0",
"@testing-library/react-hooks": "^8.0.1",
"@vector-im/compound-design-tokens": "^1.2.0",
"@vector-im/compound-web": "^4.7.0",
"@vector-im/compound-web": "^4.8.0",
"@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2",
Expand Down
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 @@ -146,15 +146,15 @@ exports[`<RoomSummaryCard /> has button to edit topic when expanded 1`] = `
Favourite
</label>
<div
class="_container_ik1u1_18"
class="_container_qnvru_18"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSS class name updates from new compound version

>
<input
class="_input_ik1u1_32"
class="_input_qnvru_32"
id=":r3:"
type="checkbox"
/>
<div
class="_ui_ik1u1_42"
class="_ui_qnvru_42"
/>
</div>
</div>
Expand Down Expand Up @@ -549,15 +549,15 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
Favourite
</label>
<div
class="_container_ik1u1_18"
class="_container_qnvru_18"
>
<input
class="_input_ik1u1_32"
class="_input_qnvru_32"
id=":r1:"
type="checkbox"
/>
<div
class="_ui_ik1u1_42"
class="_ui_qnvru_42"
/>
</div>
</div>
Expand Down Expand Up @@ -963,15 +963,15 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
Favourite
</label>
<div
class="_container_ik1u1_18"
class="_container_qnvru_18"
>
<input
class="_input_ik1u1_32"
class="_input_qnvru_32"
id=":r2:"
type="checkbox"
/>
<div
class="_ui_ik1u1_42"
class="_ui_qnvru_42"
/>
</div>
</div>
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
39 changes: 7 additions & 32 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3052,10 +3052,10 @@
dependencies:
svg2vectordrawable "^2.9.1"

"@vector-im/compound-web@^4.7.0":
version "4.7.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-4.7.0.tgz#bef1a4e0aa9a2f658ac3bec3dae60861afe11ef4"
integrity sha512-NvwMIPlq2lgr5eOiRY5pFmqCsN8Vnlak5nmAD1XBW0kscVp/B0/Qwmv0i2cwjdm0RHUTmmfzCTdHj0IJRxUBwA==
"@vector-im/compound-web@^4.8.0":
version "4.8.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-4.8.0.tgz#1fe11d78549694f8d91b40065994bad19a7cebf2"
integrity sha512-kyB8wQPbdTUFWIzAbb4HcZ4iisUUpbm0xwmEjV9ZNN1/EIodidW6nLeYATh3Vc1fBvTGTgbFiPc1DiAcBuudiw==
dependencies:
"@floating-ui/react" "^0.26.9"
"@floating-ui/react-dom" "^2.0.8"
Expand Down Expand Up @@ -8662,16 +8662,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"

"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand Down Expand Up @@ -8777,14 +8768,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down Expand Up @@ -9579,7 +9563,7 @@ which@^2.0.1:
dependencies:
isexe "^2.0.0"

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand All @@ -9597,15 +9581,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
Expand Down
Loading