From bd5d7436f6bce2c425edfbed3da47cb6b9184c71 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Wed, 20 May 2020 18:30:04 +0100 Subject: [PATCH] feat(roles): storing & enforcing roles --- app.js | 4 +-- modules/core/graph/resolvers/apitoken.js | 6 ++-- modules/core/graph/resolvers/objects.js | 16 ++++----- modules/core/graph/resolvers/streams.js | 10 +++--- modules/core/migrations/000-core.js | 35 +++++++------------ .../core/migrations/001-2020-05-18-roles.js | 8 ++--- modules/core/services/streams.js | 35 +++++++++++-------- modules/core/services/tokens.js | 4 +-- modules/core/tests/graph.spec.js | 24 ++++++------- modules/core/tests/streams.spec.js | 6 ++-- modules/core/tests/users.spec.js | 1 - modules/shared/index.js | 35 +++++++++++-------- package.json | 2 ++ 13 files changed, 95 insertions(+), 91 deletions(-) diff --git a/app.js b/app.js index 8cd1a5a81..00de29f1d 100644 --- a/app.js +++ b/app.js @@ -4,7 +4,7 @@ let http = require( 'http' ) const express = require( 'express' ) const logger = require( 'morgan-debug' ) const bodyParser = require( 'body-parser' ) -const debug = require( 'debug' )( 'speckle:errors' ) +const debug = require( 'debug' )( 'speckle:generic' ) const { ApolloServer } = require( 'apollo-server-express' ) const { contextApiTokenHelper } = require( './modules/shared' ) @@ -60,7 +60,7 @@ exports.startHttp = async ( app ) => { let server = http.createServer( app ) server.on( 'listening', ( ) => { - console.log( `Listening on ${server.address().port}` ) + debug( `Listening on ${server.address().port}` ) } ) server.listen( port ) diff --git a/modules/core/graph/resolvers/apitoken.js b/modules/core/graph/resolvers/apitoken.js index 82a688896..c361cb636 100644 --- a/modules/core/graph/resolvers/apitoken.js +++ b/modules/core/graph/resolvers/apitoken.js @@ -2,7 +2,7 @@ const root = require( 'app-root-path' ) const { AuthorizationError, ApolloError } = require( 'apollo-server-express' ) -const { createToken, revokeToken, revokeTokenById, validateToken, getUserTokens } = require( '../../services/users' ) +const { createToken, revokeToken, revokeTokenById, validateToken, getUserTokens } = require( '../../services/tokens' ) const { validateScopes, authorizeResolver } = require( `${root}/modules/shared` ) module.exports = { @@ -13,11 +13,11 @@ module.exports = { }, Mutation: { async apiTokenCreate( parent, args, context, info ) { - await validateScopes( context.scopes, 'tokens:create' ) + await validateScopes( context.scopes, 'tokens:write' ) return await createToken( context.userId, args.name, args.scopes, args.lifespan ) }, async apiTokenRevoke( parent, args, context, info ) { - await validateScopes( context.scopes, 'tokens:delete' ) + await validateScopes( context.scopes, 'tokens:write' ) await revokeToken( args.token.split( ' ' )[ 1 ], context.userId ) // let's not revoke other people's tokens return true } diff --git a/modules/core/graph/resolvers/objects.js b/modules/core/graph/resolvers/objects.js index 0ea9611cf..0787679cc 100644 --- a/modules/core/graph/resolvers/objects.js +++ b/modules/core/graph/resolvers/objects.js @@ -69,56 +69,56 @@ module.exports = { Mutation: { async objectCreate( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'write' ) + await authorizeResolver( context.userId, args.streamId, 'stream:contributor' ) let ids = await createObjects( args.objects ) return ids }, async commitCreate( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'write' ) + await authorizeResolver( context.userId, args.streamId, 'stream:contributor' ) let id = await createCommit( args.streamId, context.userId, args.commit ) return id }, async branchCreate( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'write' ) + await authorizeResolver( context.userId, args.streamId, 'stream:contributor' ) let id = await createBranch( args.branch, args.streamId, context.userId ) return id }, async branchUpdate( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'write' ) + await authorizeResolver( context.userId, args.streamId, 'stream:contributor' ) await updateBranch( args.branch ) return true }, async branchDelete( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'write' ) + await authorizeResolver( context.userId, args.streamId, 'stream:contributor' ) await deleteBranchById( args.branchId ) return true }, async tagCreate( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'write' ) + await authorizeResolver( context.userId, args.streamId, 'stream:contributor' ) let id = await createTag( args.tag, args.streamId, context.userId ) return id }, async tagUpdate( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'write' ) + await authorizeResolver( context.userId, args.streamId, 'stream:contributor' ) await updateTag( args.tag ) return true }, async tagDelete( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'write' ) + await authorizeResolver( context.userId, args.streamId, 'stream:contributor' ) await deleteTagById( args.tagId ) return true diff --git a/modules/core/graph/resolvers/streams.js b/modules/core/graph/resolvers/streams.js index df0fc8647..73f4b9573 100644 --- a/modules/core/graph/resolvers/streams.js +++ b/modules/core/graph/resolvers/streams.js @@ -8,7 +8,7 @@ module.exports = { Query: { async stream( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:read' ) - await authorizeResolver( context.userId, args.id, 'stream_acl', 'streams', 'read' ) + await authorizeResolver( context.userId, args.id, 'stream:reviewer' ) let stream = await getStream( args.id, context.userId ) return stream @@ -38,13 +38,13 @@ module.exports = { }, async streamUpdate( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.stream.id, 'stream_acl', 'streams', 'owner' ) + await authorizeResolver( context.userId, args.stream.id, 'stream:owner' ) await updateStream( args.stream ) return true }, async streamDelete( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.id, 'stream_acl', 'streams', 'owner' ) + await authorizeResolver( context.userId, args.id, 'stream:owner' ) await deleteStream( args.id ) return true @@ -54,13 +54,13 @@ module.exports = { }, async streamGrantPermission( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'owner' ) + await authorizeResolver( context.userId, args.streamId, 'stream:owner' ) if ( context.userId === args.userId ) throw new AuthorizationError( 'You cannot set roles for yourself.' ) return await grantPermissionsStream( args.streamId, args.userId, args.role.toLowerCase( ) || 'read' ) }, async streamRevokePermission( parent, args, context, info ) { await validateScopes( context.scopes, 'streams:write' ) - await authorizeResolver( context.userId, args.streamId, 'stream_acl', 'streams', 'owner' ) + await authorizeResolver( context.userId, args.streamId, 'stream:owner' ) return await revokePermissionsStream( args.streamId, args.userId ) } diff --git a/modules/core/migrations/000-core.js b/modules/core/migrations/000-core.js index 5b4fb4a63..3a1774cc6 100644 --- a/modules/core/migrations/000-core.js +++ b/modules/core/migrations/000-core.js @@ -26,7 +26,6 @@ exports.up = async knex => { table.string( 'owner', 10 ).references( 'id' ).inTable( 'users' ).notNullable( ) table.string( 'name' ) table.string( 'lastChars', 6 ) - // table.specificType( 'scopes', 'text[]' ) table.boolean( 'revoked' ).defaultTo( false ) table.bigint( 'lifespan' ).defaultTo( 3.154e+12 ) // defaults to a lifespan of 100 years table.timestamp( 'createdAt' ).defaultTo( knex.fn.now( ) ) @@ -46,13 +45,6 @@ exports.up = async knex => { table.index( [ 'tokenId', 'scopeName' ], 'token_scope_combined_idx' ) } ) - await knex.schema.createTable( 'user_roles', table => { - table.string( 'name' ).notNullable( ) - table.text( 'description' ).notNullable( ) - table.string( 'resourceTarget' ).notNullable( ) - table.string( 'aclTableName' ).notNullable( ) - table.integer( 'weight' ).defaultTo( 100 ).notNullable( ) - } ) // Streams Table await knex.schema.createTable( 'streams', table => { @@ -65,23 +57,22 @@ exports.up = async knex => { table.timestamp( 'updatedAt' ).defaultTo( knex.fn.now( ) ) } ) - // creates an enum type for stream acl roles. - await knex.raw( ` - DO $$ - BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'speckle_acl_role_type') THEN - CREATE TYPE speckle_acl_role_type AS ENUM( 'owner', 'admin', 'write', 'read' ); - END IF; - END$$; - ` ) + // Roles + await knex.schema.createTable( 'user_roles', table => { + table.string( 'name' ).primary() + table.text( 'description' ).notNullable( ) + table.string( 'resourceTarget' ).notNullable( ) + table.string( 'aclTableName' ).notNullable( ) + table.integer( 'weight' ).defaultTo( 100 ).notNullable( ) + } ) // Stream-users access control list. await knex.schema.createTable( 'stream_acl', table => { table.string( 'userId', 10 ).references( 'id' ).inTable( 'users' ).notNullable( ).onDelete( 'cascade' ) table.string( 'resourceId', 10 ).references( 'id' ).inTable( 'streams' ).notNullable( ).onDelete( 'cascade' ) + table.string( 'role' ).references( 'name' ).inTable( 'user_roles' ).notNullable( ).onDelete( 'cascade' ) table.primary( [ 'userId', 'resourceId' ] ) table.unique( [ 'userId', 'resourceId' ] ) - table.specificType( 'role', 'speckle_acl_role_type' ).defaultTo( 'write' ) } ) // Objects Table. @@ -159,21 +150,21 @@ exports.up = async knex => { exports.down = async knex => { await knex.schema.dropTableIfExists( 'stream_acl' ) await knex.schema.dropTableIfExists( 'user_roles' ) - + await knex.schema.dropTableIfExists( 'stream_commits' ) await knex.schema.dropTableIfExists( 'branch_commits' ) await knex.schema.dropTableIfExists( 'user_commits' ) await knex.schema.dropTableIfExists( 'references' ) await knex.schema.dropTableIfExists( 'object_children_closure' ) - + await knex.schema.dropTableIfExists( 'objects' ) await knex.schema.dropTableIfExists( 'streams' ) - + await knex.schema.dropTableIfExists( 'token_scopes' ) await knex.schema.dropTableIfExists( 'app_scopes' ) await knex.schema.dropTableIfExists( 'api_tokens' ) await knex.schema.dropTableIfExists( 'users' ) - + await knex.raw( `DROP TYPE IF EXISTS speckle_reference_type ` ) await knex.raw( `DROP TYPE IF EXISTS speckle_acl_role_type ` ) } \ No newline at end of file diff --git a/modules/core/migrations/001-2020-05-18-roles.js b/modules/core/migrations/001-2020-05-18-roles.js index 1cfec3b2a..a3755933a 100644 --- a/modules/core/migrations/001-2020-05-18-roles.js +++ b/modules/core/migrations/001-2020-05-18-roles.js @@ -5,19 +5,19 @@ exports.up = async knex => { debug( 'Setting up core module scopes.' ) let streamRoles = [ { - name: 'owner', + name: 'stream:owner', description: 'Has full access, including deletion rights & access control.', resourceTarget: 'streams', aclTableName: 'stream_acl', weight: 1000 }, { - name: 'contributor', + name: 'stream:contributor', description: 'Can edit, push and pull.', resourceTarget: 'streams', aclTableName: 'stream_acl', - weight: 200 + weight: 500 }, { - name: 'reviewer', + name: 'stream:reviewer', description: 'Can only view.', resourceTarget: 'streams', aclTableName: 'stream_acl', diff --git a/modules/core/services/streams.js b/modules/core/services/streams.js index ca11549c8..6559b19ce 100644 --- a/modules/core/services/streams.js +++ b/modules/core/services/streams.js @@ -14,7 +14,7 @@ module.exports = { stream.id = crs( { length: 10 } ) let [ res ] = await Streams( ).returning( 'id' ).insert( stream ) - await Acl( ).insert( { userId: ownerId, resourceId: res, role: 'owner' } ) + await Acl( ).insert( { userId: ownerId, resourceId: res, role: 'stream:owner' } ) return res }, @@ -37,13 +37,6 @@ module.exports = { }, async grantPermissionsStream( streamId, userId, role ) { - // NOTE: allow streams to have more than one owner. - // That's why the code below is commented out. - // if ( role === 'owner' ) { - // let [ ownerAcl ] = await Acl( ).where( { resourceId: streamId, role: 'owner' } ).returning( '*' ).del( ) - // await Acl( ).insert( { resourceId: streamId, userId: ownerAcl.userId, role: 'write' } ) - // } - // upsert let query = Acl( ).insert( { userId: userId, resourceId: streamId, role: role } ).toString( ) + ` on conflict on constraint stream_acl_pkey do update set role=excluded.role` @@ -54,17 +47,31 @@ module.exports = { async revokePermissionsStream( streamId, userId ) { let streamAclEntriesCount = Acl( ).count( { resourceId: streamId } ) // TODO: check if streamAclEntriesCount === 1 then throw big boo-boo (can't delete last ownership link) - - if( streamAclEntriesCount === 1 ) + + if ( streamAclEntriesCount === 1 ) throw new Error( 'Stream has only one ownership link left - cannot revoke permissions.' ) // TODO: below behaviour not correct. Flow: // Count owners // If owner count > 1, then proceed to delete, otherwise throw an error (can't delete last owner - delete stream) - let delCount = await Acl( ).where( { resourceId: streamId, userId: userId } ).whereNot( { role: 'owner' } ).del( ) + + let aclEntry = await Acl( ).where( { resourceId: streamId, userId: userId } ).select( '*' ).first( ) + + if ( aclEntry.role === 'stream:owner' ) { + let ownersCount = Acl( ).count( { resourceId: streamId, role: 'stream:owner' } ) + if ( ownersCount === 1 ) + throw new Error( 'Could not revoke permissions for user' ) + else { + await Acl( ).where( { resourceId: streamId, userId: userId } ).del() + return true + } + } + + let delCount = await Acl( ).where( { resourceId: streamId, userId: userId } ).del( ) if ( delCount === 0 ) - throw new Error( 'Could not revoke permissions for user. Is he an owner?' ) + throw new Error( 'Could not revoke permissions for user' ) + return true }, @@ -84,9 +91,9 @@ module.exports = { offset = offset || 0 limit = limit || 100 publicOnly = publicOnly !== false //defaults to true if not provided - + let query = { userId: userId } - + if ( publicOnly ) query.isPublic = true return Acl( ).where( query ) diff --git a/modules/core/services/tokens.js b/modules/core/services/tokens.js index 5158d9bd3..3d581ed83 100644 --- a/modules/core/services/tokens.js +++ b/modules/core/services/tokens.js @@ -57,7 +57,8 @@ module.exports = { if ( valid ) { await Keys( ).where( { id: tokenId } ).update( { lastUsed: knex.fn.now( ) } ) - return { valid: true, userId: token.owner, scopes: token.scopes } + let scopes = await TokenScopes( ).select( 'scopeName' ).where( { tokenId: tokenId } ) + return { valid: true, userId: token.owner, scopes: scopes.map( s => s.scopeName ) } } else return { valid: false } }, @@ -85,6 +86,5 @@ module.exports = { WHERE t."owner" = ? `, [ userId ] ) return rows - // return Keys( ).where( { owner: userId } ).select( 'id', 'name', 'lastChars', 'createdAt', 'lastUsed' ).rightJoin( 'token_scopes', 'id', '=', 'token_scopes.tokenId' ) } } \ No newline at end of file diff --git a/modules/core/tests/graph.spec.js b/modules/core/tests/graph.spec.js index ab40c42a9..67a177ff8 100644 --- a/modules/core/tests/graph.spec.js +++ b/modules/core/tests/graph.spec.js @@ -11,7 +11,8 @@ chai.use( chaiHttp ) const knex = require( `${root}/db/knex` ) -const { createUser, createToken } = require( '../services/users' ) +const { createUser } = require( '../services/users' ) +const { createToken } = require( '../services/tokens' ) const { createObject, createObjects } = require( '../services/objects' ) let addr @@ -31,11 +32,11 @@ describe( 'GraphQL API Core', ( ) => { testServer = server userA.id = await createUser( userA ) - userA.token = `Bearer ${(await createToken( userA.id, 'test token user A', [ 'streams:read', 'streams:write', 'users:read', 'users:email', 'tokens:create', 'tokens:read', 'tokens:delete' ] ))}` + userA.token = `Bearer ${(await createToken( userA.id, 'test token user A', [ 'streams:read', 'streams:write', 'users:read', 'users:email', 'tokens:write', 'tokens:read' ] ))}` userB.id = await createUser( userB ) - userB.token = `Bearer ${(await createToken( userB.id, 'test token user B', [ 'streams:read', 'streams:write', 'users:read', 'users:email', 'tokens:create', 'tokens:read', 'tokens:delete' ] ))}` + userB.token = `Bearer ${(await createToken( userB.id, 'test token user B', [ 'streams:read', 'streams:write', 'users:read', 'users:email', 'tokens:write', 'tokens:read' ] ))}` userC.id = await createUser( userC ) - userC.token = `Bearer ${(await createToken( userC.id, 'test token user B', [ 'streams:read', 'streams:write', 'users:read', 'users:email', 'tokens:create', 'tokens:read', 'tokens:delete' ] ))}` + userC.token = `Bearer ${(await createToken( userC.id, 'test token user B', [ 'streams:read', 'streams:write', 'users:read', 'users:email', 'tokens:write', 'tokens:read' ] ))}` addr = `http://localhost:${process.env.PORT || 3000}` } ) @@ -148,33 +149,32 @@ describe( 'GraphQL API Core', ( ) => { } ) it( 'Should grant some permissions', async ( ) => { - const res = await sendRequest( userA.token, { query: `mutation{ streamGrantPermission( streamId: "${ts1}", userId: "${userB.id}" role: WRITE) }` } ) - + const res = await sendRequest( userA.token, { query: `mutation{ streamGrantPermission( streamId: "${ts1}", userId: "${userB.id}" role: "stream:owner") }` } ) + expect( res ).to.be.json expect( res.body.errors ).to.not.exist expect( res.body.data.streamGrantPermission ).to.equal( true ) - const res2 = await sendRequest( userB.token, { query: `mutation{ streamGrantPermission( streamId: "${ts5}", userId: "${userA.id}" role: WRITE) }` } ) - const res3 = await sendRequest( userB.token, { query: `mutation{ streamGrantPermission( streamId: "${ts3}", userId: "${userC.id}" role: WRITE) }` } ) + const res2 = await sendRequest( userB.token, { query: `mutation{ streamGrantPermission( streamId: "${ts5}", userId: "${userA.id}" role: "stream:owner") }` } ) + const res3 = await sendRequest( userB.token, { query: `mutation{ streamGrantPermission( streamId: "${ts3}", userId: "${userC.id}" role: "stream:owner") }` } ) } ) it( 'Should fail to grant permissions if not owner', async ( ) => { - const res = await sendRequest( userB.token, { query: `mutation{ streamGrantPermission( streamId: "${ts1}", userId: "${userB.id}" role: WRITE) }` } ) + const res = await sendRequest( userB.token, { query: `mutation{ streamGrantPermission( streamId: "${ts1}", userId: "${userB.id}" role: "stream:owner") }` } ) expect( res ).to.be.json expect( res.body.errors ).to.exist } ) it( 'Should fail to grant myself permissions', async ( ) => { - const res = await sendRequest( userA.token, { query: `mutation{ streamGrantPermission( streamId: "${ts1}", userId: "${userA.id}" role: WRITE) }` } ) + const res = await sendRequest( userA.token, { query: `mutation{ streamGrantPermission( streamId: "${ts1}", userId: "${userA.id}" role: "stream:owner") }` } ) expect( res ).to.be.json expect( res.body.errors ).to.exist } ) it( 'Should update permissions', async ( ) => { - const res = await sendRequest( userA.token, { query: `mutation{ streamGrantPermission( streamId: "${ts1}", userId: "${userB.id}" role: READ) }` } ) - + const res = await sendRequest( userA.token, { query: `mutation{ streamGrantPermission( streamId: "${ts1}", userId: "${userB.id}" role: "stream:reviewer") }` } ) expect( res ).to.be.json expect( res.body.errors ).to.not.exist expect( res.body.data.streamGrantPermission ).to.equal( true ) diff --git a/modules/core/tests/streams.spec.js b/modules/core/tests/streams.spec.js index 1da158024..8c585edd8 100644 --- a/modules/core/tests/streams.spec.js +++ b/modules/core/tests/streams.spec.js @@ -93,15 +93,15 @@ describe( 'Streams', ( ) => { } ) it( 'Should share a stream with a user', async ( ) => { - await grantPermissionsStream( testStream.id, userTwo.id, 'read' ) - await grantPermissionsStream( testStream.id, userTwo.id, 'write' ) // change perms + await grantPermissionsStream( testStream.id, userTwo.id, 'stream:reviewer' ) + await grantPermissionsStream( testStream.id, userTwo.id, 'stream:contributor' ) // change perms } ) it( 'Stream should show up in the other users` list', async ( ) => { let userTwoStreams = await getUserStreams( userTwo.id ) expect( userTwoStreams ).to.have.lengthOf( 1 ) expect( userTwoStreams[ 0 ] ).to.have.property( 'role' ) - expect( userTwoStreams[ 0 ].role ).to.equal( 'write' ) + expect( userTwoStreams[ 0 ].role ).to.equal( 'stream:contributor' ) } ) it( 'Should get the users with access to a stream', async ( ) => { diff --git a/modules/core/tests/users.spec.js b/modules/core/tests/users.spec.js index b5923eac8..0f208357d 100644 --- a/modules/core/tests/users.spec.js +++ b/modules/core/tests/users.spec.js @@ -138,7 +138,6 @@ describe( 'Actors & Tokens', ( ) => { it( 'Should get the tokens of an user', async ( ) => { let userTokens = await getUserTokens( myTestActor.id ) - console.log( userTokens ) expect( userTokens ).to.be.an( 'array' ) expect( userTokens ).to.have.lengthOf( 2 ) } ) diff --git a/modules/shared/index.js b/modules/shared/index.js index 9147f7061..59469c1cc 100644 --- a/modules/shared/index.js +++ b/modules/shared/index.js @@ -3,7 +3,7 @@ const { ForbiddenError, ApolloError } = require( 'apollo-server-express' ) const debug = require( 'debug' )( 'speckle:middleware' ) const root = require( 'app-root-path' ) const knex = require( `${root}/db/knex` ) -const { validateToken } = require( `${root}/modules/core/services/users` ) +const { validateToken } = require( `${root}/modules/core/services/tokens` ) /* @@ -46,31 +46,36 @@ async function validateScopes( scopes, scope ) { */ -let roles = { admin: 1000, owner: 300, write: 200, read: 100 } +let roles -async function authorizeResolver( userId, resourceId, aclTable, resourceTable, requiredRole ) { - let ACL = ( ) => knex( aclTable ) - let Resource = ( ) => knex( resourceTable ) +async function authorizeResolver( userId, resourceId, requiredRole ) { + if ( !roles ) + roles = await knex( 'user_roles' ).select( '*' ) + + // TODO: Cache these results with a TTL of 1 mins or so, it's pointless to query the db every time we get a ping. + + let role = roles.find( r => r.name === requiredRole ) + + if ( role === undefined || role === null ) throw new ApolloError( 'Unknown role: ' + requiredRole ) try { - let { isPublic } = await Resource( ).where( { id: resourceId } ).select( 'isPublic' ).first( ) - // only return here if it's a read operation: weight < 200. + let { isPublic } = await knex( role.resourceTarget ).select( 'isPublic' ).where( { id: resourceId } ).first( ) if ( isPublic && roles[ requiredRole ] < 200 ) return true } catch ( e ) { - throw new ApolloError( `Resource of type ${resourceTable} with ${resourceId} not found.` ) + throw new ApolloError( `Resource of type ${resourceTable} with ${resourceId} not found` ) } - if ( !userId ) throw new AuthenticationError( 'No user id found.' ) + let entry = await knex( role.aclTableName ).select( '*' ).where( { resourceId: resourceId, userId: userId } ).first( ) - let [ entry ] = await ACL( ).where( { resourceId: resourceId, userId: userId } ).select( '*' ) + if ( !entry ) throw new ForbiddenError( 'You are not authorized' ) - if ( !entry ) - throw new ForbiddenError( 'You are not authorized.' ) + entry.role = roles.find( r => r.name === entry.role ) - if ( roles[ entry.role ] >= roles[ requiredRole ] ) { + if ( entry.role.weight >= role.weight ) return true - } - throw new ForbiddenError( 'You are not authorized.' ) + else + throw new ForbiddenError( 'You are not authorized' ) + } module.exports = { diff --git a/package.json b/package.json index 7daa155aa..5cae97162 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "test-watch": "PORT=3001 DEBUG=speckle:test,speckle:errors NODE_ENV=test mocha --watch -s 0 --exit", "test-graph": "PORT=3001 DEBUG=speckle:test,speckle:errors NODE_ENV=test mocha ./modules/core/tests/graph.spec.js --watch -s 0 --exit --no-config", "test-objects": "PORT=3001 DEBUG=speckle:test,speckle:errors NODE_ENV=test mocha ./modules/core/tests/objects.spec.js --watch -s 0 --exit --no-config", + "test-streams": "PORT=3001 DEBUG=speckle:test,speckle:errors NODE_ENV=test mocha ./modules/core/tests/streams.spec.js --watch -s 0 --exit --no-config", + "test-references": "PORT=3001 DEBUG=speckle:test,speckle:errors NODE_ENV=test mocha ./modules/core/tests/references.spec.js --watch -s 0 --exit --no-config", "test-users": "PORT=3001 DEBUG=speckle:test,speckle:errors NODE_ENV=test mocha ./modules/core/tests/users.spec.js --watch -s 0 --exit --no-config" }, "author": "",