610 lines
22 KiB
Ruby
610 lines
22 KiB
Ruby
require 'spec_helper_min'
|
|
require 'helpers/database_connection_helper'
|
|
|
|
module Carto
|
|
describe OauthAccessToken do
|
|
include_context 'organization with users helper'
|
|
include DatabaseConnectionHelper
|
|
|
|
describe '#validation' do
|
|
before(:all) do
|
|
@user = FactoryGirl.create(:valid_user)
|
|
@carto_user = Carto::User.find(@user.id)
|
|
@app = FactoryGirl.create(:oauth_app, user: @carto_user)
|
|
@app_user = OauthAppUser.create!(user: @carto_user, oauth_app: @app)
|
|
@user_table = FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @carto_user.id)
|
|
end
|
|
|
|
after(:all) do
|
|
@user.destroy
|
|
@app.destroy
|
|
@user_table.destroy
|
|
end
|
|
|
|
it 'does not accept invalid scopes' do
|
|
access_token = OauthAccessToken.new(oauth_app_user: @app_user, scopes: ['wadus'])
|
|
expect(access_token).to_not(be_valid)
|
|
expect(access_token.errors[:scopes]).to(include("contains unsupported scopes: wadus"))
|
|
end
|
|
|
|
it 'does accept create tables in schema scopes' do
|
|
access_token = OauthAccessToken.new(oauth_app_user: @app_user, scopes: ["schemas:c"])
|
|
expect(access_token).to(be_valid)
|
|
end
|
|
|
|
it 'does accept read datasets scopes' do
|
|
access_token = OauthAccessToken.new(oauth_app_user: @app_user, scopes: ["datasets:r:#{@user_table.name}"])
|
|
expect(access_token).to(be_valid)
|
|
end
|
|
|
|
it 'does accept read and write datasets scopes' do
|
|
access_token = OauthAccessToken.new(oauth_app_user: @app_user, scopes: ["datasets:rw:#{@user_table.name}"])
|
|
expect(access_token).to(be_valid)
|
|
end
|
|
|
|
it 'does not accept invalid permission in datasets scopes' do
|
|
access_token = OauthAccessToken.new(oauth_app_user: @app_user, scopes: ['datasets:f:wadus'])
|
|
expect(access_token).to_not(be_valid)
|
|
end
|
|
|
|
it 'does not validate tables existence on datasets scopes' do
|
|
access_token = OauthAccessToken.new(oauth_app_user: @app_user, scopes: ['datasets:r:wadus'])
|
|
expect(access_token).to(be_valid)
|
|
end
|
|
|
|
it 'does not validate schemas existence on datasets scopes' do
|
|
access_token = OauthAccessToken.new(oauth_app_user: @app_user, scopes: ['schemas:c:wadus'])
|
|
expect(access_token).to(be_valid)
|
|
end
|
|
|
|
it 'does not accept invalid datasets scopes' do
|
|
access_token = OauthAccessToken.new(oauth_app_user: @app_user, scopes: ['datasets:f'])
|
|
expect(access_token).to_not(be_valid)
|
|
end
|
|
|
|
it 'does not accept invalid schema scopes' do
|
|
access_token = OauthAccessToken.new(oauth_app_user: @app_user, scopes: ['schemas:f'])
|
|
expect(access_token).to_not(be_valid)
|
|
end
|
|
|
|
it 'auto generates api_key' do
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user)
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
end
|
|
|
|
it 'api key includes permissions for requested scopes' do
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: ['dataservices:geocoding', 'user:profile'])
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(include(type: 'apis', apis: ['sql']))
|
|
expect(access_token.api_key.grants).to(include(type: 'dataservices', services: ['geocoding']))
|
|
expect(access_token.api_key.grants).to(include(type: 'user', data: ['profile']))
|
|
end
|
|
|
|
it 'raises an error when creating an api key for an non-existent dataset' do
|
|
expect {
|
|
OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: ['datasets:r:wadus'])
|
|
}.to raise_error(ActiveRecord::RecordInvalid)
|
|
end
|
|
|
|
it 'raises an error when creating an api key for an non-existent dataset' do
|
|
expect {
|
|
OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: ['schemas:c:wadus'])
|
|
}.to raise_error(ActiveRecord::RecordInvalid)
|
|
end
|
|
|
|
it 'includes create permission for schemas scopes' do
|
|
FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @user.id)
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
schemas: [
|
|
{
|
|
name: 'public',
|
|
permissions: ['create']
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: ['schemas:c'])
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
|
|
it 'includes listing metadata permission for datasets scopes' do
|
|
FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @user.id)
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
table_metadata: []
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user, scopes: ['datasets:metadata'])
|
|
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
|
|
it 'api key includes read permissions for datasets scopes' do
|
|
user_table = FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @user.id)
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['maps', 'sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
tables: [
|
|
{
|
|
name: user_table.name,
|
|
permissions: ['select'],
|
|
schema: 'public'
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: ["datasets:r:#{user_table.name}"])
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
|
|
it 'api key includes read-write permissions for datasets scopes' do
|
|
user_table = FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @user.id)
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['maps', 'sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
tables: [
|
|
{
|
|
name: user_table.name,
|
|
permissions: ['select', 'insert', 'update', 'delete'],
|
|
schema: 'public'
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: ["datasets:rw:#{user_table.name}"])
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
|
|
it 'api key includes permissions for several datasets scopes' do
|
|
user_table = FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @user.id)
|
|
user_table2 = FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @user.id)
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['maps', 'sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
tables: [
|
|
{
|
|
name: user_table.name,
|
|
permissions: ['select'],
|
|
schema: 'public'
|
|
},
|
|
{
|
|
name: user_table2.name,
|
|
permissions: ['select', 'insert', 'update', 'delete'],
|
|
schema: 'public'
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: [
|
|
"datasets:r:#{user_table.name}",
|
|
"datasets:rw:#{user_table2.name}"
|
|
])
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
|
|
it 'includes service account permission for user scopes' do
|
|
FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @user.id)
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['do']
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user, scopes: ['apis:do'])
|
|
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
end
|
|
|
|
describe 'cdb_conf_info' do
|
|
before(:all) do
|
|
@user = FactoryGirl.create(:valid_user)
|
|
@carto_user = Carto::User.find(@user.id)
|
|
@app = FactoryGirl.create(:oauth_app, user: @carto_user)
|
|
@app_user = OauthAppUser.create!(user: @carto_user, oauth_app: @app)
|
|
@user_table = FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @carto_user.id)
|
|
@db_role = Carto::DB::Sanitize.sanitize_identifier("carto_role_#{SecureRandom.hex}")
|
|
Carto::ApiKey.any_instance.stubs(:db_role).returns(@db_role)
|
|
end
|
|
|
|
after(:all) do
|
|
@user.destroy
|
|
@app.destroy
|
|
@user_table.destroy
|
|
Carto::ApiKey.any_instance.unstub(:db_role)
|
|
end
|
|
|
|
it 'saves ownership_role_name in cdb_conf_info if schemas granted' do
|
|
Carto::ApiKey.any_instance.expects(:cdb_conf_info)
|
|
.returns(username: @app_user.user.username,
|
|
permissions: [],
|
|
ownership_role_name: @app_user.ownership_role_name)
|
|
.at_least_once
|
|
OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: [
|
|
"schemas:c"
|
|
])
|
|
end
|
|
|
|
it 'does not save ownership_role_name in cdb_conf_info if schemas not granted' do
|
|
Carto::ApiKey.any_instance.expects(:cdb_conf_info)
|
|
.returns(username: @app_user.user.username,
|
|
permissions: [],
|
|
ownership_role_name: '')
|
|
.at_least_once
|
|
OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: [
|
|
"datasets:r:#{@user_table.name}"
|
|
])
|
|
end
|
|
end
|
|
|
|
describe '#shared datasets' do
|
|
before :each do
|
|
@app = FactoryGirl.create(:oauth_app, user: @carto_org_user_1)
|
|
@app_user = OauthAppUser.create!(user: @carto_org_user_2, oauth_app: @app)
|
|
@shared_table = create_table(user_id: @carto_org_user_1.id)
|
|
@not_shared_table = create_table(user_id: @carto_org_user_1.id)
|
|
|
|
perm = @shared_table.table_visualization.permission
|
|
perm.acl = [{ type: 'user', entity: { id: @carto_org_user_2.id }, access: 'rw' }]
|
|
perm.save!
|
|
|
|
@shared_dataset_scope = "datasets:rw:#{@carto_org_user_1.database_schema}.#{@shared_table.name}"
|
|
@non_shared_dataset_scope = "datasets:rw:#{@carto_org_user_1.database_schema}.#{@not_shared_table.name}"
|
|
end
|
|
|
|
after :each do
|
|
@app_user.destroy
|
|
@app.destroy
|
|
end
|
|
|
|
it 'works with shared dataset' do
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['maps', 'sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
tables: [
|
|
{
|
|
name: @shared_table.name,
|
|
permissions: ['select', 'insert', 'update', 'delete'],
|
|
schema: @carto_org_user_1.database_schema
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user, scopes: [@shared_dataset_scope])
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
|
|
with_connection_from_api_key(access_token.api_key) do |connection|
|
|
connection.execute("select count(1) from #{@carto_org_user_1.database_schema}.#{@shared_table.name}")
|
|
end
|
|
end
|
|
|
|
it 'should fail with non shared dataset' do
|
|
expect {
|
|
OauthAccessToken.create!(oauth_app_user: @app_user, scopes: [@non_shared_dataset_scope])
|
|
}.to raise_error(ActiveRecord::RecordInvalid, /can only grant table permissions you have/)
|
|
end
|
|
|
|
it 'should fail with non shared schema' do
|
|
expect {
|
|
OauthAccessToken.create!(oauth_app_user: @app_user, scopes: ['schemas:c:non_existent'])
|
|
}.to raise_error(ActiveRecord::RecordInvalid, /can only grant schema permissions you have/)
|
|
end
|
|
|
|
it 'should fail with shared and non shared dataset' do
|
|
expect {
|
|
OauthAccessToken.create!(
|
|
oauth_app_user: @app_user,
|
|
scopes: [@shared_dataset_scope, @non_shared_dataset_scope]
|
|
)
|
|
}.to raise_error(ActiveRecord::RecordInvalid, /can only grant table permissions you have/)
|
|
end
|
|
|
|
describe 'read - write permissions' do
|
|
before :each do
|
|
@only_read_table = create_table(user_id: @carto_org_user_1.id)
|
|
|
|
perm = @only_read_table.table_visualization.permission
|
|
perm.acl = [{ type: 'user', entity: { id: @carto_org_user_2.id }, access: 'r' }]
|
|
perm.save!
|
|
end
|
|
|
|
after :each do
|
|
@only_read_table.destroy
|
|
end
|
|
|
|
it 'should fail write scope in shared dataset with only read perms' do
|
|
rw_scope = "datasets:rw:#{@carto_org_user_1.database_schema}.#{@only_read_table.name}"
|
|
expect {
|
|
OauthAccessToken.create!(oauth_app_user: @app_user, scopes: [rw_scope])
|
|
}.to raise_error(ActiveRecord::RecordInvalid, /can only grant table permissions you have/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'organization shared datasets' do
|
|
before :each do
|
|
@app = FactoryGirl.create(:oauth_app, user: @carto_org_user_1)
|
|
@app_user = OauthAppUser.create!(user: @carto_org_user_2, oauth_app: @app)
|
|
@org_shared_table = create_table(user_id: @carto_org_user_1.id)
|
|
@non_org_shared_table = create_table(user_id: @carto_org_user_1.id)
|
|
|
|
perm = @org_shared_table.table_visualization.permission
|
|
perm.acl = [
|
|
{
|
|
type: Permission::TYPE_ORGANIZATION,
|
|
entity: { id: @carto_organization.id },
|
|
access: Permission::ACCESS_READWRITE
|
|
}
|
|
]
|
|
perm.save!
|
|
|
|
@org_shared_dataset_scope = "datasets:rw:#{@carto_org_user_1.database_schema}.#{@org_shared_table.name}"
|
|
@non_org_shared_dataset_scope = "datasets:rw:#{@carto_org_user_1.database_schema}.#{@non_org_shared_table.name}"
|
|
end
|
|
|
|
after :each do
|
|
@app_user.destroy
|
|
@app.destroy
|
|
end
|
|
|
|
it 'works with shared dataset' do
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['maps', 'sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
tables: [
|
|
{
|
|
name: @org_shared_table.name,
|
|
permissions: ['select', 'insert', 'update', 'delete'],
|
|
schema: @carto_org_user_1.database_schema
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user, scopes: [@org_shared_dataset_scope])
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
|
|
it 'works with shared schema' do
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
schemas: [
|
|
{
|
|
name: @carto_org_user_2.database_schema,
|
|
permissions: ['create']
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: ["schemas:c:#{@carto_org_user_2.database_schema}"])
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
|
|
it 'should fail with non shared dataset' do
|
|
expect {
|
|
OauthAccessToken.create!(oauth_app_user: @app_user, scopes: [@non_org_shared_dataset_scope])
|
|
}.to raise_error(ActiveRecord::RecordInvalid, /can only grant table permissions you have/)
|
|
end
|
|
|
|
it 'should fail with non shared schema' do
|
|
expect {
|
|
OauthAccessToken.create!(oauth_app_user: @app_user,
|
|
scopes: ["schemas:c:wadus"])
|
|
}.to raise_error(ActiveRecord::RecordInvalid, /can only grant schema permissions you have/)
|
|
end
|
|
|
|
it 'should fail with shared and non shared dataset' do
|
|
expect {
|
|
OauthAccessToken.create!(
|
|
oauth_app_user: @app_user,
|
|
scopes: [@org_shared_dataset_scope, @non_org_shared_dataset_scope]
|
|
)
|
|
}.to raise_error(ActiveRecord::RecordInvalid, /can only grant table permissions you have/)
|
|
end
|
|
|
|
describe 'read - write permissions' do
|
|
before :each do
|
|
@only_read_table = create_table(user_id: @carto_org_user_1.id)
|
|
|
|
perm = @only_read_table.table_visualization.permission
|
|
perm.acl = [
|
|
{
|
|
type: Permission::TYPE_ORGANIZATION,
|
|
entity: { id: @carto_organization.id },
|
|
access: Permission::ACCESS_READONLY
|
|
}
|
|
]
|
|
perm.save!
|
|
end
|
|
|
|
after :each do
|
|
@only_read_table.destroy
|
|
end
|
|
|
|
it 'should fail write scope in shared dataset with only read perms' do
|
|
rw_scope = "datasets:rw:#{@carto_org_user_1.database_schema}.#{@only_read_table.name}"
|
|
expect {
|
|
OauthAccessToken.create!(oauth_app_user: @app_user, scopes: [rw_scope])
|
|
}.to raise_error(ActiveRecord::RecordInvalid, /can only grant table permissions you have/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'views' do
|
|
before :all do
|
|
@user = FactoryGirl.create(:valid_user)
|
|
@carto_user = Carto::User.find(@user.id)
|
|
@user_table = create_table(user_id: @carto_user.id)
|
|
|
|
@view_name = "#{@user_table.name}_view"
|
|
@materialized_view_name = "#{@user_table.name}_matview"
|
|
@carto_user.in_database do |db|
|
|
query = %{
|
|
CREATE VIEW #{@view_name} AS SELECT * FROM #{@user_table.name};
|
|
CREATE MATERIALIZED VIEW #{@materialized_view_name} AS SELECT * FROM #{@user_table.name};
|
|
}
|
|
db.execute(query)
|
|
end
|
|
end
|
|
|
|
before :each do
|
|
@app = FactoryGirl.create(:oauth_app, user: @carto_user)
|
|
@app_user = OauthAppUser.create!(user: @carto_user, oauth_app: @app)
|
|
end
|
|
|
|
after :each do
|
|
@app_user.destroy
|
|
@app.destroy
|
|
end
|
|
|
|
after :all do
|
|
@carto_user.in_database do |db|
|
|
query = %{
|
|
DROP VIEW #{@view_name};
|
|
DROP MATERIALIZED VIEW #{@materialized_view_name};
|
|
}
|
|
db.execute(query)
|
|
end
|
|
|
|
@user_table.destroy
|
|
@user.destroy
|
|
@carto_user.destroy
|
|
end
|
|
|
|
it 'validates view scope' do
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['maps', 'sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
tables: [
|
|
{
|
|
name: @view_name,
|
|
permissions: ['select', 'insert', 'update', 'delete'],
|
|
schema: @carto_user.database_schema
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(oauth_app_user: @app_user, scopes: ["datasets:rw:#{@view_name}"])
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
|
|
it 'validates materialized view scope' do
|
|
expected_grants =
|
|
[
|
|
{
|
|
type: 'apis',
|
|
apis: ['maps', 'sql']
|
|
},
|
|
{
|
|
type: 'database',
|
|
tables: [
|
|
{
|
|
name: @materialized_view_name,
|
|
permissions: ['select', 'insert', 'update', 'delete'],
|
|
schema: @carto_user.database_schema
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
access_token = OauthAccessToken.create!(
|
|
oauth_app_user: @app_user,
|
|
scopes: ["datasets:rw:#{@materialized_view_name}"]
|
|
)
|
|
expect(access_token.api_key).to(be)
|
|
expect(access_token.api_key.type).to(eq('oauth'))
|
|
expect(access_token.api_key.grants).to(eq(expected_grants))
|
|
end
|
|
end
|
|
end
|
|
end
|