diff --git a/doc/sql.extensions/README.ddl.txt b/doc/sql.extensions/README.ddl.txt index 1c7b6e831b..eae4e98d42 100644 --- a/doc/sql.extensions/README.ddl.txt +++ b/doc/sql.extensions/README.ddl.txt @@ -618,3 +618,9 @@ ALTER PROCEDURE SQL SECURITY {DEFINER | INVOKER} | DROP SQL SECURITY (Alexander Zhdanov) ALTER PACKAGE SQL SECURITY {DEFINER | INVOKER} | DROP SQL SECURITY + +27) Added OWNER clause to CREATE DATABASE statement. +(Dmitry Sibiryakov) + + list is expanded by "OWNER username" clause which allows to set an owner user name for the created database. +Only users with administrator rights can use this option. diff --git a/doc/sql.extensions/README.isc_dpb_xxx b/doc/sql.extensions/README.isc_dpb_xxx new file mode 100644 index 0000000000..4462de8d3b --- /dev/null +++ b/doc/sql.extensions/README.isc_dpb_xxx @@ -0,0 +1,6 @@ +New Database Parameter Block items: + +1. isc_dpb_owner : + Used for createDatabase() call to set the owner for the new database + to be different from user set with isc_user_name. + Can be used only by users with admin rights. diff --git a/src/common/IntlParametersBlock.cpp b/src/common/IntlParametersBlock.cpp index f77acbba78..df92765bcc 100644 --- a/src/common/IntlParametersBlock.cpp +++ b/src/common/IntlParametersBlock.cpp @@ -194,6 +194,7 @@ IntlParametersBlock::TagType IntlDpb::checkTag(UCHAR tag, const char** tagName) FB_IPB_TAG(isc_dpb_process_name); FB_IPB_TAG(isc_dpb_host_name); FB_IPB_TAG(isc_dpb_os_user); + FB_IPB_TAG(isc_dpb_owner); return TAG_STRING; } diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 2bff7a923d..f478e17d61 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -350,6 +350,7 @@ PARSER_TOKEN(TOK_OVER, "OVER", false) PARSER_TOKEN(TOK_OVERFLOW, "OVERFLOW", true) PARSER_TOKEN(TOK_OVERLAY, "OVERLAY", true) PARSER_TOKEN(TOK_OVERRIDING, "OVERRIDING", true) +PARSER_TOKEN(TOK_OWNER, "OWNER", true) PARSER_TOKEN(TOK_PACKAGE, "PACKAGE", true) PARSER_TOKEN(TOK_PAD, "PAD", true) PARSER_TOKEN(TOK_PAGE, "PAGE", true) diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 682dfc4be0..f0e18947a0 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -695,6 +695,7 @@ using namespace Firebird; %token TIMEZONE_NAME %token UNICODE_CHAR %token UNICODE_VAL +%token OWNER // tokens added for Firebird 6.0 @@ -2155,6 +2156,8 @@ db_initial_option($alterDatabaseNode) : PAGE_SIZE equals NUMBER32BIT | USER symbol_user_name | USER utf_string + | OWNER symbol_user_name + | OWNER utf_string | ROLE valid_symbol_name | ROLE utf_string | PASSWORD utf_string @@ -9464,6 +9467,7 @@ non_reserved_word // added in FB 6.0 | ANY_VALUE | FORMAT + | OWNER ; %% diff --git a/src/include/firebird/impl/consts_pub.h b/src/include/firebird/impl/consts_pub.h index d7f7004962..961396be26 100644 --- a/src/include/firebird/impl/consts_pub.h +++ b/src/include/firebird/impl/consts_pub.h @@ -132,6 +132,7 @@ #define isc_dpb_upgrade_db 97 #define isc_dpb_parallel_workers 100 #define isc_dpb_worker_attach 101 +#define isc_dpb_owner 102 /**************************************************/ diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index fec52a3055..d755096694 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -4044,6 +4044,7 @@ const isc_dpb_upgrade_db = byte(97); isc_dpb_parallel_workers = byte(100); isc_dpb_worker_attach = byte(101); + isc_dpb_owner = byte(102); isc_dpb_address = byte(1); isc_dpb_addr_protocol = byte(1); isc_dpb_addr_endpoint = byte(2); diff --git a/src/jrd/DbCreators.cpp b/src/jrd/DbCreators.cpp index eede63f863..d075b4ed21 100644 --- a/src/jrd/DbCreators.cpp +++ b/src/jrd/DbCreators.cpp @@ -104,11 +104,11 @@ bool openDb(const char* securityDb, RefPtr& att, RefPtr att; RefPtr tra; @@ -169,10 +169,10 @@ bool checkCreateDatabaseGrant(const MetaString& userName, const MetaString& trus role = trustedRole; if (role == ADMIN_ROLE) - return true; + return CreateGrant::ASSUMED; if (!hasDb) - return false; + return CreateGrant::NONE; // check db creators table Message gr; @@ -198,13 +198,13 @@ bool checkCreateDatabaseGrant(const MetaString& userName, const MetaString& trus { // isc_dsql_relation_err when exec SQL - i.e. table RDB$DB_CREATORS // is missing due to non-FB3 security DB - return false; + return CreateGrant::NONE; } check("IAttachment::execute", &st); } if (cnt > 0) - return true; + return CreateGrant::GRANTED; if (!role.hasData()) role = "NONE"; @@ -247,7 +247,7 @@ bool checkCreateDatabaseGrant(const MetaString& userName, const MetaString& trus check("IResultSet::fetchNext", &st); - return wrk.test(CREATE_DATABASE); + return wrk.test(CREATE_DATABASE) ? CreateGrant::GRANTED : CreateGrant::NONE; } diff --git a/src/jrd/DbCreators.h b/src/jrd/DbCreators.h index 2a2f18b51f..a3a767891a 100644 --- a/src/jrd/DbCreators.h +++ b/src/jrd/DbCreators.h @@ -36,7 +36,14 @@ namespace Jrd { -bool checkCreateDatabaseGrant(const Firebird::MetaString& userName, const Firebird::MetaString& trustedRole, +enum class CreateGrant +{ + NONE, // This user cannot create databases + GRANTED, // User was granted privilege to create database + ASSUMED // User is admin and has all rights +}; + +CreateGrant checkCreateDatabaseGrant(const Firebird::MetaString& userName, const Firebird::MetaString& trustedRole, const Firebird::MetaString& sqlRole, const char* securityDb); class DbCreatorsScan: public VirtualTableScan diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index a00c9b009b..9cf3029cfd 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -1111,6 +1111,7 @@ namespace Jrd PathName dpb_set_bind; string dpb_decfloat_round; string dpb_decfloat_traps; + string dpb_owner; public: static const ULONG DPB_FLAGS_MASK = DBB_damaged; @@ -1337,7 +1338,7 @@ static void start_transaction(thread_db* tdbb, bool transliterate, jrd_tra** tr static void rollback(thread_db*, jrd_tra*, const bool); static void purge_attachment(thread_db* tdbb, StableAttachmentPart* sAtt, unsigned flags = 0); static void getUserInfo(UserId&, const DatabaseOptions&, const char*, - const RefPtr*, bool, Mapping& mapping, bool); + const RefPtr*, Mapping& mapping, bool); static void waitForShutdown(Semaphore&); static THREAD_ENTRY_DECLARE shutdown_thread(THREAD_ENTRY_PARAM); @@ -1353,7 +1354,7 @@ TraceFailedConnection::TraceFailedConnection(const char* filename, const Databas { Mapping mapping(Mapping::MAP_ERROR_HANDLER, NULL); mapping.setAuthBlock(m_options->dpb_auth_block); - getUserInfo(m_id, *m_options, m_filename, NULL, false, mapping, false); + getUserInfo(m_id, *m_options, m_filename, NULL, mapping, false); } @@ -1949,7 +1950,7 @@ JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const ch try { mapping.setDb(filename, expanded_name.c_str(), jAtt); - getUserInfo(userId, options, filename, &config, false, mapping, options.dpb_reset_icu); + getUserInfo(userId, options, filename, &config, mapping, options.dpb_reset_icu); } catch(const Exception&) { @@ -2847,7 +2848,41 @@ JAttachment* JProvider::createDatabase(CheckStatusWrapper* user_status, const ch // Check for correct credentials supplied mapping.setSecurityDbAlias(config->getSecurityDatabase(), nullptr); - getUserInfo(userId, options, filename, &config, true, mapping, false); + getUserInfo(userId, options, filename, &config, mapping, false); + + // Check user's power level + CreateGrant powerLevel = CreateGrant::ASSUMED; // By default it is a boot build or embedded mode where everything is allowed + if (options.dpb_auth_block.hasData()) + { + powerLevel = checkCreateDatabaseGrant(userId.getUserName(), userId.getTrustedRole(), userId.getSqlRole(), config->getSecurityDatabase()); + } + + switch (powerLevel) + { + case CreateGrant::NONE: + (Arg::Gds(isc_no_priv) << "CREATE" << "DATABASE" << filename).raise(); + + case CreateGrant::ASSUMED: + if (options.dpb_owner.hasData()) + { + // Superuser can create databases for anyone other + fb_utils::dpbItemUpper(options.dpb_owner); + userId.setUserName(options.dpb_owner); + } + break; + + case CreateGrant::GRANTED: + if (options.dpb_owner.hasData()) + { + // Superuser can create databases for anyone other + fb_utils::dpbItemUpper(options.dpb_owner); + if (userId.getUserName() != options.dpb_owner) + { + (Arg::Gds(isc_no_priv) << "IMPERSONATE USER" << "DATABASE" << filename).raise(); + } + } + break; + } #ifdef WIN_NT guardDbInit.enter(); // Required to correctly expand name of just created database @@ -7274,6 +7309,10 @@ void DatabaseOptions::get(const UCHAR* dpb, USHORT dpb_length, bool& invalid_cli dpb_upgrade_db = true; break; + case isc_dpb_owner: + getString(rdr, dpb_owner); + break; + default: break; } @@ -8543,13 +8582,12 @@ static VdnResult verifyDatabaseName(const PathName& name, FbStatusVector* status @param aliasName @param dbName @param config - @param creating @param iAtt @param cryptCb **/ static void getUserInfo(UserId& user, const DatabaseOptions& options, const char* aliasName, - const RefPtr* config, bool creating, Mapping& mapping, bool icuReset) + const RefPtr* config, Mapping& mapping, bool icuReset) { bool wheel = false; int id = -1, group = -1; // CVC: This var contained trash @@ -8580,12 +8618,6 @@ static void getUserInfo(UserId& user, const DatabaseOptions& options, const char if (mapping.mapUser(name, trusted_role) & Mapping::MAP_DOWN) user.setFlag(USR_mapdown); - - if (creating && config) // when config is NULL we are in error handler - { - if (!checkCreateDatabaseGrant(name, trusted_role, options.dpb_role_name, (*config)->getSecurityDatabase())) - (Arg::Gds(isc_no_priv) << "CREATE" << "DATABASE" << aliasName).raise(); - } } else { diff --git a/src/yvalve/preparse.cpp b/src/yvalve/preparse.cpp index d300447686..3e133f325a 100644 --- a/src/yvalve/preparse.cpp +++ b/src/yvalve/preparse.cpp @@ -47,7 +47,8 @@ enum pp_vals { PP_PAGE = 9, PP_SET = 10, PP_NAMES = 11, - PP_ROLE = 12 + PP_ROLE = 12, + PP_OWNER = 13 }; @@ -76,6 +77,7 @@ static const pp_table pp_symbols[] = {"SET", PP_SET}, {"NAMES", PP_NAMES}, {"ROLE", PP_ROLE}, + {"OWNER", PP_OWNER}, {"", 0} }; @@ -310,6 +312,13 @@ bool PREPARSE_execute(CheckStatusWrapper* status, Why::YAttachment** ptrAtt, case PP_PAGES: matched = true; break; + + case PP_OWNER: + token = getToken(pos, tks); + + dpb.insertString(isc_dpb_owner, token); + matched = true; + break; } // switch } // if } // for