From d353d8ecf01b7a618369adedcd91c2856f527459 Mon Sep 17 00:00:00 2001 From: Douglas Hall Date: Thu, 9 May 2019 17:05:42 -0400 Subject: [PATCH] Make certain field uneditable for enterprise learners. --- package-lock.json | 47 ++++++++++++++------ package.json | 2 +- src/account-settings/AccountSettingsPage.jsx | 42 +++++++++++++++++ src/account-settings/actions.js | 4 +- src/account-settings/reducers.js | 1 + src/account-settings/sagas.js | 11 +++-- src/account-settings/selectors.js | 18 ++++++++ src/account-settings/service.js | 27 ++++++++++- 8 files changed, 131 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0062fb4..ad8f4ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1026,9 +1026,9 @@ } }, "@edx/frontend-auth": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@edx/frontend-auth/-/frontend-auth-5.2.0.tgz", - "integrity": "sha512-/S5IMEIQXXUzXEQThZczWh4jRCyTvMB/oWFjMTbYr0WseyRhH2DWpy2X6WRgWObwnyiFRvYLzktIpcGoHLFA9A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@edx/frontend-auth/-/frontend-auth-5.3.0.tgz", + "integrity": "sha512-HiuwVlgePsBgQYlw47Z/lEKcmFrRbwYactOHMPXUgxaHKPXZwdVDvqBbf21/J/wPX0t1Kzmn0U5qfMd838hvYg==", "requires": { "@edx/frontend-logging": "^2.0.1", "axios": "^0.18.0", @@ -8582,7 +8582,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -8603,12 +8604,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8623,17 +8626,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -8750,7 +8756,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -8762,6 +8769,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8776,6 +8784,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8783,12 +8792,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8807,6 +8818,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -8887,7 +8899,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -8899,6 +8912,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -8984,7 +8998,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -9020,6 +9035,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9039,6 +9055,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9082,12 +9099,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/package.json b/package.json index e1948a3..e3510fa 100755 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@cospired/i18n-iso-languages": "^2.0.2", "@edx/edx-bootstrap": "^2.1.0", "@edx/frontend-analytics": "^1.0.0", - "@edx/frontend-auth": "^5.2.0", + "@edx/frontend-auth": "^5.3.0", "@edx/frontend-component-footer": "^2.0.3", "@edx/frontend-component-site-header": "^2.1.4", "@edx/frontend-logging": "^2.0.0", diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx index fd0ed70..1a0f46e 100644 --- a/src/account-settings/AccountSettingsPage.jsx +++ b/src/account-settings/AccountSettingsPage.jsx @@ -1,6 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { Hyperlink } from '@edx/paragon'; +import { FormattedMessage } from 'react-intl'; import { injectIntl, intlShape, @@ -8,10 +10,12 @@ import { import messages from './AccountSettingsPage.messages'; +import { configuration } from '../environment'; import { fetchSettings, saveSettings, updateDraft } from './actions'; import { accountSettingsPageSelector } from './selectors'; import { PageLoading } from '../common'; +import Alert from './components/Alert'; import EditableField from './components/EditableField'; import PasswordReset from './components/PasswordReset'; import ThirdPartyAuth from './components/ThirdPartyAuth'; @@ -58,6 +62,36 @@ class AccountSettingsPage extends React.Component { this.props.saveSettings(formId, values); } + renderManagedProfileMessage() { + if (!this.props.profileDataManager) { + return (null); + } + + return ( +
+ + {this.props.profileDataManager}, + support: ( + + + + ), + }} + /> + +
+ ); + } + renderContent() { const editableFieldProps = { onChange: this.handleEditableFieldChange, @@ -72,6 +106,7 @@ class AccountSettingsPage extends React.Component {

{this.props.intl.formatMessage(messages['account.settings.section.account.information'])}

{this.props.intl.formatMessage(messages['account.settings.section.account.information.description'])}

+ {this.renderManagedProfileMessage()} @@ -113,6 +150,7 @@ class AccountSettingsPage extends React.Component { value={this.props.formValues.country} options={this.props.countryOptions} label={this.props.intl.formatMessage(messages['account.settings.field.country'])} + isEditable={!this.props.staticFields.includes('country')} {...editableFieldProps} /> @@ -277,6 +315,8 @@ AccountSettingsPage.propTypes = { label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), })), + profileDataManager: PropTypes.string, + staticFields: PropTypes.arrayOf(PropTypes.string), fetchSiteLanguages: PropTypes.func.isRequired, updateDraft: PropTypes.func.isRequired, @@ -292,6 +332,8 @@ AccountSettingsPage.defaultProps = { siteLanguageOptions: [], countryOptions: [], languageProficiencyOptions: [], + profileDataManager: null, + staticFields: [], }; diff --git a/src/account-settings/actions.js b/src/account-settings/actions.js index 4fa8c82..b8d3193 100644 --- a/src/account-settings/actions.js +++ b/src/account-settings/actions.js @@ -21,9 +21,9 @@ export const fetchSettingsBegin = () => ({ type: FETCH_SETTINGS.BEGIN, }); -export const fetchSettingsSuccess = ({ values, thirdPartyAuthProviders }) => ({ +export const fetchSettingsSuccess = ({ values, thirdPartyAuthProviders, profileDataManager }) => ({ type: FETCH_SETTINGS.SUCCESS, - payload: { values, thirdPartyAuthProviders }, + payload: { values, thirdPartyAuthProviders, profileDataManager }, }); export const fetchSettingsFailure = error => ({ diff --git a/src/account-settings/reducers.js b/src/account-settings/reducers.js index 90964de..cb726e1 100644 --- a/src/account-settings/reducers.js +++ b/src/account-settings/reducers.js @@ -37,6 +37,7 @@ const accountSettingsReducer = (state = defaultState, action) => { ...state, values: Object.assign({}, state.values, action.payload.values), authProviders: action.payload.thirdPartyAuthProviders, + profileDataManager: action.payload.profileDataManager, loading: false, loaded: true, loadingError: null, diff --git a/src/account-settings/sagas.js b/src/account-settings/sagas.js index ee35bdf..c660975 100644 --- a/src/account-settings/sagas.js +++ b/src/account-settings/sagas.js @@ -18,7 +18,7 @@ import { resetPasswordBegin, resetPasswordSuccess, } from './actions'; -import { usernameSelector } from './selectors'; +import { usernameSelector, userRolesSelector } from './selectors'; // Services import * as ApiService from './service'; @@ -29,10 +29,15 @@ export function* handleFetchSettings() { try { yield put(fetchSettingsBegin()); const username = yield select(usernameSelector); + const userRoles = yield select(userRolesSelector); - const { thirdPartyAuthProviders, ...values } = yield call(ApiService.getSettings, username); + const { + thirdPartyAuthProviders, + profileDataManager, + ...values + } = yield call(ApiService.getSettings, username, userRoles); - yield put(fetchSettingsSuccess({ values, thirdPartyAuthProviders })); + yield put(fetchSettingsSuccess({ values, thirdPartyAuthProviders, profileDataManager })); } catch (e) { logAPIErrorResponse(e); yield put(fetchSettingsFailure(e.message)); diff --git a/src/account-settings/selectors.js b/src/account-settings/selectors.js index 3f28ec1..5a3f34a 100644 --- a/src/account-settings/selectors.js +++ b/src/account-settings/selectors.js @@ -12,6 +12,8 @@ export const storeName = 'accountSettings'; export const usernameSelector = state => state.authentication.username; +export const userRolesSelector = state => state.authentication.roles || []; + export const accountSettingsSelector = state => ({ ...state[storeName] }); const editableFieldNameSelector = (state, props) => props.name; @@ -74,6 +76,16 @@ export const thirdPartyAuthSelector = createSelector( }), ); +export const profileDataManagerSelector = createSelector( + accountSettingsSelector, + accountSettings => accountSettings.profileDataManager, +); + +export const staticFieldsSelector = createSelector( + accountSettingsSelector, + accountSettings => (accountSettings.profileDataManager ? ['name', 'email', 'country'] : []), +); + /** * If there's no draft present at all (undefined), use the original committed value. */ @@ -123,6 +135,8 @@ export const accountSettingsPageSelector = createSelector( languageProficiencyOptionsSelector, formValuesSelector, siteLanguageSelector, + profileDataManagerSelector, + staticFieldsSelector, ( accountSettings, siteLanguageOptions, @@ -130,6 +144,8 @@ export const accountSettingsPageSelector = createSelector( languageProficiencyOptions, formValues, siteLanguage, + profileDataManager, + staticFields, ) => ({ siteLanguageOptions, countryOptions, @@ -139,5 +155,7 @@ export const accountSettingsPageSelector = createSelector( loaded: accountSettings.loaded, loadingError: accountSettings.loadingError, formValues, + profileDataManager, + staticFields, }), ); diff --git a/src/account-settings/service.js b/src/account-settings/service.js index 5a437e3..2d75873 100644 --- a/src/account-settings/service.js +++ b/src/account-settings/service.js @@ -128,21 +128,46 @@ export async function getThirdPartyAuthProviders() { })); } +/** + * Determine if the user's profile data is managed by a third-party identity provider. + */ +export async function getProfileDataManager(username, userRoles) { + const userRoleNames = userRoles.map(role => role.split(':')[0]); + + if (userRoleNames.includes('enterprise_learner')) { + const url = `${config.LMS_BASE_URL}/enterprise/api/v1/enterprise-learner/?username=${username}`; + const { data } = await apiClient.get(url).catch(handleRequestError); + + if ('results' in data) { + for (let i = 0; i < data.results.length; i += 1) { + const enterprise = data.results[i].enterprise_customer; + if (enterprise.sync_learner_profile_data) { + return enterprise.name; + } + } + } + } + + return null; +} + /** * A single function to GET everything considered a setting. * Currently encapsulates Account, Preferences, and ThirdPartyAuth */ -export async function getSettings(username) { +export async function getSettings(username, userRoles) { const results = await Promise.all([ getAccount(username), getPreferences(username), getThirdPartyAuthProviders(), + getProfileDataManager(username, userRoles), ]); return { ...results[0], ...results[1], thirdPartyAuthProviders: results[2], + profileDataManager: results[3], }; }