mirror of
https://github.com/aljazceru/gitpear.git
synced 2025-12-19 23:24:22 +01:00
50
src/rpc-handlers/acl.js
Normal file
50
src/rpc-handlers/acl.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
const ACL = require('../acl')
|
||||||
|
|
||||||
|
async getACLHandler (publicKey, req) {
|
||||||
|
const { repoName, userId } = await this.parseACLRequest(publicKey, req)
|
||||||
|
return Buffer.from(JSON.stringify(ACL.getACL(repoName)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async addACLHandler (publicKey, req) {
|
||||||
|
const { repoName, userId, acl } = await this.parseACLRequest(publicKey, req)
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
const aclData = JSON.parse(acl)
|
||||||
|
ACL.setACL(repoName, aclData)
|
||||||
|
return Buffer.from('ACL updated')
|
||||||
|
}
|
||||||
|
|
||||||
|
async delACLHandler (publicKey, req) {
|
||||||
|
const { repoName, userId, acl } = await this.parseACLRequest(publicKey, req)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async chgACLHandler (publicKey, req) {
|
||||||
|
const { repoName, userId, acl } = await this.parseACLRequest(publicKey, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async parseACLRequest(publicKey, req) {
|
||||||
|
if (!req) throw new Error('Request is empty')
|
||||||
|
const request = JSON.parse(req.toString())
|
||||||
|
const userId = await this.authenticate(publicKey, request)
|
||||||
|
const isOwner = ACL.getOwnder(repoName).includes(userId)
|
||||||
|
if (!isOwner) throw new Error('You are not allowed to access this repo')
|
||||||
|
|
||||||
|
const repoName = request.body.url?.split('/')?.pop()
|
||||||
|
// TODO: check if repo exists
|
||||||
|
|
||||||
|
return {
|
||||||
|
repoName,
|
||||||
|
userId,
|
||||||
|
// FIXME
|
||||||
|
acl: request.body.acl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getACLHandler,
|
||||||
|
addACLHandler,
|
||||||
|
delACLHandler,
|
||||||
|
chgACLHandler,
|
||||||
|
}
|
||||||
35
src/rpc-handlers/bpr.js
Normal file
35
src/rpc-handlers/bpr.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const ACL = require('../acl')
|
||||||
|
|
||||||
|
async getBPRHandler (publicKey, req) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async addBPRHandler (publicKey, req) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async delBPRHandler (publicKey, req) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseBPRRequest(publicKey, req) {
|
||||||
|
if (!req) throw new Error('Request is empty')
|
||||||
|
const request = JSON.parse(req.toString())
|
||||||
|
const userId = await this.authenticate(publicKey, request)
|
||||||
|
|
||||||
|
const isOwner = ACL.getOwnder(repoName).includes(userId)
|
||||||
|
if (!isOwner) throw new Error('You are not allowed to access this repo')
|
||||||
|
|
||||||
|
const repoName = request.body.url?.split('/')?.pop()
|
||||||
|
// TODO: check if repo exists
|
||||||
|
|
||||||
|
return {
|
||||||
|
repoName,
|
||||||
|
userId,
|
||||||
|
// FIXME
|
||||||
|
acl: request.body.acl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getBPRHandler,
|
||||||
|
addBPRHandler,
|
||||||
|
delBPRHandler,
|
||||||
|
}
|
||||||
124
src/rpc-handlers/git.js
Normal file
124
src/rpc-handlers/git.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
|
||||||
|
async getReposHandler (publicKey, req) {
|
||||||
|
const { branch, url, userId } = await this.parseReq(publicKey, req)
|
||||||
|
|
||||||
|
const res = {}
|
||||||
|
for (const repoName in this.repositories) {
|
||||||
|
// TODO: add only public repos and those which are shared with the peer
|
||||||
|
// Alternatively return only requested repo
|
||||||
|
const isPublic = (ACL.getACL(repoName).visibility === 'public')
|
||||||
|
if (isPublic || ACL.getViewers(repoName).includes(userId)) {
|
||||||
|
res[repoName] = this.drives[repoName].key.toString('hex')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Buffer.from(JSON.stringify(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRefsHandler (publicKey, req) {
|
||||||
|
const { repoName, branch, url, userId } = await this.parseReq(publicKey, req)
|
||||||
|
const res = this.repositories[repoName]
|
||||||
|
|
||||||
|
const isPublic = (ACL.getACL(repoName).visibility === 'public')
|
||||||
|
if (isPublic || ACL.getViewers(repoName).includes(userId)) {
|
||||||
|
return Buffer.from(JSON.stringify(res))
|
||||||
|
} else {
|
||||||
|
throw new Error('You are not allowed to access this repo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pushHandler (publicKey, req) {
|
||||||
|
const { url, repoName, branch, userId } = await this.parseReq(publicKey, req)
|
||||||
|
const isContributor = ACL.getContributors(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
||||||
|
|
||||||
|
const isProtectedBranch = ACL.getACL(repoName).protectedBranches.includes(branch)
|
||||||
|
const isAdmin = ACL.getAdmins(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
||||||
|
const child = spawn('git', ['fetch', url, `${branch}:${branch}`], { env })
|
||||||
|
let errBuffer = Buffer.from('')
|
||||||
|
child.stderr.on('data', data => {
|
||||||
|
errBuffer = Buffer.concat([errBuffer, data])
|
||||||
|
})
|
||||||
|
|
||||||
|
child.on('close', code => {
|
||||||
|
return code === 0 ? resolve(errBuffer) : reject(errBuffer)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async forcePushHandler (publicKey, req) {
|
||||||
|
const { url, repoName, branch, userId } = await this.parseReq(publicKey, req)
|
||||||
|
const isContributor = ACL.getContributors(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
||||||
|
|
||||||
|
const isProtectedBranch = ACL.getACL(repoName).protectedBranches.includes(branch)
|
||||||
|
const isAdmin = ACL.getAdmins(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
||||||
|
const child = spawn('git', ['fetch', url, `${branch}:${branch}`, '--force'], { env })
|
||||||
|
let errBuffer = Buffer.from('')
|
||||||
|
child.stderr.on('data', data => {
|
||||||
|
errBuffer = Buffer.concat([errBuffer, data])
|
||||||
|
})
|
||||||
|
|
||||||
|
child.on('close', code => {
|
||||||
|
return code === 0 ? resolve(errBuffer) : reject(errBuffer)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteBranchHandler (publicKey, req) {
|
||||||
|
const { url, repoName, branch, userId } = await this.parseReq(publicKey, req)
|
||||||
|
const isContributor = ACL.getContributors(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
||||||
|
|
||||||
|
const isProtectedBranch = ACL.getACL(repoName).protectedBranches.includes(branch)
|
||||||
|
const isAdmin = ACL.getAdmins(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
||||||
|
const child = spawn('git', ['branch', '-D', branch], { env })
|
||||||
|
let errBuffer = Buffer.from('')
|
||||||
|
child.stderr.on('data', data => {
|
||||||
|
errBuffer = Buffer.concat([errBuffer, data])
|
||||||
|
})
|
||||||
|
|
||||||
|
child.on('close', code => {
|
||||||
|
return code === 0 ? resolve(errBuffer) : reject(errBuffer)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async parseReq(publicKey, req) {
|
||||||
|
if (!req) throw new Error('Request is empty')
|
||||||
|
const request = JSON.parse(req.toString())
|
||||||
|
const parsed = {
|
||||||
|
repoName: request.body.url?.split('/')?.pop(),
|
||||||
|
branch: request.body.data?.split('#')[0],
|
||||||
|
url: request.body.url,
|
||||||
|
userId: await this.authenticate(publicKey, request),
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getReposHandler,
|
||||||
|
getRefsHandler,
|
||||||
|
pushHandler,
|
||||||
|
forcePushHandler,
|
||||||
|
deleteBranchHandler,
|
||||||
|
}
|
||||||
|
|
||||||
9
src/rpc-handlers/index.js
Normal file
9
src/rpc-handlers/index.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const git = require('./git')
|
||||||
|
const acl = require('./acl')
|
||||||
|
const bpr = require('./bpr')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
git,
|
||||||
|
acl,
|
||||||
|
bpr,
|
||||||
|
}
|
||||||
143
src/rpc.js
143
src/rpc.js
@@ -3,7 +3,7 @@ const SecretStream = require('@hyperswarm/secret-stream')
|
|||||||
const { spawn } = require('child_process')
|
const { spawn } = require('child_process')
|
||||||
const home = require('./home')
|
const home = require('./home')
|
||||||
const auth = require('./auth')
|
const auth = require('./auth')
|
||||||
const acl = require('./acl')
|
const { git, acl, bpr } = require('./rpc-handlers')
|
||||||
|
|
||||||
module.exports = class RPC {
|
module.exports = class RPC {
|
||||||
constructor (announcedRefs, repositories, drives) {
|
constructor (announcedRefs, repositories, drives) {
|
||||||
@@ -15,8 +15,9 @@ module.exports = class RPC {
|
|||||||
|
|
||||||
async setHandlers (socket, peerInfo) {
|
async setHandlers (socket, peerInfo) {
|
||||||
if (this.connections[peerInfo.publicKey]) return this.connections[peerInfo.publicKey]
|
if (this.connections[peerInfo.publicKey]) return this.connections[peerInfo.publicKey]
|
||||||
|
|
||||||
const rpc = new ProtomuxRPC(socket)
|
const rpc = new ProtomuxRPC(socket)
|
||||||
|
this.connections[peerInfo.publicKey] = rpc
|
||||||
|
|
||||||
rpc.on('error', err => console.error('rpc error', err))
|
rpc.on('error', err => console.error('rpc error', err))
|
||||||
rpc.on('close', () => delete this.connections[peerInfo.publicKey])
|
rpc.on('close', () => delete this.connections[peerInfo.publicKey])
|
||||||
// XXX: handshaking can be used for access and permission management
|
// XXX: handshaking can be used for access and permission management
|
||||||
@@ -24,131 +25,27 @@ 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(socket.remotePublicKey, req))
|
rpc.respond('get-repos', async req => await git.getReposHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
rpc.respond('get-refs', async req => await this.getRefsHandler(socket.remotePublicKey, req))
|
rpc.respond('get-refs', async req => await git.getRefsHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
|
|
||||||
|
if (!process.env.GIT_PEAR_AUTH) return
|
||||||
|
|
||||||
if (process.env.GIT_PEAR_AUTH) {
|
|
||||||
/* -- PUSH HANDLERS -- */
|
/* -- PUSH HANDLERS -- */
|
||||||
rpc.respond('push', async req => await this.pushHandler(socket.remotePublicKey, req))
|
rpc.respond('push', async req => await git.pushHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
rpc.respond('f-push', async req => await this.forcePushHandler(socket.remotePublicKey, req))
|
rpc.respond('f-push', async req => await git.forcePushHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
rpc.respond('d-branch', async req => await this.deleteBranchHandler(socket.remotePublicKey, req))
|
rpc.respond('d-branch', async req => await git.deleteBranchHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
}
|
|
||||||
|
|
||||||
this.connections[peerInfo.publicKey] = rpc
|
/* -- REPO ADMINISTRATION HANDLERS -- */
|
||||||
}
|
|
||||||
|
|
||||||
async getReposHandler (publicKey, req) {
|
/* -- ACL HANDLERS -- */
|
||||||
const { branch, url, userId } = await this.parseReq(publicKey, req)
|
rpc.respond('get-acl', async req => await acl.getACLHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
|
rpc.respond('add-acl', async req => await acl.addACLHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
const res = {}
|
rpc.respond('chg-acl', async req => await acl.chgCLHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
for (const repoName in this.repositories) {
|
rpc.respond('del-acl', async req => await acl.delACLHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
// TODO: add only public repos and those which are shared with the peer
|
/* -- BRANCH HANDLERS -- */
|
||||||
// Alternatively return only requested repo
|
rpc.respond('get-bpr', async req => await bpr.getBPRHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
const isPublic = (acl.getACL(repoName).visibility === 'public')
|
rpc.respond('add-bpr', async req => await bpr.addBPRHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
if (isPublic || acl.getViewers(repoName).includes(userId)) {
|
rpc.respond('del-bpr', async req => await bpr.delBPRHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
res[repoName] = this.drives[repoName].key.toString('hex')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Buffer.from(JSON.stringify(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRefsHandler (publicKey, req) {
|
|
||||||
const { repoName, branch, url, userId } = await this.parseReq(publicKey, req)
|
|
||||||
const res = this.repositories[repoName]
|
|
||||||
|
|
||||||
const isPublic = (acl.getACL(repoName).visibility === 'public')
|
|
||||||
if (isPublic || acl.getViewers(repoName).includes(userId)) {
|
|
||||||
return Buffer.from(JSON.stringify(res))
|
|
||||||
} else {
|
|
||||||
throw new Error('You are not allowed to access this repo')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async pushHandler (publicKey, req) {
|
|
||||||
const { url, repoName, branch, userId } = await this.parseReq(publicKey, req)
|
|
||||||
const isContributor = acl.getContributors(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
|
||||||
|
|
||||||
const isProtectedBranch = acl.getACL(repoName).protectedBranches.includes(branch)
|
|
||||||
const isAdmin = acl.getAdmins(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
|
||||||
const child = spawn('git', ['fetch', url, `${branch}:${branch}`], { env })
|
|
||||||
let errBuffer = Buffer.from('')
|
|
||||||
child.stderr.on('data', data => {
|
|
||||||
errBuffer = Buffer.concat([errBuffer, data])
|
|
||||||
})
|
|
||||||
|
|
||||||
child.on('close', code => {
|
|
||||||
return code === 0 ? resolve(errBuffer) : reject(errBuffer)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async forcePushHandler (publicKey, req) {
|
|
||||||
const { url, repoName, branch, userId } = await this.parseReq(publicKey, req)
|
|
||||||
const isContributor = acl.getContributors(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
|
||||||
|
|
||||||
const isProtectedBranch = acl.getACL(repoName).protectedBranches.includes(branch)
|
|
||||||
const isAdmin = acl.getAdmins(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
|
||||||
const child = spawn('git', ['fetch', url, `${branch}:${branch}`, '--force'], { env })
|
|
||||||
let errBuffer = Buffer.from('')
|
|
||||||
child.stderr.on('data', data => {
|
|
||||||
errBuffer = Buffer.concat([errBuffer, data])
|
|
||||||
})
|
|
||||||
|
|
||||||
child.on('close', code => {
|
|
||||||
return code === 0 ? resolve(errBuffer) : reject(errBuffer)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteBranchHandler (publicKey, req) {
|
|
||||||
const { url, repoName, branch, userId } = await this.parseReq(publicKey, req)
|
|
||||||
const isContributor = acl.getContributors(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
|
||||||
|
|
||||||
const isProtectedBranch = acl.getACL(repoName).protectedBranches.includes(branch)
|
|
||||||
const isAdmin = acl.getAdmins(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
|
||||||
const child = spawn('git', ['branch', '-D', branch], { env })
|
|
||||||
let errBuffer = Buffer.from('')
|
|
||||||
child.stderr.on('data', data => {
|
|
||||||
errBuffer = Buffer.concat([errBuffer, data])
|
|
||||||
})
|
|
||||||
|
|
||||||
child.on('close', code => {
|
|
||||||
return code === 0 ? resolve(errBuffer) : reject(errBuffer)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseReq(publicKey, req) {
|
|
||||||
if (!req) throw new Error('Request is empty')
|
|
||||||
const request = JSON.parse(req.toString())
|
|
||||||
const parsed = {
|
|
||||||
repoName: request.body.url?.split('/')?.pop(),
|
|
||||||
branch: request.body.data?.split('#')[0],
|
|
||||||
url: request.body.url,
|
|
||||||
userId: await this.authenticate(publicKey, request),
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticate (publicKey, request) {
|
async authenticate (publicKey, request) {
|
||||||
|
|||||||
Reference in New Issue
Block a user