Skip to content

Commit

Permalink
add dialog for creating branches
Browse files Browse the repository at this point in the history
Feat: Add dialog for creating branches and indicators for protected branches
  • Loading branch information
jgadsden authored Jan 29, 2025
2 parents 08f4bd5 + a80e91c commit 9c9d95f
Show file tree
Hide file tree
Showing 33 changed files with 986 additions and 337 deletions.
2 changes: 2 additions & 0 deletions td.server/src/config/routes.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const routes = (router) => {
router.get('/api/threatmodel/:organisation/:repo/:branch/models', threatmodelController.models);
router.get('/api/threatmodel/:organisation/:repo/:branch/:model/data', threatmodelController.model);

router.post('/api/threatmodel/:organisation/:repo/:branch/createBranch', threatmodelController.createBranch);

// removed because of security denial of service concerns (denial of models)
//router.delete('/api/threatmodel/:organisation/:repo/:branch/:model', threatmodelController.deleteModel);

Expand Down
29 changes: 27 additions & 2 deletions td.server/src/controllers/threatmodelcontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ const branches = (req, res) => responseWrapper.sendResponseAsync(async () => {
const headers = branchesResp[1];
const pageLinks = branchesResp[2];

const branchNames = branches.map((x) => x.name);
const branchNames = branches.map((x) => ({
name: x.name,
// Protected branches are not so easy to determine from the API on Bitbucket
protected: x.protected||false
}));

const pagination = getPagination(headers, pageLinks, repoInfo.page);

Expand Down Expand Up @@ -212,6 +216,26 @@ const organisation = (req, res) => {
return res.status(200).send(organisation);
};

const createBranch = async (req, res) => {
const repository = repositories.get();

const branchInfo = {
organisation: req.params.organisation,
repo: req.params.repo,
branch: req.params.branch,
ref: req.body.refBranch
};
logger.debug(`API createBranch request: ${logger.transformToString(req)}`);

try {
const createBranchResp = await repository.createBranchAsync(branchInfo, req.provider.access_token);
return res.status(201).send(createBranchResp);
} catch (err) {
logger.error(err);
return serverError('Error creating branch', res, logger);
}
};

export default {
branches,
create,
Expand All @@ -220,5 +244,6 @@ export default {
models,
organisation,
repos,
update
update,
createBranch
};
21 changes: 20 additions & 1 deletion td.server/src/repositories/bitbucketrepo.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,28 @@ export const deleteAsync = async (modelInfo, accessToken) => {
throw new Error(`Bitbucket deleteAsync is not implemented yet`);
};

export const createBranchAsync = (repoInfo, accessToken) => {
const workspace = env.get().config.BITBUCKET_WORKSPACE;

const client = getClient(accessToken);
const repo = getRepoFullName(repoInfo);
return client.refs.createBranch({
_body: {
name: repoInfo.branch,
target: {
hash: repoInfo.ref
}
},
repo_slug: repo,
workspace: workspace
});
};

const getRepoFullName = (info) => `${info.repo}`;
const getModelPath = (modelInfo) => `${repoRootDirectory()}/${modelInfo.model}/${modelInfo.model}.json`;
const getModelContent = (modelInfo) => JSON.stringify(modelInfo.body, null, ' ');


export default {
branchesAsync,
createAsync,
Expand All @@ -168,5 +186,6 @@ export default {
searchAsync,
updateAsync,
userAsync,
getClient
getClient,
createBranchAsync
};
11 changes: 10 additions & 1 deletion td.server/src/repositories/githubrepo.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ const deleteAsync = async (modelInfo, accessToken) => {
);
};

const createBranchAsync = async (repoInfo, accessToken) => {
const client = getClient(accessToken);
const repo = getRepoFullName(repoInfo);
const resp = await client.repo(repo).refAsync(`heads/${repoInfo.ref}`);
const sha = resp[0].object.sha;
return client.repo(repo).createRefAsync(`refs/heads/${repoInfo.branch}`, sha);
};

const getRepoFullName = (info) => `${info.organisation}/${info.repo}`;
const getModelPath = (modelInfo) => `${repoRootDirectory()}/${modelInfo.model}/${modelInfo.model}.json`;
const getModelContent = (modelInfo) => JSON.stringify(modelInfo.body, null, ' ');
Expand All @@ -94,5 +102,6 @@ export default {
reposAsync,
searchAsync,
updateAsync,
userAsync
userAsync,
createBranchAsync
};
9 changes: 8 additions & 1 deletion td.server/src/repositories/gitlabrepo.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,18 @@ export const deleteAsync = (modelInfo, accessToken) => getClient(accessToken).Re
'Deleted by OWASP Threat Dragon',
);

export const createBranchAsync = (repoInfo, accessToken) => {
const client = getClient(accessToken);
const repo = getRepoFullName(repoInfo);
return client.Branches.create(repo, repoInfo.branch, repoInfo.ref);
};

const getRepoFullName = (info) => `${info.organisation}/${info.repo}`;
const getModelPath = (modelInfo) => `${repoRootDirectory()}/${modelInfo.model}/${modelInfo.model}.json`;
const getModelContent = (modelInfo) => JSON.stringify(modelInfo.body, null, ' ');

export default {
createBranchAsync,
branchesAsync,
createAsync,
deleteAsync,
Expand All @@ -113,5 +120,5 @@ export default {
reposAsync,
searchAsync,
updateAsync,
userAsync
userAsync,
};
49 changes: 36 additions & 13 deletions td.server/test/repositories/bitbucketrepo.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {getClient, userAsync} from '../../src/repositories/bitbucketrepo.js';

describe('repositories/bitbucketrepo.js', () => {
const workspace = 'threat-workspace';
const repoPath = 'ThreatDragonModels'
const repoPath = 'ThreatDragonModels';

const info = {
body: {
Expand All @@ -19,6 +19,8 @@ describe('repositories/bitbucketrepo.js', () => {
branch: 'testBranch',
organisation: 'test org',
page: 'testPage',
repo: 'test repo',
ref: 'testRef',
listBranches: {
workspace: workspace,
repo_slug: 'repoInfo.repo',
Expand Down Expand Up @@ -68,7 +70,8 @@ describe('repositories/bitbucketrepo.js', () => {
values: [
{full_name: 'Threat-Workspace/Repo1'},
{full_name: 'Threat-Workspace/Repo2'},
{full_name: 'Threat-Workspace/Repo3'},]
{full_name: 'Threat-Workspace/Repo3'},
]
}
})),
getBranch: sinon.stub().returns(Promise.resolve({data: {target: {hash: info.readInfo.commit}}})),
Expand All @@ -82,6 +85,9 @@ describe('repositories/bitbucketrepo.js', () => {
users: {
getAuthedUser: sinon.stub().returns(Promise.resolve({data: {id: 1, username: 'Test User'}})),
},
refs:{
createBranch: sinon.stub().returns(Promise.resolve({data: {values: []}})),
}
};

const clientOptions = {
Expand Down Expand Up @@ -124,7 +130,7 @@ describe('repositories/bitbucketrepo.js', () => {
});

it('creates the bitbucket client', () => {
threatModelRepository.userAsync(accessToken)
threatModelRepository.userAsync(accessToken);
sinon.stub(env, 'get').returns({config: {BITBUCKET_WORKSPACE: workspace}});
expect(BitbucketClientWrapper.getClient).to.have.been.calledWith(clientOptions);

Expand All @@ -146,7 +152,7 @@ describe('repositories/bitbucketrepo.js', () => {
});

// Setup the transformed data
let transformedReposData = [
const transformedReposData = [
{full_name: 'repo1'},
{full_name: 'repo2'},
{full_name: 'repo3'},
Expand All @@ -163,7 +169,7 @@ describe('repositories/bitbucketrepo.js', () => {
});

describe('branchesAsync', () => {
let repoInfo = {page: info.listBranches.page, repo: info.listBranches.repo_slug}
const repoInfo = {page: info.listBranches.page, repo: info.listBranches.repo_slug};

beforeEach(async () => {
sinon.stub(env, 'get').returns({config: {BITBUCKET_WORKSPACE: workspace}});
Expand All @@ -181,13 +187,15 @@ describe('repositories/bitbucketrepo.js', () => {
});

describe('modelsAsync', () => {
let branchInfo = {branch: info.branchInfo.name, repo: info.branchInfo.repo_slug};
const branchInfo = {branch: info.branchInfo.name, repo: info.branchInfo.repo_slug};

beforeEach(async () => {
sinon.stub(env, 'get').returns({config: {BITBUCKET_WORKSPACE: workspace, BITBUCKET_REPO_ROOT_DIRECTORY: repoPath}});
sinon.stub(mockClient.source, 'read').returns(Promise.resolve({data: {values: [{path: 'ThreatDragonModels/model1'},
sinon.stub(mockClient.source, 'read').returns(Promise.resolve({data: {values: [
{path: 'ThreatDragonModels/model1'},
{path: 'ThreatDragonModels/model2'},
{path: 'ThreatDragonModels/model3'}]}}));
{path: 'ThreatDragonModels/model3'}
]}}));
await threatModelRepository.modelsAsync(branchInfo, accessToken);
});

Expand Down Expand Up @@ -222,7 +230,7 @@ describe('repositories/bitbucketrepo.js', () => {
});

describe('modelAsync', () => {
let modelInfo = {branch: info.modelInfo.name, repo: info.modelInfo.repo_slug, model: info.modelInfo.model};
const modelInfo = {branch: info.modelInfo.name, repo: info.modelInfo.repo_slug, model: info.modelInfo.model};

beforeEach(async () => {
sinon.stub(env, 'get').returns({config: {BITBUCKET_WORKSPACE: workspace, BITBUCKET_REPO_ROOT_DIRECTORY: repoPath}});
Expand All @@ -245,14 +253,14 @@ describe('repositories/bitbucketrepo.js', () => {
});

describe('createAsync', () => {
let createAysncInfo = {
const createAysncInfo = {
branch: info.createAsync.name,
repo: info.createAsync.repo_slug,
model: info.createAsync.model,
body: info.createAsync.body
};

let createFileCommitInfo = {
const createFileCommitInfo = {
'ThreatDragonModels/my model/my model.json': JSON.stringify(createAysncInfo.body, null, ' '),
repo_slug: createAysncInfo.repo,
files: 'ThreatDragonModels/my model/my model.json',
Expand Down Expand Up @@ -280,14 +288,14 @@ describe('repositories/bitbucketrepo.js', () => {

describe('updateAsync', () => {

let updateAysncInfo = {
const updateAysncInfo = {
branch: info.createAsync.name,
repo: info.createAsync.repo_slug,
model: info.createAsync.model,
body: info.createAsync.body
};

let updateFileCommitInfo = {
const updateFileCommitInfo = {
'ThreatDragonModels/my model/my model.json': JSON.stringify(updateAysncInfo.body, null, ' '),
repo_slug: updateAysncInfo.repo,
files: 'ThreatDragonModels/my model/my model.json',
Expand Down Expand Up @@ -324,6 +332,21 @@ describe('repositories/bitbucketrepo.js', () => {

});

describe('create branch', () => {
beforeEach(async () => {
sinon.stub(env, 'get').returns({config: {BITBUCKET_WORKSPACE: workspace, BITBUCKET_REPO_ROOT_DIRECTORY: repoPath}});
await threatModelRepository.createBranchAsync(info, accessToken);
});
it('creates a new branch', () => {
expect(mockClient.refs.createBranch).to.have.been.calledWith(
{
_body: { name: info.branch, target: { hash: info.ref } },
repo_slug: info.repo,
workspace: workspace
}
);
});
});

});

Loading

0 comments on commit 9c9d95f

Please sign in to comment.