mirror of
https://github.com/aljazceru/gitpear.git
synced 2025-12-17 14:14:22 +01:00
@@ -68,6 +68,7 @@ program
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('unshare')
|
.command('unshare')
|
||||||
.description('unshare a gitpear repo')
|
.description('unshare a gitpear repo')
|
||||||
|
|||||||
63
src/home.js
63
src/home.js
@@ -10,31 +10,37 @@ function createAppFolder (name) {
|
|||||||
fs.mkdirSync(`${APP_HOME}/${name}/code`, { recursive: true })
|
fs.mkdirSync(`${APP_HOME}/${name}/code`, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
function shareAppFolder (name) {
|
function shareAppFolder (name, entry) {
|
||||||
fs.openSync(`${APP_HOME}/${name}/.git-daemon-export-ok`, 'w')
|
const p = `${APP_HOME}/${name}/.git-daemon-export-ok`
|
||||||
|
fs.openSync(p, 'a')
|
||||||
|
const aclFile = fs.readFileSync(p, 'utf8')
|
||||||
|
const aclJson = JSON.parse(aclFile || '{ "protectedBranches": ["master"], "ACL": {}}')
|
||||||
|
|
||||||
|
let [userId = '*', permissions = 'r', branch = '*'] = entry?.split(':') || []
|
||||||
|
|
||||||
|
if (!aclJson.ACL[userId]) aclJson[userId] = { [branch]: permissions }
|
||||||
|
fs.writeFileSync(p, JSON.stringify(aclJson))
|
||||||
}
|
}
|
||||||
|
|
||||||
function shareWith (userId, branch = '*', permissions = 'rw') {
|
function addProtectedBranch (name, branch) {
|
||||||
if (!fs.existsSync(`${APP_HOME}/.git-daemon-export-ok`)) {
|
const aclFile = fs.readFileSync(`${APP_HOME}/${name}/.git-daemon-export-ok`, 'utf8')
|
||||||
fs.writeFileSync(`${APP_HOME}/.git-daemon-export-ok`, '')
|
const aclJson = JSON.parse(aclFile || '{ "protectedBranches": [], "ACL": {}}')
|
||||||
|
if (!aclJson.protectedBranches.includes(branch)) aclJson.protectedBranches.push(branch)
|
||||||
|
fs.writeFileSync(aclFile, JSON.stringify(aclJson))
|
||||||
}
|
}
|
||||||
if (permissions.split('').some(p => !['r', 'w'].includes(p))) {
|
|
||||||
throw new Error('Permissions must be r, w or rw')
|
function removeProtectedBranch (name, branch) {
|
||||||
|
const aclFile = fs.readFileSync(`${APP_HOME}/${name}/.git-daemon-export-ok`, 'a')
|
||||||
|
const aclJson = JSON.parse(aclFile || '{ "protectedBranches": [], "ACL": {}}')
|
||||||
|
aclJson.protectedBranches = aclJson.protectedBranches.filter(b => b !== branch)
|
||||||
|
fs.writeFileSync(aclFile, JSON.stringify(aclJson))
|
||||||
}
|
}
|
||||||
// TODO: read file
|
|
||||||
// generate new conent
|
function removeUserFromACL (name, userId) {
|
||||||
// merge with old file
|
const aclFile = fs.readFileSync(`${APP_HOME}/${name}/.git-daemon-export-ok`, 'a')
|
||||||
// store file
|
const aclJson = JSON.parse(aclFile || '{ "protectedBranches": [], "ACL": {}}')
|
||||||
//
|
delete aclJson.ACL[userId]
|
||||||
// EXAMPLE:
|
fs.writeFileSync(aclFile, JSON.stringify(aclJson))
|
||||||
// {
|
|
||||||
// protectedBranches: ['master'],
|
|
||||||
// ACL: {
|
|
||||||
// '<userId>': { '<branch name | *>': 'r|w|rw' },
|
|
||||||
// '*': { '*': 'r' }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
fs.appendFileSync(`${APP_HOME}/.git-daemon-export-ok`, `${userId}\t${branch}\t${permissions}\n`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function unshareAppFolder (name) {
|
function unshareAppFolder (name) {
|
||||||
@@ -50,15 +56,11 @@ function isShared (name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getACL (name) {
|
function getACL (name) {
|
||||||
const entries = fs.readFileSync(`${APP_HOME}/${name}/.git-daemon-export-ok`).toString().split('\n').filter(Boolean)
|
if (!fs.existsSync(`${APP_HOME}/${name}/.git-daemon-export-ok`)) throw new Error('Repo is not shared')
|
||||||
const res = {}
|
|
||||||
for (const entry of entries) {
|
const aclFile = fs.readFileSync(`${APP_HOME}/${name}/.git-daemon-export-ok`, 'r')
|
||||||
const [userId, branch, permissions] = entry.split('\t')
|
aclJson = JSON.parse(aclFile || '{ "protectedBranches": [], "ACL": {}}')
|
||||||
if (!res[userId]) res[userId] = []
|
return aclJson
|
||||||
res[userId].push({ branch, permissions })
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
// TODO: have protected branch setting - the ACL must be assigned explicitly
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function list (sharedOnly) {
|
function list (sharedOnly) {
|
||||||
@@ -155,6 +157,5 @@ module.exports = {
|
|||||||
getDaemonPid,
|
getDaemonPid,
|
||||||
isDaemonRunning,
|
isDaemonRunning,
|
||||||
removeDaemonPid,
|
removeDaemonPid,
|
||||||
shareWith,
|
|
||||||
getACL,
|
getACL,
|
||||||
}
|
}
|
||||||
|
|||||||
69
src/rpc.js
69
src/rpc.js
@@ -20,19 +20,19 @@ module.exports = class RPC {
|
|||||||
// which can in turn be stored in a .git-daemon-export-ok file
|
// which can in turn be stored in a .git-daemon-export-ok file
|
||||||
|
|
||||||
/* -- PULL HANDLERS -- */
|
/* -- PULL HANDLERS -- */
|
||||||
rpc.respond('get-repos', async req => await this.getReposHandler(req))
|
rpc.respond('get-repos', async req => await this.getReposHandler(peerInfo.publicKey, req))
|
||||||
rpc.respond('get-refs', async req => await this.getRefsHandler(req))
|
rpc.respond('get-refs', async req => await this.getRefsHandler(peerInfo.publicKey, req))
|
||||||
|
|
||||||
/* -- PUSH HANDLERS -- */
|
/* -- PUSH HANDLERS -- */
|
||||||
rpc.respond('push', async req => await this.pushHandler(req))
|
rpc.respond('push', async req => await this.pushHandler(peerInfo.publicKey, req))
|
||||||
rpc.respond('f-push', async req => await this.forcePushHandler(req))
|
rpc.respond('f-push', async req => await this.forcePushHandler(peerInfo.publicKey, req))
|
||||||
rpc.respond('d-branch', async req => await this.deleteBranchHandler(req))
|
rpc.respond('d-branch', async req => await this.deleteBranchHandler(peerInfo.publicKey, req))
|
||||||
|
|
||||||
this.connections[peerInfo.publicKey] = rpc
|
this.connections[peerInfo.publicKey] = rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
async getReposHandler (req) {
|
async getReposHandler (publicKey, req) {
|
||||||
const { branch, url } = await this.parseReq(req)
|
const { branch, url } = await this.parseReq(publicKey, req, 'r')
|
||||||
|
|
||||||
const res = {}
|
const res = {}
|
||||||
for (const repoName in this.repositories) {
|
for (const repoName in this.repositories) {
|
||||||
@@ -43,15 +43,15 @@ module.exports = class RPC {
|
|||||||
return Buffer.from(JSON.stringify(res))
|
return Buffer.from(JSON.stringify(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRefsHandler (req) {
|
async getRefsHandler (publicKey, req) {
|
||||||
const { repoName, branch, url } = await this.parseReq(req)
|
const { repoName, branch, url } = await this.parseReq(publicKey, req, 'r')
|
||||||
const res = this.repositories[repoName]
|
const res = this.repositories[repoName]
|
||||||
|
|
||||||
return Buffer.from(JSON.stringify(res))
|
return Buffer.from(JSON.stringify(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
async pushHandler (req) {
|
async pushHandler (publicKey, req) {
|
||||||
const { url, repoName, branch } = await this.parseReq(req)
|
const { url, repoName, branch } = await this.parseReq(publicKey, req, 'w')
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
||||||
const child = spawn('git', ['fetch', url, `${branch}:${branch}`], { env })
|
const child = spawn('git', ['fetch', url, `${branch}:${branch}`], { env })
|
||||||
@@ -66,8 +66,8 @@ module.exports = class RPC {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async forcePushHandler (req) {
|
async forcePushHandler (publicKey, req) {
|
||||||
const { url, repoName, branch } = await this.parseReq(req)
|
const { url, repoName, branch } = await this.parseReq(publicKey, req, 'w')
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
||||||
const child = spawn('git', ['fetch', url, `${branch}:${branch}`, '--force'], { env })
|
const child = spawn('git', ['fetch', url, `${branch}:${branch}`, '--force'], { env })
|
||||||
@@ -82,8 +82,8 @@ module.exports = class RPC {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteBranchHandler (req) {
|
async deleteBranchHandler (publicKey, req) {
|
||||||
const { url, repoName, branch } = await this.parseReq(req)
|
const { url, repoName, branch } = await this.parseReq(publicKey, req, 'w')
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
||||||
const child = spawn('git', ['branch', '-D', branch], { env })
|
const child = spawn('git', ['branch', '-D', branch], { env })
|
||||||
@@ -98,20 +98,41 @@ module.exports = class RPC {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseReq(req, access, branch = '*') {
|
async parseReq(publicKey, req, access, branch = '*') {
|
||||||
let payload
|
if (!req) throw new Error('Request is empty')
|
||||||
let request = JSON.parse(req.toString())
|
let request = JSON.parse(req.toString())
|
||||||
const result = {
|
const parsed = {
|
||||||
repoName: request.body.url?.split('/')?.pop(),
|
repoName: request.body.url?.split('/')?.pop(),
|
||||||
branch: request.body.data?.split('#')[0],
|
branch: request.body.data?.split('#')[0],
|
||||||
url: request.body.url
|
url: request.body.url
|
||||||
}
|
}
|
||||||
if (!process.env.GIT_PEAR_AUTH) return result
|
if (!process.env.GIT_PEAR_AUTH) return parsed
|
||||||
if (!request.header) throw new Error('You are not allowed to access this repo')
|
|
||||||
|
|
||||||
payload = await acl.getId({ ...request.body, payload: request.header })
|
if (process.env.GIT_PEAR_AUTH !== 'naitive' && !request.header) {
|
||||||
const aclList = home.getACL(result.repoName)
|
throw new Error('You are not allowed to access this repo')
|
||||||
const userACL = aclList[payload.userId]
|
}
|
||||||
|
|
||||||
|
let userId
|
||||||
|
if (process.env.GIT_PEAR_AUTH === 'naitive') {
|
||||||
|
userId = publicKey
|
||||||
|
} else {
|
||||||
|
userId = (await acl.getId({ ...request.body, payload: request.header })).userId
|
||||||
|
}
|
||||||
|
const aclObj = home.getACL(parsed.repoName)
|
||||||
|
const userACL = aclObj[userId] || aclObj['*']
|
||||||
if (!userACL) throw new Error('You are not allowed to access this repo')
|
if (!userACL) throw new Error('You are not allowed to access this repo')
|
||||||
|
|
||||||
if (result.branch !== 'master'
|
if (aclObj.protectecBranches.includes(branch)) {
|
||||||
|
// protected branch must have exaplicit access grant
|
||||||
|
if (access === 'w') {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ test('e2e', async t => {
|
|||||||
clientStore.replicate(socket)
|
clientStore.replicate(socket)
|
||||||
const rpc = new ProtomuxRPC(socket)
|
const rpc = new ProtomuxRPC(socket)
|
||||||
|
|
||||||
const reposRes = await rpc.request('get-repos')
|
let payload = Buffer.from(JSON.stringify({ body: { url, method: 'get-repos' } }))
|
||||||
|
const reposRes = await rpc.request('get-repos', payload)
|
||||||
const reposJSON = JSON.parse(reposRes.toString())
|
const reposJSON = JSON.parse(reposRes.toString())
|
||||||
|
|
||||||
const driveKey = Buffer.from(reposJSON.foo, 'hex')
|
const driveKey = Buffer.from(reposJSON.foo, 'hex')
|
||||||
@@ -53,7 +54,8 @@ test('e2e', async t => {
|
|||||||
|
|
||||||
await drive.core.update({ wait: true })
|
await drive.core.update({ wait: true })
|
||||||
|
|
||||||
const refsRes = await rpc.request('get-refs', Buffer.from('foo'))
|
payload = Buffer.from(JSON.stringify({ body: { url, method: 'get-refs', data: 'foo' }}))
|
||||||
|
const refsRes = await rpc.request('get-refs', payload)
|
||||||
t.ok(refsRes)
|
t.ok(refsRes)
|
||||||
|
|
||||||
const want = Object.values(JSON.parse(refsRes.toString()))[0]
|
const want = Object.values(JSON.parse(refsRes.toString()))[0]
|
||||||
|
|||||||
Reference in New Issue
Block a user