Skip to content

Commit

Permalink
add permission check for post endpoint (#597)
Browse files Browse the repository at this point in the history
* add permission check for post endpoint

* update linter ruleset to allow access_token
  • Loading branch information
pb82 authored Jul 1, 2020
1 parent 39d154c commit bab3d79
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 65 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
'import/no-dynamic-require': 'off',
'global-require': 'off',
'react/jsx-curly-brace-presence': 'off',
'react/no-unused-prop-types': 'off'
'react/no-unused-prop-types': 'off',
'camelcase': ['error', { 'allow': ['access_token', 'auth_token'] }]
}
};
45 changes: 10 additions & 35 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const url = require('url');
const fs = require('fs');
const asciidoctor = require('asciidoctor.js');
const Mustache = require('mustache');
const { fetchOpenshiftUser } = require('./server_middleware');
const { requireRoles } = require('./server_middleware');
const giteaClient = require('./gitea_client');
const gitClient = require('./git_client');
const bodyParser = require('body-parser');
Expand Down Expand Up @@ -70,6 +70,12 @@ const WALKTHROUGH_LOCATION_DEFAULT = {
header: null
};

const backendRequiredRoles = [
'system:cluster-admins',
'system:dedicated-admins',
'dedicated-admins'
];

const walkthroughs = [];
let server;

Expand All @@ -83,6 +89,7 @@ app.get('/metrics', (req, res) => {
res.end(Prometheus.register.metrics());
});


// Get all user defined walkthrough repositories
app.get('/user_walkthroughs', (req, res) =>
getUserWalkthroughs()
Expand All @@ -101,7 +108,8 @@ app.get('/user_walkthroughs', (req, res) =>
);

// Insert new user defined walkthrough repositories
app.post('/user_walkthroughs', (req, res) => {
// This requires cluster- or dedicated admin permissions
app.post('/user_walkthroughs', requireRoles(backendRequiredRoles), (req, res) => {
const { data } = req.body;
return setUserWalkthroughs(data)
.then(({ value }) => res.json(value))
Expand All @@ -111,39 +119,6 @@ app.post('/user_walkthroughs', (req, res) => {
});
});

// Init custom walkthroughs dependencies
app.post('/initThread', fetchOpenshiftUser, (req, res) => {
if (!req.body || !req.body.dependencies) {
console.warn('Dependencies not provided in request body. Skipping thread initialization.');
res.sendStatus(200);
return;
}
const {
dependencies: { repos },
openshiftUser
} = req.body;

// Return success in mock mode without actually creating any repositories
if (!process.env.OPENSHIFT_HOST) {
console.warn('OPENSHIFT_HOST not set. Skipping thread initialization.');
res.sendStatus(200);
return;
}

if (!repos || repos.length === 0) {
res.sendStatus(200);
return;
}

// eslint-disable-next-line consistent-return
return Promise.all(repos.map(repo => giteaClient.createRepoForUser(openshiftUser, repo)))
.then(() => res.sendStatus(200))
.catch(err => {
console.error(`Error creating repositories: ${err}`);
return res.status(500).json({ error: err.message });
});
});

// Dynamic configuration for openshift API calls
app.get('/config.js', (req, res) => {
if (!process.env.OPENSHIFT_HOST) {
Expand Down
87 changes: 62 additions & 25 deletions server_middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,73 @@ const insecureAgent = new https.Agent({
rejectUnauthorized: false
});

exports.fetchOpenshiftUser = (req, res, next) => {
// Not much we can do if the openshift host is not set. This probably
// means we are running in a mock environment
if (!OPENSHIFT_HOST) {
return next();

function getApiHost() {
if (process.env.OPENSHIFT_VERSION === '4') {
return `https://${process.env.OPENSHIFT_API}/apis/user.openshift.io/v1/users/~`;
}
return `https://${OPENSHIFT_HOST}/oapi/v1/users/~`;
}

function checkRoles(memberGroups, requestedGroups) {
// If no groups were requested the request is always authorized
if (!requestedGroups) {
return true;
}

const token = req.get('X-Forwarded-Access-Token');
if (!token) {
return res.sendStatus(403);
// If groups were requested but the user is not a member of any group the request is
// always unauthorized
if (!memberGroups) {
return false;
}

return axios({
httpsAgent: insecureAgent,
url: `https://${OPENSHIFT_HOST}/oapi/v1/users/~`,
headers: {
authorization: `Bearer ${token}`,
Accept: 'application/json'
// Require that at least one of the requested groups are part of the members group array
return requestedGroups.map(group => {
console.log(`checking against ${group}`);
return memberGroups.indexOf(group) >= 0;
}).reduce((acc, current) => {
return acc || current;
}, false);
}

exports.requireRoles = config => {
return function(req, res, next) {
// Not much we can do if the openshift host is not set. This probably
// means we are running in a mock environment
if (!OPENSHIFT_HOST) {
return next();
}
})
.then(response => {
const {
kind,
metadata: { name }
} = response.data;
if (kind === 'User') {
req.body.openshiftUser = name;
return next();

const token = req.get('X-Forwarded-Access-Token');
if (!token) {
return res.sendStatus(401);
}

return axios({
httpsAgent: insecureAgent,
url: getApiHost(),
headers: {
authorization: `Bearer ${token}`
}
return res.sendStatus(403);
})
.catch(err => next(err));
.then(response => {
const {
kind,
groups,
metadata: { name }
} = response.data;
if (kind === 'User') {
if (checkRoles(groups, config)) {
console.error(`Access granted to user ${name}`);
return next();
}
console.error(`Access denied to user ${name}`);
}
return res.sendStatus(403);
})
.catch(err => {
console.error(err);
return res.sendStatus(401);
});
};
};
8 changes: 6 additions & 2 deletions src/pages/settings/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { connect, reduxActions } from '../../redux';
import Breadcrumb from '../../components/breadcrumb/breadcrumb';
import { setUserWalkthroughs, getUserWalkthroughs } from '../../services/walkthroughServices';
import { getCurrentRhmiConfig, updateRhmiConfig } from '../../services/rhmiConfigServices';
import { getUser } from '../../services/openshiftServices';

const moment = require('moment');

Expand Down Expand Up @@ -145,8 +146,11 @@ class SettingsPage extends React.Component {
saveSolutionPatternSettings = (e, value) => {
e.preventDefault();
const { history } = this.props;
setUserWalkthroughs(value);
history.push(`/`);
getUser().then(({ access_token }) => {
setUserWalkthroughs(value, access_token).then(() => {
history.push(`/`);
});
});
};

saveMockBackupSettings = (e, value) => {
Expand Down
7 changes: 5 additions & 2 deletions src/services/walkthroughServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,13 +373,16 @@ const getUserWalkthroughs = () =>
/**
* Saves the user-defined GitHub repositories from the UI to the database.
*/
const setUserWalkthroughs = (data = {}) =>
const setUserWalkthroughs = (data = {}, token) =>
axios(
serviceConfig(
{
method: 'post',
url: `/user_walkthroughs`,
data: { data }
data: { data },
headers: {
'X-Forwarded-Access-Token': token
}
},
false
)
Expand Down

0 comments on commit bab3d79

Please sign in to comment.