Skip to content

Commit

Permalink
Merge branch 'main' into motabbir
Browse files Browse the repository at this point in the history
  • Loading branch information
jelveh authored Nov 20, 2024
2 parents d43e792 + c7419d1 commit 546a268
Show file tree
Hide file tree
Showing 12 changed files with 749 additions and 433 deletions.
12 changes: 12 additions & 0 deletions src/backend/src/modules/puterai/AITestModeService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const BaseService = require("../../services/BaseService");

class AITestModeService extends BaseService {
async _init () {
const svc_driver = this.services.get('driver');
svc_driver.register_test_service('puter-chat-completion', 'openai-completion');
}
}

module.exports = {
AITestModeService,
};
5 changes: 5 additions & 0 deletions src/backend/src/modules/puterai/AWSPollyService.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ class AWSPollyService extends BaseService {
}

static IMPLEMENTS = {
['driver-capabilities']: {
supports_test_mode (iface, method_name) {
return iface === 'puter-tts' && method_name === 'synthesize';
}
},
['puter-tts']: {
async list_voices () {
const polly_voices = await this.describe_voices();
Expand Down
5 changes: 5 additions & 0 deletions src/backend/src/modules/puterai/AWSTextractService.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ class AWSTextractService extends BaseService {
}

static IMPLEMENTS = {
['driver-capabilities']: {
supports_test_mode (iface, method_name) {
return iface === 'puter-ocr' && method_name === 'recognize';
}
},
['puter-ocr']: {
async recognize ({ source, test_mode }) {
if ( test_mode ) {
Expand Down
6 changes: 6 additions & 0 deletions src/backend/src/modules/puterai/OpenAICompletionService.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class OpenAICompletionService extends BaseService {
}

static IMPLEMENTS = {
['driver-capabilities']: {
supports_test_mode (iface, method_name) {
return iface === 'puter-chat-completion' &&
method_name === 'complete';
}
},
['puter-chat-completion']: {
async list () {
return [
Expand Down
3 changes: 3 additions & 0 deletions src/backend/src/modules/puterai/PuterAIModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class PuterAIModule extends AdvancedBase {

const { FakeChatService } = require('./FakeChatService');
services.registerService('fake-chat', FakeChatService);

const{ AITestModeService } = require('./AITestModeService');
services.registerService('ai-test-mode', AITestModeService);
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/backend/src/services/ShareService.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ class ShareService extends BaseService {
user: issuer_user,
});

const svc_permission = this.services.get('permission');
// const svc_permission = this.services.get('permission');
const svc_acl = this.services.get('acl');

for ( const permission of share.data.permissions ) {
await svc_permission.grant_user_user_permission(
issuer_actor,
user.username,
permission,
await svc_acl.set_user_user(
issuer_actor, user.username, permission, undefined,
{ only_if_higher: true },
);
}

Expand All @@ -96,7 +96,7 @@ class ShareService extends BaseService {
}
});
}

['__on_install.routes'] (_, { app }) {
this.install_sharelink_endpoints({ app });
this.install_share_endpoint({ app });
Expand Down
204 changes: 204 additions & 0 deletions src/backend/src/services/auth/ACLService.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../../api/APIError");
const FSNodeParam = require("../../api/filesystem/FSNodeParam");
const { NodePathSelector } = require("../../filesystem/node/selectors");
const { get_user } = require("../../helpers");
const configurable_auth = require("../../middleware/configurable_auth");
const { Context } = require("../../util/context");
const { Endpoint } = require("../../util/expressutil");
const BaseService = require("../BaseService");
const { AppUnderUserActorType, UserActorType, Actor, SystemActorType, AccessTokenActorType } = require("./Actor");
const { PermissionUtil } = require("./PermissionService");

class ACLService extends BaseService {
static MODULES = {
express: require('express'),
};

async _init () {
const svc_featureFlag = this.services.get('feature-flag');
svc_featureFlag.register('public-folders', {
Expand All @@ -44,6 +52,202 @@ class ACLService extends BaseService {
});
}

async ['__on_install.routes'] (_, { app }) {
const r_acl = (() => {
const require = this.require;
const express = require('express');
return express.Router();
})();

app.use('/acl', r_acl);

Endpoint({
route: '/stat-user-user',
methods: ['POST'],
mw: [configurable_auth()],
handler: async (req, res) => {
// Only user actor is allowed
if ( ! (req.actor.type instanceof UserActorType) ) {
return res.status(403).json({
error: 'forbidden',
});
}

const holder_user = await get_user({
username: req.body.user,
});

if ( ! holder_user ) {
throw APIError.create('user_does_not_exist', null, {
username: req.body.user,
});
}

const issuer = req.actor;
const holder = new Actor({
type: new UserActorType({
user: holder_user,
}),
});

const node = await (new FSNodeParam('path')).consolidate({
req,
getParam: () => req.body.resource,
});

const permissions = await this.stat_user_user(issuer, holder, node);

res.json({ permissions });
}
}).attach(r_acl);

Endpoint({
route: '/set-user-user',
methods: ['POST'],
mw: [configurable_auth()],
handler: async (req, res) => {
// Only user actor is allowed
if ( ! (req.actor.type instanceof UserActorType) ) {
return res.status(403).json({
error: 'forbidden',
});
}

const holder_user = await get_user({
username: req.body.user,
});

if ( ! holder_user ) {
throw APIError.create('user_does_not_exist', null, {
username: req.body.user,
});
}

const issuer = req.actor;
const holder = new Actor({
type: new UserActorType({
user: holder_user,
}),
});

const node = await (new FSNodeParam('path')).consolidate({
req,
getParam: () => req.body.resource,
});

await this.set_user_user(issuer, holder, node, req.body.mode, req.body.options ?? {});

res.json({});
}
}).attach(r_acl);
}

async set_user_user (issuer, holder, resource, mode, options = {}) {
const svc_perm = this.services.get('permission');
const svc_fs = this.services.get('filesystem');

if ( typeof holder === 'string' ) {
const holder_user = await get_user({ username: holder });
if ( ! holder_user ) {
throw APIError.create('user_does_not_exist', null, { username: holder });
}

holder = new Actor({
type: new UserActorType({ user: holder_user }),
});
}

let uid, _;

if ( typeof resource === 'string' && mode === undefined ) {
const perm_parts = PermissionUtil.split(resource);
([_, uid, mode] = perm_parts);
resource = await svc_fs.node(new NodePathSelector(uid));
if ( ! resource ) {
throw APIError.create('subject_does_not_exist');
}
}

if ( ! (issuer.type instanceof UserActorType) ) {
throw new Error('issuer must be a UserActorType');
}
if ( ! (holder.type instanceof UserActorType) ) {
throw new Error('holder must be a UserActorType');
}

const stat = await this.stat_user_user(issuer, holder, resource);

// this.log.info('stat object', {
// stat,
// path: await resource.get('path')
// });

const perms_on_this = stat[await resource.get('path')] ?? [];

const mode_parts = perms_on_this.map(perm => PermissionUtil.split(perm)[2]);

// If mode already present, do nothing
if ( mode_parts.includes(mode) ) {
return false;
}

// If higher mode already present, do nothing
if ( options.only_if_higher ) {
const higher_modes = this._higher_modes(mode);
if ( mode_parts.some(m => higher_modes.includes(m)) ) {
return false;
}
}

uid = uid ?? await resource.get('uid');

// If mode not present, add it
await svc_perm.grant_user_user_permission(
issuer, holder.type.user.username,
PermissionUtil.join('fs', uid, mode),
);

// Remove other modes
for ( const perm of perms_on_this ) {
const perm_parts = PermissionUtil.split(perm);
if ( perm_parts[2] === mode ) continue;

await svc_perm.revoke_user_user_permission(
issuer, holder.type.user.username,
perm,
);
}
}

async stat_user_user (issuer, holder, resource) {
const svc_perm = this.services.get('permission');

if ( ! (issuer.type instanceof UserActorType) ) {
throw new Error('issuer must be a UserActorType');
}
if ( ! (holder.type instanceof UserActorType) ) {
throw new Error('holder must be a UserActorType');
}

const permissions = {};

let perm_fsNode = resource;
while ( ! await perm_fsNode.get('is-root') ) {
const prefix = PermissionUtil.join('fs', await perm_fsNode.get('uid'));

const these_permissions = await
svc_perm.query_issuer_holder_permissions_by_prefix(issuer, holder, prefix);

if ( these_permissions.length > 0 ) {
permissions[await perm_fsNode.get('path')] = these_permissions;
}

perm_fsNode = await perm_fsNode.getParent();
}

return permissions;
}

async _check_fsNode (actor, fsNode, mode) {
const context = Context.get();

Expand Down
32 changes: 32 additions & 0 deletions src/backend/src/services/auth/PermissionService.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ class PermissionService extends BaseService {
}

async scan (actor, permission_options, _reserved, state) {
if ( ! state ) this.log.info('scan', {
actor: actor.uid,
permission_options,
});
const reading = [];

if ( ! state ) {
Expand Down Expand Up @@ -634,6 +638,34 @@ class PermissionService extends BaseService {

return retval;
}

/**
* List the permissions that the specified actor (the "issuer")
* has granted to the specified user (the "holder") which have
* some specified prefix in the permission key (ex: "fs:FILE-UUID")
*
* Note that if the prefix contains a literal '%' character
* the behavior may not be as expected.
*
* This is a "flat" (non-cascading) view.
*
* @param {*} issuer
* @param {*} holder
* @param {*} prefix
* @returns
*/
async query_issuer_holder_permissions_by_prefix (issuer, holder, prefix) {
const user_perms = await this.db.read(
'SELECT permission ' +
'FROM `user_to_user_permissions` ' +
'WHERE issuer_user_id = ? ' +
'AND holder_user_id = ? ' +
'AND permission LIKE ?',
[issuer.type.user.id, holder.type.user.id, prefix + '%'],
);

return user_perms.map(row => row.permission);
}

async get_higher_permissions (permission) {
const higher_perms = new Set()
Expand Down
Loading

0 comments on commit 546a268

Please sign in to comment.