Skip to content

Commit

Permalink
Make refreshing the XSRF token conditional on whether the openapi end…
Browse files Browse the repository at this point in the history
…point needs authentication. (#3626)

* Baseline openapi bindings.

* Make refreshing the XSRF token conditional on whether the openapi endpoint needs authentication.

* Update the test for the new middleware behavior.
  • Loading branch information
jyasskin committed Jan 31, 2024
1 parent c09679c commit 812d418
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 17 deletions.
12 changes: 7 additions & 5 deletions client-src/js-src/openapi-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class ChromeStatusOpenApiClient extends Api {
constructor() {
super(new Configuration({
credentials: 'same-origin',
apiKey: 'placeholder-api-key',
middleware: [
{pre: ChromeStatusMiddlewares.xsrfMiddleware},
{post: ChromeStatusMiddlewares.xssiMiddleware},
Expand All @@ -45,12 +46,13 @@ class ChromeStatusMiddlewares {
* @return {Promise<import('chromestatus-openapi').FetchParams>}
*/
static async xsrfMiddleware(req) {
return window.csClient.ensureTokenIsValid().then(() => {
const headers = req.init.headers || {};
headers['X-Xsrf-Token'] = [window.csClient.token];
const headers = req.init.headers || {};
if ('X-Xsrf-Token' in headers) {
await window.csClient.ensureTokenIsValid();
headers['X-Xsrf-Token'] = window.csClient.token;
req.init.headers = headers;
return req;
});
}
return req;
}


Expand Down
17 changes: 11 additions & 6 deletions client-src/js-src/openapi-client_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,34 @@ describe('openapi-client', () => {
});
describe('Middlewares', () => {
describe('xsrfMiddleware', () => {
it('should add the XSRF token to the request with existing headers', async () => {
it('should update the XSRF token to the request with existing headers', async () => {
const tokenValidStub = sinon.stub(window.csClient, 'ensureTokenIsValid').resolves();
/** @type {import('chromestatus-openapi').RequestContext} */
const req = {
init: {
headers: {'content-type': ['application/json']},
headers: {
'content-type': ['application/json'],
'X-Xsrf-Token': 'Wrong-value',
},
},
};
/** @type {import('chromestatus-openapi').FetchParams} */
const params = await ChromeStatusMiddlewares.xsrfMiddleware(req);
assert.equal(params.init.headers['content-type'][0], 'application/json');
assert.equal(params.init.headers['X-Xsrf-Token'][0], 'fake_token');
assert.equal(params.init.headers['X-Xsrf-Token'], 'fake_token');
tokenValidStub.restore();
});
it('should add the XSRF token to the request with no existing headers', async () => {
it('should not add the XSRF token to the request with no existing header', async () => {
const tokenValidStub = sinon.stub(window.csClient, 'ensureTokenIsValid').resolves();
/** @type {import('chromestatus-openapi').RequestContext} */
const req = {
init: {},
init: {
headers: {'content-type': ['application/json']},
},
};
/** @type {import('chromestatus-openapi').FetchParams} */
const params = await ChromeStatusMiddlewares.xsrfMiddleware(req);
assert.equal(params.init.headers['X-Xsrf-Token'][0], 'fake_token');
assert.notExists(params.init.headers['X-Xsrf-Token']);
tokenValidStub.restore();
});
});
Expand Down
2 changes: 1 addition & 1 deletion gen/js/chromestatus-openapi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
"prepare": "npm run build"
},
"devDependencies": {
"typescript": "^5.3"
"typescript": "^4.0"
}
}
12 changes: 12 additions & 0 deletions gen/js/chromestatus-openapi/src/apis/DefaultApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface {

headerParameters['Content-Type'] = 'application/json';

if (this.configuration && this.configuration.apiKey) {
headerParameters["X-Xsrf-Token"] = this.configuration.apiKey("X-Xsrf-Token"); // XsrfToken authentication
}

const response = await this.request({
path: `/components/{componentId}/users/{userId}`.replace(`{${"componentId"}}`, encodeURIComponent(String(requestParameters.componentId))).replace(`{${"userId"}}`, encodeURIComponent(String(requestParameters.userId))),
method: 'PUT',
Expand All @@ -143,6 +147,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface {

const headerParameters: runtime.HTTPHeaders = {};

if (this.configuration && this.configuration.apiKey) {
headerParameters["X-Xsrf-Token"] = this.configuration.apiKey("X-Xsrf-Token"); // XsrfToken authentication
}

const response = await this.request({
path: `/componentsusers`,
method: 'GET',
Expand Down Expand Up @@ -179,6 +187,10 @@ export class DefaultApi extends runtime.BaseAPI implements DefaultApiInterface {

headerParameters['Content-Type'] = 'application/json';

if (this.configuration && this.configuration.apiKey) {
headerParameters["X-Xsrf-Token"] = this.configuration.apiKey("X-Xsrf-Token"); // XsrfToken authentication
}

const response = await this.request({
path: `/components/{componentId}/users/{userId}`.replace(`{${"componentId"}}`, encodeURIComponent(String(requestParameters.componentId))).replace(`{${"userId"}}`, encodeURIComponent(String(requestParameters.userId))),
method: 'DELETE',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
from typing import List


def info_from_XsrfToken(api_key, required_scopes):
"""
Check and retrieve authentication information from api_key.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
:param api_key API key provided by Authorization header
:type api_key: str
:param required_scopes Always None. Used for other authentication method
:type required_scopes: None
:return: Information attached to provided api_key or None if api_key is invalid or does not allow access to called API
:rtype: dict | None
"""
return {'uid': 'user_id'}

Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ paths:
responses:
"200":
description: Success
security:
- XsrfToken: []
summary: Remove a user from a component
x-openapi-router-controller: chromestatus_openapi.controllers.default_controller
put:
Expand Down Expand Up @@ -70,6 +72,8 @@ paths:
responses:
"200":
description: Success
security:
- XsrfToken: []
summary: Add a user to a component
x-openapi-router-controller: chromestatus_openapi.controllers.default_controller
/componentsusers:
Expand All @@ -83,6 +87,8 @@ paths:
$ref: '#/components/schemas/ComponentsUsersResponse'
description: List of all the potential users and components with existing
subscribers and owners.
security:
- XsrfToken: []
summary: List all components and possible users
x-openapi-router-controller: chromestatus_openapi.controllers.default_controller
components:
Expand Down Expand Up @@ -189,3 +195,9 @@ components:
title: owner
type: boolean
title: ComponentUsersRequest
securitySchemes:
XsrfToken:
in: header
name: X-Xsrf-Token
type: apiKey
x-apikeyInfoFunc: chromestatus_openapi.controllers.security_controller.info_from_XsrfToken
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def test_add_user_to_component(self):
component_users_request = {"owner":True}
headers = {
'Content-Type': 'application/json',
'XsrfToken': 'special-key',
}
response = self.client.open(
'/api/v0/components/{component_id}/users/{user_id}'.format(component_id=56, user_id=56),
Expand All @@ -35,6 +36,7 @@ def test_list_component_users(self):
"""
headers = {
'Accept': 'application/json',
'XsrfToken': 'special-key',
}
response = self.client.open(
'/api/v0/componentsusers',
Expand All @@ -51,6 +53,7 @@ def test_remove_user_from_component(self):
component_users_request = {"owner":True}
headers = {
'Content-Type': 'application/json',
'XsrfToken': 'special-key',
}
response = self.client.open(
'/api/v0/components/{component_id}/users/{user_id}'.format(component_id=56, user_id=56),
Expand Down
4 changes: 2 additions & 2 deletions gen/py/chromestatus_openapi/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ connexion[swagger-ui] <= 2.3.0; python_version=="3.5" or python_version=="3.4"
# connexion requires werkzeug but connexion < 2.4.0 does not install werkzeug
# we must peg werkzeug versions below to fix connexion
# https://github.com/zalando/connexion/pull/1044
werkzeug == 3.0.1; python_version=="3.5" or python_version=="3.4"
werkzeug == 0.16.1; python_version=="3.5" or python_version=="3.4"
swagger-ui-bundle >= 0.0.2
python_dateutil >= 2.6.0
setuptools >= 21.0.0
Flask == 2.3.2
Flask == 2.1.1
11 changes: 11 additions & 0 deletions openapi/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ paths:
get:
summary: List all components and possible users
operationId: listComponentUsers
security:
- XsrfToken: []
responses:
'200':
description: List of all the potential users and components with existing subscribers and owners.
Expand All @@ -30,6 +32,8 @@ paths:
put:
summary: Add a user to a component
operationId: addUserToComponent
security:
- XsrfToken: []
parameters:
- in: path
name: componentId
Expand All @@ -54,6 +58,8 @@ paths:
delete:
summary: Remove a user from a component
operationId: removeUserFromComponent
security:
- XsrfToken: []
parameters:
- in: path
name: componentId
Expand All @@ -76,6 +82,11 @@ paths:
schema:
$ref: '#/components/schemas/ComponentUsersRequest'
components:
securitySchemes:
XsrfToken:
type: apiKey
in: header
name: X-Xsrf-Token
schemas:
ComponentsUsersResponse:
properties:
Expand Down
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 812d418

Please sign in to comment.