Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions src/backend/src/routers/auth/grant-user-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
*/
const APIError = require("../../api/APIError");
const eggspress = require("../../api/eggspress");
const { UserActorType } = require("../../services/auth/Actor");
const { UserActorType, AppUnderUserActorType } = require("../../services/auth/Actor");
const { PermissionUtil } = require("../../services/auth/PermissionService");
const { Context } = require("../../util/context");

module.exports = eggspress('/auth/grant-user-group', {
Expand All @@ -31,8 +32,21 @@ module.exports = eggspress('/auth/grant-user-group', {

// Only users can grant user-group permissions
const actor = Context.get('actor');
if ( ! (actor.type instanceof UserActorType) ) {
throw APIError.create('forbidden');

// Check for permission to configure permissions
{
if ( ! (
actor.type instanceof UserActorType ||
actor.type instanceof AppUnderUserActorType
) ) throw APIError.create('forbidden');

const perm = PermissionUtil.join(
'permission', 'config', actor.type.user.uuid,
...PermissionUtil.split(req.body.permission),
);
if ( ! await svc_permission.check(actor, perm) ) {
throw APIError.create('forbidden');
}
}

if ( ! req.body.group_uid ) {
Expand Down
21 changes: 17 additions & 4 deletions src/backend/src/routers/auth/grant-user-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
*/
const APIError = require("../../api/APIError");
const eggspress = require("../../api/eggspress");
const { UserActorType } = require("../../services/auth/Actor");
const { UserActorType, AppUnderUserActorType } = require("../../services/auth/Actor");
const { PermissionUtil } = require("../../services/auth/PermissionService");
const { Context } = require("../../util/context");

module.exports = eggspress('/auth/grant-user-user', {
Expand All @@ -29,10 +30,22 @@ module.exports = eggspress('/auth/grant-user-user', {
const x = Context.get();
const svc_permission = x.get('services').get('permission');

// Only users can grant user-user permissions
const actor = Context.get('actor');
if ( ! (actor.type instanceof UserActorType) ) {
throw APIError.create('forbidden');

// Check for permission to configure permissions
{
if ( ! (
actor.type instanceof UserActorType ||
actor.type instanceof AppUnderUserActorType
) ) throw APIError.create('forbidden');

const perm = PermissionUtil.join(
'permission', 'config', actor.type.user.uuid,
...PermissionUtil.split(req.body.permission),
);
if ( ! await svc_permission.check(actor, perm) ) {
throw APIError.create('forbidden');
}
}

if ( ! req.body.target_username ) {
Expand Down
21 changes: 17 additions & 4 deletions src/backend/src/routers/auth/revoke-user-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
*/
const APIError = require("../../api/APIError");
const eggspress = require("../../api/eggspress");
const { UserActorType } = require("../../services/auth/Actor");
const { UserActorType, AppUnderUserActorType } = require("../../services/auth/Actor");
const { PermissionUtil } = require("../../services/auth/PermissionService");
const { Context } = require("../../util/context");

module.exports = eggspress('/auth/revoke-user-group', {
Expand All @@ -29,10 +30,22 @@ module.exports = eggspress('/auth/revoke-user-group', {
const x = Context.get();
const svc_permission = x.get('services').get('permission');

// Only users can grant user-user permissions
const actor = Context.get('actor');
if ( ! (actor.type instanceof UserActorType) ) {
throw APIError.create('forbidden');

// Check for permission to configure permissions
{
if ( ! (
actor.type instanceof UserActorType ||
actor.type instanceof AppUnderUserActorType
) ) throw APIError.create('forbidden');

const perm = PermissionUtil.join(
'permission', 'config', actor.type.user.uuid,
...PermissionUtil.split(req.body.permission),
);
if ( ! await svc_permission.check(actor, perm) ) {
throw APIError.create('forbidden');
}
}

if ( ! req.body.group_uid ) {
Expand Down
21 changes: 17 additions & 4 deletions src/backend/src/routers/auth/revoke-user-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
*/
const APIError = require("../../api/APIError");
const eggspress = require("../../api/eggspress");
const { UserActorType } = require("../../services/auth/Actor");
const { UserActorType, AppUnderUserActorType } = require("../../services/auth/Actor");
const { PermissionUtil } = require("../../services/auth/PermissionService");
const { Context } = require("../../util/context");

module.exports = eggspress('/auth/revoke-user-user', {
Expand All @@ -29,10 +30,22 @@ module.exports = eggspress('/auth/revoke-user-user', {
const x = Context.get();
const svc_permission = x.get('services').get('permission');

// Only users can grant user-user permissions
const actor = Context.get('actor');
if ( ! (actor.type instanceof UserActorType) ) {
throw APIError.create('forbidden');

// Check for permission to configure permissions
{
if ( ! (
actor.type instanceof UserActorType ||
actor.type instanceof AppUnderUserActorType
) ) throw APIError.create('forbidden');

const perm = PermissionUtil.join(
'permission', 'config', actor.type.user.uuid,
...PermissionUtil.split(req.body.permission),
);
if ( ! await svc_permission.check(actor, perm) ) {
throw APIError.create('forbidden');
}
}

if ( ! req.body.target_username ) {
Expand Down
77 changes: 75 additions & 2 deletions src/backend/src/services/auth/ACLService.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
*/
const APIError = require("../../api/APIError");
const FSNodeParam = require("../../api/filesystem/FSNodeParam");
const { NodePathSelector } = require("../../filesystem/node/selectors");
const { NodePathSelector, NodeUIDSelector } = 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");
const { PermissionUtil, PermissionImplicator } = require("./PermissionService");


/**
Expand Down Expand Up @@ -58,6 +58,79 @@ class ACLService extends BaseService {
$: 'config-flag',
value: this.global_config.enable_public_folders ?? false,
});

const svc_fs = this.services.get('filesystem');

const svc_permission = this.services.get('permission');
svc_permission.register_implicator(PermissionImplicator.create({
id: 'apps-can-config-perms-on-appdata',
matcher: permission => {
console.log('\x1B[32;1m 1245124512451245', permission);
return permission.startsWith('permission:config:');
},
checker: async ({ actor, permission }) => {
if ( !(actor.type instanceof AppUnderUserActorType) ) {
return undefined;
}

const [_0, _1, userUUID, ...innerPerm] = PermissionUtil.split(permission);

// The current actor must be the permission issuer of the
// permission being configured, otherwise this implicator
// is not applicable.
if ( actor.type.user.uuid !== userUUID ) {
return undefined;
}


// For this permission implicator we're only concerned with
// `fs:`-scoped permissions that specify a node.
if ( innerPerm.length < 2 || innerPerm[0] !== 'fs' ) {
return undefined;
}

const node = await svc_fs.node(
innerPerm[1].startsWith('/')
? new NodePathSelector(innerPerm[1])
: new NodeUIDSelector(innerPerm[1])
);

if ( ! await node.exists() ) {
return undefined;
}

const owner_id = await node.get('user_id');

// These conditions should never happen
if ( ! owner_id || ! actor.type.user.id ) {
throw new Error(
'something unexpected happened'
);
}

// If this file isn't owner by the current user, we're going to
// play it safe and not involve this permission implicator.
if ( owner_id !== actor.type.user.id ) {
return undefined;
}

// Is this file in the current app's AppData subdirectory?
// If it is, the app is allowed to configure permissions on it
const appdata_path = `/${actor.type.user.username}/AppData/${actor.type.app.uid}`;
const appdata_node = await svc_fs.node(new NodePathSelector(appdata_path));

if (
await appdata_node.exists() && (
await appdata_node.is(node) ||
await appdata_node.is_above(node)
)
) {
return true;
}

return undefined;
},
}));
}
/**
* Checks if an actor has permission to perform a specific mode of access on a resource
Expand Down
20 changes: 20 additions & 0 deletions src/backend/src/services/auth/PermissionService.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,26 @@ class PermissionService extends BaseService {
async _init () {
this.db = this.services.get('database').get(DB_WRITE, 'permissions');
this._register_commands(this.services.get('commands'));

// Register permission implicator for permission to configure permissions
// (yes, this part is incredibly meta)
const svc_permission = this;
svc_permission.register_implicator(PermissionImplicator.create({
id: 'user-can-configure-perms-from-self',
matcher: permission => {
return permission.startsWith('permission:config:');
},
checker: async ({ actor, permission }) => {
// This permission is only implied to user actors
if ( ! (actor.type instanceof UserActorType) ) return undefined;

// Granted if user UUID for "from" user matches
const [_0, _1, fromUserUID] = PermissionUtil.split(permission);
if ( fromUserUID === actor.type.user.uuid ) return {};

return undefined;
}
}))
}


Expand Down