8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-23 10:03:03 +01:00
firebird-mirror/src/jrd/DbCreators.cpp
Dimitry Sibiryakov b495b3f174
Allow to create database with different owner (#7718)
* Allow to create database with different owner via API

* Support for OWNER clause in executeCreateDatabase()

* squash! Support for OWNER clause in executeCreateDatabase()

Fix rebase error
2023-11-20 18:02:59 +03:00

354 lines
10 KiB
C++

/*
* PROGRAM: JRD access method
* MODULE: DbCreators.cpp
* DESCRIPTION: Checks CREATE DATABASE right (in security.db)
*
* The contents of this file are subject to the Initial
* Developer's Public License Version 1.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
* http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
*
* Software distributed under the License is distributed AS IS,
* WITHOUT WARRANTY OF ANY KIND, either express or implied.
* See the License for the specific language governing rights
* and limitations under the License.
*
* The Original Code was created by Alex Peshkov
* for the Firebird Open Source RDBMS project.
*
* Copyright (c) 2014 Alex Peshkov <peshkoff at mail.ru>
* and all contributors signed below.
*
* All Rights Reserved.
* Contributor(s): ______________________________________.
*
*
*/
#include "firebird.h"
#include "firebird/Interface.h"
#include "../auth/SecureRemotePassword/Message.h"
#include "iberror.h"
#include "../jrd/constants.h"
#include "../common/classes/ClumpletWriter.h"
#include "../common/classes/init.h"
#include "../common/classes/Hash.h"
#include "../common/classes/GenericMap.h"
#include "../common/classes/RefMutex.h"
#include "../common/classes/SyncObject.h"
#include "../jrd/MetaName.h"
#include "../common/isc_s_proto.h"
#include "../common/isc_proto.h"
#include "../common/ThreadStart.h"
#include "../common/db_alias.h"
#include "../common/classes/ParsedList.h"
#include "../jrd/DbCreators.h"
#include "../jrd/tra.h"
#include "../jrd/ini.h"
#include "../jrd/status.h"
#include "../jrd/ids.h"
#define DBC_DEBUG(A)
using namespace Firebird;
using namespace Jrd;
using namespace Auth;
namespace {
void check(const char* s, IStatus* st)
{
if (!(st->getState() & IStatus::STATE_ERRORS))
return;
Arg::StatusVector newStatus(st);
newStatus << Arg::Gds(isc_crdb_load) << s;
newStatus.raise();
}
bool openDb(const char* securityDb, RefPtr<IAttachment>& att, RefPtr<ITransaction>& tra)
{
ClumpletWriter embeddedAttach(ClumpletWriter::dpbList, MAX_DPB_SIZE);
embeddedAttach.insertString(isc_dpb_user_name, DBA_USER_NAME, fb_strlen(DBA_USER_NAME));
embeddedAttach.insertByte(isc_dpb_sec_attach, TRUE);
embeddedAttach.insertString(isc_dpb_config, ParsedList::getNonLoopbackProviders(securityDb));
embeddedAttach.insertByte(isc_dpb_no_db_triggers, TRUE);
FbLocalStatus st;
DispatcherPtr prov;
att.assignRefNoIncr(prov->attachDatabase(&st, securityDb,
embeddedAttach.getBufferLength(), embeddedAttach.getBuffer()));
if (st->getState() & IStatus::STATE_ERRORS)
{
if (!fb_utils::containsErrorCode(st->getErrors(), isc_io_error))
check("IProvider::attachDatabase", &st);
// missing security DB - checking granted rights not possible
return false;
}
ClumpletWriter readOnly(ClumpletWriter::Tpb, MAX_DPB_SIZE, isc_tpb_version1);
readOnly.insertTag(isc_tpb_read);
readOnly.insertTag(isc_tpb_wait);
tra.assignRefNoIncr(att->startTransaction(&st, readOnly.getBufferLength(), readOnly.getBuffer()));
check("IAttachment::startTransaction", &st);
return true;
}
} // anonymous namespace
namespace Jrd {
CreateGrant checkCreateDatabaseGrant(const MetaString& userName, const MetaString& trustedRole,
const MetaString& sqlRole, const char* securityDb)
{
if (userName == DBA_USER_NAME)
return CreateGrant::ASSUMED;
RefPtr<IAttachment> att;
RefPtr<ITransaction> tra;
bool hasDb = openDb(securityDb, att, tra);
FbLocalStatus st;
MetaString role(sqlRole);
if (hasDb && role.hasData())
{
const UCHAR info[] = { isc_info_db_sql_dialect, isc_info_end };
UCHAR buffer[BUFFER_TINY];
att->getInfo(&st, sizeof(info), info, sizeof(buffer), buffer);
check("IAttachment::getInfo", &st);
int dialect = SQL_DIALECT_V5; // reasonable default
for (ClumpletReader p(ClumpletReader::InfoResponse, buffer, sizeof(buffer)); !p.isEof(); p.moveNext())
{
switch (p.getClumpTag())
{
case isc_info_db_sql_dialect:
dialect = p.getInt();
break;
}
}
UserId::makeRoleName(role, dialect);
// We need to check is role granted to userName in security DB
const char* sql = "select count(*) from RDB$USER_PRIVILEGES "
"where RDB$USER = ? and RDB$RELATION_NAME = ? and RDB$PRIVILEGE = 'M'";
Message prm;
Field<Varying> u(prm, MAX_SQL_IDENTIFIER_LEN);
Field<Varying> r(prm, MAX_SQL_IDENTIFIER_LEN);
u = userName.c_str();
r = role.c_str();
Message result;
Field<ISC_INT64> cnt(result);
att->execute(&st, tra, 0, sql, SQL_DIALECT_V6, prm.getMetadata(), prm.getBuffer(),
result.getMetadata(), result.getBuffer());
if (st->getState() & IStatus::STATE_ERRORS)
{
// isc_dsql_relation_err when exec SQL - i.e. table RDB$USER_PRIVILEGES
// is missing due to non-FB security DB
if (!fb_utils::containsErrorCode(st->getErrors(), isc_dsql_relation_err))
check("IAttachment::execute", &st);
role = "";
}
else if (cnt == 0)
role = "";
}
else
role = trustedRole;
if (role == ADMIN_ROLE)
return CreateGrant::ASSUMED;
if (!hasDb)
return CreateGrant::NONE;
// check db creators table
Message gr;
Field<ISC_SHORT> uType(gr);
Field<Varying> u(gr, MAX_SQL_IDENTIFIER_LEN);
Field<ISC_SHORT> rType(gr);
Field<Varying> r(gr, MAX_SQL_IDENTIFIER_LEN);
uType = obj_user;
u = userName.c_str();
rType = role.hasData() ? obj_sql_role : 255;
r = role.c_str();
Message result;
Field<ISC_INT64> cnt(result);
att->execute(&st, tra, 0,
"select count(*) from RDB$DB_CREATORS"
" where (RDB$USER_TYPE = ? and RDB$USER = ?) or (RDB$USER_TYPE = ? and RDB$USER = ?)",
SQL_DIALECT_V6, gr.getMetadata(), gr.getBuffer(), result.getMetadata(), result.getBuffer());
if (st->getState() & IStatus::STATE_ERRORS)
{
if (fb_utils::containsErrorCode(st->getErrors(), isc_dsql_relation_err))
{
// isc_dsql_relation_err when exec SQL - i.e. table RDB$DB_CREATORS
// is missing due to non-FB3 security DB
return CreateGrant::NONE;
}
check("IAttachment::execute", &st);
}
if (cnt > 0)
return CreateGrant::GRANTED;
if (!role.hasData())
role = "NONE";
Message par2;
Field<ISC_SHORT> uType2(par2);
Field<Varying> u2(par2, MAX_SQL_IDENTIFIER_LEN);
Field<Varying> r2(par2, MAX_SQL_IDENTIFIER_LEN);
uType2 = obj_user;
u2 = userName.c_str();
r2 = role.c_str();
Message res2;
Field<Text> priv(res2, 8);
const char* sql =
"with recursive role_tree as ( "
" select rdb$relation_name as nm, 0 as ur from rdb$user_privileges "
" where rdb$privilege = 'M' and rdb$field_name = 'D' and rdb$user_type = ? and rdb$user = ? "
" union all "
" select rdb$role_name as nm, 1 as ur from rdb$roles "
" where rdb$role_name = ? "
" union all "
" select p.rdb$relation_name as nm, t.ur from rdb$user_privileges p "
" join role_tree t on t.nm = p.rdb$user "
" where p.rdb$privilege = 'M' and (p.rdb$field_name = 'D' or t.ur = 1)) "
"select r.rdb$system_privileges "
" from role_tree t join rdb$roles r on t.nm = r.rdb$role_name ";
RefPtr<IResultSet> rs(REF_NO_INCR, att->openCursor(&st, tra, 0, sql,
SQL_DIALECT_V6, par2.getMetadata(), par2.getBuffer(), res2.getMetadata(), NULL, 0));
check("IAttachment::execute", &st);
UserId::Privileges privileges, wrk;
while (rs->fetchNext(&st, res2.getBuffer()) == IStatus::RESULT_OK)
{
wrk.load(&priv);
privileges |= wrk;
}
check("IResultSet::fetchNext", &st);
return wrk.test(CREATE_DATABASE) ? CreateGrant::GRANTED : CreateGrant::NONE;
}
const Format* DbCreatorsScan::getFormat(thread_db* tdbb, jrd_rel* relation) const
{
jrd_tra* const transaction = tdbb->getTransaction();
return transaction->getDbCreatorsList()->getList(tdbb, relation)->getFormat();
}
bool DbCreatorsScan::retrieveRecord(thread_db* tdbb, jrd_rel* relation,
FB_UINT64 position, Record* record) const
{
jrd_tra* const transaction = tdbb->getTransaction();
return transaction->getDbCreatorsList()->getList(tdbb, relation)->fetch(position, record);
}
DbCreatorsList::DbCreatorsList(jrd_tra* tra)
: SnapshotData(*tra->tra_pool)
{ }
RecordBuffer* DbCreatorsList::makeBuffer(thread_db* tdbb)
{
MemoryPool* const pool = tdbb->getTransaction()->tra_pool;
allocBuffer(tdbb, *pool, rel_sec_db_creators);
return getData(rel_sec_db_creators);
}
RecordBuffer* DbCreatorsList::getList(thread_db* tdbb, jrd_rel* relation)
{
fb_assert(relation);
fb_assert(relation->rel_id == rel_sec_db_creators);
RecordBuffer* buffer = getData(relation);
if (buffer)
{
return buffer;
}
RefPtr<IAttachment> att;
RefPtr<ITransaction> tra;
const char* dbName = tdbb->getDatabase()->dbb_config->getSecurityDatabase();
if (!openDb(dbName, att, tra))
{
// In embedded mode we are not raising any errors - silent return
if (MasterInterfacePtr()->serverMode(-1) < 0)
return makeBuffer(tdbb);
(Arg::Gds(isc_crdb_nodb) << dbName).raise();
}
Message gr;
Field<ISC_SHORT> uType(gr);
Field<Varying> u(gr, MAX_SQL_IDENTIFIER_LEN);
FbLocalStatus st;
RefPtr<IResultSet> curs(REF_NO_INCR, att->openCursor(&st, tra, 0,
"select RDB$USER_TYPE, RDB$USER from RDB$DB_CREATORS",
SQL_DIALECT_V6, NULL, NULL, gr.getMetadata(), NULL, 0));
if (st->getState() & IStatus::STATE_ERRORS)
{
if (!fb_utils::containsErrorCode(st->getErrors(), isc_dsql_relation_err))
check("IAttachment::openCursor", &st);
// isc_dsql_relation_err when opening cursor - i.e. table
// is missing due to non-FB3 security DB
// In embedded mode we are not raising any errors - silent return
if (MasterInterfacePtr()->serverMode(-1) < 0)
return makeBuffer(tdbb);
(Arg::Gds(isc_crdb_notable) << dbName).raise();
}
try
{
buffer = makeBuffer(tdbb);
while (curs->fetchNext(&st, gr.getBuffer()) == IStatus::RESULT_OK)
{
Record* record = buffer->getTempRecord();
record->nullify();
putField(tdbb, record,
DumpField(f_sec_crt_user, VALUE_STRING, u->len, u->data));
SINT64 v = uType;
putField(tdbb, record,
DumpField(f_sec_crt_u_type, VALUE_INTEGER, sizeof(v), &v));
buffer->store(record);
}
check("IResultSet::fetchNext", &st);
}
catch (const Exception&)
{
clearSnapshot();
throw;
}
return getData(relation);
}
} // namespace Jrd