Skip to content

Commit

Permalink
Add CSRF code to demographics modal (openedx#24998)
Browse files Browse the repository at this point in the history
* Add CSRF tokens to demographics modal PATCH

We have temporarilly copied over the CSRF code from frontend-platform to
use with the demographics modal. This code is most likely temporary and
is not maintained like frontend-platform.
  • Loading branch information
Tj-Tracy authored Sep 16, 2020
1 parent 2116898 commit 35bbf06
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SelectWithInput } from './SelectWithInput'
import { MultiselectDropdown } from './MultiselectDropdown';
import AxiosJwtTokenService from '../jwt_auth/AxiosJwtTokenService';
import StringUtils from 'edx-ui-toolkit/js/utils/string-utils';
import AxiosCsrfTokenService from '../jwt_auth/AxiosCsrfTokenService';

const FIELD_NAMES = {
CURRENT_WORK: "current_work_sector",
Expand Down Expand Up @@ -51,6 +52,7 @@ class DemographicsCollectionModal extends React.Component {
accessToken,
refreshUrl,
);
this.csrfTokenService = new AxiosCsrfTokenService(this.props.csrfTokenPath)
}

async componentDidMount() {
Expand All @@ -59,7 +61,6 @@ class DemographicsCollectionModal extends React.Component {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRFTOKEN': Cookies.get('demographics_csrftoken'),
'USE-JWT-COOKIE': true
},
};
Expand Down Expand Up @@ -135,14 +136,14 @@ class DemographicsCollectionModal extends React.Component {
}

async handleSelectChange(e) {
const url = `${this.props.demographicsBaseUrl}/demographics/api/v1/demographics/${this.props.user}/`;
const name = e.target.name;
const value = e.target.value;
const options = {
method: 'PATCH',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRFTOKEN': Cookies.get('demographics_csrftoken'),
'USE-JWT-COOKIE': true
},
body: JSON.stringify({
Expand All @@ -152,7 +153,8 @@ class DemographicsCollectionModal extends React.Component {

try {
await this.jwtTokenService.getJwtToken();
await fetch(`${this.props.demographicsBaseUrl}/demographics/api/v1/demographics/${this.props.user}/`, options)
options.headers['X-CSRFToken'] = await this.csrfTokenService.getCsrfToken(url);
await fetch(url, options)
} catch (error) {
this.setState({ loading: false, fieldError: true, errorMessage: error });
}
Expand Down
64 changes: 64 additions & 0 deletions lms/static/js/jwt_auth/AxiosCsrfTokenService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Service class to support CSRF.
*
* Temporarily copied from the edx/frontend-platform
*/
import axios from 'axios';
import { getUrlParts, processAxiosErrorAndThrow } from './utils';

export default class AxiosCsrfTokenService {
constructor(csrfTokenApiPath) {
this.csrfTokenApiPath = csrfTokenApiPath;
this.httpClient = axios.create();
// Set withCredentials to true. Enables cross-site Access-Control requests
// to be made using cookies, authorization headers or TLS client
// certificates. More on MDN:
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
this.httpClient.defaults.withCredentials = true;
this.httpClient.defaults.headers.common['USE-JWT-COOKIE'] = true;

this.csrfTokenCache = {};
this.csrfTokenRequestPromises = {};
}

async getCsrfToken(url) {
let urlParts;
try {
urlParts = getUrlParts(url);
} catch (e) {
// If the url is not parsable it's likely because a relative
// path was supplied as the url. This is acceptable and in
// this case we should use the current origin of the page.
urlParts = getUrlParts(global.location.origin);
}
const { protocol, domain } = urlParts;
const csrfToken = this.csrfTokenCache[domain];

if (csrfToken) {
return csrfToken;
}

if (!this.csrfTokenRequestPromises[domain]) {
this.csrfTokenRequestPromises[domain] = this.httpClient
.get(`${protocol}://${domain}${this.csrfTokenApiPath}`)
.then((response) => {
this.csrfTokenCache[domain] = response.data.csrfToken;
return this.csrfTokenCache[domain];
})
.catch(processAxiosErrorAndThrow)
.finally(() => {
delete this.csrfTokenRequestPromises[domain];
});
}

return this.csrfTokenRequestPromises[domain];
}

clearCsrfTokenCache() {
this.csrfTokenCache = {};
}

getHttpClient() {
return this.httpClient;
}
}
1 change: 1 addition & 0 deletions themes/edx.org/lms/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@
"demographicsBaseUrl": getattr(settings, 'DEMOGRAPHICS_BASE_URL', ''),
"marketingSiteBaseUrl": getattr(settings, 'MKTG_URLS', {}).get('ROOT', ''),
"jwtAuthToken": getattr(settings, 'JWT_AUTH', {}).get('JWT_AUTH_COOKIE_HEADER_PAYLOAD', ''),
"csrfTokenPath": getattr(settings, 'DEMOGRAPHICS_CSRF_TOKEN_API_PATH', ''),
"bannerLogo": bannerLogoPath
},
)}
Expand Down

0 comments on commit 35bbf06

Please sign in to comment.