feat(roles): storing & enforcing roles
This commit is contained in:
@@ -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 )
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 )
|
||||
}
|
||||
|
||||
@@ -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 ` )
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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' )
|
||||
}
|
||||
}
|
||||
@@ -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 )
|
||||
|
||||
@@ -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 ( ) => {
|
||||
|
||||
@@ -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 )
|
||||
} )
|
||||
|
||||
+20
-15
@@ -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 = {
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
Reference in New Issue
Block a user