diff options
Diffstat (limited to 'src/mongo/db/auth')
86 files changed, 10905 insertions, 11064 deletions
diff --git a/src/mongo/db/auth/action_set.cpp b/src/mongo/db/auth/action_set.cpp index 85bcc079814..6f89222679b 100644 --- a/src/mongo/db/auth/action_set.cpp +++ b/src/mongo/db/auth/action_set.cpp @@ -41,110 +41,109 @@ namespace mongo { - void ActionSet::addAction(const ActionType& action) { - if (action == ActionType::anyAction) { - addAllActions(); - return; - } - _actions.set(action.getIdentifier(), true); +void ActionSet::addAction(const ActionType& action) { + if (action == ActionType::anyAction) { + addAllActions(); + return; } + _actions.set(action.getIdentifier(), true); +} - void ActionSet::addAllActionsFromSet(const ActionSet& actions) { - if (actions.contains(ActionType::anyAction)) { - addAllActions(); - return; - } - _actions |= actions._actions; +void ActionSet::addAllActionsFromSet(const ActionSet& actions) { + if (actions.contains(ActionType::anyAction)) { + addAllActions(); + return; } + _actions |= actions._actions; +} - void ActionSet::addAllActions() { - _actions = ~std::bitset<ActionType::NUM_ACTION_TYPES>(); - } +void ActionSet::addAllActions() { + _actions = ~std::bitset<ActionType::NUM_ACTION_TYPES>(); +} + +void ActionSet::removeAction(const ActionType& action) { + _actions.set(action.getIdentifier(), false); + _actions.set(ActionType::anyAction.getIdentifier(), false); +} - void ActionSet::removeAction(const ActionType& action) { - _actions.set(action.getIdentifier(), false); +void ActionSet::removeAllActionsFromSet(const ActionSet& other) { + _actions &= ~other._actions; + if (!other.empty()) { _actions.set(ActionType::anyAction.getIdentifier(), false); } - - void ActionSet::removeAllActionsFromSet(const ActionSet& other) { - _actions &= ~other._actions; - if (!other.empty()) { - _actions.set(ActionType::anyAction.getIdentifier(), false); +} + +void ActionSet::removeAllActions() { + _actions = std::bitset<ActionType::NUM_ACTION_TYPES>(); +} + +bool ActionSet::contains(const ActionType& action) const { + return _actions[action.getIdentifier()]; +} + +bool ActionSet::isSupersetOf(const ActionSet& other) const { + return (_actions & other._actions) == other._actions; +} + +Status ActionSet::parseActionSetFromString(const std::string& actionsString, ActionSet* result) { + std::vector<std::string> actionsList; + splitStringDelim(actionsString, &actionsList, ','); + return parseActionSetFromStringVector(actionsList, result); +} + +Status ActionSet::parseActionSetFromStringVector(const std::vector<std::string>& actionsVector, + ActionSet* result) { + ActionSet actions; + for (size_t i = 0; i < actionsVector.size(); i++) { + ActionType action; + Status status = ActionType::parseActionFromString(actionsVector[i], &action); + if (status != Status::OK()) { + ActionSet empty; + *result = empty; + return status; } + if (action == ActionType::anyAction) { + actions.addAllActions(); + break; + } + actions.addAction(action); } + *result = actions; + return Status::OK(); +} - void ActionSet::removeAllActions() { - _actions = std::bitset<ActionType::NUM_ACTION_TYPES>(); - } - - bool ActionSet::contains(const ActionType& action) const { - return _actions[action.getIdentifier()]; - } - - bool ActionSet::isSupersetOf(const ActionSet& other) const { - return (_actions & other._actions) == other._actions; - } - - Status ActionSet::parseActionSetFromString(const std::string& actionsString, - ActionSet* result) { - std::vector<std::string> actionsList; - splitStringDelim(actionsString, &actionsList, ','); - return parseActionSetFromStringVector(actionsList, result); +std::string ActionSet::toString() const { + if (contains(ActionType::anyAction)) { + return ActionType::anyAction.toString(); } - - Status ActionSet::parseActionSetFromStringVector(const std::vector<std::string>& actionsVector, - ActionSet* result) { - ActionSet actions; - for (size_t i = 0; i < actionsVector.size(); i++) { - ActionType action; - Status status = ActionType::parseActionFromString(actionsVector[i], &action); - if (status != Status::OK()) { - ActionSet empty; - *result = empty; - return status; + StringBuilder str; + bool addedOne = false; + for (int i = 0; i < ActionType::actionTypeEndValue; i++) { + ActionType action(i); + if (contains(action)) { + if (addedOne) { + str << ","; } - if (action == ActionType::anyAction) { - actions.addAllActions(); - break; - } - actions.addAction(action); + str << ActionType::actionToString(action); + addedOne = true; } - *result = actions; - return Status::OK(); } + return str.str(); +} - std::string ActionSet::toString() const { - if (contains(ActionType::anyAction)) { - return ActionType::anyAction.toString(); - } - StringBuilder str; - bool addedOne = false; - for (int i = 0; i < ActionType::actionTypeEndValue; i++) { - ActionType action(i); - if (contains(action)) { - if (addedOne) { - str << ","; - } - str << ActionType::actionToString(action); - addedOne = true; - } - } - return str.str(); +std::vector<std::string> ActionSet::getActionsAsStrings() const { + std::vector<std::string> result; + if (contains(ActionType::anyAction)) { + result.push_back(ActionType::anyAction.toString()); + return result; } - - std::vector<std::string> ActionSet::getActionsAsStrings() const { - std::vector<std::string> result; - if (contains(ActionType::anyAction)) { - result.push_back(ActionType::anyAction.toString()); - return result; + for (int i = 0; i < ActionType::actionTypeEndValue; i++) { + ActionType action(i); + if (contains(action)) { + result.push_back(ActionType::actionToString(action)); } - for (int i = 0; i < ActionType::actionTypeEndValue; i++) { - ActionType action(i); - if (contains(action)) { - result.push_back(ActionType::actionToString(action)); - } - } - return result; } + return result; +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/action_set.h b/src/mongo/db/auth/action_set.h index 4a5c6ef45d0..339a68f019c 100644 --- a/src/mongo/db/auth/action_set.h +++ b/src/mongo/db/auth/action_set.h @@ -35,60 +35,62 @@ namespace mongo { - /* - * An ActionSet is a bitmask of ActionTypes that represents a set of actions. - * These are the actions that a Privilege can grant a user to perform on a resource. - * If the special ActionType::anyAction is granted to this set, it automatically sets all bits - * in the bitmask, indicating that it contains all possible actions. - */ - class ActionSet { - public: - - ActionSet() : _actions(0) {} - - void addAction(const ActionType& action); - void addAllActionsFromSet(const ActionSet& actionSet); - void addAllActions(); - - // Removes action from the set. Also removes the "anyAction" action, if present. - // Note: removing the "anyAction" action does *not* remove all other actions. - void removeAction(const ActionType& action); - void removeAllActionsFromSet(const ActionSet& actionSet); - void removeAllActions(); - - bool empty() const { return _actions.none(); } - - bool equals(const ActionSet& other) const { return this->_actions == other._actions; } +/* + * An ActionSet is a bitmask of ActionTypes that represents a set of actions. + * These are the actions that a Privilege can grant a user to perform on a resource. + * If the special ActionType::anyAction is granted to this set, it automatically sets all bits + * in the bitmask, indicating that it contains all possible actions. + */ +class ActionSet { +public: + ActionSet() : _actions(0) {} + + void addAction(const ActionType& action); + void addAllActionsFromSet(const ActionSet& actionSet); + void addAllActions(); + + // Removes action from the set. Also removes the "anyAction" action, if present. + // Note: removing the "anyAction" action does *not* remove all other actions. + void removeAction(const ActionType& action); + void removeAllActionsFromSet(const ActionSet& actionSet); + void removeAllActions(); + + bool empty() const { + return _actions.none(); + } - bool contains(const ActionType& action) const; + bool equals(const ActionSet& other) const { + return this->_actions == other._actions; + } - // Returns true only if this ActionSet contains all the actions present in the 'other' - // ActionSet. - bool isSupersetOf(const ActionSet& other) const; + bool contains(const ActionType& action) const; - // Returns the std::string representation of this ActionSet - std::string toString() const; + // Returns true only if this ActionSet contains all the actions present in the 'other' + // ActionSet. + bool isSupersetOf(const ActionSet& other) const; - // Returns a vector of strings representing the actions in the ActionSet. - std::vector<std::string> getActionsAsStrings() const; + // Returns the std::string representation of this ActionSet + std::string toString() const; - // Takes a comma-separated std::string of action type std::string representations and returns - // an int bitmask of the actions. - static Status parseActionSetFromString(const std::string& actionsString, ActionSet* result); + // Returns a vector of strings representing the actions in the ActionSet. + std::vector<std::string> getActionsAsStrings() const; - // Takes a vector of action type std::string representations and returns an ActionSet of the - // actions. - static Status parseActionSetFromStringVector(const std::vector<std::string>& actionsVector, - ActionSet* result); + // Takes a comma-separated std::string of action type std::string representations and returns + // an int bitmask of the actions. + static Status parseActionSetFromString(const std::string& actionsString, ActionSet* result); - private: + // Takes a vector of action type std::string representations and returns an ActionSet of the + // actions. + static Status parseActionSetFromStringVector(const std::vector<std::string>& actionsVector, + ActionSet* result); - // bitmask of actions this privilege grants - std::bitset<ActionType::NUM_ACTION_TYPES> _actions; - }; +private: + // bitmask of actions this privilege grants + std::bitset<ActionType::NUM_ACTION_TYPES> _actions; +}; - static inline bool operator==(const ActionSet& lhs, const ActionSet& rhs) { - return lhs.equals(rhs); - } +static inline bool operator==(const ActionSet& lhs, const ActionSet& rhs) { + return lhs.equals(rhs); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/action_set_test.cpp b/src/mongo/db/auth/action_set_test.cpp index efe9f6f2761..9689a656549 100644 --- a/src/mongo/db/auth/action_set_test.cpp +++ b/src/mongo/db/auth/action_set_test.cpp @@ -36,125 +36,125 @@ namespace mongo { namespace { - TEST(ActionSetTest, ParseActionSetFromString) { - ActionSet result; - ASSERT_OK(ActionSet::parseActionSetFromString("find,insert,update,remove", &result)); - ASSERT_TRUE(result.contains(ActionType::find)); - ASSERT_TRUE(result.contains(ActionType::insert)); - ASSERT_TRUE(result.contains(ActionType::update)); - ASSERT_TRUE(result.contains(ActionType::remove)); - - // Order of the strings doesn't matter - ASSERT_OK(ActionSet::parseActionSetFromString("update,find,remove,insert", &result)); - ASSERT_TRUE(result.contains(ActionType::find)); - ASSERT_TRUE(result.contains(ActionType::insert)); - ASSERT_TRUE(result.contains(ActionType::update)); - ASSERT_TRUE(result.contains(ActionType::remove)); - - ASSERT_OK(ActionSet::parseActionSetFromString("find", &result)); - - ASSERT_TRUE(result.contains(ActionType::find)); - ASSERT_FALSE(result.contains(ActionType::insert)); - ASSERT_FALSE(result.contains(ActionType::update)); - ASSERT_FALSE(result.contains(ActionType::remove)); - - ASSERT_OK(ActionSet::parseActionSetFromString("", &result)); - - ASSERT_FALSE(result.contains(ActionType::find)); - ASSERT_FALSE(result.contains(ActionType::insert)); - ASSERT_FALSE(result.contains(ActionType::update)); - ASSERT_FALSE(result.contains(ActionType::remove)); - - ASSERT_EQUALS(ErrorCodes::FailedToParse, - ActionSet::parseActionSetFromString("INVALID INPUT", &result).code()); - } - - TEST(ActionSetTest, ToString) { - ActionSet actionSet; - - ASSERT_EQUALS("", actionSet.toString()); - actionSet.addAction(ActionType::find); - ASSERT_EQUALS("find", actionSet.toString()); - actionSet.addAction(ActionType::insert); - ASSERT_EQUALS("find,insert", actionSet.toString()); - actionSet.addAction(ActionType::update); - ASSERT_EQUALS("find,insert,update", actionSet.toString()); - actionSet.addAction(ActionType::remove); - ASSERT_EQUALS("find,insert,remove,update", actionSet.toString()); - - // Now make sure adding actions in a different order doesn't change anything. - ActionSet actionSet2; - ASSERT_EQUALS("", actionSet2.toString()); - actionSet2.addAction(ActionType::insert); - ASSERT_EQUALS("insert", actionSet2.toString()); - actionSet2.addAction(ActionType::remove); - ASSERT_EQUALS("insert,remove", actionSet2.toString()); - actionSet2.addAction(ActionType::find); - ASSERT_EQUALS("find,insert,remove", actionSet2.toString()); - actionSet2.addAction(ActionType::update); - ASSERT_EQUALS("find,insert,remove,update", actionSet2.toString()); - } - - TEST(ActionSetTest, IsSupersetOf) { - ActionSet set1, set2, set3; - ASSERT_OK(ActionSet::parseActionSetFromString("find,update,insert", &set1)); - ASSERT_OK(ActionSet::parseActionSetFromString("find,update,remove", &set2)); - ASSERT_OK(ActionSet::parseActionSetFromString("find,update", &set3)); - - ASSERT_FALSE(set1.isSupersetOf(set2)); - ASSERT_TRUE(set1.isSupersetOf(set3)); - - ASSERT_FALSE(set2.isSupersetOf(set1)); - ASSERT_TRUE(set2.isSupersetOf(set3)); - - ASSERT_FALSE(set3.isSupersetOf(set1)); - ASSERT_FALSE(set3.isSupersetOf(set2)); - } - - TEST(ActionSetTest, anyAction) { - ActionSet set; - - ASSERT_OK(ActionSet::parseActionSetFromString("anyAction", &set)); - ASSERT_TRUE(set.contains(ActionType::find)); - ASSERT_TRUE(set.contains(ActionType::insert)); - ASSERT_TRUE(set.contains(ActionType::anyAction)); - - set.removeAllActions(); - set.addAllActions(); - ASSERT_TRUE(set.contains(ActionType::find)); - ASSERT_TRUE(set.contains(ActionType::insert)); - ASSERT_TRUE(set.contains(ActionType::anyAction)); - - set.removeAllActions(); - set.addAction(ActionType::anyAction); - ASSERT_TRUE(set.contains(ActionType::find)); - ASSERT_TRUE(set.contains(ActionType::insert)); - ASSERT_TRUE(set.contains(ActionType::anyAction)); - - set.removeAction(ActionType::find); - ASSERT_FALSE(set.contains(ActionType::find)); - ASSERT_TRUE(set.contains(ActionType::insert)); - ASSERT_FALSE(set.contains(ActionType::anyAction)); - - set.addAction(ActionType::find); - ASSERT_TRUE(set.contains(ActionType::find)); - ASSERT_TRUE(set.contains(ActionType::insert)); - ASSERT_FALSE(set.contains(ActionType::anyAction)); - - set.addAction(ActionType::anyAction); - ASSERT_TRUE(set.contains(ActionType::find)); - ASSERT_TRUE(set.contains(ActionType::insert)); - ASSERT_TRUE(set.contains(ActionType::anyAction)); - - ASSERT_EQUALS("anyAction", set.toString()); - - set.removeAction(ActionType::anyAction); - ASSERT_TRUE(set.contains(ActionType::find)); - ASSERT_TRUE(set.contains(ActionType::insert)); - ASSERT_FALSE(set.contains(ActionType::anyAction)); - - ASSERT_NOT_EQUALS("anyAction", set.toString()); - } +TEST(ActionSetTest, ParseActionSetFromString) { + ActionSet result; + ASSERT_OK(ActionSet::parseActionSetFromString("find,insert,update,remove", &result)); + ASSERT_TRUE(result.contains(ActionType::find)); + ASSERT_TRUE(result.contains(ActionType::insert)); + ASSERT_TRUE(result.contains(ActionType::update)); + ASSERT_TRUE(result.contains(ActionType::remove)); + + // Order of the strings doesn't matter + ASSERT_OK(ActionSet::parseActionSetFromString("update,find,remove,insert", &result)); + ASSERT_TRUE(result.contains(ActionType::find)); + ASSERT_TRUE(result.contains(ActionType::insert)); + ASSERT_TRUE(result.contains(ActionType::update)); + ASSERT_TRUE(result.contains(ActionType::remove)); + + ASSERT_OK(ActionSet::parseActionSetFromString("find", &result)); + + ASSERT_TRUE(result.contains(ActionType::find)); + ASSERT_FALSE(result.contains(ActionType::insert)); + ASSERT_FALSE(result.contains(ActionType::update)); + ASSERT_FALSE(result.contains(ActionType::remove)); + + ASSERT_OK(ActionSet::parseActionSetFromString("", &result)); + + ASSERT_FALSE(result.contains(ActionType::find)); + ASSERT_FALSE(result.contains(ActionType::insert)); + ASSERT_FALSE(result.contains(ActionType::update)); + ASSERT_FALSE(result.contains(ActionType::remove)); + + ASSERT_EQUALS(ErrorCodes::FailedToParse, + ActionSet::parseActionSetFromString("INVALID INPUT", &result).code()); +} + +TEST(ActionSetTest, ToString) { + ActionSet actionSet; + + ASSERT_EQUALS("", actionSet.toString()); + actionSet.addAction(ActionType::find); + ASSERT_EQUALS("find", actionSet.toString()); + actionSet.addAction(ActionType::insert); + ASSERT_EQUALS("find,insert", actionSet.toString()); + actionSet.addAction(ActionType::update); + ASSERT_EQUALS("find,insert,update", actionSet.toString()); + actionSet.addAction(ActionType::remove); + ASSERT_EQUALS("find,insert,remove,update", actionSet.toString()); + + // Now make sure adding actions in a different order doesn't change anything. + ActionSet actionSet2; + ASSERT_EQUALS("", actionSet2.toString()); + actionSet2.addAction(ActionType::insert); + ASSERT_EQUALS("insert", actionSet2.toString()); + actionSet2.addAction(ActionType::remove); + ASSERT_EQUALS("insert,remove", actionSet2.toString()); + actionSet2.addAction(ActionType::find); + ASSERT_EQUALS("find,insert,remove", actionSet2.toString()); + actionSet2.addAction(ActionType::update); + ASSERT_EQUALS("find,insert,remove,update", actionSet2.toString()); +} + +TEST(ActionSetTest, IsSupersetOf) { + ActionSet set1, set2, set3; + ASSERT_OK(ActionSet::parseActionSetFromString("find,update,insert", &set1)); + ASSERT_OK(ActionSet::parseActionSetFromString("find,update,remove", &set2)); + ASSERT_OK(ActionSet::parseActionSetFromString("find,update", &set3)); + + ASSERT_FALSE(set1.isSupersetOf(set2)); + ASSERT_TRUE(set1.isSupersetOf(set3)); + + ASSERT_FALSE(set2.isSupersetOf(set1)); + ASSERT_TRUE(set2.isSupersetOf(set3)); + + ASSERT_FALSE(set3.isSupersetOf(set1)); + ASSERT_FALSE(set3.isSupersetOf(set2)); +} + +TEST(ActionSetTest, anyAction) { + ActionSet set; + + ASSERT_OK(ActionSet::parseActionSetFromString("anyAction", &set)); + ASSERT_TRUE(set.contains(ActionType::find)); + ASSERT_TRUE(set.contains(ActionType::insert)); + ASSERT_TRUE(set.contains(ActionType::anyAction)); + + set.removeAllActions(); + set.addAllActions(); + ASSERT_TRUE(set.contains(ActionType::find)); + ASSERT_TRUE(set.contains(ActionType::insert)); + ASSERT_TRUE(set.contains(ActionType::anyAction)); + + set.removeAllActions(); + set.addAction(ActionType::anyAction); + ASSERT_TRUE(set.contains(ActionType::find)); + ASSERT_TRUE(set.contains(ActionType::insert)); + ASSERT_TRUE(set.contains(ActionType::anyAction)); + + set.removeAction(ActionType::find); + ASSERT_FALSE(set.contains(ActionType::find)); + ASSERT_TRUE(set.contains(ActionType::insert)); + ASSERT_FALSE(set.contains(ActionType::anyAction)); + + set.addAction(ActionType::find); + ASSERT_TRUE(set.contains(ActionType::find)); + ASSERT_TRUE(set.contains(ActionType::insert)); + ASSERT_FALSE(set.contains(ActionType::anyAction)); + + set.addAction(ActionType::anyAction); + ASSERT_TRUE(set.contains(ActionType::find)); + ASSERT_TRUE(set.contains(ActionType::insert)); + ASSERT_TRUE(set.contains(ActionType::anyAction)); + + ASSERT_EQUALS("anyAction", set.toString()); + + set.removeAction(ActionType::anyAction); + ASSERT_TRUE(set.contains(ActionType::find)); + ASSERT_TRUE(set.contains(ActionType::insert)); + ASSERT_FALSE(set.contains(ActionType::anyAction)); + + ASSERT_NOT_EQUALS("anyAction", set.toString()); +} } // namespace } // namespace mongo diff --git a/src/mongo/db/auth/auth_decorations.cpp b/src/mongo/db/auth/auth_decorations.cpp index cbd4ca3083a..bc93e494ff2 100644 --- a/src/mongo/db/auth/auth_decorations.cpp +++ b/src/mongo/db/auth/auth_decorations.cpp @@ -45,99 +45,93 @@ namespace mongo { namespace { - MONGO_INITIALIZER_WITH_PREREQUISITES(CreateAuthorizationManager, - ("SetupInternalSecurityUser", - "OIDGeneration", - "SetGlobalEnvironment", - "CreateAuthorizationExternalStateFactory", - "EndStartupOptionStorage")) - (InitializerContext* context) { - auto authzManager = stdx::make_unique<AuthorizationManager>( - AuthzManagerExternalState::create()); - authzManager->setAuthEnabled(serverGlobalParams.isAuthEnabled); - AuthorizationManager::set(getGlobalServiceContext(), std::move(authzManager)); - return Status::OK(); +MONGO_INITIALIZER_WITH_PREREQUISITES(CreateAuthorizationManager, + ("SetupInternalSecurityUser", + "OIDGeneration", + "SetGlobalEnvironment", + "CreateAuthorizationExternalStateFactory", + "EndStartupOptionStorage")) +(InitializerContext* context) { + auto authzManager = + stdx::make_unique<AuthorizationManager>(AuthzManagerExternalState::create()); + authzManager->setAuthEnabled(serverGlobalParams.isAuthEnabled); + AuthorizationManager::set(getGlobalServiceContext(), std::move(authzManager)); + return Status::OK(); +} + +const auto getAuthenticationSession = + ClientBasic::declareDecoration<std::unique_ptr<AuthenticationSession>>(); + +const auto getAuthorizationManager = + ServiceContext::declareDecoration<std::unique_ptr<AuthorizationManager>>(); + +const auto getAuthorizationSession = + ClientBasic::declareDecoration<std::unique_ptr<AuthorizationSession>>(); + +class AuthzClientObserver final : public ServiceContext::ClientObserver { +public: + void onCreateClient(Client* client) override { + auto service = client->getServiceContext(); + AuthorizationSession::set(client, + AuthorizationManager::get(service)->makeAuthorizationSession()); } - const auto getAuthenticationSession = - ClientBasic::declareDecoration<std::unique_ptr<AuthenticationSession>>(); + void onDestroyClient(Client* client) override {} - const auto getAuthorizationManager = - ServiceContext::declareDecoration<std::unique_ptr<AuthorizationManager>>(); - - const auto getAuthorizationSession = - ClientBasic::declareDecoration<std::unique_ptr<AuthorizationSession>>(); - - class AuthzClientObserver final : public ServiceContext::ClientObserver { - public: - void onCreateClient(Client* client) override { - auto service = client->getServiceContext(); - AuthorizationSession::set( - client, - AuthorizationManager::get(service)->makeAuthorizationSession()); - } - - void onDestroyClient(Client* client) override {} - - void onCreateOperationContext(OperationContext* opCtx) override {} - void onDestroyOperationContext(OperationContext* opCtx) override {} - }; + void onCreateOperationContext(OperationContext* opCtx) override {} + void onDestroyOperationContext(OperationContext* opCtx) override {} +}; } // namespace - void AuthenticationSession::set( - ClientBasic* client, - std::unique_ptr<AuthenticationSession> newSession) { - getAuthenticationSession(client) = std::move(newSession); - } - - void AuthenticationSession::swap( - ClientBasic* client, - std::unique_ptr<AuthenticationSession>& other) { - using std::swap; - swap(getAuthenticationSession(client), other); - } - - AuthorizationManager* AuthorizationManager::get(ServiceContext* service) { - return getAuthorizationManager(service).get(); - } - - AuthorizationManager* AuthorizationManager::get(ServiceContext& service) { - return getAuthorizationManager(service).get(); - } - - void AuthorizationManager::set(ServiceContext* service, - std::unique_ptr<AuthorizationManager> authzManager) { - auto& manager = getAuthorizationManager(service); - invariant(authzManager); - invariant(!manager); - manager = std::move(authzManager); - service->registerClientObserver(stdx::make_unique<AuthzClientObserver>()); - } - - AuthorizationSession* AuthorizationSession::get(ClientBasic* client) { - return get(*client); - } - - AuthorizationSession* AuthorizationSession::get(ClientBasic& client) { - AuthorizationSession* retval = getAuthorizationSession(client).get(); - massert(16481, - "No AuthorizationManager has been set up for this connection", - retval); - return retval; - } - - bool AuthorizationSession::exists(ClientBasic* client) { - return getAuthorizationSession(client).get(); - } - - void AuthorizationSession::set( - ClientBasic* client, - std::unique_ptr<AuthorizationSession> authorizationSession) { - auto& authzSession = getAuthorizationSession(client); - invariant(authorizationSession); - invariant(!authzSession); - authzSession = std::move(authorizationSession); - } +void AuthenticationSession::set(ClientBasic* client, + std::unique_ptr<AuthenticationSession> newSession) { + getAuthenticationSession(client) = std::move(newSession); +} + +void AuthenticationSession::swap(ClientBasic* client, + std::unique_ptr<AuthenticationSession>& other) { + using std::swap; + swap(getAuthenticationSession(client), other); +} + +AuthorizationManager* AuthorizationManager::get(ServiceContext* service) { + return getAuthorizationManager(service).get(); +} + +AuthorizationManager* AuthorizationManager::get(ServiceContext& service) { + return getAuthorizationManager(service).get(); +} + +void AuthorizationManager::set(ServiceContext* service, + std::unique_ptr<AuthorizationManager> authzManager) { + auto& manager = getAuthorizationManager(service); + invariant(authzManager); + invariant(!manager); + manager = std::move(authzManager); + service->registerClientObserver(stdx::make_unique<AuthzClientObserver>()); +} + +AuthorizationSession* AuthorizationSession::get(ClientBasic* client) { + return get(*client); +} + +AuthorizationSession* AuthorizationSession::get(ClientBasic& client) { + AuthorizationSession* retval = getAuthorizationSession(client).get(); + massert(16481, "No AuthorizationManager has been set up for this connection", retval); + return retval; +} + +bool AuthorizationSession::exists(ClientBasic* client) { + return getAuthorizationSession(client).get(); +} + +void AuthorizationSession::set(ClientBasic* client, + std::unique_ptr<AuthorizationSession> authorizationSession) { + auto& authzSession = getAuthorizationSession(client); + invariant(authorizationSession); + invariant(!authzSession); + authzSession = std::move(authorizationSession); +} } // namespace mongo diff --git a/src/mongo/db/auth/auth_index_d.cpp b/src/mongo/db/auth/auth_index_d.cpp index 8cbd093c2c7..546df4c1da9 100644 --- a/src/mongo/db/auth/auth_index_d.cpp +++ b/src/mongo/db/auth/auth_index_d.cpp @@ -46,85 +46,79 @@ namespace mongo { - using std::endl; +using std::endl; namespace authindex { namespace { - BSONObj v1SystemUsersKeyPattern; - BSONObj v3SystemUsersKeyPattern; - BSONObj v3SystemRolesKeyPattern; - std::string v3SystemUsersIndexName; - std::string v3SystemRolesIndexName; - - MONGO_INITIALIZER(AuthIndexKeyPatterns)(InitializerContext*) { - v1SystemUsersKeyPattern = BSON("user" << 1 << "userSource" << 1); - v3SystemUsersKeyPattern = BSON(AuthorizationManager::USER_NAME_FIELD_NAME << 1 << - AuthorizationManager::USER_DB_FIELD_NAME << 1); - v3SystemRolesKeyPattern = BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << 1 << - AuthorizationManager::ROLE_DB_FIELD_NAME << 1); - v3SystemUsersIndexName = std::string( - str::stream() << - AuthorizationManager::USER_NAME_FIELD_NAME << "_1_" << - AuthorizationManager::USER_DB_FIELD_NAME << "_1"); - v3SystemRolesIndexName = std::string( - str::stream() << - AuthorizationManager::ROLE_NAME_FIELD_NAME << "_1_" << - AuthorizationManager::ROLE_DB_FIELD_NAME << "_1"); - - return Status::OK(); - } +BSONObj v1SystemUsersKeyPattern; +BSONObj v3SystemUsersKeyPattern; +BSONObj v3SystemRolesKeyPattern; +std::string v3SystemUsersIndexName; +std::string v3SystemRolesIndexName; + +MONGO_INITIALIZER(AuthIndexKeyPatterns)(InitializerContext*) { + v1SystemUsersKeyPattern = BSON("user" << 1 << "userSource" << 1); + v3SystemUsersKeyPattern = BSON(AuthorizationManager::USER_NAME_FIELD_NAME + << 1 << AuthorizationManager::USER_DB_FIELD_NAME << 1); + v3SystemRolesKeyPattern = BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME + << 1 << AuthorizationManager::ROLE_DB_FIELD_NAME << 1); + v3SystemUsersIndexName = + std::string(str::stream() << AuthorizationManager::USER_NAME_FIELD_NAME << "_1_" + << AuthorizationManager::USER_DB_FIELD_NAME << "_1"); + v3SystemRolesIndexName = + std::string(str::stream() << AuthorizationManager::ROLE_NAME_FIELD_NAME << "_1_" + << AuthorizationManager::ROLE_DB_FIELD_NAME << "_1"); + + return Status::OK(); +} } // namespace - Status verifySystemIndexes(OperationContext* txn) { - const NamespaceString systemUsers = AuthorizationManager::usersCollectionNamespace; +Status verifySystemIndexes(OperationContext* txn) { + const NamespaceString systemUsers = AuthorizationManager::usersCollectionNamespace; - // Make sure the old unique index from v2.4 on system.users doesn't exist. - ScopedTransaction scopedXact(txn, MODE_IX); - AutoGetDb autoDb(txn, systemUsers.db(), MODE_X); - if (!autoDb.getDb()) { - return Status::OK(); - } - - Collection* collection = autoDb.getDb()->getCollection(NamespaceString(systemUsers)); - if (!collection) { - return Status::OK(); - } + // Make sure the old unique index from v2.4 on system.users doesn't exist. + ScopedTransaction scopedXact(txn, MODE_IX); + AutoGetDb autoDb(txn, systemUsers.db(), MODE_X); + if (!autoDb.getDb()) { + return Status::OK(); + } - IndexCatalog* indexCatalog = collection->getIndexCatalog(); - IndexDescriptor* oldIndex = NULL; + Collection* collection = autoDb.getDb()->getCollection(NamespaceString(systemUsers)); + if (!collection) { + return Status::OK(); + } - if (indexCatalog && - (oldIndex = indexCatalog->findIndexByKeyPattern(txn, v1SystemUsersKeyPattern))) { - return Status(ErrorCodes::AuthSchemaIncompatible, - "Old 2.4 style user index identified. " - "The authentication schema needs to be updated by " - "running authSchemaUpgrade on a 2.6 server."); - } + IndexCatalog* indexCatalog = collection->getIndexCatalog(); + IndexDescriptor* oldIndex = NULL; - return Status::OK(); + if (indexCatalog && + (oldIndex = indexCatalog->findIndexByKeyPattern(txn, v1SystemUsersKeyPattern))) { + return Status(ErrorCodes::AuthSchemaIncompatible, + "Old 2.4 style user index identified. " + "The authentication schema needs to be updated by " + "running authSchemaUpgrade on a 2.6 server."); } - void createSystemIndexes(OperationContext* txn, Collection* collection) { - invariant( collection ); - const NamespaceString& ns = collection->ns(); - if (ns == AuthorizationManager::usersCollectionNamespace) { - collection->getIndexCatalog()->createIndexOnEmptyCollection( - txn, - BSON("name" << v3SystemUsersIndexName - << "ns" << collection->ns().ns() - << "key" << v3SystemUsersKeyPattern - << "unique" << true)); - } else if (ns == AuthorizationManager::rolesCollectionNamespace) { - collection->getIndexCatalog()->createIndexOnEmptyCollection( - txn, - BSON("name" << v3SystemRolesIndexName - << "ns" << collection->ns().ns() - << "key" << v3SystemRolesKeyPattern - << "unique" << true)); - } + return Status::OK(); +} + +void createSystemIndexes(OperationContext* txn, Collection* collection) { + invariant(collection); + const NamespaceString& ns = collection->ns(); + if (ns == AuthorizationManager::usersCollectionNamespace) { + collection->getIndexCatalog()->createIndexOnEmptyCollection( + txn, + BSON("name" << v3SystemUsersIndexName << "ns" << collection->ns().ns() << "key" + << v3SystemUsersKeyPattern << "unique" << true)); + } else if (ns == AuthorizationManager::rolesCollectionNamespace) { + collection->getIndexCatalog()->createIndexOnEmptyCollection( + txn, + BSON("name" << v3SystemRolesIndexName << "ns" << collection->ns().ns() << "key" + << v3SystemRolesKeyPattern << "unique" << true)); } +} } // namespace authindex } // namespace mongo diff --git a/src/mongo/db/auth/auth_index_d.h b/src/mongo/db/auth/auth_index_d.h index b643e7c8a11..9b85e02e000 100644 --- a/src/mongo/db/auth/auth_index_d.h +++ b/src/mongo/db/auth/auth_index_d.h @@ -32,22 +32,22 @@ namespace mongo { - class Collection; - class OperationContext; +class Collection; +class OperationContext; namespace authindex { - /** - * Creates the appropriate indexes on _new_ system collections supporting authentication and - * authorization. - */ - void createSystemIndexes(OperationContext* txn, Collection* collection); - - /** - * Verifies that only the appropriate indexes to support authentication and authorization - * are present in the admin database - */ - Status verifySystemIndexes(OperationContext* txn); +/** + * Creates the appropriate indexes on _new_ system collections supporting authentication and + * authorization. + */ +void createSystemIndexes(OperationContext* txn, Collection* collection); + +/** + * Verifies that only the appropriate indexes to support authentication and authorization + * are present in the admin database + */ +Status verifySystemIndexes(OperationContext* txn); } // namespace authindex } // namespace mongo diff --git a/src/mongo/db/auth/authentication_session.h b/src/mongo/db/auth/authentication_session.h index 8870abefc76..1c3b34b16ee 100644 --- a/src/mongo/db/auth/authentication_session.h +++ b/src/mongo/db/auth/authentication_session.h @@ -33,44 +33,47 @@ namespace mongo { - class ClientBasic; +class ClientBasic; + +/** + * Abstract type representing an ongoing authentication session. + * + * An example subclass is MongoAuthenticationSession. + */ +class AuthenticationSession { + MONGO_DISALLOW_COPYING(AuthenticationSession); + +public: + enum SessionType { + SESSION_TYPE_MONGO, // The mongo-specific challenge-response authentication mechanism. + SESSION_TYPE_SASL // SASL authentication mechanism. + }; /** - * Abstract type representing an ongoing authentication session. - * - * An example subclass is MongoAuthenticationSession. + * Sets the authentication session for the given "client" to "newSession". */ - class AuthenticationSession { - MONGO_DISALLOW_COPYING(AuthenticationSession); - public: - enum SessionType { - SESSION_TYPE_MONGO, // The mongo-specific challenge-response authentication mechanism. - SESSION_TYPE_SASL // SASL authentication mechanism. - }; - - /** - * Sets the authentication session for the given "client" to "newSession". - */ - static void set(ClientBasic* client, std::unique_ptr<AuthenticationSession> newSession); + static void set(ClientBasic* client, std::unique_ptr<AuthenticationSession> newSession); - /** - * Swaps "client"'s current authentication session with "other". - */ - static void swap(ClientBasic* client, std::unique_ptr<AuthenticationSession>& other); + /** + * Swaps "client"'s current authentication session with "other". + */ + static void swap(ClientBasic* client, std::unique_ptr<AuthenticationSession>& other); - virtual ~AuthenticationSession() = default; + virtual ~AuthenticationSession() = default; - /** - * Return an identifer of the type of session, so that a caller can safely cast it and - * extract the type-specific data stored within. - */ - SessionType getType() const { return _sessionType; } + /** + * Return an identifer of the type of session, so that a caller can safely cast it and + * extract the type-specific data stored within. + */ + SessionType getType() const { + return _sessionType; + } - protected: - explicit AuthenticationSession(SessionType sessionType) : _sessionType(sessionType) {} +protected: + explicit AuthenticationSession(SessionType sessionType) : _sessionType(sessionType) {} - private: - const SessionType _sessionType; - }; +private: + const SessionType _sessionType; +}; } // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index 016d8da33c6..30123e78177 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -64,428 +64,418 @@ namespace mongo { - using std::endl; - using std::string; - using std::vector; - - AuthInfo internalSecurity; - - MONGO_INITIALIZER_WITH_PREREQUISITES(SetupInternalSecurityUser, MONGO_NO_PREREQUISITES)( - InitializerContext* context) { - - User* user = new User(UserName("__system", "local")); - - user->incrementRefCount(); // Pin this user so the ref count never drops below 1. - ActionSet allActions; - allActions.addAllActions(); - PrivilegeVector privileges; - RoleGraph::generateUniversalPrivileges(&privileges); - user->addPrivileges(privileges); - internalSecurity.user = user; - - return Status::OK(); - } - - const std::string AuthorizationManager::USER_NAME_FIELD_NAME = "user"; - const std::string AuthorizationManager::USER_DB_FIELD_NAME = "db"; - const std::string AuthorizationManager::ROLE_NAME_FIELD_NAME = "role"; - const std::string AuthorizationManager::ROLE_DB_FIELD_NAME = "db"; - const std::string AuthorizationManager::PASSWORD_FIELD_NAME = "pwd"; - const std::string AuthorizationManager::V1_USER_NAME_FIELD_NAME = "user"; - const std::string AuthorizationManager::V1_USER_SOURCE_FIELD_NAME = "userSource"; - - const NamespaceString AuthorizationManager::adminCommandNamespace("admin.$cmd"); - const NamespaceString AuthorizationManager::rolesCollectionNamespace("admin.system.roles"); - const NamespaceString AuthorizationManager::usersAltCollectionNamespace( - "admin.system.new_users"); - const NamespaceString AuthorizationManager::usersBackupCollectionNamespace( - "admin.system.backup_users"); - const NamespaceString AuthorizationManager::usersCollectionNamespace("admin.system.users"); - const NamespaceString AuthorizationManager::versionCollectionNamespace("admin.system.version"); - const NamespaceString AuthorizationManager::defaultTempUsersCollectionNamespace( - "admin.tempusers"); - const NamespaceString AuthorizationManager::defaultTempRolesCollectionNamespace( - "admin.temproles"); - - const BSONObj AuthorizationManager::versionDocumentQuery = BSON("_id" << "authSchema"); - - const std::string AuthorizationManager::schemaVersionFieldName = "currentVersion"; +using std::endl; +using std::string; +using std::vector; + +AuthInfo internalSecurity; + +MONGO_INITIALIZER_WITH_PREREQUISITES(SetupInternalSecurityUser, + MONGO_NO_PREREQUISITES)(InitializerContext* context) { + User* user = new User(UserName("__system", "local")); + + user->incrementRefCount(); // Pin this user so the ref count never drops below 1. + ActionSet allActions; + allActions.addAllActions(); + PrivilegeVector privileges; + RoleGraph::generateUniversalPrivileges(&privileges); + user->addPrivileges(privileges); + internalSecurity.user = user; + + return Status::OK(); +} + +const std::string AuthorizationManager::USER_NAME_FIELD_NAME = "user"; +const std::string AuthorizationManager::USER_DB_FIELD_NAME = "db"; +const std::string AuthorizationManager::ROLE_NAME_FIELD_NAME = "role"; +const std::string AuthorizationManager::ROLE_DB_FIELD_NAME = "db"; +const std::string AuthorizationManager::PASSWORD_FIELD_NAME = "pwd"; +const std::string AuthorizationManager::V1_USER_NAME_FIELD_NAME = "user"; +const std::string AuthorizationManager::V1_USER_SOURCE_FIELD_NAME = "userSource"; + +const NamespaceString AuthorizationManager::adminCommandNamespace("admin.$cmd"); +const NamespaceString AuthorizationManager::rolesCollectionNamespace("admin.system.roles"); +const NamespaceString AuthorizationManager::usersAltCollectionNamespace("admin.system.new_users"); +const NamespaceString AuthorizationManager::usersBackupCollectionNamespace( + "admin.system.backup_users"); +const NamespaceString AuthorizationManager::usersCollectionNamespace("admin.system.users"); +const NamespaceString AuthorizationManager::versionCollectionNamespace("admin.system.version"); +const NamespaceString AuthorizationManager::defaultTempUsersCollectionNamespace("admin.tempusers"); +const NamespaceString AuthorizationManager::defaultTempRolesCollectionNamespace("admin.temproles"); + +const BSONObj AuthorizationManager::versionDocumentQuery = BSON("_id" + << "authSchema"); + +const std::string AuthorizationManager::schemaVersionFieldName = "currentVersion"; #ifndef _MSC_EXTENSIONS - const int AuthorizationManager::schemaVersion24; - const int AuthorizationManager::schemaVersion26Upgrade; - const int AuthorizationManager::schemaVersion26Final; - const int AuthorizationManager::schemaVersion28SCRAM; +const int AuthorizationManager::schemaVersion24; +const int AuthorizationManager::schemaVersion26Upgrade; +const int AuthorizationManager::schemaVersion26Final; +const int AuthorizationManager::schemaVersion28SCRAM; #endif +/** + * Guard object for synchronizing accesses to data cached in AuthorizationManager instances. + * This guard allows one thread to access the cache at a time, and provides an exception-safe + * mechanism for a thread to release the cache mutex while performing network or disk operations + * while allowing other readers to proceed. + * + * There are two ways to use this guard. One may simply instantiate the guard like a + * std::lock_guard, and perform reads or writes of the cache. + * + * Alternatively, one may instantiate the guard, examine the cache, and then enter into an + * update mode by first wait()ing until otherUpdateInFetchPhase() is false, and then + * calling beginFetchPhase(). At this point, other threads may acquire the guard in the simple + * manner and do reads, but other threads may not enter into a fetch phase. During the fetch + * phase, the thread should perform required network or disk activity to determine what update + * it will make to the cache. Then, it should call endFetchPhase(), to reacquire the user cache + * mutex. At that point, the thread can make its modifications to the cache and let the guard + * go out of scope. + * + * All updates by guards using a fetch-phase are totally ordered with respect to one another, + * and all guards using no fetch phase are totally ordered with respect to one another, but + * there is not a total ordering among all guard objects. + * + * The cached data has an associated counter, called the cache generation. If the cache + * generation changes while a guard is in fetch phase, the fetched data should not be stored + * into the cache, because some invalidation event occurred during the fetch phase. + * + * NOTE: It is not safe to enter fetch phase while holding a database lock. Fetch phase + * operations are allowed to acquire database locks themselves, so entering fetch while holding + * a database lock may lead to deadlock. + */ +class AuthorizationManager::CacheGuard { + MONGO_DISALLOW_COPYING(CacheGuard); + +public: + enum FetchSynchronization { fetchSynchronizationAutomatic, fetchSynchronizationManual }; + /** - * Guard object for synchronizing accesses to data cached in AuthorizationManager instances. - * This guard allows one thread to access the cache at a time, and provides an exception-safe - * mechanism for a thread to release the cache mutex while performing network or disk operations - * while allowing other readers to proceed. - * - * There are two ways to use this guard. One may simply instantiate the guard like a - * std::lock_guard, and perform reads or writes of the cache. - * - * Alternatively, one may instantiate the guard, examine the cache, and then enter into an - * update mode by first wait()ing until otherUpdateInFetchPhase() is false, and then - * calling beginFetchPhase(). At this point, other threads may acquire the guard in the simple - * manner and do reads, but other threads may not enter into a fetch phase. During the fetch - * phase, the thread should perform required network or disk activity to determine what update - * it will make to the cache. Then, it should call endFetchPhase(), to reacquire the user cache - * mutex. At that point, the thread can make its modifications to the cache and let the guard - * go out of scope. - * - * All updates by guards using a fetch-phase are totally ordered with respect to one another, - * and all guards using no fetch phase are totally ordered with respect to one another, but - * there is not a total ordering among all guard objects. - * - * The cached data has an associated counter, called the cache generation. If the cache - * generation changes while a guard is in fetch phase, the fetched data should not be stored - * into the cache, because some invalidation event occurred during the fetch phase. - * - * NOTE: It is not safe to enter fetch phase while holding a database lock. Fetch phase - * operations are allowed to acquire database locks themselves, so entering fetch while holding - * a database lock may lead to deadlock. + * Constructs a cache guard, locking the mutex that synchronizes user cache accesses. */ - class AuthorizationManager::CacheGuard { - MONGO_DISALLOW_COPYING(CacheGuard); - public: - enum FetchSynchronization { - fetchSynchronizationAutomatic, - fetchSynchronizationManual - }; - - /** - * Constructs a cache guard, locking the mutex that synchronizes user cache accesses. - */ - CacheGuard(AuthorizationManager* authzManager, - const FetchSynchronization sync = fetchSynchronizationAutomatic) : - _isThisGuardInFetchPhase(false), - _authzManager(authzManager), - _lock(authzManager->_cacheMutex) { - - if (fetchSynchronizationAutomatic == sync) { - synchronizeWithFetchPhase(); - } - } - - /** - * Releases the mutex that synchronizes user cache access, if held, and notifies - * any threads waiting for their own opportunity to update the user cache. - */ - ~CacheGuard() { - if (!_lock.owns_lock()) { - _lock.lock(); - } - if (_isThisGuardInFetchPhase) { - fassert(17190, _authzManager->_isFetchPhaseBusy); - _authzManager->_isFetchPhaseBusy = false; - _authzManager->_fetchPhaseIsReady.notify_all(); - } - } - - /** - * Returns true of the authzManager reports that it is in fetch phase. - */ - bool otherUpdateInFetchPhase() { return _authzManager->_isFetchPhaseBusy; } - - /** - * Waits on the _authzManager->_fetchPhaseIsReady condition. - */ - void wait() { - fassert(17222, !_isThisGuardInFetchPhase); - _authzManager->_fetchPhaseIsReady.wait(_lock); - } - - /** - * Enters fetch phase, releasing the _authzManager->_cacheMutex after recording the current - * cache generation. - */ - void beginFetchPhase() { - fassert(17191, !_authzManager->_isFetchPhaseBusy); - _isThisGuardInFetchPhase = true; - _authzManager->_isFetchPhaseBusy = true; - _startGeneration = _authzManager->_cacheGeneration; - _lock.unlock(); + CacheGuard(AuthorizationManager* authzManager, + const FetchSynchronization sync = fetchSynchronizationAutomatic) + : _isThisGuardInFetchPhase(false), + _authzManager(authzManager), + _lock(authzManager->_cacheMutex) { + if (fetchSynchronizationAutomatic == sync) { + synchronizeWithFetchPhase(); } + } - /** - * Exits the fetch phase, reacquiring the _authzManager->_cacheMutex. - */ - void endFetchPhase() { + /** + * Releases the mutex that synchronizes user cache access, if held, and notifies + * any threads waiting for their own opportunity to update the user cache. + */ + ~CacheGuard() { + if (!_lock.owns_lock()) { _lock.lock(); - // We do not clear _authzManager->_isFetchPhaseBusy or notify waiters until - // ~CacheGuard(), for two reasons. First, there's no value to notifying the waiters - // before you're ready to release the mutex, because they'll just go to sleep on the - // mutex. Second, in order to meaningfully check the preconditions of - // isSameCacheGeneration(), we need a state that means "fetch phase was entered and now - // has been exited." That state is _isThisGuardInFetchPhase == true and - // _lock.owns_lock() == true. } - - /** - * Returns true if _authzManager->_cacheGeneration remained the same while this guard was - * in fetch phase. Behavior is undefined if this guard never entered fetch phase. - * - * If this returns true, do not update the cached data with this - */ - bool isSameCacheGeneration() const { - fassert(17223, _isThisGuardInFetchPhase); - fassert(17231, _lock.owns_lock()); - return _startGeneration == _authzManager->_cacheGeneration; - } - - private: - void synchronizeWithFetchPhase() { - while (otherUpdateInFetchPhase()) - wait(); - fassert(17192, !_authzManager->_isFetchPhaseBusy); - _isThisGuardInFetchPhase = true; - _authzManager->_isFetchPhaseBusy = true; - } - - OID _startGeneration; - bool _isThisGuardInFetchPhase; - AuthorizationManager* _authzManager; - stdx::unique_lock<stdx::mutex> _lock; - }; - - AuthorizationManager::AuthorizationManager( - std::unique_ptr<AuthzManagerExternalState> externalState) : - _authEnabled(false), - _privilegeDocsExist(false), - _externalState(std::move(externalState)), - _version(schemaVersionInvalid), - _isFetchPhaseBusy(false) { - _updateCacheGeneration_inlock(); - } - - AuthorizationManager::~AuthorizationManager() { - for (unordered_map<UserName, User*>::iterator it = _userCache.begin(); - it != _userCache.end(); ++it) { - fassert(17265, it->second != internalSecurity.user); - delete it->second ; + if (_isThisGuardInFetchPhase) { + fassert(17190, _authzManager->_isFetchPhaseBusy); + _authzManager->_isFetchPhaseBusy = false; + _authzManager->_fetchPhaseIsReady.notify_all(); } } - std::unique_ptr<AuthorizationSession> AuthorizationManager::makeAuthorizationSession() { - return stdx::make_unique<AuthorizationSession>( - _externalState->makeAuthzSessionExternalState(this)); - } - - Status AuthorizationManager::getAuthorizationVersion(OperationContext* txn, int* version) { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - int newVersion = _version; - if (schemaVersionInvalid == newVersion) { - while (guard.otherUpdateInFetchPhase()) - guard.wait(); - guard.beginFetchPhase(); - Status status = _externalState->getStoredAuthorizationVersion(txn, &newVersion); - guard.endFetchPhase(); - if (!status.isOK()) { - warning() << "Problem fetching the stored schema version of authorization data: " - << status; - *version = schemaVersionInvalid; - return status; - } - - if (guard.isSameCacheGeneration()) { - _version = newVersion; - } - } - *version = newVersion; - return Status::OK(); + /** + * Returns true of the authzManager reports that it is in fetch phase. + */ + bool otherUpdateInFetchPhase() { + return _authzManager->_isFetchPhaseBusy; } - OID AuthorizationManager::getCacheGeneration() { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - return _cacheGeneration; + /** + * Waits on the _authzManager->_fetchPhaseIsReady condition. + */ + void wait() { + fassert(17222, !_isThisGuardInFetchPhase); + _authzManager->_fetchPhaseIsReady.wait(_lock); } - void AuthorizationManager::setAuthEnabled(bool enabled) { - _authEnabled = enabled; + /** + * Enters fetch phase, releasing the _authzManager->_cacheMutex after recording the current + * cache generation. + */ + void beginFetchPhase() { + fassert(17191, !_authzManager->_isFetchPhaseBusy); + _isThisGuardInFetchPhase = true; + _authzManager->_isFetchPhaseBusy = true; + _startGeneration = _authzManager->_cacheGeneration; + _lock.unlock(); } - bool AuthorizationManager::isAuthEnabled() const { - return _authEnabled; + /** + * Exits the fetch phase, reacquiring the _authzManager->_cacheMutex. + */ + void endFetchPhase() { + _lock.lock(); + // We do not clear _authzManager->_isFetchPhaseBusy or notify waiters until + // ~CacheGuard(), for two reasons. First, there's no value to notifying the waiters + // before you're ready to release the mutex, because they'll just go to sleep on the + // mutex. Second, in order to meaningfully check the preconditions of + // isSameCacheGeneration(), we need a state that means "fetch phase was entered and now + // has been exited." That state is _isThisGuardInFetchPhase == true and + // _lock.owns_lock() == true. } - bool AuthorizationManager::hasAnyPrivilegeDocuments(OperationContext* txn) { - stdx::unique_lock<stdx::mutex> lk(_privilegeDocsExistMutex); - if (_privilegeDocsExist) { - // If we know that a user exists, don't re-check. - return true; - } - - lk.unlock(); - bool privDocsExist = _externalState->hasAnyPrivilegeDocuments(txn); - lk.lock(); - - if (privDocsExist) { - _privilegeDocsExist = true; - } - - return _privilegeDocsExist; + /** + * Returns true if _authzManager->_cacheGeneration remained the same while this guard was + * in fetch phase. Behavior is undefined if this guard never entered fetch phase. + * + * If this returns true, do not update the cached data with this + */ + bool isSameCacheGeneration() const { + fassert(17223, _isThisGuardInFetchPhase); + fassert(17231, _lock.owns_lock()); + return _startGeneration == _authzManager->_cacheGeneration; } - Status AuthorizationManager::getBSONForPrivileges(const PrivilegeVector& privileges, - mutablebson::Element resultArray) { - for (PrivilegeVector::const_iterator it = privileges.begin(); - it != privileges.end(); ++it) { - std::string errmsg; - ParsedPrivilege privilege; - if (!ParsedPrivilege::privilegeToParsedPrivilege(*it, &privilege, &errmsg)) { - return Status(ErrorCodes::BadValue, errmsg); - } - resultArray.appendObject("privileges", privilege.toBSON()); - } - return Status::OK(); +private: + void synchronizeWithFetchPhase() { + while (otherUpdateInFetchPhase()) + wait(); + fassert(17192, !_authzManager->_isFetchPhaseBusy); + _isThisGuardInFetchPhase = true; + _authzManager->_isFetchPhaseBusy = true; } - Status AuthorizationManager::getBSONForRole(RoleGraph* graph, - const RoleName& roleName, - mutablebson::Element result) { - if (!graph->roleExists(roleName)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << roleName.getFullName() << - "does not name an existing role"); - } - std::string id = mongoutils::str::stream() << roleName.getDB() << "." << roleName.getRole(); - result.appendString("_id", id); - result.appendString(ROLE_NAME_FIELD_NAME, roleName.getRole()); - result.appendString(ROLE_DB_FIELD_NAME, roleName.getDB()); - - // Build privileges array - mutablebson::Element privilegesArrayElement = - result.getDocument().makeElementArray("privileges"); - result.pushBack(privilegesArrayElement); - const PrivilegeVector& privileges = graph->getDirectPrivileges(roleName); - Status status = getBSONForPrivileges(privileges, privilegesArrayElement); + OID _startGeneration; + bool _isThisGuardInFetchPhase; + AuthorizationManager* _authzManager; + stdx::unique_lock<stdx::mutex> _lock; +}; + +AuthorizationManager::AuthorizationManager(std::unique_ptr<AuthzManagerExternalState> externalState) + : _authEnabled(false), + _privilegeDocsExist(false), + _externalState(std::move(externalState)), + _version(schemaVersionInvalid), + _isFetchPhaseBusy(false) { + _updateCacheGeneration_inlock(); +} + +AuthorizationManager::~AuthorizationManager() { + for (unordered_map<UserName, User*>::iterator it = _userCache.begin(); it != _userCache.end(); + ++it) { + fassert(17265, it->second != internalSecurity.user); + delete it->second; + } +} + +std::unique_ptr<AuthorizationSession> AuthorizationManager::makeAuthorizationSession() { + return stdx::make_unique<AuthorizationSession>( + _externalState->makeAuthzSessionExternalState(this)); +} + +Status AuthorizationManager::getAuthorizationVersion(OperationContext* txn, int* version) { + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + int newVersion = _version; + if (schemaVersionInvalid == newVersion) { + while (guard.otherUpdateInFetchPhase()) + guard.wait(); + guard.beginFetchPhase(); + Status status = _externalState->getStoredAuthorizationVersion(txn, &newVersion); + guard.endFetchPhase(); if (!status.isOK()) { + warning() << "Problem fetching the stored schema version of authorization data: " + << status; + *version = schemaVersionInvalid; return status; } - // Build roles array - mutablebson::Element rolesArrayElement = result.getDocument().makeElementArray("roles"); - result.pushBack(rolesArrayElement); - for (RoleNameIterator roles = graph->getDirectSubordinates(roleName); - roles.more(); - roles.next()) { - - const RoleName& subRole = roles.get(); - mutablebson::Element roleObj = result.getDocument().makeElementObject(""); - roleObj.appendString(ROLE_NAME_FIELD_NAME, subRole.getRole()); - roleObj.appendString(ROLE_DB_FIELD_NAME, subRole.getDB()); - rolesArrayElement.pushBack(roleObj); + if (guard.isSameCacheGeneration()) { + _version = newVersion; } + } + *version = newVersion; + return Status::OK(); +} + +OID AuthorizationManager::getCacheGeneration() { + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + return _cacheGeneration; +} + +void AuthorizationManager::setAuthEnabled(bool enabled) { + _authEnabled = enabled; +} + +bool AuthorizationManager::isAuthEnabled() const { + return _authEnabled; +} + +bool AuthorizationManager::hasAnyPrivilegeDocuments(OperationContext* txn) { + stdx::unique_lock<stdx::mutex> lk(_privilegeDocsExistMutex); + if (_privilegeDocsExist) { + // If we know that a user exists, don't re-check. + return true; + } - return Status::OK(); + lk.unlock(); + bool privDocsExist = _externalState->hasAnyPrivilegeDocuments(txn); + lk.lock(); + + if (privDocsExist) { + _privilegeDocsExist = true; } - Status AuthorizationManager::_initializeUserFromPrivilegeDocument( - User* user, const BSONObj& privDoc) { - V2UserDocumentParser parser; - std::string userName = parser.extractUserNameFromUserDocument(privDoc); - if (userName != user->getName().getUser()) { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << "User name from privilege document \"" - << userName - << "\" doesn't match name of provided User \"" - << user->getName().getUser() - << "\"", - 0); - } + return _privilegeDocsExist; +} - Status status = parser.initializeUserCredentialsFromUserDocument(user, privDoc); - if (!status.isOK()) { - return status; - } - status = parser.initializeUserRolesFromUserDocument(privDoc, user); - if (!status.isOK()) { - return status; - } - status = parser.initializeUserIndirectRolesFromUserDocument(privDoc, user); - if (!status.isOK()) { - return status; +Status AuthorizationManager::getBSONForPrivileges(const PrivilegeVector& privileges, + mutablebson::Element resultArray) { + for (PrivilegeVector::const_iterator it = privileges.begin(); it != privileges.end(); ++it) { + std::string errmsg; + ParsedPrivilege privilege; + if (!ParsedPrivilege::privilegeToParsedPrivilege(*it, &privilege, &errmsg)) { + return Status(ErrorCodes::BadValue, errmsg); } - status = parser.initializeUserPrivilegesFromUserDocument(privDoc, user); - if (!status.isOK()) { - return status; - } - - return Status::OK(); + resultArray.appendObject("privileges", privilege.toBSON()); + } + return Status::OK(); +} + +Status AuthorizationManager::getBSONForRole(RoleGraph* graph, + const RoleName& roleName, + mutablebson::Element result) { + if (!graph->roleExists(roleName)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << roleName.getFullName() + << "does not name an existing role"); + } + std::string id = mongoutils::str::stream() << roleName.getDB() << "." << roleName.getRole(); + result.appendString("_id", id); + result.appendString(ROLE_NAME_FIELD_NAME, roleName.getRole()); + result.appendString(ROLE_DB_FIELD_NAME, roleName.getDB()); + + // Build privileges array + mutablebson::Element privilegesArrayElement = + result.getDocument().makeElementArray("privileges"); + result.pushBack(privilegesArrayElement); + const PrivilegeVector& privileges = graph->getDirectPrivileges(roleName); + Status status = getBSONForPrivileges(privileges, privilegesArrayElement); + if (!status.isOK()) { + return status; } - Status AuthorizationManager::getUserDescription(OperationContext* txn, - const UserName& userName, - BSONObj* result) { - return _externalState->getUserDescription(txn, userName, result); + // Build roles array + mutablebson::Element rolesArrayElement = result.getDocument().makeElementArray("roles"); + result.pushBack(rolesArrayElement); + for (RoleNameIterator roles = graph->getDirectSubordinates(roleName); roles.more(); + roles.next()) { + const RoleName& subRole = roles.get(); + mutablebson::Element roleObj = result.getDocument().makeElementObject(""); + roleObj.appendString(ROLE_NAME_FIELD_NAME, subRole.getRole()); + roleObj.appendString(ROLE_DB_FIELD_NAME, subRole.getDB()); + rolesArrayElement.pushBack(roleObj); } - Status AuthorizationManager::getRoleDescription(const RoleName& roleName, - bool showPrivileges, - BSONObj* result) { - return _externalState->getRoleDescription(roleName, showPrivileges, result); + return Status::OK(); +} + +Status AuthorizationManager::_initializeUserFromPrivilegeDocument(User* user, + const BSONObj& privDoc) { + V2UserDocumentParser parser; + std::string userName = parser.extractUserNameFromUserDocument(privDoc); + if (userName != user->getName().getUser()) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "User name from privilege document \"" + << userName + << "\" doesn't match name of provided User \"" + << user->getName().getUser() << "\"", + 0); } - Status AuthorizationManager::getRoleDescriptionsForDB(const std::string dbname, - bool showPrivileges, - bool showBuiltinRoles, - vector<BSONObj>* result) { - return _externalState->getRoleDescriptionsForDB(dbname, - showPrivileges, - showBuiltinRoles, - result); + Status status = parser.initializeUserCredentialsFromUserDocument(user, privDoc); + if (!status.isOK()) { + return status; + } + status = parser.initializeUserRolesFromUserDocument(privDoc, user); + if (!status.isOK()) { + return status; + } + status = parser.initializeUserIndirectRolesFromUserDocument(privDoc, user); + if (!status.isOK()) { + return status; + } + status = parser.initializeUserPrivilegesFromUserDocument(privDoc, user); + if (!status.isOK()) { + return status; } - Status AuthorizationManager::acquireUser( - OperationContext* txn, const UserName& userName, User** acquiredUser) { - if (userName == internalSecurity.user->getName()) { - *acquiredUser = internalSecurity.user; - return Status::OK(); - } + return Status::OK(); +} + +Status AuthorizationManager::getUserDescription(OperationContext* txn, + const UserName& userName, + BSONObj* result) { + return _externalState->getUserDescription(txn, userName, result); +} + +Status AuthorizationManager::getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result) { + return _externalState->getRoleDescription(roleName, showPrivileges, result); +} + +Status AuthorizationManager::getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + vector<BSONObj>* result) { + return _externalState->getRoleDescriptionsForDB( + dbname, showPrivileges, showBuiltinRoles, result); +} + +Status AuthorizationManager::acquireUser(OperationContext* txn, + const UserName& userName, + User** acquiredUser) { + if (userName == internalSecurity.user->getName()) { + *acquiredUser = internalSecurity.user; + return Status::OK(); + } - unordered_map<UserName, User*>::iterator it; + unordered_map<UserName, User*>::iterator it; - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - while ((_userCache.end() == (it = _userCache.find(userName))) && - guard.otherUpdateInFetchPhase()) { + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + while ((_userCache.end() == (it = _userCache.find(userName))) && + guard.otherUpdateInFetchPhase()) { + guard.wait(); + } - guard.wait(); - } + if (it != _userCache.end()) { + fassert(16914, it->second); + fassert(17003, it->second->isValid()); + fassert(17008, it->second->getRefCount() > 0); + it->second->incrementRefCount(); + *acquiredUser = it->second; + return Status::OK(); + } - if (it != _userCache.end()) { - fassert(16914, it->second); - fassert(17003, it->second->isValid()); - fassert(17008, it->second->getRefCount() > 0); - it->second->incrementRefCount(); - *acquiredUser = it->second; - return Status::OK(); - } + std::unique_ptr<User> user; - std::unique_ptr<User> user; + int authzVersion = _version; + guard.beginFetchPhase(); - int authzVersion = _version; - guard.beginFetchPhase(); + // Number of times to retry a user document that fetches due to transient + // AuthSchemaIncompatible errors. These errors should only ever occur during and shortly + // after schema upgrades. + static const int maxAcquireRetries = 2; + Status status = Status::OK(); + for (int i = 0; i < maxAcquireRetries; ++i) { + if (authzVersion == schemaVersionInvalid) { + Status status = _externalState->getStoredAuthorizationVersion(txn, &authzVersion); + if (!status.isOK()) + return status; + } - // Number of times to retry a user document that fetches due to transient - // AuthSchemaIncompatible errors. These errors should only ever occur during and shortly - // after schema upgrades. - static const int maxAcquireRetries = 2; - Status status = Status::OK(); - for (int i = 0; i < maxAcquireRetries; ++i) { - if (authzVersion == schemaVersionInvalid) { - Status status = _externalState->getStoredAuthorizationVersion(txn, &authzVersion); - if (!status.isOK()) - return status; - } - - switch (authzVersion) { + switch (authzVersion) { default: - status = Status(ErrorCodes::BadValue, mongoutils::str::stream() << - "Illegal value for authorization data schema version, " << - authzVersion); + status = Status(ErrorCodes::BadValue, + mongoutils::str::stream() + << "Illegal value for authorization data schema version, " + << authzVersion); break; case schemaVersion28SCRAM: case schemaVersion26Final: @@ -493,182 +483,174 @@ namespace mongo { status = _fetchUserV2(txn, userName, &user); break; case schemaVersion24: - status = Status(ErrorCodes::AuthSchemaIncompatible, mongoutils::str::stream() << - "Authorization data schema version " << schemaVersion24 << - " not supported after MongoDB version 2.6."); - break; - } - if (status.isOK()) + status = Status(ErrorCodes::AuthSchemaIncompatible, + mongoutils::str::stream() + << "Authorization data schema version " << schemaVersion24 + << " not supported after MongoDB version 2.6."); break; - if (status != ErrorCodes::AuthSchemaIncompatible) - return status; - - authzVersion = schemaVersionInvalid; } - if (!status.isOK()) + if (status.isOK()) + break; + if (status != ErrorCodes::AuthSchemaIncompatible) return status; - guard.endFetchPhase(); - - user->incrementRefCount(); - // NOTE: It is not safe to throw an exception from here to the end of the method. - if (guard.isSameCacheGeneration()) { - _userCache.insert(std::make_pair(userName, user.get())); - if (_version == schemaVersionInvalid) - _version = authzVersion; - } - else { - // If the cache generation changed while this thread was in fetch mode, the data - // associated with the user may now be invalid, so we must mark it as such. The caller - // may still opt to use the information for a short while, but not indefinitely. - user->invalidate(); - } - *acquiredUser = user.release(); - - return Status::OK(); + authzVersion = schemaVersionInvalid; + } + if (!status.isOK()) + return status; + + guard.endFetchPhase(); + + user->incrementRefCount(); + // NOTE: It is not safe to throw an exception from here to the end of the method. + if (guard.isSameCacheGeneration()) { + _userCache.insert(std::make_pair(userName, user.get())); + if (_version == schemaVersionInvalid) + _version = authzVersion; + } else { + // If the cache generation changed while this thread was in fetch mode, the data + // associated with the user may now be invalid, so we must mark it as such. The caller + // may still opt to use the information for a short while, but not indefinitely. + user->invalidate(); + } + *acquiredUser = user.release(); + + return Status::OK(); +} + +Status AuthorizationManager::_fetchUserV2(OperationContext* txn, + const UserName& userName, + std::unique_ptr<User>* acquiredUser) { + BSONObj userObj; + Status status = getUserDescription(txn, userName, &userObj); + if (!status.isOK()) { + return status; } - Status AuthorizationManager::_fetchUserV2(OperationContext* txn, - const UserName& userName, - std::unique_ptr<User>* acquiredUser) { - BSONObj userObj; - Status status = getUserDescription(txn, userName, &userObj); - if (!status.isOK()) { - return status; - } - - // Put the new user into an unique_ptr temporarily in case there's an error while - // initializing the user. - std::unique_ptr<User> user(new User(userName)); + // Put the new user into an unique_ptr temporarily in case there's an error while + // initializing the user. + std::unique_ptr<User> user(new User(userName)); - status = _initializeUserFromPrivilegeDocument(user.get(), userObj); - if (!status.isOK()) { - return status; - } - acquiredUser->reset(user.release()); - return Status::OK(); + status = _initializeUserFromPrivilegeDocument(user.get(), userObj); + if (!status.isOK()) { + return status; } + acquiredUser->reset(user.release()); + return Status::OK(); +} - void AuthorizationManager::releaseUser(User* user) { - if (user == internalSecurity.user) { - return; - } +void AuthorizationManager::releaseUser(User* user) { + if (user == internalSecurity.user) { + return; + } - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - user->decrementRefCount(); - if (user->getRefCount() == 0) { - // If it's been invalidated then it's not in the _userCache anymore. - if (user->isValid()) { - MONGO_COMPILER_VARIABLE_UNUSED bool erased = _userCache.erase(user->getName()); - dassert(erased); - } - delete user; + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + user->decrementRefCount(); + if (user->getRefCount() == 0) { + // If it's been invalidated then it's not in the _userCache anymore. + if (user->isValid()) { + MONGO_COMPILER_VARIABLE_UNUSED bool erased = _userCache.erase(user->getName()); + dassert(erased); } + delete user; + } +} + +void AuthorizationManager::invalidateUserByName(const UserName& userName) { + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + _updateCacheGeneration_inlock(); + unordered_map<UserName, User*>::iterator it = _userCache.find(userName); + if (it == _userCache.end()) { + return; } - void AuthorizationManager::invalidateUserByName(const UserName& userName) { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - _updateCacheGeneration_inlock(); - unordered_map<UserName, User*>::iterator it = _userCache.find(userName); - if (it == _userCache.end()) { - return; - } + User* user = it->second; + _userCache.erase(it); + user->invalidate(); +} +void AuthorizationManager::invalidateUsersFromDB(const std::string& dbname) { + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + _updateCacheGeneration_inlock(); + unordered_map<UserName, User*>::iterator it = _userCache.begin(); + while (it != _userCache.end()) { User* user = it->second; - _userCache.erase(it); - user->invalidate(); - } - - void AuthorizationManager::invalidateUsersFromDB(const std::string& dbname) { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - _updateCacheGeneration_inlock(); - unordered_map<UserName, User*>::iterator it = _userCache.begin(); - while (it != _userCache.end()) { - User* user = it->second; - if (user->getName().getDB() == dbname) { - _userCache.erase(it++); - user->invalidate(); - } else { - ++it; - } + if (user->getName().getDB() == dbname) { + _userCache.erase(it++); + user->invalidate(); + } else { + ++it; } } - - void AuthorizationManager::invalidateUserCache() { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - _invalidateUserCache_inlock(); +} + +void AuthorizationManager::invalidateUserCache() { + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + _invalidateUserCache_inlock(); +} + +void AuthorizationManager::_invalidateUserCache_inlock() { + _updateCacheGeneration_inlock(); + for (unordered_map<UserName, User*>::iterator it = _userCache.begin(); it != _userCache.end(); + ++it) { + fassert(17266, it->second != internalSecurity.user); + it->second->invalidate(); } + _userCache.clear(); - void AuthorizationManager::_invalidateUserCache_inlock() { - _updateCacheGeneration_inlock(); - for (unordered_map<UserName, User*>::iterator it = _userCache.begin(); - it != _userCache.end(); ++it) { - fassert(17266, it->second != internalSecurity.user); - it->second->invalidate(); - } - _userCache.clear(); + // Reread the schema version before acquiring the next user. + _version = schemaVersionInvalid; +} - // Reread the schema version before acquiring the next user. - _version = schemaVersionInvalid; - } +Status AuthorizationManager::initialize(OperationContext* txn) { + invalidateUserCache(); + Status status = _externalState->initialize(txn); + if (!status.isOK()) + return status; - Status AuthorizationManager::initialize(OperationContext* txn) { - invalidateUserCache(); - Status status = _externalState->initialize(txn); - if (!status.isOK()) - return status; - - return Status::OK(); - } + return Status::OK(); +} namespace { - bool isAuthzNamespace(StringData ns) { - return (ns == AuthorizationManager::rolesCollectionNamespace.ns() || - ns == AuthorizationManager::usersCollectionNamespace.ns() || - ns == AuthorizationManager::versionCollectionNamespace.ns()); - } - - bool isAuthzCollection(StringData coll) { - return (coll == AuthorizationManager::rolesCollectionNamespace.coll() || - coll == AuthorizationManager::usersCollectionNamespace.coll() || - coll == AuthorizationManager::versionCollectionNamespace.coll()); - } - - bool loggedCommandOperatesOnAuthzData(const char* ns, const BSONObj& cmdObj) { - if (ns != AuthorizationManager::adminCommandNamespace.ns()) - return false; - const StringData cmdName(cmdObj.firstElement().fieldNameStringData()); - if (cmdName == "drop") { - return isAuthzCollection(cmdObj.firstElement().valueStringData()); - } - else if (cmdName == "dropDatabase") { - return true; - } - else if (cmdName == "renameCollection") { - return isAuthzCollection(cmdObj.firstElement().str()) || - isAuthzCollection(cmdObj["to"].str()); - } - else if (cmdName == "dropIndexes" || cmdName == "deleteIndexes") { - return false; - } - else if (cmdName == "create") { - return false; - } - else { - return true; - } +bool isAuthzNamespace(StringData ns) { + return (ns == AuthorizationManager::rolesCollectionNamespace.ns() || + ns == AuthorizationManager::usersCollectionNamespace.ns() || + ns == AuthorizationManager::versionCollectionNamespace.ns()); +} + +bool isAuthzCollection(StringData coll) { + return (coll == AuthorizationManager::rolesCollectionNamespace.coll() || + coll == AuthorizationManager::usersCollectionNamespace.coll() || + coll == AuthorizationManager::versionCollectionNamespace.coll()); +} + +bool loggedCommandOperatesOnAuthzData(const char* ns, const BSONObj& cmdObj) { + if (ns != AuthorizationManager::adminCommandNamespace.ns()) + return false; + const StringData cmdName(cmdObj.firstElement().fieldNameStringData()); + if (cmdName == "drop") { + return isAuthzCollection(cmdObj.firstElement().valueStringData()); + } else if (cmdName == "dropDatabase") { + return true; + } else if (cmdName == "renameCollection") { + return isAuthzCollection(cmdObj.firstElement().str()) || + isAuthzCollection(cmdObj["to"].str()); + } else if (cmdName == "dropIndexes" || cmdName == "deleteIndexes") { + return false; + } else if (cmdName == "create") { + return false; + } else { + return true; } +} - bool appliesToAuthzData( - const char* op, - const char* ns, - const BSONObj& o) { - - switch (*op) { +bool appliesToAuthzData(const char* op, const char* ns, const BSONObj& o) { + switch (*op) { case 'i': case 'u': case 'd': - if (op[1] != '\0') return false; // "db" op type + if (op[1] != '\0') + return false; // "db" op type return isAuthzNamespace(ns); case 'c': return loggedCommandOperatesOnAuthzData(ns, o); @@ -677,71 +659,66 @@ namespace { return false; default: return true; - } } - - // Updates to users in the oplog are done by matching on the _id, which will always have the - // form "<dbname>.<username>". This function extracts the UserName from that string. - StatusWith<UserName> extractUserNameFromIdString(StringData idstr) { - size_t splitPoint = idstr.find('.'); - if (splitPoint == string::npos) { - return StatusWith<UserName>( - ErrorCodes::FailedToParse, - mongoutils::str::stream() << "_id entries for user documents must be of " - "the form <dbname>.<username>. Found: " << idstr); - } - return StatusWith<UserName>(UserName(idstr.substr(splitPoint + 1), - idstr.substr(0, splitPoint))); +} + +// Updates to users in the oplog are done by matching on the _id, which will always have the +// form "<dbname>.<username>". This function extracts the UserName from that string. +StatusWith<UserName> extractUserNameFromIdString(StringData idstr) { + size_t splitPoint = idstr.find('.'); + if (splitPoint == string::npos) { + return StatusWith<UserName>(ErrorCodes::FailedToParse, + mongoutils::str::stream() + << "_id entries for user documents must be of " + "the form <dbname>.<username>. Found: " << idstr); } + return StatusWith<UserName>( + UserName(idstr.substr(splitPoint + 1), idstr.substr(0, splitPoint))); +} } // namespace - void AuthorizationManager::_updateCacheGeneration_inlock() { - _cacheGeneration = OID::gen(); +void AuthorizationManager::_updateCacheGeneration_inlock() { + _cacheGeneration = OID::gen(); +} + +void AuthorizationManager::_invalidateRelevantCacheData(const char* op, + const char* ns, + const BSONObj& o, + const BSONObj* o2) { + if (ns == AuthorizationManager::rolesCollectionNamespace.ns() || + ns == AuthorizationManager::versionCollectionNamespace.ns()) { + invalidateUserCache(); + return; } - void AuthorizationManager::_invalidateRelevantCacheData(const char* op, - const char* ns, - const BSONObj& o, - const BSONObj* o2) { - if (ns == AuthorizationManager::rolesCollectionNamespace.ns() || - ns == AuthorizationManager::versionCollectionNamespace.ns()) { - invalidateUserCache(); - return; - } + if (*op == 'i' || *op == 'd' || *op == 'u') { + // If you got into this function isAuthzNamespace() must have returned true, and we've + // already checked that it's not the roles or version collection. + invariant(ns == AuthorizationManager::usersCollectionNamespace.ns()); - if (*op == 'i' || *op == 'd' || *op == 'u') { - // If you got into this function isAuthzNamespace() must have returned true, and we've - // already checked that it's not the roles or version collection. - invariant(ns == AuthorizationManager::usersCollectionNamespace.ns()); - - StatusWith<UserName> userName = (*op == 'u') ? - extractUserNameFromIdString((*o2)["_id"].str()) : - extractUserNameFromIdString(o["_id"].str()); - - if (!userName.isOK()) { - warning() << "Invalidating user cache based on user being updated failed, will " - "invalidate the entire cache instead: " << userName.getStatus() << endl; - invalidateUserCache(); - return; - } - invalidateUserByName(userName.getValue()); - } else { + StatusWith<UserName> userName = (*op == 'u') + ? extractUserNameFromIdString((*o2)["_id"].str()) + : extractUserNameFromIdString(o["_id"].str()); + + if (!userName.isOK()) { + warning() << "Invalidating user cache based on user being updated failed, will " + "invalidate the entire cache instead: " << userName.getStatus() << endl; invalidateUserCache(); + return; } + invalidateUserByName(userName.getValue()); + } else { + invalidateUserCache(); } +} - void AuthorizationManager::logOp( - OperationContext* txn, - const char* op, - const char* ns, - const BSONObj& o, - BSONObj* o2) { - - _externalState->logOp(txn, op, ns, o, o2); - if (appliesToAuthzData(op, ns, o)) { - _invalidateRelevantCacheData(op, ns, o, o2); - } +void AuthorizationManager::logOp( + OperationContext* txn, const char* op, const char* ns, const BSONObj& o, BSONObj* o2) { + _externalState->logOp(txn, op, ns, o, o2); + if (appliesToAuthzData(op, ns, o)) { + _invalidateRelevantCacheData(op, ns, o, o2); } +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 8f7afa9f3fc..9c7fdbaf9d0 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -50,363 +50,363 @@ namespace mongo { - class AuthorizationSession; - class AuthzManagerExternalState; - class OperationContext; - class ServiceContext; - class UserDocumentParser; - - /** - * Internal secret key info. - */ - struct AuthInfo { - User* user; - }; - extern AuthInfo internalSecurity; // set at startup and not changed after initialization. - - /** - * Contains server/cluster-wide information about Authorization. - */ - class AuthorizationManager { - MONGO_DISALLOW_COPYING(AuthorizationManager); - public: - static AuthorizationManager* get(ServiceContext* service); - static AuthorizationManager* get(ServiceContext& service); - static void set(ServiceContext* service, - std::unique_ptr<AuthorizationManager> authzManager); - - // The newly constructed AuthorizationManager takes ownership of "externalState" - explicit AuthorizationManager(std::unique_ptr<AuthzManagerExternalState> externalState); - - ~AuthorizationManager(); - - static const std::string USER_NAME_FIELD_NAME; - static const std::string USER_DB_FIELD_NAME; - static const std::string ROLE_NAME_FIELD_NAME; - static const std::string ROLE_DB_FIELD_NAME; - static const std::string PASSWORD_FIELD_NAME; - static const std::string V1_USER_NAME_FIELD_NAME; - static const std::string V1_USER_SOURCE_FIELD_NAME; - - static const NamespaceString adminCommandNamespace; - static const NamespaceString rolesCollectionNamespace; - static const NamespaceString usersAltCollectionNamespace; - static const NamespaceString usersBackupCollectionNamespace; - static const NamespaceString usersCollectionNamespace; - static const NamespaceString versionCollectionNamespace; - static const NamespaceString defaultTempUsersCollectionNamespace; // for mongorestore - static const NamespaceString defaultTempRolesCollectionNamespace; // for mongorestore - - /** - * Query to match the auth schema version document in the versionCollectionNamespace. - */ - static const BSONObj versionDocumentQuery; - - /** - * Name of the field in the auth schema version document containing the current schema - * version. - */ - static const std::string schemaVersionFieldName; - - /** - * Value used to represent that the schema version is not cached or invalid. - */ - static const int schemaVersionInvalid = 0; - - /** - * Auth schema version for MongoDB v2.4 and prior. - */ - static const int schemaVersion24 = 1; - - /** - * Auth schema version for MongoDB v2.6 during the upgrade process. Same as - * schemaVersion26Final, except that user documents are found in admin.new.users, and user - * management commands are disabled. - */ - static const int schemaVersion26Upgrade = 2; - - /** - * Auth schema version for MongoDB 2.6 and 3.0 MONGODB-CR/SCRAM mixed auth mode. - * Users are stored in admin.system.users, roles in admin.system.roles. - */ - static const int schemaVersion26Final = 3; - - /** - * Auth schema version for MongoDB 3.0 SCRAM only mode. - * Users are stored in admin.system.users, roles in admin.system.roles. - * MONGODB-CR credentials have been replaced with SCRAM credentials in the user documents. - */ - static const int schemaVersion28SCRAM = 5; - - // TODO: Make the following functions no longer static. - - /** - * Takes a vector of privileges and fills the output param "resultArray" with a BSON array - * representation of the privileges. - */ - static Status getBSONForPrivileges(const PrivilegeVector& privileges, - mutablebson::Element resultArray); - - /** - * Takes a role name and a role graph and fills the output param "result" with a BSON - * representation of the role object. - * This function does no locking - it is up to the caller to synchronize access to the - * role graph. - * Note: The passed in RoleGraph can't be marked const because some of its accessors can - * actually modify it internally (to set up built-in roles). - */ - static Status getBSONForRole(/*const*/ RoleGraph* graph, - const RoleName& roleName, - mutablebson::Element result); - - /** - * Returns a new AuthorizationSession for use with this AuthorizationManager. - */ - std::unique_ptr<AuthorizationSession> makeAuthorizationSession(); - - /** - * Sets whether or not access control enforcement is enabled for this manager. - */ - void setAuthEnabled(bool enabled); - - /** - * Returns true if access control is enabled for this manager . - */ - bool isAuthEnabled() const; - - /** - * Returns via the output parameter "version" the version number of the authorization - * system. Returns Status::OK() if it was able to successfully fetch the current - * authorization version. If it has problems fetching the most up to date version it - * returns a non-OK status. When returning a non-OK status, *version will be set to - * schemaVersionInvalid (0). - */ - Status getAuthorizationVersion(OperationContext* txn, int* version); - - /** - * Returns the user cache generation identifier. - */ - OID getCacheGeneration(); - - /** - * Returns true if there exists at least one privilege document in the system. - * Used by the AuthorizationSession to determine whether localhost connections should be - * granted special access to bootstrap the system. - * NOTE: If this method ever returns true, the result is cached in _privilegeDocsExist, - * meaning that once this method returns true it will continue to return true for the - * lifetime of this process, even if all users are subsequently dropped from the system. - */ - bool hasAnyPrivilegeDocuments(OperationContext* txn); - - // Checks to see if "doc" is a valid privilege document, assuming it is stored in the - // "system.users" collection of database "dbname". - // - // Returns Status::OK() if the document is good, or Status(ErrorCodes::BadValue), otherwise. - Status checkValidPrivilegeDocument(StringData dbname, const BSONObj& doc); - - // Given a database name and a readOnly flag return an ActionSet describing all the actions - // that an old-style user with those attributes should be given. - ActionSet getActionsForOldStyleUser(const std::string& dbname, bool readOnly) const; - - /** - * Writes into "result" a document describing the named user and returns Status::OK(). The - * description includes the user credentials and customData, if present, the user's role - * membership and delegation information, a full list of the user's privileges, and a full - * list of the user's roles, including those roles held implicitly through other roles - * (indirect roles). In the event that some of this information is inconsistent, the - * document will contain a "warnings" array, with std::string messages describing - * inconsistencies. - * - * If the user does not exist, returns ErrorCodes::UserNotFound. - */ - Status getUserDescription(OperationContext* txn, const UserName& userName, BSONObj* result); - - /** - * Writes into "result" a document describing the named role and returns Status::OK(). The - * description includes the roles in which the named role has membership and a full list of - * the roles of which the named role is a member, including those roles memberships held - * implicitly through other roles (indirect roles). If "showPrivileges" is true, then the - * description documents will also include a full list of the role's privileges. - * In the event that some of this information is inconsistent, the document will contain a - * "warnings" array, with std::string messages describing inconsistencies. - * - * If the role does not exist, returns ErrorCodes::RoleNotFound. - */ - Status getRoleDescription(const RoleName& roleName, bool showPrivileges, BSONObj* result); - - /** - * Writes into "result" documents describing the roles that are defined on the given - * database. Each role description document includes the other roles in which the role has - * membership and a full list of the roles of which the named role is a member, - * including those roles memberships held implicitly through other roles (indirect roles). - * If showPrivileges is true, then the description documents will also include a full list - * of the role's privileges. If showBuiltinRoles is true, then the result array will - * contain description documents for all the builtin roles for the given database, if it - * is false the result will just include user defined roles. - * In the event that some of the information in a given role description is inconsistent, - * the document will contain a "warnings" array, with std::string messages describing - * inconsistencies. - */ - Status getRoleDescriptionsForDB(const std::string dbname, - bool showPrivileges, - bool showBuiltinRoles, - std::vector<BSONObj>* result); - - /** - * Returns the User object for the given userName in the out parameter "acquiredUser". - * If the user cache already has a user object for this user, it increments the refcount - * on that object and gives out a pointer to it. If no user object for this user name - * exists yet in the cache, reads the user's privilege document from disk, builds up - * a User object, sets the refcount to 1, and gives that out. The returned user may - * be invalid by the time the caller gets access to it. - * The AuthorizationManager retains ownership of the returned User object. - * On non-OK Status return values, acquiredUser will not be modified. - */ - Status acquireUser(OperationContext* txn, const UserName& userName, User** acquiredUser); - - /** - * Decrements the refcount of the given User object. If the refcount has gone to zero, - * deletes the User. Caller must stop using its pointer to "user" after calling this. - */ - void releaseUser(User* user); - - /** - * Marks the given user as invalid and removes it from the user cache. - */ - void invalidateUserByName(const UserName& user); - - /** - * Invalidates all users who's source is "dbname" and removes them from the user cache. - */ - void invalidateUsersFromDB(const std::string& dbname); - - /** - * Initializes the authorization manager. Depending on what version the authorization - * system is at, this may involve building up the user cache and/or the roles graph. - * Call this function at startup and after resynchronizing a slave/secondary. - */ - Status initialize(OperationContext* txn); - - /** - * Invalidates all of the contents of the user cache. - */ - void invalidateUserCache(); - - /** - * Parses privDoc and fully initializes the user object (credentials, roles, and privileges) - * with the information extracted from the privilege document. - * This should never be called from outside the AuthorizationManager - the only reason it's - * public instead of private is so it can be unit tested. - */ - Status _initializeUserFromPrivilegeDocument(User* user, const BSONObj& privDoc); - - /** - * Hook called by replication code to let the AuthorizationManager observe changes - * to relevant collections. - */ - void logOp(OperationContext* txn, - const char* opstr, - const char* ns, - const BSONObj& obj, - BSONObj* patt); - - private: - /** - * Type used to guard accesses and updates to the user cache. - */ - class CacheGuard; - friend class AuthorizationManager::CacheGuard; - - /** - * Invalidates all User objects in the cache and removes them from the cache. - * Should only be called when already holding _cacheMutex. - */ - void _invalidateUserCache_inlock(); - - /** - * Given the objects describing an oplog entry that affects authorization data, invalidates - * the portion of the user cache that is affected by that operation. Should only be called - * with oplog entries that have been pre-verified to actually affect authorization data. - */ - void _invalidateRelevantCacheData(const char* op, - const char* ns, - const BSONObj& o, - const BSONObj* o2); - - /** - * Updates _cacheGeneration to a new OID - */ - void _updateCacheGeneration_inlock(); - - /** - * Fetches user information from a v2-schema user document for the named user, - * and stores a pointer to a new user object into *acquiredUser on success. - */ - Status _fetchUserV2(OperationContext* txn, - const UserName& userName, - std::unique_ptr<User>* acquiredUser); - - /** - * True if access control enforcement is enabled in this AuthorizationManager. - * - * Defaults to false. Changes to its value are not synchronized, so it should only be set - * at initalization-time. - */ - bool _authEnabled; - - /** - * A cache of whether there are any users set up for the cluster. - */ - bool _privilegeDocsExist; - - // Protects _privilegeDocsExist - mutable stdx::mutex _privilegeDocsExistMutex; - - std::unique_ptr<AuthzManagerExternalState> _externalState; - - /** - * Cached value of the authorization schema version. - * - * May be set by acquireUser() and getAuthorizationVersion(). Invalidated by - * invalidateUserCache(). - * - * Reads and writes guarded by CacheGuard. - */ - int _version; - - /** - * Caches User objects with information about user privileges, to avoid the need to - * go to disk to read user privilege documents whenever possible. Every User object - * has a reference count - the AuthorizationManager must not delete a User object in the - * cache unless its reference count is zero. - */ - unordered_map<UserName, User*> _userCache; - - /** - * Current generation of cached data. Updated every time part of the cache gets - * invalidated. Protected by CacheGuard. - */ - OID _cacheGeneration; - - /** - * True if there is an update to the _userCache in progress, and that update is currently in - * the "fetch phase", during which it does not hold the _cacheMutex. - * - * Manipulated via CacheGuard. - */ - bool _isFetchPhaseBusy; - - /** - * Protects _userCache, _cacheGeneration, _version and _isFetchPhaseBusy. Manipulated - * via CacheGuard. - */ - stdx::mutex _cacheMutex; - - /** - * Condition used to signal that it is OK for another CacheGuard to enter a fetch phase. - * Manipulated via CacheGuard. - */ - stdx::condition_variable _fetchPhaseIsReady; - }; - -} // namespace mongo +class AuthorizationSession; +class AuthzManagerExternalState; +class OperationContext; +class ServiceContext; +class UserDocumentParser; + +/** + * Internal secret key info. + */ +struct AuthInfo { + User* user; +}; +extern AuthInfo internalSecurity; // set at startup and not changed after initialization. + +/** + * Contains server/cluster-wide information about Authorization. + */ +class AuthorizationManager { + MONGO_DISALLOW_COPYING(AuthorizationManager); + +public: + static AuthorizationManager* get(ServiceContext* service); + static AuthorizationManager* get(ServiceContext& service); + static void set(ServiceContext* service, std::unique_ptr<AuthorizationManager> authzManager); + + // The newly constructed AuthorizationManager takes ownership of "externalState" + explicit AuthorizationManager(std::unique_ptr<AuthzManagerExternalState> externalState); + + ~AuthorizationManager(); + + static const std::string USER_NAME_FIELD_NAME; + static const std::string USER_DB_FIELD_NAME; + static const std::string ROLE_NAME_FIELD_NAME; + static const std::string ROLE_DB_FIELD_NAME; + static const std::string PASSWORD_FIELD_NAME; + static const std::string V1_USER_NAME_FIELD_NAME; + static const std::string V1_USER_SOURCE_FIELD_NAME; + + static const NamespaceString adminCommandNamespace; + static const NamespaceString rolesCollectionNamespace; + static const NamespaceString usersAltCollectionNamespace; + static const NamespaceString usersBackupCollectionNamespace; + static const NamespaceString usersCollectionNamespace; + static const NamespaceString versionCollectionNamespace; + static const NamespaceString defaultTempUsersCollectionNamespace; // for mongorestore + static const NamespaceString defaultTempRolesCollectionNamespace; // for mongorestore + + /** + * Query to match the auth schema version document in the versionCollectionNamespace. + */ + static const BSONObj versionDocumentQuery; + + /** + * Name of the field in the auth schema version document containing the current schema + * version. + */ + static const std::string schemaVersionFieldName; + + /** + * Value used to represent that the schema version is not cached or invalid. + */ + static const int schemaVersionInvalid = 0; + + /** + * Auth schema version for MongoDB v2.4 and prior. + */ + static const int schemaVersion24 = 1; + + /** + * Auth schema version for MongoDB v2.6 during the upgrade process. Same as + * schemaVersion26Final, except that user documents are found in admin.new.users, and user + * management commands are disabled. + */ + static const int schemaVersion26Upgrade = 2; + + /** + * Auth schema version for MongoDB 2.6 and 3.0 MONGODB-CR/SCRAM mixed auth mode. + * Users are stored in admin.system.users, roles in admin.system.roles. + */ + static const int schemaVersion26Final = 3; + + /** + * Auth schema version for MongoDB 3.0 SCRAM only mode. + * Users are stored in admin.system.users, roles in admin.system.roles. + * MONGODB-CR credentials have been replaced with SCRAM credentials in the user documents. + */ + static const int schemaVersion28SCRAM = 5; + + // TODO: Make the following functions no longer static. + + /** + * Takes a vector of privileges and fills the output param "resultArray" with a BSON array + * representation of the privileges. + */ + static Status getBSONForPrivileges(const PrivilegeVector& privileges, + mutablebson::Element resultArray); + + /** + * Takes a role name and a role graph and fills the output param "result" with a BSON + * representation of the role object. + * This function does no locking - it is up to the caller to synchronize access to the + * role graph. + * Note: The passed in RoleGraph can't be marked const because some of its accessors can + * actually modify it internally (to set up built-in roles). + */ + static Status getBSONForRole(/*const*/ RoleGraph* graph, + const RoleName& roleName, + mutablebson::Element result); + + /** + * Returns a new AuthorizationSession for use with this AuthorizationManager. + */ + std::unique_ptr<AuthorizationSession> makeAuthorizationSession(); + + /** + * Sets whether or not access control enforcement is enabled for this manager. + */ + void setAuthEnabled(bool enabled); + + /** + * Returns true if access control is enabled for this manager . + */ + bool isAuthEnabled() const; + + /** + * Returns via the output parameter "version" the version number of the authorization + * system. Returns Status::OK() if it was able to successfully fetch the current + * authorization version. If it has problems fetching the most up to date version it + * returns a non-OK status. When returning a non-OK status, *version will be set to + * schemaVersionInvalid (0). + */ + Status getAuthorizationVersion(OperationContext* txn, int* version); + + /** + * Returns the user cache generation identifier. + */ + OID getCacheGeneration(); + + /** + * Returns true if there exists at least one privilege document in the system. + * Used by the AuthorizationSession to determine whether localhost connections should be + * granted special access to bootstrap the system. + * NOTE: If this method ever returns true, the result is cached in _privilegeDocsExist, + * meaning that once this method returns true it will continue to return true for the + * lifetime of this process, even if all users are subsequently dropped from the system. + */ + bool hasAnyPrivilegeDocuments(OperationContext* txn); + + // Checks to see if "doc" is a valid privilege document, assuming it is stored in the + // "system.users" collection of database "dbname". + // + // Returns Status::OK() if the document is good, or Status(ErrorCodes::BadValue), otherwise. + Status checkValidPrivilegeDocument(StringData dbname, const BSONObj& doc); + + // Given a database name and a readOnly flag return an ActionSet describing all the actions + // that an old-style user with those attributes should be given. + ActionSet getActionsForOldStyleUser(const std::string& dbname, bool readOnly) const; + + /** + * Writes into "result" a document describing the named user and returns Status::OK(). The + * description includes the user credentials and customData, if present, the user's role + * membership and delegation information, a full list of the user's privileges, and a full + * list of the user's roles, including those roles held implicitly through other roles + * (indirect roles). In the event that some of this information is inconsistent, the + * document will contain a "warnings" array, with std::string messages describing + * inconsistencies. + * + * If the user does not exist, returns ErrorCodes::UserNotFound. + */ + Status getUserDescription(OperationContext* txn, const UserName& userName, BSONObj* result); + + /** + * Writes into "result" a document describing the named role and returns Status::OK(). The + * description includes the roles in which the named role has membership and a full list of + * the roles of which the named role is a member, including those roles memberships held + * implicitly through other roles (indirect roles). If "showPrivileges" is true, then the + * description documents will also include a full list of the role's privileges. + * In the event that some of this information is inconsistent, the document will contain a + * "warnings" array, with std::string messages describing inconsistencies. + * + * If the role does not exist, returns ErrorCodes::RoleNotFound. + */ + Status getRoleDescription(const RoleName& roleName, bool showPrivileges, BSONObj* result); + + /** + * Writes into "result" documents describing the roles that are defined on the given + * database. Each role description document includes the other roles in which the role has + * membership and a full list of the roles of which the named role is a member, + * including those roles memberships held implicitly through other roles (indirect roles). + * If showPrivileges is true, then the description documents will also include a full list + * of the role's privileges. If showBuiltinRoles is true, then the result array will + * contain description documents for all the builtin roles for the given database, if it + * is false the result will just include user defined roles. + * In the event that some of the information in a given role description is inconsistent, + * the document will contain a "warnings" array, with std::string messages describing + * inconsistencies. + */ + Status getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + std::vector<BSONObj>* result); + + /** + * Returns the User object for the given userName in the out parameter "acquiredUser". + * If the user cache already has a user object for this user, it increments the refcount + * on that object and gives out a pointer to it. If no user object for this user name + * exists yet in the cache, reads the user's privilege document from disk, builds up + * a User object, sets the refcount to 1, and gives that out. The returned user may + * be invalid by the time the caller gets access to it. + * The AuthorizationManager retains ownership of the returned User object. + * On non-OK Status return values, acquiredUser will not be modified. + */ + Status acquireUser(OperationContext* txn, const UserName& userName, User** acquiredUser); + + /** + * Decrements the refcount of the given User object. If the refcount has gone to zero, + * deletes the User. Caller must stop using its pointer to "user" after calling this. + */ + void releaseUser(User* user); + + /** + * Marks the given user as invalid and removes it from the user cache. + */ + void invalidateUserByName(const UserName& user); + + /** + * Invalidates all users who's source is "dbname" and removes them from the user cache. + */ + void invalidateUsersFromDB(const std::string& dbname); + + /** + * Initializes the authorization manager. Depending on what version the authorization + * system is at, this may involve building up the user cache and/or the roles graph. + * Call this function at startup and after resynchronizing a slave/secondary. + */ + Status initialize(OperationContext* txn); + + /** + * Invalidates all of the contents of the user cache. + */ + void invalidateUserCache(); + + /** + * Parses privDoc and fully initializes the user object (credentials, roles, and privileges) + * with the information extracted from the privilege document. + * This should never be called from outside the AuthorizationManager - the only reason it's + * public instead of private is so it can be unit tested. + */ + Status _initializeUserFromPrivilegeDocument(User* user, const BSONObj& privDoc); + + /** + * Hook called by replication code to let the AuthorizationManager observe changes + * to relevant collections. + */ + void logOp(OperationContext* txn, + const char* opstr, + const char* ns, + const BSONObj& obj, + BSONObj* patt); + +private: + /** + * Type used to guard accesses and updates to the user cache. + */ + class CacheGuard; + friend class AuthorizationManager::CacheGuard; + + /** + * Invalidates all User objects in the cache and removes them from the cache. + * Should only be called when already holding _cacheMutex. + */ + void _invalidateUserCache_inlock(); + + /** + * Given the objects describing an oplog entry that affects authorization data, invalidates + * the portion of the user cache that is affected by that operation. Should only be called + * with oplog entries that have been pre-verified to actually affect authorization data. + */ + void _invalidateRelevantCacheData(const char* op, + const char* ns, + const BSONObj& o, + const BSONObj* o2); + + /** + * Updates _cacheGeneration to a new OID + */ + void _updateCacheGeneration_inlock(); + + /** + * Fetches user information from a v2-schema user document for the named user, + * and stores a pointer to a new user object into *acquiredUser on success. + */ + Status _fetchUserV2(OperationContext* txn, + const UserName& userName, + std::unique_ptr<User>* acquiredUser); + + /** + * True if access control enforcement is enabled in this AuthorizationManager. + * + * Defaults to false. Changes to its value are not synchronized, so it should only be set + * at initalization-time. + */ + bool _authEnabled; + + /** + * A cache of whether there are any users set up for the cluster. + */ + bool _privilegeDocsExist; + + // Protects _privilegeDocsExist + mutable stdx::mutex _privilegeDocsExistMutex; + + std::unique_ptr<AuthzManagerExternalState> _externalState; + + /** + * Cached value of the authorization schema version. + * + * May be set by acquireUser() and getAuthorizationVersion(). Invalidated by + * invalidateUserCache(). + * + * Reads and writes guarded by CacheGuard. + */ + int _version; + + /** + * Caches User objects with information about user privileges, to avoid the need to + * go to disk to read user privilege documents whenever possible. Every User object + * has a reference count - the AuthorizationManager must not delete a User object in the + * cache unless its reference count is zero. + */ + unordered_map<UserName, User*> _userCache; + + /** + * Current generation of cached data. Updated every time part of the cache gets + * invalidated. Protected by CacheGuard. + */ + OID _cacheGeneration; + + /** + * True if there is an update to the _userCache in progress, and that update is currently in + * the "fetch phase", during which it does not hold the _cacheMutex. + * + * Manipulated via CacheGuard. + */ + bool _isFetchPhaseBusy; + + /** + * Protects _userCache, _cacheGeneration, _version and _isFetchPhaseBusy. Manipulated + * via CacheGuard. + */ + stdx::mutex _cacheMutex; + + /** + * Condition used to signal that it is OK for another CacheGuard to enter a fetch phase. + * Manipulated via CacheGuard. + */ + stdx::condition_variable _fetchPhaseIsReady; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager_global.cpp b/src/mongo/db/auth/authorization_manager_global.cpp index cc5ee6513f5..2fc20deef25 100644 --- a/src/mongo/db/auth/authorization_manager_global.cpp +++ b/src/mongo/db/auth/authorization_manager_global.cpp @@ -38,51 +38,50 @@ namespace mongo { namespace { - class AuthzVersionParameter : public ServerParameter { - MONGO_DISALLOW_COPYING(AuthzVersionParameter); - public: - AuthzVersionParameter(ServerParameterSet* sps, const std::string& name); - virtual void append(OperationContext* txn, BSONObjBuilder& b, const std::string& name); - virtual Status set(const BSONElement& newValueElement); - virtual Status setFromString(const std::string& str); - }; +class AuthzVersionParameter : public ServerParameter { + MONGO_DISALLOW_COPYING(AuthzVersionParameter); - MONGO_INITIALIZER_GENERAL(AuthzSchemaParameter, - MONGO_NO_PREREQUISITES, - ("BeginStartupOptionParsing"))(InitializerContext*) { - new AuthzVersionParameter(ServerParameterSet::getGlobal(), - authSchemaVersionServerParameter); - return Status::OK(); - } +public: + AuthzVersionParameter(ServerParameterSet* sps, const std::string& name); + virtual void append(OperationContext* txn, BSONObjBuilder& b, const std::string& name); + virtual Status set(const BSONElement& newValueElement); + virtual Status setFromString(const std::string& str); +}; - AuthzVersionParameter::AuthzVersionParameter(ServerParameterSet* sps, const std::string& name) : - ServerParameter(sps, name, false, false) {} +MONGO_INITIALIZER_GENERAL(AuthzSchemaParameter, + MONGO_NO_PREREQUISITES, + ("BeginStartupOptionParsing"))(InitializerContext*) { + new AuthzVersionParameter(ServerParameterSet::getGlobal(), authSchemaVersionServerParameter); + return Status::OK(); +} - void AuthzVersionParameter::append( - OperationContext* txn, BSONObjBuilder& b, const std::string& name) { - int authzVersion; - uassertStatusOK( - getGlobalAuthorizationManager()->getAuthorizationVersion(txn, &authzVersion)); - b.append(name, authzVersion); - } +AuthzVersionParameter::AuthzVersionParameter(ServerParameterSet* sps, const std::string& name) + : ServerParameter(sps, name, false, false) {} - Status AuthzVersionParameter::set(const BSONElement& newValueElement) { - return Status(ErrorCodes::InternalError, "set called on unsettable server parameter"); - } +void AuthzVersionParameter::append(OperationContext* txn, + BSONObjBuilder& b, + const std::string& name) { + int authzVersion; + uassertStatusOK(getGlobalAuthorizationManager()->getAuthorizationVersion(txn, &authzVersion)); + b.append(name, authzVersion); +} - Status AuthzVersionParameter::setFromString(const std::string& newValueString) { - return Status(ErrorCodes::InternalError, "set called on unsettable server parameter"); - } +Status AuthzVersionParameter::set(const BSONElement& newValueElement) { + return Status(ErrorCodes::InternalError, "set called on unsettable server parameter"); +} + +Status AuthzVersionParameter::setFromString(const std::string& newValueString) { + return Status(ErrorCodes::InternalError, "set called on unsettable server parameter"); +} } // namespace - const std::string authSchemaVersionServerParameter = "authSchemaVersion"; +const std::string authSchemaVersionServerParameter = "authSchemaVersion"; - AuthorizationManager* getGlobalAuthorizationManager() { - AuthorizationManager* globalAuthManager = AuthorizationManager::get( - getGlobalServiceContext()); - fassert(16842, globalAuthManager != nullptr); - return globalAuthManager; - } +AuthorizationManager* getGlobalAuthorizationManager() { + AuthorizationManager* globalAuthManager = AuthorizationManager::get(getGlobalServiceContext()); + fassert(16842, globalAuthManager != nullptr); + return globalAuthManager; +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager_global.h b/src/mongo/db/auth/authorization_manager_global.h index b0ef39f0069..3e6e936e520 100644 --- a/src/mongo/db/auth/authorization_manager_global.h +++ b/src/mongo/db/auth/authorization_manager_global.h @@ -32,12 +32,12 @@ namespace mongo { - /** - * Name of the server parameter used to report the auth schema version (via getParameter). - */ - extern const std::string authSchemaVersionServerParameter; +/** + * Name of the server parameter used to report the auth schema version (via getParameter). + */ +extern const std::string authSchemaVersionServerParameter; - // Gets the singleton AuthorizationManager object for this server process. - AuthorizationManager* getGlobalAuthorizationManager(); +// Gets the singleton AuthorizationManager object for this server process. +AuthorizationManager* getGlobalAuthorizationManager(); -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager_mock_init.cpp b/src/mongo/db/auth/authorization_manager_mock_init.cpp index eadf9711604..890ee2c0a2b 100644 --- a/src/mongo/db/auth/authorization_manager_mock_init.cpp +++ b/src/mongo/db/auth/authorization_manager_mock_init.cpp @@ -41,18 +41,18 @@ namespace mongo { namespace { - std::unique_ptr<AuthzManagerExternalState> createAuthzManagerExternalStateMock() { - return stdx::make_unique<AuthzManagerExternalStateMock>(); - } +std::unique_ptr<AuthzManagerExternalState> createAuthzManagerExternalStateMock() { + return stdx::make_unique<AuthzManagerExternalStateMock>(); +} - MONGO_INITIALIZER(CreateAuthorizationExternalStateFactory) (InitializerContext* context) { - AuthzManagerExternalState::create = &createAuthzManagerExternalStateMock; - return Status::OK(); - } +MONGO_INITIALIZER(CreateAuthorizationExternalStateFactory)(InitializerContext* context) { + AuthzManagerExternalState::create = &createAuthzManagerExternalStateMock; + return Status::OK(); +} - MONGO_INITIALIZER(SetGlobalEnvironment)(InitializerContext* context) { - setGlobalServiceContext(stdx::make_unique<ServiceContextNoop>()); - return Status::OK(); - } +MONGO_INITIALIZER(SetGlobalEnvironment)(InitializerContext* context) { + setGlobalServiceContext(stdx::make_unique<ServiceContextNoop>()); + return Status::OK(); +} } } diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp index 65a2f84243c..9114e8268c0 100644 --- a/src/mongo/db/auth/authorization_manager_test.cpp +++ b/src/mongo/db/auth/authorization_manager_test.cpp @@ -49,172 +49,185 @@ namespace mongo { namespace { - using std::vector; - - TEST(RoleParsingTest, BuildRoleBSON) { - RoleGraph graph; - RoleName roleA("roleA", "dbA"); - RoleName roleB("roleB", "dbB"); - RoleName roleC("roleC", "dbC"); - ActionSet actions; - actions.addAction(ActionType::find); - actions.addAction(ActionType::insert); - - ASSERT_OK(graph.createRole(roleA)); - ASSERT_OK(graph.createRole(roleB)); - ASSERT_OK(graph.createRole(roleC)); - - ASSERT_OK(graph.addRoleToRole(roleA, roleC)); - ASSERT_OK(graph.addRoleToRole(roleA, roleB)); - ASSERT_OK(graph.addRoleToRole(roleB, roleC)); - - ASSERT_OK(graph.addPrivilegeToRole( - roleA, Privilege(ResourcePattern::forAnyNormalResource(), actions))); - ASSERT_OK(graph.addPrivilegeToRole( - roleB, Privilege(ResourcePattern::forExactNamespace(NamespaceString("dbB.foo")), - actions))); - ASSERT_OK(graph.addPrivilegeToRole( - roleC, Privilege(ResourcePattern::forClusterResource(), actions))); - ASSERT_OK(graph.recomputePrivilegeData()); - - - // Role A - mutablebson::Document doc; - ASSERT_OK(AuthorizationManager::getBSONForRole(&graph, roleA, doc.root())); - BSONObj roleDoc = doc.getObject(); - - ASSERT_EQUALS("dbA.roleA", roleDoc["_id"].String()); - ASSERT_EQUALS("roleA", roleDoc["role"].String()); - ASSERT_EQUALS("dbA", roleDoc["db"].String()); - - vector<BSONElement> privs = roleDoc["privileges"].Array(); - ASSERT_EQUALS(1U, privs.size()); - ASSERT_EQUALS("", privs[0].Obj()["resource"].Obj()["db"].String()); - ASSERT_EQUALS("", privs[0].Obj()["resource"].Obj()["collection"].String()); - ASSERT(privs[0].Obj()["resource"].Obj()["cluster"].eoo()); - vector<BSONElement> actionElements = privs[0].Obj()["actions"].Array(); - ASSERT_EQUALS(2U, actionElements.size()); - ASSERT_EQUALS("find", actionElements[0].String()); - ASSERT_EQUALS("insert", actionElements[1].String()); - - vector<BSONElement> roles = roleDoc["roles"].Array(); - ASSERT_EQUALS(2U, roles.size()); - ASSERT_EQUALS("roleC", roles[0].Obj()["role"].String()); - ASSERT_EQUALS("dbC", roles[0].Obj()["db"].String()); - ASSERT_EQUALS("roleB", roles[1].Obj()["role"].String()); - ASSERT_EQUALS("dbB", roles[1].Obj()["db"].String()); - - // Role B - doc.reset(); - ASSERT_OK(AuthorizationManager::getBSONForRole(&graph, roleB, doc.root())); - roleDoc = doc.getObject(); - - ASSERT_EQUALS("dbB.roleB", roleDoc["_id"].String()); - ASSERT_EQUALS("roleB", roleDoc["role"].String()); - ASSERT_EQUALS("dbB", roleDoc["db"].String()); - - privs = roleDoc["privileges"].Array(); - ASSERT_EQUALS(1U, privs.size()); - ASSERT_EQUALS("dbB", privs[0].Obj()["resource"].Obj()["db"].String()); - ASSERT_EQUALS("foo", privs[0].Obj()["resource"].Obj()["collection"].String()); - ASSERT(privs[0].Obj()["resource"].Obj()["cluster"].eoo()); - actionElements = privs[0].Obj()["actions"].Array(); - ASSERT_EQUALS(2U, actionElements.size()); - ASSERT_EQUALS("find", actionElements[0].String()); - ASSERT_EQUALS("insert", actionElements[1].String()); - - roles = roleDoc["roles"].Array(); - ASSERT_EQUALS(1U, roles.size()); - ASSERT_EQUALS("roleC", roles[0].Obj()["role"].String()); - ASSERT_EQUALS("dbC", roles[0].Obj()["db"].String()); - - // Role C - doc.reset(); - ASSERT_OK(AuthorizationManager::getBSONForRole(&graph, roleC, doc.root())); - roleDoc = doc.getObject(); - - ASSERT_EQUALS("dbC.roleC", roleDoc["_id"].String()); - ASSERT_EQUALS("roleC", roleDoc["role"].String()); - ASSERT_EQUALS("dbC", roleDoc["db"].String()); - - privs = roleDoc["privileges"].Array(); - ASSERT_EQUALS(1U, privs.size()); - ASSERT(privs[0].Obj()["resource"].Obj()["cluster"].Bool()); - ASSERT(privs[0].Obj()["resource"].Obj()["db"].eoo()); - ASSERT(privs[0].Obj()["resource"].Obj()["collection"].eoo()); - actionElements = privs[0].Obj()["actions"].Array(); - ASSERT_EQUALS(2U, actionElements.size()); - ASSERT_EQUALS("find", actionElements[0].String()); - ASSERT_EQUALS("insert", actionElements[1].String()); - - roles = roleDoc["roles"].Array(); - ASSERT_EQUALS(0U, roles.size()); +using std::vector; + +TEST(RoleParsingTest, BuildRoleBSON) { + RoleGraph graph; + RoleName roleA("roleA", "dbA"); + RoleName roleB("roleB", "dbB"); + RoleName roleC("roleC", "dbC"); + ActionSet actions; + actions.addAction(ActionType::find); + actions.addAction(ActionType::insert); + + ASSERT_OK(graph.createRole(roleA)); + ASSERT_OK(graph.createRole(roleB)); + ASSERT_OK(graph.createRole(roleC)); + + ASSERT_OK(graph.addRoleToRole(roleA, roleC)); + ASSERT_OK(graph.addRoleToRole(roleA, roleB)); + ASSERT_OK(graph.addRoleToRole(roleB, roleC)); + + ASSERT_OK(graph.addPrivilegeToRole( + roleA, Privilege(ResourcePattern::forAnyNormalResource(), actions))); + ASSERT_OK(graph.addPrivilegeToRole( + roleB, Privilege(ResourcePattern::forExactNamespace(NamespaceString("dbB.foo")), actions))); + ASSERT_OK( + graph.addPrivilegeToRole(roleC, Privilege(ResourcePattern::forClusterResource(), actions))); + ASSERT_OK(graph.recomputePrivilegeData()); + + + // Role A + mutablebson::Document doc; + ASSERT_OK(AuthorizationManager::getBSONForRole(&graph, roleA, doc.root())); + BSONObj roleDoc = doc.getObject(); + + ASSERT_EQUALS("dbA.roleA", roleDoc["_id"].String()); + ASSERT_EQUALS("roleA", roleDoc["role"].String()); + ASSERT_EQUALS("dbA", roleDoc["db"].String()); + + vector<BSONElement> privs = roleDoc["privileges"].Array(); + ASSERT_EQUALS(1U, privs.size()); + ASSERT_EQUALS("", privs[0].Obj()["resource"].Obj()["db"].String()); + ASSERT_EQUALS("", privs[0].Obj()["resource"].Obj()["collection"].String()); + ASSERT(privs[0].Obj()["resource"].Obj()["cluster"].eoo()); + vector<BSONElement> actionElements = privs[0].Obj()["actions"].Array(); + ASSERT_EQUALS(2U, actionElements.size()); + ASSERT_EQUALS("find", actionElements[0].String()); + ASSERT_EQUALS("insert", actionElements[1].String()); + + vector<BSONElement> roles = roleDoc["roles"].Array(); + ASSERT_EQUALS(2U, roles.size()); + ASSERT_EQUALS("roleC", roles[0].Obj()["role"].String()); + ASSERT_EQUALS("dbC", roles[0].Obj()["db"].String()); + ASSERT_EQUALS("roleB", roles[1].Obj()["role"].String()); + ASSERT_EQUALS("dbB", roles[1].Obj()["db"].String()); + + // Role B + doc.reset(); + ASSERT_OK(AuthorizationManager::getBSONForRole(&graph, roleB, doc.root())); + roleDoc = doc.getObject(); + + ASSERT_EQUALS("dbB.roleB", roleDoc["_id"].String()); + ASSERT_EQUALS("roleB", roleDoc["role"].String()); + ASSERT_EQUALS("dbB", roleDoc["db"].String()); + + privs = roleDoc["privileges"].Array(); + ASSERT_EQUALS(1U, privs.size()); + ASSERT_EQUALS("dbB", privs[0].Obj()["resource"].Obj()["db"].String()); + ASSERT_EQUALS("foo", privs[0].Obj()["resource"].Obj()["collection"].String()); + ASSERT(privs[0].Obj()["resource"].Obj()["cluster"].eoo()); + actionElements = privs[0].Obj()["actions"].Array(); + ASSERT_EQUALS(2U, actionElements.size()); + ASSERT_EQUALS("find", actionElements[0].String()); + ASSERT_EQUALS("insert", actionElements[1].String()); + + roles = roleDoc["roles"].Array(); + ASSERT_EQUALS(1U, roles.size()); + ASSERT_EQUALS("roleC", roles[0].Obj()["role"].String()); + ASSERT_EQUALS("dbC", roles[0].Obj()["db"].String()); + + // Role C + doc.reset(); + ASSERT_OK(AuthorizationManager::getBSONForRole(&graph, roleC, doc.root())); + roleDoc = doc.getObject(); + + ASSERT_EQUALS("dbC.roleC", roleDoc["_id"].String()); + ASSERT_EQUALS("roleC", roleDoc["role"].String()); + ASSERT_EQUALS("dbC", roleDoc["db"].String()); + + privs = roleDoc["privileges"].Array(); + ASSERT_EQUALS(1U, privs.size()); + ASSERT(privs[0].Obj()["resource"].Obj()["cluster"].Bool()); + ASSERT(privs[0].Obj()["resource"].Obj()["db"].eoo()); + ASSERT(privs[0].Obj()["resource"].Obj()["collection"].eoo()); + actionElements = privs[0].Obj()["actions"].Array(); + ASSERT_EQUALS(2U, actionElements.size()); + ASSERT_EQUALS("find", actionElements[0].String()); + ASSERT_EQUALS("insert", actionElements[1].String()); + + roles = roleDoc["roles"].Array(); + ASSERT_EQUALS(0U, roles.size()); +} + +class AuthorizationManagerTest : public ::mongo::unittest::Test { +public: + virtual ~AuthorizationManagerTest() { + if (authzManager) + authzManager->invalidateUserCache(); } - class AuthorizationManagerTest : public ::mongo::unittest::Test { - public: - virtual ~AuthorizationManagerTest() { - if (authzManager) - authzManager->invalidateUserCache(); - } - - void setUp() { - auto localExternalState = stdx::make_unique<AuthzManagerExternalStateMock>(); - externalState = localExternalState.get(); - externalState->setAuthzVersion(AuthorizationManager::schemaVersion26Final); - authzManager = stdx::make_unique<AuthorizationManager>(std::move(localExternalState)); - externalState->setAuthorizationManager(authzManager.get()); - authzManager->setAuthEnabled(true); - } - - std::unique_ptr<AuthorizationManager> authzManager; - AuthzManagerExternalStateMock* externalState; - }; - - TEST_F(AuthorizationManagerTest, testAcquireV2User) { + void setUp() { + auto localExternalState = stdx::make_unique<AuthzManagerExternalStateMock>(); + externalState = localExternalState.get(); externalState->setAuthzVersion(AuthorizationManager::schemaVersion26Final); - - OperationContextNoop txn; - - ASSERT_OK(externalState->insertPrivilegeDocument( - &txn, - BSON("_id" << "admin.v2read" << - "user" << "v2read" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "password") << - "roles" << BSON_ARRAY(BSON("role" << "read" << "db" << "test"))), - BSONObj())); - ASSERT_OK(externalState->insertPrivilegeDocument( - &txn, - BSON("_id" << "admin.v2cluster" << - "user" << "v2cluster" << - "db" << "admin" << - "credentials" << BSON("MONGODB-CR" << "password") << - "roles" << BSON_ARRAY(BSON("role" << "clusterAdmin" << "db" << "admin"))), - BSONObj())); - - User* v2read; - ASSERT_OK(authzManager->acquireUser(&txn, UserName("v2read", "test"), &v2read)); - ASSERT_EQUALS(UserName("v2read", "test"), v2read->getName()); - ASSERT(v2read->isValid()); - ASSERT_EQUALS(1U, v2read->getRefCount()); - RoleNameIterator roles = v2read->getRoles(); - ASSERT_EQUALS(RoleName("read", "test"), roles.next()); - ASSERT_FALSE(roles.more()); - // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure - authzManager->releaseUser(v2read); - - User* v2cluster; - ASSERT_OK(authzManager->acquireUser(&txn, UserName("v2cluster", "admin"), &v2cluster)); - ASSERT_EQUALS(UserName("v2cluster", "admin"), v2cluster->getName()); - ASSERT(v2cluster->isValid()); - ASSERT_EQUALS(1U, v2cluster->getRefCount()); - RoleNameIterator clusterRoles = v2cluster->getRoles(); - ASSERT_EQUALS(RoleName("clusterAdmin", "admin"), clusterRoles.next()); - ASSERT_FALSE(clusterRoles.more()); - // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure - authzManager->releaseUser(v2cluster); + authzManager = stdx::make_unique<AuthorizationManager>(std::move(localExternalState)); + externalState->setAuthorizationManager(authzManager.get()); + authzManager->setAuthEnabled(true); } + std::unique_ptr<AuthorizationManager> authzManager; + AuthzManagerExternalStateMock* externalState; +}; + +TEST_F(AuthorizationManagerTest, testAcquireV2User) { + externalState->setAuthzVersion(AuthorizationManager::schemaVersion26Final); + + OperationContextNoop txn; + + ASSERT_OK( + externalState->insertPrivilegeDocument(&txn, + BSON("_id" + << "admin.v2read" + << "user" + << "v2read" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "password") + << "roles" << BSON_ARRAY(BSON("role" + << "read" + << "db" + << "test"))), + BSONObj())); + ASSERT_OK( + externalState->insertPrivilegeDocument(&txn, + BSON("_id" + << "admin.v2cluster" + << "user" + << "v2cluster" + << "db" + << "admin" + << "credentials" << BSON("MONGODB-CR" + << "password") + << "roles" << BSON_ARRAY(BSON("role" + << "clusterAdmin" + << "db" + << "admin"))), + BSONObj())); + + User* v2read; + ASSERT_OK(authzManager->acquireUser(&txn, UserName("v2read", "test"), &v2read)); + ASSERT_EQUALS(UserName("v2read", "test"), v2read->getName()); + ASSERT(v2read->isValid()); + ASSERT_EQUALS(1U, v2read->getRefCount()); + RoleNameIterator roles = v2read->getRoles(); + ASSERT_EQUALS(RoleName("read", "test"), roles.next()); + ASSERT_FALSE(roles.more()); + // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure + authzManager->releaseUser(v2read); + + User* v2cluster; + ASSERT_OK(authzManager->acquireUser(&txn, UserName("v2cluster", "admin"), &v2cluster)); + ASSERT_EQUALS(UserName("v2cluster", "admin"), v2cluster->getName()); + ASSERT(v2cluster->isValid()); + ASSERT_EQUALS(1U, v2cluster->getRefCount()); + RoleNameIterator clusterRoles = v2cluster->getRoles(); + ASSERT_EQUALS(RoleName("clusterAdmin", "admin"), clusterRoles.next()); + ASSERT_FALSE(clusterRoles.more()); + // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure + authzManager->releaseUser(v2cluster); +} + } // namespace } // namespace mongo diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp index bcc66f45d17..22bd1b1f191 100644 --- a/src/mongo/db/auth/authorization_session.cpp +++ b/src/mongo/db/auth/authorization_session.cpp @@ -51,461 +51,437 @@ namespace mongo { - using std::vector; +using std::vector; namespace { - const std::string ADMIN_DBNAME = "admin"; +const std::string ADMIN_DBNAME = "admin"; } // namespace - AuthorizationSession::AuthorizationSession( - std::unique_ptr<AuthzSessionExternalState> externalState) - : _externalState(std::move(externalState)), - _impersonationFlag(false) {} - - AuthorizationSession::~AuthorizationSession() { - for (UserSet::iterator it = _authenticatedUsers.begin(); - it != _authenticatedUsers.end(); ++it) { - getAuthorizationManager().releaseUser(*it); +AuthorizationSession::AuthorizationSession(std::unique_ptr<AuthzSessionExternalState> externalState) + : _externalState(std::move(externalState)), _impersonationFlag(false) {} + +AuthorizationSession::~AuthorizationSession() { + for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); + ++it) { + getAuthorizationManager().releaseUser(*it); + } +} + +AuthorizationManager& AuthorizationSession::getAuthorizationManager() { + return _externalState->getAuthorizationManager(); +} + +void AuthorizationSession::startRequest(OperationContext* txn) { + _externalState->startRequest(txn); + _refreshUserInfoAsNeeded(txn); +} + +Status AuthorizationSession::addAndAuthorizeUser(OperationContext* txn, const UserName& userName) { + User* user; + Status status = getAuthorizationManager().acquireUser(txn, userName, &user); + if (!status.isOK()) { + return status; + } + + // Calling add() on the UserSet may return a user that was replaced because it was from the + // same database. + User* replacedUser = _authenticatedUsers.add(user); + if (replacedUser) { + getAuthorizationManager().releaseUser(replacedUser); + } + + // If there are any users and roles in the impersonation data, clear it out. + clearImpersonatedUserData(); + + _buildAuthenticatedRolesVector(); + return Status::OK(); +} + +User* AuthorizationSession::lookupUser(const UserName& name) { + return _authenticatedUsers.lookup(name); +} + +void AuthorizationSession::logoutDatabase(const std::string& dbname) { + User* removedUser = _authenticatedUsers.removeByDBName(dbname); + if (removedUser) { + getAuthorizationManager().releaseUser(removedUser); + } + clearImpersonatedUserData(); + _buildAuthenticatedRolesVector(); +} + +UserNameIterator AuthorizationSession::getAuthenticatedUserNames() { + return _authenticatedUsers.getNames(); +} + +RoleNameIterator AuthorizationSession::getAuthenticatedRoleNames() { + return makeRoleNameIterator(_authenticatedRoleNames.begin(), _authenticatedRoleNames.end()); +} + +std::string AuthorizationSession::getAuthenticatedUserNamesToken() { + std::string ret; + for (UserNameIterator nameIter = getAuthenticatedUserNames(); nameIter.more(); + nameIter.next()) { + ret += '\0'; // Using a NUL byte which isn't valid in usernames to separate them. + ret += nameIter->getFullName(); + } + + return ret; +} + +void AuthorizationSession::grantInternalAuthorization() { + _authenticatedUsers.add(internalSecurity.user); + _buildAuthenticatedRolesVector(); +} + +PrivilegeVector AuthorizationSession::getDefaultPrivileges() { + PrivilegeVector defaultPrivileges; + + // If localhost exception is active (and no users exist), + // return a vector of the minimum privileges required to bootstrap + // a system and add the first user. + if (_externalState->shouldAllowLocalhost()) { + ResourcePattern adminDBResource = ResourcePattern::forDatabaseName(ADMIN_DBNAME); + ActionSet setupAdminUserActionSet; + setupAdminUserActionSet.addAction(ActionType::createUser); + setupAdminUserActionSet.addAction(ActionType::grantRole); + Privilege setupAdminUserPrivilege = Privilege(adminDBResource, setupAdminUserActionSet); + + ResourcePattern externalDBResource = ResourcePattern::forDatabaseName("$external"); + Privilege setupExternalUserPrivilege = + Privilege(externalDBResource, ActionType::createUser); + + ActionSet setupServerConfigActionSet; + + // If this server is an arbiter, add specific privileges meant to circumvent + // the behavior of an arbiter in an authenticated replset. See SERVER-5479. + if (_externalState->serverIsArbiter()) { + setupServerConfigActionSet.addAction(ActionType::getCmdLineOpts); + setupServerConfigActionSet.addAction(ActionType::getParameter); + setupServerConfigActionSet.addAction(ActionType::serverStatus); + setupServerConfigActionSet.addAction(ActionType::shutdown); } - } - - AuthorizationManager& AuthorizationSession::getAuthorizationManager() { - return _externalState->getAuthorizationManager(); - } - void AuthorizationSession::startRequest(OperationContext* txn) { - _externalState->startRequest(txn); - _refreshUserInfoAsNeeded(txn); - } - - Status AuthorizationSession::addAndAuthorizeUser( - OperationContext* txn, const UserName& userName) { - User* user; - Status status = getAuthorizationManager().acquireUser(txn, userName, &user); - if (!status.isOK()) { - return status; - } + setupServerConfigActionSet.addAction(ActionType::addShard); + setupServerConfigActionSet.addAction(ActionType::replSetConfigure); + setupServerConfigActionSet.addAction(ActionType::replSetGetStatus); + Privilege setupServerConfigPrivilege = + Privilege(ResourcePattern::forClusterResource(), setupServerConfigActionSet); - // Calling add() on the UserSet may return a user that was replaced because it was from the - // same database. - User* replacedUser = _authenticatedUsers.add(user); - if (replacedUser) { - getAuthorizationManager().releaseUser(replacedUser); - } - - // If there are any users and roles in the impersonation data, clear it out. - clearImpersonatedUserData(); - - _buildAuthenticatedRolesVector(); - return Status::OK(); - } - - User* AuthorizationSession::lookupUser(const UserName& name) { - return _authenticatedUsers.lookup(name); - } - - void AuthorizationSession::logoutDatabase(const std::string& dbname) { - User* removedUser = _authenticatedUsers.removeByDBName(dbname); - if (removedUser) { - getAuthorizationManager().releaseUser(removedUser); - } - clearImpersonatedUserData(); - _buildAuthenticatedRolesVector(); - } - - UserNameIterator AuthorizationSession::getAuthenticatedUserNames() { - return _authenticatedUsers.getNames(); - } - - RoleNameIterator AuthorizationSession::getAuthenticatedRoleNames() { - return makeRoleNameIterator(_authenticatedRoleNames.begin(), - _authenticatedRoleNames.end()); + Privilege::addPrivilegeToPrivilegeVector(&defaultPrivileges, setupAdminUserPrivilege); + Privilege::addPrivilegeToPrivilegeVector(&defaultPrivileges, setupExternalUserPrivilege); + Privilege::addPrivilegeToPrivilegeVector(&defaultPrivileges, setupServerConfigPrivilege); + return defaultPrivileges; } - std::string AuthorizationSession::getAuthenticatedUserNamesToken() { - std::string ret; - for (UserNameIterator nameIter = getAuthenticatedUserNames(); - nameIter.more(); - nameIter.next()) { - ret += '\0'; // Using a NUL byte which isn't valid in usernames to separate them. - ret += nameIter->getFullName(); - } + return defaultPrivileges; +} - return ret; +Status AuthorizationSession::checkAuthForQuery(const NamespaceString& ns, const BSONObj& query) { + if (MONGO_unlikely(ns.isCommand())) { + return Status(ErrorCodes::InternalError, + str::stream() << "Checking query auth on command namespace " << ns.ns()); } - - void AuthorizationSession::grantInternalAuthorization() { - _authenticatedUsers.add(internalSecurity.user); - _buildAuthenticatedRolesVector(); + if (!isAuthorizedForActionsOnNamespace(ns, ActionType::find)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized for query on " << ns.ns()); } + return Status::OK(); +} - PrivilegeVector AuthorizationSession::getDefaultPrivileges() { - PrivilegeVector defaultPrivileges; - - // If localhost exception is active (and no users exist), - // return a vector of the minimum privileges required to bootstrap - // a system and add the first user. - if (_externalState->shouldAllowLocalhost()) { - ResourcePattern adminDBResource = ResourcePattern::forDatabaseName(ADMIN_DBNAME); - ActionSet setupAdminUserActionSet; - setupAdminUserActionSet.addAction(ActionType::createUser); - setupAdminUserActionSet.addAction(ActionType::grantRole); - Privilege setupAdminUserPrivilege = - Privilege(adminDBResource, setupAdminUserActionSet); - - ResourcePattern externalDBResource = ResourcePattern::forDatabaseName("$external"); - Privilege setupExternalUserPrivilege = - Privilege(externalDBResource, ActionType::createUser); - - ActionSet setupServerConfigActionSet; - - // If this server is an arbiter, add specific privileges meant to circumvent - // the behavior of an arbiter in an authenticated replset. See SERVER-5479. - if (_externalState->serverIsArbiter()) { - setupServerConfigActionSet.addAction(ActionType::getCmdLineOpts); - setupServerConfigActionSet.addAction(ActionType::getParameter); - setupServerConfigActionSet.addAction(ActionType::serverStatus); - setupServerConfigActionSet.addAction(ActionType::shutdown); - } - - setupServerConfigActionSet.addAction(ActionType::addShard); - setupServerConfigActionSet.addAction(ActionType::replSetConfigure); - setupServerConfigActionSet.addAction(ActionType::replSetGetStatus); - Privilege setupServerConfigPrivilege = - Privilege(ResourcePattern::forClusterResource(), setupServerConfigActionSet); - - Privilege::addPrivilegeToPrivilegeVector(&defaultPrivileges, setupAdminUserPrivilege); - Privilege::addPrivilegeToPrivilegeVector(&defaultPrivileges, - setupExternalUserPrivilege); - Privilege::addPrivilegeToPrivilegeVector(&defaultPrivileges, - setupServerConfigPrivilege); - return defaultPrivileges; +Status AuthorizationSession::checkAuthForGetMore(const NamespaceString& ns, long long cursorID) { + // "ns" can be in one of three formats: "listCollections" format, "listIndexes" format, and + // normal format. + if (ns.isListCollectionsGetMore()) { + // "ns" is of the form "<db>.$cmd.listCollections". Check if we can perform the + // listCollections action on the database resource for "<db>". + if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(ns.db()), + ActionType::listCollections)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized for listCollections getMore on " + << ns.ns()); } - - return defaultPrivileges; - } - - Status AuthorizationSession::checkAuthForQuery(const NamespaceString& ns, - const BSONObj& query) { - if (MONGO_unlikely(ns.isCommand())) { - return Status(ErrorCodes::InternalError, str::stream() << - "Checking query auth on command namespace " << ns.ns()); + } else if (ns.isListIndexesGetMore()) { + // "ns" is of the form "<db>.$cmd.listIndexes.<coll>". Check if we can perform the + // listIndexes action on the "<db>.<coll>" namespace. + NamespaceString targetNS = ns.getTargetNSForListIndexesGetMore(); + if (!isAuthorizedForActionsOnNamespace(targetNS, ActionType::listIndexes)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized for listIndexes getMore on " << ns.ns()); } + } else { + // "ns" is a regular namespace string. Check if we can perform the find action on it. if (!isAuthorizedForActionsOnNamespace(ns, ActionType::find)) { return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for query on " << ns.ns()); + str::stream() << "not authorized for getMore on " << ns.ns()); } - return Status::OK(); } + return Status::OK(); +} - Status AuthorizationSession::checkAuthForGetMore(const NamespaceString& ns, - long long cursorID) { - // "ns" can be in one of three formats: "listCollections" format, "listIndexes" format, and - // normal format. - if (ns.isListCollectionsGetMore()) { - // "ns" is of the form "<db>.$cmd.listCollections". Check if we can perform the - // listCollections action on the database resource for "<db>". - if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(ns.db()), - ActionType::listCollections)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for listCollections getMore on " - << ns.ns()); - } +Status AuthorizationSession::checkAuthForInsert(const NamespaceString& ns, + const BSONObj& document) { + if (ns.coll() == StringData("system.indexes", StringData::LiteralTag())) { + BSONElement nsElement = document["ns"]; + if (nsElement.type() != String) { + return Status(ErrorCodes::Unauthorized, + "Cannot authorize inserting into " + "system.indexes documents without a string-typed \"ns\" field."); } - else if (ns.isListIndexesGetMore()) { - // "ns" is of the form "<db>.$cmd.listIndexes.<coll>". Check if we can perform the - // listIndexes action on the "<db>.<coll>" namespace. - NamespaceString targetNS = ns.getTargetNSForListIndexesGetMore(); - if (!isAuthorizedForActionsOnNamespace(targetNS, ActionType::listIndexes)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for listIndexes getMore on " - << ns.ns()); - } + NamespaceString indexNS(nsElement.str()); + if (!isAuthorizedForActionsOnNamespace(indexNS, ActionType::createIndex)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized to create index on " << indexNS.ns()); } - else { - // "ns" is a regular namespace string. Check if we can perform the find action on it. - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::find)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for getMore on " << ns.ns()); - } + } else { + if (!isAuthorizedForActionsOnNamespace(ns, ActionType::insert)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized for insert on " << ns.ns()); } - return Status::OK(); } - Status AuthorizationSession::checkAuthForInsert(const NamespaceString& ns, - const BSONObj& document) { - if (ns.coll() == StringData("system.indexes", StringData::LiteralTag())) { - BSONElement nsElement = document["ns"]; - if (nsElement.type() != String) { - return Status(ErrorCodes::Unauthorized, "Cannot authorize inserting into " - "system.indexes documents without a string-typed \"ns\" field."); - } - NamespaceString indexNS(nsElement.str()); - if (!isAuthorizedForActionsOnNamespace(indexNS, ActionType::createIndex)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized to create index on " << - indexNS.ns()); - } - } else { - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::insert)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for insert on " << ns.ns()); - } - } - - return Status::OK(); - } + return Status::OK(); +} - Status AuthorizationSession::checkAuthForUpdate(const NamespaceString& ns, - const BSONObj& query, - const BSONObj& update, - bool upsert) { - if (!upsert) { - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::update)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for update on " << ns.ns()); - } +Status AuthorizationSession::checkAuthForUpdate(const NamespaceString& ns, + const BSONObj& query, + const BSONObj& update, + bool upsert) { + if (!upsert) { + if (!isAuthorizedForActionsOnNamespace(ns, ActionType::update)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized for update on " << ns.ns()); } - else { - ActionSet required; - required.addAction(ActionType::update); - required.addAction(ActionType::insert); - if (!isAuthorizedForActionsOnNamespace(ns, required)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for upsert on " << ns.ns()); - } + } else { + ActionSet required; + required.addAction(ActionType::update); + required.addAction(ActionType::insert); + if (!isAuthorizedForActionsOnNamespace(ns, required)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized for upsert on " << ns.ns()); } - return Status::OK(); } + return Status::OK(); +} - Status AuthorizationSession::checkAuthForDelete(const NamespaceString& ns, - const BSONObj& query) { - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::remove)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized to remove from " << ns.ns()); - } - return Status::OK(); +Status AuthorizationSession::checkAuthForDelete(const NamespaceString& ns, const BSONObj& query) { + if (!isAuthorizedForActionsOnNamespace(ns, ActionType::remove)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized to remove from " << ns.ns()); } + return Status::OK(); +} - Status AuthorizationSession::checkAuthForKillCursors(const NamespaceString& ns, - long long cursorID) { - // See implementation comments in checkAuthForGetMore(). This method looks very similar. - if (ns.isListCollectionsGetMore()) { - if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(ns.db()), - ActionType::killCursors)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized to kill listCollections cursor on " - << ns.ns()); - } +Status AuthorizationSession::checkAuthForKillCursors(const NamespaceString& ns, + long long cursorID) { + // See implementation comments in checkAuthForGetMore(). This method looks very similar. + if (ns.isListCollectionsGetMore()) { + if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(ns.db()), + ActionType::killCursors)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized to kill listCollections cursor on " + << ns.ns()); } - else if (ns.isListIndexesGetMore()) { - NamespaceString targetNS = ns.getTargetNSForListIndexesGetMore(); - if (!isAuthorizedForActionsOnNamespace(targetNS, ActionType::killCursors)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized to kill listIndexes cursor on " - << ns.ns()); - } + } else if (ns.isListIndexesGetMore()) { + NamespaceString targetNS = ns.getTargetNSForListIndexesGetMore(); + if (!isAuthorizedForActionsOnNamespace(targetNS, ActionType::killCursors)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized to kill listIndexes cursor on " + << ns.ns()); } - else { - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::killCursors)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized to kill cursor on " << ns.ns()); - } + } else { + if (!isAuthorizedForActionsOnNamespace(ns, ActionType::killCursors)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized to kill cursor on " << ns.ns()); } - return Status::OK(); } + return Status::OK(); +} - Status AuthorizationSession::checkAuthorizedToGrantPrivilege(const Privilege& privilege) { - const ResourcePattern& resource = privilege.getResourcePattern(); - if (resource.isDatabasePattern() || resource.isExactNamespacePattern()) { - if (!isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(resource.databaseToMatch()), - ActionType::grantRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to grant privileges on the " - << resource.databaseToMatch() << "database"); - } - } else if (!isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName("admin"), - ActionType::grantRole)) { +Status AuthorizationSession::checkAuthorizedToGrantPrivilege(const Privilege& privilege) { + const ResourcePattern& resource = privilege.getResourcePattern(); + if (resource.isDatabasePattern() || resource.isExactNamespacePattern()) { + if (!isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(resource.databaseToMatch()), + ActionType::grantRole)) { return Status(ErrorCodes::Unauthorized, - "To grant privileges affecting multiple databases or the cluster," - " must be authorized to grant roles from the admin database"); + str::stream() << "Not authorized to grant privileges on the " + << resource.databaseToMatch() << "database"); } - return Status::OK(); - } - - - Status AuthorizationSession::checkAuthorizedToRevokePrivilege(const Privilege& privilege) { - const ResourcePattern& resource = privilege.getResourcePattern(); - if (resource.isDatabasePattern() || resource.isExactNamespacePattern()) { - if (!isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(resource.databaseToMatch()), - ActionType::revokeRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to revoke privileges on the " - << resource.databaseToMatch() << "database"); - } - } else if (!isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName("admin"), - ActionType::revokeRole)) { + } else if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName("admin"), + ActionType::grantRole)) { + return Status(ErrorCodes::Unauthorized, + "To grant privileges affecting multiple databases or the cluster," + " must be authorized to grant roles from the admin database"); + } + return Status::OK(); +} + + +Status AuthorizationSession::checkAuthorizedToRevokePrivilege(const Privilege& privilege) { + const ResourcePattern& resource = privilege.getResourcePattern(); + if (resource.isDatabasePattern() || resource.isExactNamespacePattern()) { + if (!isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(resource.databaseToMatch()), + ActionType::revokeRole)) { return Status(ErrorCodes::Unauthorized, - "To revoke privileges affecting multiple databases or the cluster," - " must be authorized to revoke roles from the admin database"); + str::stream() << "Not authorized to revoke privileges on the " + << resource.databaseToMatch() << "database"); } - return Status::OK(); - } - - bool AuthorizationSession::isAuthorizedToGrantRole(const RoleName& role) { - return isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(role.getDB()), - ActionType::grantRole); - } - - bool AuthorizationSession::isAuthorizedToRevokeRole(const RoleName& role) { - return isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(role.getDB()), - ActionType::revokeRole); - } - - bool AuthorizationSession::isAuthorizedForPrivilege(const Privilege& privilege) { - if (_externalState->shouldIgnoreAuthChecks()) - return true; - - return _isAuthorizedForPrivilege(privilege); - } - - bool AuthorizationSession::isAuthorizedForPrivileges(const vector<Privilege>& privileges) { - if (_externalState->shouldIgnoreAuthChecks()) - return true; + } else if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName("admin"), + ActionType::revokeRole)) { + return Status(ErrorCodes::Unauthorized, + "To revoke privileges affecting multiple databases or the cluster," + " must be authorized to revoke roles from the admin database"); + } + return Status::OK(); +} + +bool AuthorizationSession::isAuthorizedToGrantRole(const RoleName& role) { + return isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(role.getDB()), + ActionType::grantRole); +} + +bool AuthorizationSession::isAuthorizedToRevokeRole(const RoleName& role) { + return isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(role.getDB()), + ActionType::revokeRole); +} + +bool AuthorizationSession::isAuthorizedForPrivilege(const Privilege& privilege) { + if (_externalState->shouldIgnoreAuthChecks()) + return true; - for (size_t i = 0; i < privileges.size(); ++i) { - if (!_isAuthorizedForPrivilege(privileges[i])) - return false; - } + return _isAuthorizedForPrivilege(privilege); +} +bool AuthorizationSession::isAuthorizedForPrivileges(const vector<Privilege>& privileges) { + if (_externalState->shouldIgnoreAuthChecks()) return true; - } - bool AuthorizationSession::isAuthorizedForActionsOnResource(const ResourcePattern& resource, - ActionType action) { - return isAuthorizedForPrivilege(Privilege(resource, action)); + for (size_t i = 0; i < privileges.size(); ++i) { + if (!_isAuthorizedForPrivilege(privileges[i])) + return false; } - bool AuthorizationSession::isAuthorizedForActionsOnResource(const ResourcePattern& resource, - const ActionSet& actions) { - return isAuthorizedForPrivilege(Privilege(resource, actions)); - } + return true; +} - bool AuthorizationSession::isAuthorizedForActionsOnNamespace(const NamespaceString& ns, - ActionType action) { - return isAuthorizedForPrivilege( - Privilege(ResourcePattern::forExactNamespace(ns), action)); - } +bool AuthorizationSession::isAuthorizedForActionsOnResource(const ResourcePattern& resource, + ActionType action) { + return isAuthorizedForPrivilege(Privilege(resource, action)); +} - bool AuthorizationSession::isAuthorizedForActionsOnNamespace(const NamespaceString& ns, - const ActionSet& actions) { - return isAuthorizedForPrivilege( - Privilege(ResourcePattern::forExactNamespace(ns), actions)); - } +bool AuthorizationSession::isAuthorizedForActionsOnResource(const ResourcePattern& resource, + const ActionSet& actions) { + return isAuthorizedForPrivilege(Privilege(resource, actions)); +} - static const int resourceSearchListCapacity = 5; - /** - * Builds from "target" an exhaustive list of all ResourcePatterns that match "target". - * - * Stores the resulting list into resourceSearchList, and returns the length. - * - * The seach lists are as follows, depending on the type of "target": - * - * target is ResourcePattern::forAnyResource(): - * searchList = { ResourcePattern::forAnyResource(), ResourcePattern::forAnyResource() } - * target is the ResourcePattern::forClusterResource(): - * searchList = { ResourcePattern::forAnyResource(), ResourcePattern::forClusterResource() } - * target is a database, db: - * searchList = { ResourcePattern::forAnyResource(), - * ResourcePattern::forAnyNormalResource(), - * db } - * target is a non-system collection, db.coll: - * searchList = { ResourcePattern::forAnyResource(), - * ResourcePattern::forAnyNormalResource(), - * db, - * coll, - * db.coll } - * target is a system collection, db.system.coll: - * searchList = { ResourcePattern::forAnyResource(), - * system.coll, - * db.system.coll } - */ - static int buildResourceSearchList( - const ResourcePattern& target, - ResourcePattern resourceSearchList[resourceSearchListCapacity]) { - - int size = 0; - resourceSearchList[size++] = ResourcePattern::forAnyResource(); - if (target.isExactNamespacePattern()) { - if (!target.ns().isSystem()) { - resourceSearchList[size++] = ResourcePattern::forAnyNormalResource(); - resourceSearchList[size++] = ResourcePattern::forDatabaseName(target.ns().db()); - } - resourceSearchList[size++] = ResourcePattern::forCollectionName(target.ns().coll()); - } - else if (target.isDatabasePattern()) { - resourceSearchList[size++] = ResourcePattern::forAnyNormalResource(); - } - resourceSearchList[size++] = target; - dassert(size <= resourceSearchListCapacity); - return size; - } +bool AuthorizationSession::isAuthorizedForActionsOnNamespace(const NamespaceString& ns, + ActionType action) { + return isAuthorizedForPrivilege(Privilege(ResourcePattern::forExactNamespace(ns), action)); +} - bool AuthorizationSession::isAuthorizedToChangeAsUser(const UserName& userName, ActionType actionType) { - User* user = lookupUser(userName); - if (!user) { - return false; - } - ResourcePattern resourceSearchList[resourceSearchListCapacity]; - const int resourceSearchListLength = - buildResourceSearchList(ResourcePattern::forDatabaseName(userName.getDB()), - resourceSearchList); +bool AuthorizationSession::isAuthorizedForActionsOnNamespace(const NamespaceString& ns, + const ActionSet& actions) { + return isAuthorizedForPrivilege(Privilege(ResourcePattern::forExactNamespace(ns), actions)); +} - ActionSet actions; - for (int i = 0; i < resourceSearchListLength; ++i) { - actions.addAllActionsFromSet(user->getActionsForResource(resourceSearchList[i])); +static const int resourceSearchListCapacity = 5; +/** + * Builds from "target" an exhaustive list of all ResourcePatterns that match "target". + * + * Stores the resulting list into resourceSearchList, and returns the length. + * + * The seach lists are as follows, depending on the type of "target": + * + * target is ResourcePattern::forAnyResource(): + * searchList = { ResourcePattern::forAnyResource(), ResourcePattern::forAnyResource() } + * target is the ResourcePattern::forClusterResource(): + * searchList = { ResourcePattern::forAnyResource(), ResourcePattern::forClusterResource() } + * target is a database, db: + * searchList = { ResourcePattern::forAnyResource(), + * ResourcePattern::forAnyNormalResource(), + * db } + * target is a non-system collection, db.coll: + * searchList = { ResourcePattern::forAnyResource(), + * ResourcePattern::forAnyNormalResource(), + * db, + * coll, + * db.coll } + * target is a system collection, db.system.coll: + * searchList = { ResourcePattern::forAnyResource(), + * system.coll, + * db.system.coll } + */ +static int buildResourceSearchList(const ResourcePattern& target, + ResourcePattern resourceSearchList[resourceSearchListCapacity]) { + int size = 0; + resourceSearchList[size++] = ResourcePattern::forAnyResource(); + if (target.isExactNamespacePattern()) { + if (!target.ns().isSystem()) { + resourceSearchList[size++] = ResourcePattern::forAnyNormalResource(); + resourceSearchList[size++] = ResourcePattern::forDatabaseName(target.ns().db()); } - return actions.contains(actionType); + resourceSearchList[size++] = ResourcePattern::forCollectionName(target.ns().coll()); + } else if (target.isDatabasePattern()) { + resourceSearchList[size++] = ResourcePattern::forAnyNormalResource(); + } + resourceSearchList[size++] = target; + dassert(size <= resourceSearchListCapacity); + return size; +} + +bool AuthorizationSession::isAuthorizedToChangeAsUser(const UserName& userName, + ActionType actionType) { + User* user = lookupUser(userName); + if (!user) { + return false; } + ResourcePattern resourceSearchList[resourceSearchListCapacity]; + const int resourceSearchListLength = buildResourceSearchList( + ResourcePattern::forDatabaseName(userName.getDB()), resourceSearchList); - bool AuthorizationSession::isAuthorizedToChangeOwnPasswordAsUser(const UserName& userName) { - return AuthorizationSession::isAuthorizedToChangeAsUser(userName, ActionType::changeOwnPassword); + ActionSet actions; + for (int i = 0; i < resourceSearchListLength; ++i) { + actions.addAllActionsFromSet(user->getActionsForResource(resourceSearchList[i])); } + return actions.contains(actionType); +} - bool AuthorizationSession::isAuthorizedToChangeOwnCustomDataAsUser(const UserName& userName) { - return AuthorizationSession::isAuthorizedToChangeAsUser(userName, ActionType::changeOwnCustomData); - } +bool AuthorizationSession::isAuthorizedToChangeOwnPasswordAsUser(const UserName& userName) { + return AuthorizationSession::isAuthorizedToChangeAsUser(userName, + ActionType::changeOwnPassword); +} - bool AuthorizationSession::isAuthenticatedAsUserWithRole(const RoleName& roleName) { - for (UserSet::iterator it = _authenticatedUsers.begin(); - it != _authenticatedUsers.end(); ++it) { - if ((*it)->hasRole(roleName)) { - return true; - } +bool AuthorizationSession::isAuthorizedToChangeOwnCustomDataAsUser(const UserName& userName) { + return AuthorizationSession::isAuthorizedToChangeAsUser(userName, + ActionType::changeOwnCustomData); +} + +bool AuthorizationSession::isAuthenticatedAsUserWithRole(const RoleName& roleName) { + for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); + ++it) { + if ((*it)->hasRole(roleName)) { + return true; } - return false; } + return false; +} - void AuthorizationSession::_refreshUserInfoAsNeeded(OperationContext* txn) { - AuthorizationManager& authMan = getAuthorizationManager(); - UserSet::iterator it = _authenticatedUsers.begin(); - while (it != _authenticatedUsers.end()) { - User* user = *it; +void AuthorizationSession::_refreshUserInfoAsNeeded(OperationContext* txn) { + AuthorizationManager& authMan = getAuthorizationManager(); + UserSet::iterator it = _authenticatedUsers.begin(); + while (it != _authenticatedUsers.end()) { + User* user = *it; - if (!user->isValid()) { - // Make a good faith effort to acquire an up-to-date user object, since the one - // we've cached is marked "out-of-date." - UserName name = user->getName(); - User* updatedUser; + if (!user->isValid()) { + // Make a good faith effort to acquire an up-to-date user object, since the one + // we've cached is marked "out-of-date." + UserName name = user->getName(); + User* updatedUser; - Status status = authMan.acquireUser(txn, name, &updatedUser); - switch (status.code()) { + Status status = authMan.acquireUser(txn, name, &updatedUser); + switch (status.code()) { case ErrorCodes::OK: { // Success! Replace the old User object with the updated one. fassert(17067, _authenticatedUsers.replaceAt(it, updatedUser) == user); @@ -517,103 +493,98 @@ namespace { // User does not exist anymore; remove it from _authenticatedUsers. fassert(17068, _authenticatedUsers.removeAt(it) == user); authMan.releaseUser(user); - log() << "Removed deleted user " << name << - " from session cache of user information."; + log() << "Removed deleted user " << name + << " from session cache of user information."; continue; // No need to advance "it" in this case. } default: // Unrecognized error; assume that it's transient, and continue working with the // out-of-date privilege data. - warning() << "Could not fetch updated user privilege information for " << - name << "; continuing to use old information. Reason is " << status; + warning() << "Could not fetch updated user privilege information for " << name + << "; continuing to use old information. Reason is " << status; break; - } } - ++it; } - _buildAuthenticatedRolesVector(); - } - - void AuthorizationSession::_buildAuthenticatedRolesVector() { - _authenticatedRoleNames.clear(); - for (UserSet::iterator it = _authenticatedUsers.begin(); - it != _authenticatedUsers.end(); - ++it) { - RoleNameIterator roles = (*it)->getIndirectRoles(); - while (roles.more()) { - RoleName roleName = roles.next(); - _authenticatedRoleNames.push_back(RoleName(roleName.getRole(), - roleName.getDB())); - } + ++it; + } + _buildAuthenticatedRolesVector(); +} + +void AuthorizationSession::_buildAuthenticatedRolesVector() { + _authenticatedRoleNames.clear(); + for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); + ++it) { + RoleNameIterator roles = (*it)->getIndirectRoles(); + while (roles.more()) { + RoleName roleName = roles.next(); + _authenticatedRoleNames.push_back(RoleName(roleName.getRole(), roleName.getDB())); } } +} - bool AuthorizationSession::_isAuthorizedForPrivilege(const Privilege& privilege) { - const ResourcePattern& target(privilege.getResourcePattern()); - - ResourcePattern resourceSearchList[resourceSearchListCapacity]; - const int resourceSearchListLength = buildResourceSearchList(target, resourceSearchList); +bool AuthorizationSession::_isAuthorizedForPrivilege(const Privilege& privilege) { + const ResourcePattern& target(privilege.getResourcePattern()); - ActionSet unmetRequirements = privilege.getActions(); + ResourcePattern resourceSearchList[resourceSearchListCapacity]; + const int resourceSearchListLength = buildResourceSearchList(target, resourceSearchList); - PrivilegeVector defaultPrivileges = getDefaultPrivileges(); - for (PrivilegeVector::iterator it = defaultPrivileges.begin(); - it != defaultPrivileges.end(); ++it) { + ActionSet unmetRequirements = privilege.getActions(); - for (int i = 0; i < resourceSearchListLength; ++i) { - if (!(it->getResourcePattern() == resourceSearchList[i])) - continue; + PrivilegeVector defaultPrivileges = getDefaultPrivileges(); + for (PrivilegeVector::iterator it = defaultPrivileges.begin(); it != defaultPrivileges.end(); + ++it) { + for (int i = 0; i < resourceSearchListLength; ++i) { + if (!(it->getResourcePattern() == resourceSearchList[i])) + continue; - ActionSet userActions = it->getActions(); - unmetRequirements.removeAllActionsFromSet(userActions); + ActionSet userActions = it->getActions(); + unmetRequirements.removeAllActionsFromSet(userActions); - if (unmetRequirements.empty()) - return true; - } + if (unmetRequirements.empty()) + return true; } + } - for (UserSet::iterator it = _authenticatedUsers.begin(); - it != _authenticatedUsers.end(); ++it) { - User* user = *it; - for (int i = 0; i < resourceSearchListLength; ++i) { - ActionSet userActions = user->getActionsForResource(resourceSearchList[i]); - unmetRequirements.removeAllActionsFromSet(userActions); + for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); + ++it) { + User* user = *it; + for (int i = 0; i < resourceSearchListLength; ++i) { + ActionSet userActions = user->getActionsForResource(resourceSearchList[i]); + unmetRequirements.removeAllActionsFromSet(userActions); - if (unmetRequirements.empty()) - return true; - } + if (unmetRequirements.empty()) + return true; } - - return false; } - void AuthorizationSession::setImpersonatedUserData(std::vector<UserName> usernames, - std::vector<RoleName> roles) { - _impersonatedUserNames = usernames; - _impersonatedRoleNames = roles; - _impersonationFlag = true; - } + return false; +} - UserNameIterator AuthorizationSession::getImpersonatedUserNames() { - return makeUserNameIterator(_impersonatedUserNames.begin(), - _impersonatedUserNames.end()); - } +void AuthorizationSession::setImpersonatedUserData(std::vector<UserName> usernames, + std::vector<RoleName> roles) { + _impersonatedUserNames = usernames; + _impersonatedRoleNames = roles; + _impersonationFlag = true; +} - RoleNameIterator AuthorizationSession::getImpersonatedRoleNames() { - return makeRoleNameIterator(_impersonatedRoleNames.begin(), - _impersonatedRoleNames.end()); - } +UserNameIterator AuthorizationSession::getImpersonatedUserNames() { + return makeUserNameIterator(_impersonatedUserNames.begin(), _impersonatedUserNames.end()); +} - // Clear the vectors of impersonated usernames and roles. - void AuthorizationSession::clearImpersonatedUserData() { - _impersonatedUserNames.clear(); - _impersonatedRoleNames.clear(); - _impersonationFlag = false; - } +RoleNameIterator AuthorizationSession::getImpersonatedRoleNames() { + return makeRoleNameIterator(_impersonatedRoleNames.begin(), _impersonatedRoleNames.end()); +} +// Clear the vectors of impersonated usernames and roles. +void AuthorizationSession::clearImpersonatedUserData() { + _impersonatedUserNames.clear(); + _impersonatedRoleNames.clear(); + _impersonationFlag = false; +} - bool AuthorizationSession::isImpersonating() const { - return _impersonationFlag; - } -} // namespace mongo +bool AuthorizationSession::isImpersonating() const { + return _impersonationFlag; +} + +} // namespace mongo diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index 0fe4f1c46cb..d6fe06e11fa 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -44,234 +44,233 @@ #include "mongo/db/namespace_string.h" namespace mongo { - class ClientBasic; +class ClientBasic; +/** + * Contains all the authorization logic for a single client connection. It contains a set of + * the users which have been authenticated, as well as a set of privileges that have been + * granted to those users to perform various actions. + * + * An AuthorizationSession object is present within every mongo::ClientBasic object. + * + * Users in the _authenticatedUsers cache may get marked as invalid by the AuthorizationManager, + * for instance if their privileges are changed by a user or role modification command. At the + * beginning of every user-initiated operation startRequest() gets called which updates + * the cached information about any users who have been marked as invalid. This guarantees that + * every operation looks at one consistent view of each user for every auth check required over + * the lifetime of the operation. + */ +class AuthorizationSession { + MONGO_DISALLOW_COPYING(AuthorizationSession); + +public: /** - * Contains all the authorization logic for a single client connection. It contains a set of - * the users which have been authenticated, as well as a set of privileges that have been - * granted to those users to perform various actions. + * Gets the AuthorizationSession associated with the given "client", or nullptr. * - * An AuthorizationSession object is present within every mongo::ClientBasic object. + * The "client" object continues to own the returned AuthorizationSession. + */ + static AuthorizationSession* get(ClientBasic* client); + + /** + * Gets the AuthorizationSession associated with the given "client", or nullptr. + * + * The "client" object continues to own the returned AuthorizationSession. + */ + static AuthorizationSession* get(ClientBasic& client); + + /** + * Returns false if AuthorizationSession::get(client) would return nullptr. + */ + static bool exists(ClientBasic* client); + + /** + * Sets the AuthorizationSession associated with "client" to "session". * - * Users in the _authenticatedUsers cache may get marked as invalid by the AuthorizationManager, - * for instance if their privileges are changed by a user or role modification command. At the - * beginning of every user-initiated operation startRequest() gets called which updates - * the cached information about any users who have been marked as invalid. This guarantees that - * every operation looks at one consistent view of each user for every auth check required over - * the lifetime of the operation. + * "session" must not be NULL, and it is only legal to call this function once + * on each instance of "client". */ - class AuthorizationSession { - MONGO_DISALLOW_COPYING(AuthorizationSession); - public: - /** - * Gets the AuthorizationSession associated with the given "client", or nullptr. - * - * The "client" object continues to own the returned AuthorizationSession. - */ - static AuthorizationSession* get(ClientBasic* client); - - /** - * Gets the AuthorizationSession associated with the given "client", or nullptr. - * - * The "client" object continues to own the returned AuthorizationSession. - */ - static AuthorizationSession* get(ClientBasic& client); - - /** - * Returns false if AuthorizationSession::get(client) would return nullptr. - */ - static bool exists(ClientBasic* client); - - /** - * Sets the AuthorizationSession associated with "client" to "session". - * - * "session" must not be NULL, and it is only legal to call this function once - * on each instance of "client". - */ - static void set(ClientBasic* client, std::unique_ptr<AuthorizationSession> session); - - // Takes ownership of the externalState. - explicit AuthorizationSession(std::unique_ptr<AuthzSessionExternalState> externalState); - ~AuthorizationSession(); - - AuthorizationManager& getAuthorizationManager(); - - // Should be called at the beginning of every new request. This performs the checks - // necessary to determine if localhost connections should be given full access. - // TODO: try to eliminate the need for this call. - void startRequest(OperationContext* txn); - - /** - * Adds the User identified by "UserName" to the authorization session, acquiring privileges - * for it in the process. - */ - Status addAndAuthorizeUser(OperationContext* txn, const UserName& userName); - - // Returns the authenticated user with the given name. Returns NULL - // if no such user is found. - // The user remains in the _authenticatedUsers set for this AuthorizationSession, - // and ownership of the user stays with the AuthorizationManager - User* lookupUser(const UserName& name); - - // Gets an iterator over the names of all authenticated users stored in this manager. - UserNameIterator getAuthenticatedUserNames(); - - // Gets an iterator over the roles of all authenticated users stored in this manager. - RoleNameIterator getAuthenticatedRoleNames(); - - // Returns a std::string representing all logged-in users on the current session. - // WARNING: this std::string will contain NUL bytes so don't call c_str()! - std::string getAuthenticatedUserNamesToken(); - - // Removes any authenticated principals whose authorization credentials came from the given - // database, and revokes any privileges that were granted via that principal. - void logoutDatabase(const std::string& dbname); - - // Adds the internalSecurity user to the set of authenticated users. - // Used to grant internal threads full access. - void grantInternalAuthorization(); - - // Generates a vector of default privileges that are granted to any user, - // regardless of which roles that user does or does not possess. - // If localhost exception is active, the permissions include the ability to create - // the first user and the ability to run the commands needed to bootstrap the system - // into a state where the first user can be created. - PrivilegeVector getDefaultPrivileges(); - - // Checks if this connection has the privileges necessary to perform the given query on the - // given namespace. - Status checkAuthForQuery(const NamespaceString& ns, const BSONObj& query); - - // Checks if this connection has the privileges necessary to perform a getMore operation on - // the identified cursor, supposing that cursor is associated with the supplied namespace - // identifier. - Status checkAuthForGetMore(const NamespaceString& ns, long long cursorID); - - // Checks if this connection has the privileges necessary to perform the given update on the - // given namespace. - Status checkAuthForUpdate(const NamespaceString& ns, - const BSONObj& query, - const BSONObj& update, - bool upsert); - - // Checks if this connection has the privileges necessary to insert the given document - // to the given namespace. Correctly interprets inserts to system.indexes and performs - // the proper auth checks for index building. - Status checkAuthForInsert(const NamespaceString& ns, const BSONObj& document); - - // Checks if this connection has the privileges necessary to perform a delete on the given - // namespace. - Status checkAuthForDelete(const NamespaceString& ns, const BSONObj& query); - - // Checks if this connection has the privileges necessary to perform a killCursor on - // the identified cursor, supposing that cursor is associated with the supplied namespace - // identifier. - Status checkAuthForKillCursors(const NamespaceString& ns, long long cursorID); - - // Checks if this connection has the privileges necessary to grant the given privilege - // to a role. - Status checkAuthorizedToGrantPrivilege(const Privilege& privilege); - - // Checks if this connection has the privileges necessary to revoke the given privilege - // from a role. - Status checkAuthorizedToRevokePrivilege(const Privilege& privilege); - - // Utility function for isAuthorizedForActionsOnResource( - // ResourcePattern::forDatabaseName(role.getDB()), ActionType::grantAnyRole) - bool isAuthorizedToGrantRole(const RoleName& role); - - // Utility function for isAuthorizedForActionsOnResource( - // ResourcePattern::forDatabaseName(role.getDB()), ActionType::grantAnyRole) - bool isAuthorizedToRevokeRole(const RoleName& role); - - // Utility function for isAuthorizedToChangeOwnPasswordAsUser and isAuthorizedToChangeOwnCustomDataAsUser - bool isAuthorizedToChangeAsUser(const UserName& userName, ActionType actionType); - - // Returns true if the current session is authenticated as the given user and that user - // is allowed to change his/her own password - bool isAuthorizedToChangeOwnPasswordAsUser(const UserName& userName); - - // Returns true if the current session is authenticated as the given user and that user - // is allowed to change his/her own customData. - bool isAuthorizedToChangeOwnCustomDataAsUser(const UserName& userName); - - // Returns true if any of the authenticated users on this session have the given role. - // NOTE: this does not refresh any of the users even if they are marked as invalid. - bool isAuthenticatedAsUserWithRole(const RoleName& roleName); - - // Returns true if this session is authorized for the given Privilege. - // - // Contains all the authorization logic including handling things like the localhost - // exception. - bool isAuthorizedForPrivilege(const Privilege& privilege); - - // Like isAuthorizedForPrivilege, above, except returns true if the session is authorized - // for all of the listed privileges. - bool isAuthorizedForPrivileges(const std::vector<Privilege>& privileges); - - // Utility function for isAuthorizedForPrivilege(Privilege(resource, action)). - bool isAuthorizedForActionsOnResource(const ResourcePattern& resource, ActionType action); - - // Utility function for isAuthorizedForPrivilege(Privilege(resource, actions)). - bool isAuthorizedForActionsOnResource(const ResourcePattern& resource, - const ActionSet& actions); - - // Utility function for - // isAuthorizedForActionsOnResource(ResourcePattern::forExactNamespace(ns), action). - bool isAuthorizedForActionsOnNamespace(const NamespaceString& ns, ActionType action); - - // Utility function for - // isAuthorizedForActionsOnResource(ResourcePattern::forExactNamespace(ns), actions). - bool isAuthorizedForActionsOnNamespace(const NamespaceString& ns, - const ActionSet& actions); - - // Replaces the data for users that a system user is impersonating with new data. - // The auditing system adds these users and their roles to each audit record in the log. - void setImpersonatedUserData(std::vector<UserName> usernames, std::vector<RoleName> roles); - - // Gets an iterator over the names of all users that the system user is impersonating. - UserNameIterator getImpersonatedUserNames(); - - // Gets an iterator over the roles of all users that the system user is impersonating. - RoleNameIterator getImpersonatedRoleNames(); - - // Clears the data for impersonated users. - void clearImpersonatedUserData(); - - // Tells whether impersonation is active or not. This state is set when - // setImpersonatedUserData is called and cleared when clearImpersonatedUserData is - // called. - bool isImpersonating() const; - - private: - - // If any users authenticated on this session are marked as invalid this updates them with - // up-to-date information. May require a read lock on the "admin" db to read the user data. - void _refreshUserInfoAsNeeded(OperationContext* txn); - - // Builds a vector of all roles held by users who are authenticated on this connection. The - // vector is stored in _authenticatedRoleNames. This function is called when users are - // logged in or logged out, as well as when the user cache is determined to be out of date. - void _buildAuthenticatedRolesVector(); + static void set(ClientBasic* client, std::unique_ptr<AuthorizationSession> session); - // Checks if this connection is authorized for the given Privilege, ignoring whether or not - // we should even be doing authorization checks in general. Note: this may acquire a read - // lock on the admin database (to update out-of-date user privilege information). - bool _isAuthorizedForPrivilege(const Privilege& privilege); + // Takes ownership of the externalState. + explicit AuthorizationSession(std::unique_ptr<AuthzSessionExternalState> externalState); + ~AuthorizationSession(); - std::unique_ptr<AuthzSessionExternalState> _externalState; + AuthorizationManager& getAuthorizationManager(); - // All Users who have been authenticated on this connection. - UserSet _authenticatedUsers; - // The roles of the authenticated users. This vector is generated when the authenticated - // users set is changed. - std::vector<RoleName> _authenticatedRoleNames; + // Should be called at the beginning of every new request. This performs the checks + // necessary to determine if localhost connections should be given full access. + // TODO: try to eliminate the need for this call. + void startRequest(OperationContext* txn); + + /** + * Adds the User identified by "UserName" to the authorization session, acquiring privileges + * for it in the process. + */ + Status addAndAuthorizeUser(OperationContext* txn, const UserName& userName); + + // Returns the authenticated user with the given name. Returns NULL + // if no such user is found. + // The user remains in the _authenticatedUsers set for this AuthorizationSession, + // and ownership of the user stays with the AuthorizationManager + User* lookupUser(const UserName& name); + + // Gets an iterator over the names of all authenticated users stored in this manager. + UserNameIterator getAuthenticatedUserNames(); + + // Gets an iterator over the roles of all authenticated users stored in this manager. + RoleNameIterator getAuthenticatedRoleNames(); + + // Returns a std::string representing all logged-in users on the current session. + // WARNING: this std::string will contain NUL bytes so don't call c_str()! + std::string getAuthenticatedUserNamesToken(); + + // Removes any authenticated principals whose authorization credentials came from the given + // database, and revokes any privileges that were granted via that principal. + void logoutDatabase(const std::string& dbname); + + // Adds the internalSecurity user to the set of authenticated users. + // Used to grant internal threads full access. + void grantInternalAuthorization(); + + // Generates a vector of default privileges that are granted to any user, + // regardless of which roles that user does or does not possess. + // If localhost exception is active, the permissions include the ability to create + // the first user and the ability to run the commands needed to bootstrap the system + // into a state where the first user can be created. + PrivilegeVector getDefaultPrivileges(); + + // Checks if this connection has the privileges necessary to perform the given query on the + // given namespace. + Status checkAuthForQuery(const NamespaceString& ns, const BSONObj& query); + + // Checks if this connection has the privileges necessary to perform a getMore operation on + // the identified cursor, supposing that cursor is associated with the supplied namespace + // identifier. + Status checkAuthForGetMore(const NamespaceString& ns, long long cursorID); + + // Checks if this connection has the privileges necessary to perform the given update on the + // given namespace. + Status checkAuthForUpdate(const NamespaceString& ns, + const BSONObj& query, + const BSONObj& update, + bool upsert); + + // Checks if this connection has the privileges necessary to insert the given document + // to the given namespace. Correctly interprets inserts to system.indexes and performs + // the proper auth checks for index building. + Status checkAuthForInsert(const NamespaceString& ns, const BSONObj& document); + + // Checks if this connection has the privileges necessary to perform a delete on the given + // namespace. + Status checkAuthForDelete(const NamespaceString& ns, const BSONObj& query); + + // Checks if this connection has the privileges necessary to perform a killCursor on + // the identified cursor, supposing that cursor is associated with the supplied namespace + // identifier. + Status checkAuthForKillCursors(const NamespaceString& ns, long long cursorID); + + // Checks if this connection has the privileges necessary to grant the given privilege + // to a role. + Status checkAuthorizedToGrantPrivilege(const Privilege& privilege); + + // Checks if this connection has the privileges necessary to revoke the given privilege + // from a role. + Status checkAuthorizedToRevokePrivilege(const Privilege& privilege); + + // Utility function for isAuthorizedForActionsOnResource( + // ResourcePattern::forDatabaseName(role.getDB()), ActionType::grantAnyRole) + bool isAuthorizedToGrantRole(const RoleName& role); + + // Utility function for isAuthorizedForActionsOnResource( + // ResourcePattern::forDatabaseName(role.getDB()), ActionType::grantAnyRole) + bool isAuthorizedToRevokeRole(const RoleName& role); + + // Utility function for isAuthorizedToChangeOwnPasswordAsUser and isAuthorizedToChangeOwnCustomDataAsUser + bool isAuthorizedToChangeAsUser(const UserName& userName, ActionType actionType); + + // Returns true if the current session is authenticated as the given user and that user + // is allowed to change his/her own password + bool isAuthorizedToChangeOwnPasswordAsUser(const UserName& userName); + + // Returns true if the current session is authenticated as the given user and that user + // is allowed to change his/her own customData. + bool isAuthorizedToChangeOwnCustomDataAsUser(const UserName& userName); + + // Returns true if any of the authenticated users on this session have the given role. + // NOTE: this does not refresh any of the users even if they are marked as invalid. + bool isAuthenticatedAsUserWithRole(const RoleName& roleName); + + // Returns true if this session is authorized for the given Privilege. + // + // Contains all the authorization logic including handling things like the localhost + // exception. + bool isAuthorizedForPrivilege(const Privilege& privilege); + + // Like isAuthorizedForPrivilege, above, except returns true if the session is authorized + // for all of the listed privileges. + bool isAuthorizedForPrivileges(const std::vector<Privilege>& privileges); + + // Utility function for isAuthorizedForPrivilege(Privilege(resource, action)). + bool isAuthorizedForActionsOnResource(const ResourcePattern& resource, ActionType action); + + // Utility function for isAuthorizedForPrivilege(Privilege(resource, actions)). + bool isAuthorizedForActionsOnResource(const ResourcePattern& resource, + const ActionSet& actions); + + // Utility function for + // isAuthorizedForActionsOnResource(ResourcePattern::forExactNamespace(ns), action). + bool isAuthorizedForActionsOnNamespace(const NamespaceString& ns, ActionType action); + + // Utility function for + // isAuthorizedForActionsOnResource(ResourcePattern::forExactNamespace(ns), actions). + bool isAuthorizedForActionsOnNamespace(const NamespaceString& ns, const ActionSet& actions); + + // Replaces the data for users that a system user is impersonating with new data. + // The auditing system adds these users and their roles to each audit record in the log. + void setImpersonatedUserData(std::vector<UserName> usernames, std::vector<RoleName> roles); + + // Gets an iterator over the names of all users that the system user is impersonating. + UserNameIterator getImpersonatedUserNames(); + + // Gets an iterator over the roles of all users that the system user is impersonating. + RoleNameIterator getImpersonatedRoleNames(); + + // Clears the data for impersonated users. + void clearImpersonatedUserData(); + + // Tells whether impersonation is active or not. This state is set when + // setImpersonatedUserData is called and cleared when clearImpersonatedUserData is + // called. + bool isImpersonating() const; + +private: + // If any users authenticated on this session are marked as invalid this updates them with + // up-to-date information. May require a read lock on the "admin" db to read the user data. + void _refreshUserInfoAsNeeded(OperationContext* txn); + + // Builds a vector of all roles held by users who are authenticated on this connection. The + // vector is stored in _authenticatedRoleNames. This function is called when users are + // logged in or logged out, as well as when the user cache is determined to be out of date. + void _buildAuthenticatedRolesVector(); + + // Checks if this connection is authorized for the given Privilege, ignoring whether or not + // we should even be doing authorization checks in general. Note: this may acquire a read + // lock on the admin database (to update out-of-date user privilege information). + bool _isAuthorizedForPrivilege(const Privilege& privilege); + + std::unique_ptr<AuthzSessionExternalState> _externalState; - // A vector of impersonated UserNames and a vector of those users' RoleNames. - // These are used in the auditing system. They are not used for authz checks. - std::vector<UserName> _impersonatedUserNames; - std::vector<RoleName> _impersonatedRoleNames; - bool _impersonationFlag; - }; + // All Users who have been authenticated on this connection. + UserSet _authenticatedUsers; + // The roles of the authenticated users. This vector is generated when the authenticated + // users set is changed. + std::vector<RoleName> _authenticatedRoleNames; + + // A vector of impersonated UserNames and a vector of those users' RoleNames. + // These are used in the auditing system. They are not used for authz checks. + std::vector<UserName> _impersonatedUserNames; + std::vector<RoleName> _impersonatedRoleNames; + bool _impersonationFlag; +}; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index 5ce06edb4a7..0f5b4936e9d 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -46,409 +46,464 @@ namespace mongo { namespace { - class FailureCapableAuthzManagerExternalStateMock : - public AuthzManagerExternalStateMock { - public: - FailureCapableAuthzManagerExternalStateMock() : _findsShouldFail(false) {} - virtual ~FailureCapableAuthzManagerExternalStateMock() {} - - void setFindsShouldFail(bool enable) { _findsShouldFail = enable; } - - virtual Status findOne(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - BSONObj* result) { - if (_findsShouldFail && - collectionName == AuthorizationManager::usersCollectionNamespace) { - - return Status(ErrorCodes::UnknownError, - "findOne on admin.system.users set to fail in mock."); - } - return AuthzManagerExternalStateMock::findOne(txn, collectionName, query, result); - } - - private: - bool _findsShouldFail; - }; - - class AuthorizationSessionTest : public ::mongo::unittest::Test { - public: - FailureCapableAuthzManagerExternalStateMock* managerState; - OperationContextNoop _txn; - AuthzSessionExternalStateMock* sessionState; - std::unique_ptr<AuthorizationManager> authzManager; - std::unique_ptr<AuthorizationSession> authzSession; - - void setUp() { - auto localManagerState = stdx::make_unique<FailureCapableAuthzManagerExternalStateMock>(); - managerState = localManagerState.get(); - managerState->setAuthzVersion(AuthorizationManager::schemaVersion26Final); - authzManager = stdx::make_unique<AuthorizationManager>(std::move(localManagerState)); - auto localSessionState = stdx::make_unique<AuthzSessionExternalStateMock>(authzManager.get()); - sessionState = localSessionState.get(); - authzSession = stdx::make_unique<AuthorizationSession>(std::move(localSessionState)); - authzManager->setAuthEnabled(true); - } - }; - - const ResourcePattern testDBResource(ResourcePattern::forDatabaseName("test")); - const ResourcePattern otherDBResource(ResourcePattern::forDatabaseName("other")); - const ResourcePattern adminDBResource(ResourcePattern::forDatabaseName("admin")); - const ResourcePattern testFooCollResource( - ResourcePattern::forExactNamespace(NamespaceString("test.foo"))); - const ResourcePattern otherFooCollResource( - ResourcePattern::forExactNamespace(NamespaceString("other.foo"))); - const ResourcePattern thirdFooCollResource( - ResourcePattern::forExactNamespace(NamespaceString("third.foo"))); - const ResourcePattern adminFooCollResource( - ResourcePattern::forExactNamespace(NamespaceString("admin.foo"))); - const ResourcePattern testUsersCollResource( - ResourcePattern::forExactNamespace(NamespaceString("test.system.users"))); - const ResourcePattern otherUsersCollResource( - ResourcePattern::forExactNamespace(NamespaceString("other.system.users"))); - const ResourcePattern thirdUsersCollResource( - ResourcePattern::forExactNamespace(NamespaceString("third.system.users"))); - const ResourcePattern testIndexesCollResource( - ResourcePattern::forExactNamespace(NamespaceString("test.system.indexes"))); - const ResourcePattern otherIndexesCollResource( - ResourcePattern::forExactNamespace(NamespaceString("other.system.indexes"))); - const ResourcePattern thirdIndexesCollResource( - ResourcePattern::forExactNamespace(NamespaceString("third.system.indexes"))); - const ResourcePattern testProfileCollResource( - ResourcePattern::forExactNamespace(NamespaceString("test.system.profile"))); - const ResourcePattern otherProfileCollResource( - ResourcePattern::forExactNamespace(NamespaceString("other.system.profile"))); - const ResourcePattern thirdProfileCollResource( - ResourcePattern::forExactNamespace(NamespaceString("third.system.profile"))); - - TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { - // Check that disabling auth checks works - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - sessionState->setReturnValueForShouldIgnoreAuthChecks(true); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - sessionState->setReturnValueForShouldIgnoreAuthChecks(false); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - - // Check that you can't authorize a user that doesn't exist. - ASSERT_EQUALS(ErrorCodes::UserNotFound, - authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); - - // Add a user with readWrite and dbAdmin on the test DB - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "readWrite" << - "db" << "test") << - BSON("role" << "dbAdmin" << - "db" << "test"))), - BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); - - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testDBResource, ActionType::dbStats)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherFooCollResource, ActionType::insert)); - - // Add an admin user with readWriteAnyDatabase - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "admin" << - "db" << "admin" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "readWriteAnyDatabase" << - "db" << "admin"))), - BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("admin", "admin"))); - - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forExactNamespace( - NamespaceString("anydb.somecollection")), - ActionType::insert)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - otherDBResource, ActionType::insert)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - otherFooCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherFooCollResource, ActionType::collMod)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - - authzSession->logoutDatabase("test"); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - otherFooCollResource, ActionType::insert)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::collMod)); - - authzSession->logoutDatabase("admin"); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherFooCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::collMod)); - } +class FailureCapableAuthzManagerExternalStateMock : public AuthzManagerExternalStateMock { +public: + FailureCapableAuthzManagerExternalStateMock() : _findsShouldFail(false) {} + virtual ~FailureCapableAuthzManagerExternalStateMock() {} - TEST_F(AuthorizationSessionTest, DuplicateRolesOK) { - // Add a user with doubled-up readWrite and single dbAdmin on the test DB - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "readWrite" << - "db" << "test") << - BSON("role" << "dbAdmin" << - "db" << "test") << - BSON("role" << "readWrite" << - "db" << "test"))), - BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); - - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testDBResource, ActionType::dbStats)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherFooCollResource, ActionType::insert)); + void setFindsShouldFail(bool enable) { + _findsShouldFail = enable; } - TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "rw" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "readWrite" << - "db" << "test") << - BSON("role" << "dbAdmin" << - "db" << "test"))), - BSONObj())); - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "useradmin" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "userAdmin" << - "db" << "test"))), - BSONObj())); - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "rwany" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "readWriteAnyDatabase" << - "db" << "admin") << - BSON("role" << "dbAdminAnyDatabase" << - "db" << "admin"))), - BSONObj())); - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "useradminany" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "userAdminAnyDatabase" << - "db" << "admin"))), - BSONObj())); - - ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("rwany", "test"))); - - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testUsersCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testUsersCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherUsersCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherUsersCollResource, ActionType::find)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testIndexesCollResource, ActionType::find)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testProfileCollResource, ActionType::find)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - otherIndexesCollResource, ActionType::find)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - otherProfileCollResource, ActionType::find)); - - // Logging in as useradminany@test implicitly logs out rwany@test. - ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("useradminany", "test"))); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testUsersCollResource, ActionType::insert)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testUsersCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherUsersCollResource, ActionType::insert)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - otherUsersCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testIndexesCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testProfileCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherIndexesCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherProfileCollResource, ActionType::find)); - - // Logging in as rw@test implicitly logs out useradminany@test. - ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("rw", "test"))); - - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testUsersCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testUsersCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherUsersCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherUsersCollResource, ActionType::find)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testIndexesCollResource, ActionType::find)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testProfileCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherIndexesCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherProfileCollResource, ActionType::find)); - - - // Logging in as useradmin@test implicitly logs out rw@test. - ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("useradmin", "test"))); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testUsersCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testUsersCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherUsersCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherUsersCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testIndexesCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testProfileCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherIndexesCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - otherProfileCollResource, ActionType::find)); - } - - TEST_F(AuthorizationSessionTest, InvalidateUser) { - // Add a readWrite user - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "readWrite" << - "db" << "test"))), - BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); - - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::find)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - - User* user = authzSession->lookupUser(UserName("spencer", "test")); - ASSERT(user->isValid()); - - // Change the user to be read-only - int ignored; - managerState->remove( - &_txn, - AuthorizationManager::usersCollectionNamespace, - BSONObj(), - BSONObj(), - &ignored); - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "read" << - "db" << "test"))), - BSONObj())); - - // Make sure that invalidating the user causes the session to reload its privileges. - authzManager->invalidateUserByName(user->getName()); - authzSession->startRequest(&_txn); // Refreshes cached data for invalid users - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - - user = authzSession->lookupUser(UserName("spencer", "test")); - ASSERT(user->isValid()); - - // Delete the user. - managerState->remove( - &_txn, - AuthorizationManager::usersCollectionNamespace, - BSONObj(), - BSONObj(), - &ignored); - // Make sure that invalidating the user causes the session to reload its privileges. - authzManager->invalidateUserByName(user->getName()); - authzSession->startRequest(&_txn); // Refreshes cached data for invalid users - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->lookupUser(UserName("spencer", "test"))); + virtual Status findOne(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + BSONObj* result) { + if (_findsShouldFail && collectionName == AuthorizationManager::usersCollectionNamespace) { + return Status(ErrorCodes::UnknownError, + "findOne on admin.system.users set to fail in mock."); + } + return AuthzManagerExternalStateMock::findOne(txn, collectionName, query, result); } - TEST_F(AuthorizationSessionTest, UseOldUserInfoInFaceOfConnectivityProblems) { - // Add a readWrite user - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "readWrite" << - "db" << "test"))), - BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); - - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::find)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - - User* user = authzSession->lookupUser(UserName("spencer", "test")); - ASSERT(user->isValid()); - - // Change the user to be read-only - int ignored; - managerState->setFindsShouldFail(true); - managerState->remove( - &_txn, - AuthorizationManager::usersCollectionNamespace, - BSONObj(), - BSONObj(), - &ignored); - ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "read" << - "db" << "test"))), - BSONObj())); - - // Even though the user's privileges have been reduced, since we've configured user - // document lookup to fail, the authz session should continue to use its known out-of-date - // privilege data. - authzManager->invalidateUserByName(user->getName()); - authzSession->startRequest(&_txn); // Refreshes cached data for invalid users - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::find)); - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); - - // Once we configure document lookup to succeed again, authorization checks should - // observe the new values. - managerState->setFindsShouldFail(false); - authzSession->startRequest(&_txn); // Refreshes cached data for invalid users - ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::find)); - ASSERT_FALSE(authzSession->isAuthorizedForActionsOnResource( - testFooCollResource, ActionType::insert)); +private: + bool _findsShouldFail; +}; + +class AuthorizationSessionTest : public ::mongo::unittest::Test { +public: + FailureCapableAuthzManagerExternalStateMock* managerState; + OperationContextNoop _txn; + AuthzSessionExternalStateMock* sessionState; + std::unique_ptr<AuthorizationManager> authzManager; + std::unique_ptr<AuthorizationSession> authzSession; + + void setUp() { + auto localManagerState = stdx::make_unique<FailureCapableAuthzManagerExternalStateMock>(); + managerState = localManagerState.get(); + managerState->setAuthzVersion(AuthorizationManager::schemaVersion26Final); + authzManager = stdx::make_unique<AuthorizationManager>(std::move(localManagerState)); + auto localSessionState = + stdx::make_unique<AuthzSessionExternalStateMock>(authzManager.get()); + sessionState = localSessionState.get(); + authzSession = stdx::make_unique<AuthorizationSession>(std::move(localSessionState)); + authzManager->setAuthEnabled(true); } +}; + +const ResourcePattern testDBResource(ResourcePattern::forDatabaseName("test")); +const ResourcePattern otherDBResource(ResourcePattern::forDatabaseName("other")); +const ResourcePattern adminDBResource(ResourcePattern::forDatabaseName("admin")); +const ResourcePattern testFooCollResource( + ResourcePattern::forExactNamespace(NamespaceString("test.foo"))); +const ResourcePattern otherFooCollResource( + ResourcePattern::forExactNamespace(NamespaceString("other.foo"))); +const ResourcePattern thirdFooCollResource( + ResourcePattern::forExactNamespace(NamespaceString("third.foo"))); +const ResourcePattern adminFooCollResource( + ResourcePattern::forExactNamespace(NamespaceString("admin.foo"))); +const ResourcePattern testUsersCollResource( + ResourcePattern::forExactNamespace(NamespaceString("test.system.users"))); +const ResourcePattern otherUsersCollResource( + ResourcePattern::forExactNamespace(NamespaceString("other.system.users"))); +const ResourcePattern thirdUsersCollResource( + ResourcePattern::forExactNamespace(NamespaceString("third.system.users"))); +const ResourcePattern testIndexesCollResource( + ResourcePattern::forExactNamespace(NamespaceString("test.system.indexes"))); +const ResourcePattern otherIndexesCollResource( + ResourcePattern::forExactNamespace(NamespaceString("other.system.indexes"))); +const ResourcePattern thirdIndexesCollResource( + ResourcePattern::forExactNamespace(NamespaceString("third.system.indexes"))); +const ResourcePattern testProfileCollResource( + ResourcePattern::forExactNamespace(NamespaceString("test.system.profile"))); +const ResourcePattern otherProfileCollResource( + ResourcePattern::forExactNamespace(NamespaceString("other.system.profile"))); +const ResourcePattern thirdProfileCollResource( + ResourcePattern::forExactNamespace(NamespaceString("third.system.profile"))); + +TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { + // Check that disabling auth checks works + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + sessionState->setReturnValueForShouldIgnoreAuthChecks(true); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + sessionState->setReturnValueForShouldIgnoreAuthChecks(false); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + + // Check that you can't authorize a user that doesn't exist. + ASSERT_EQUALS(ErrorCodes::UserNotFound, + authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); + + // Add a user with readWrite and dbAdmin on the test DB + ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "readWrite" + << "db" + << "test") + << BSON("role" + << "dbAdmin" + << "db" + << "test"))), + BSONObj())); + ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); + + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testDBResource, ActionType::dbStats)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert)); + + // Add an admin user with readWriteAnyDatabase + ASSERT_OK( + managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "admin" + << "db" + << "admin" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "readWriteAnyDatabase" + << "db" + << "admin"))), + BSONObj())); + ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("admin", "admin"))); + + ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forExactNamespace(NamespaceString("anydb.somecollection")), + ActionType::insert)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(otherDBResource, ActionType::insert)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::collMod)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + + authzSession->logoutDatabase("test"); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::collMod)); + + authzSession->logoutDatabase("admin"); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::collMod)); +} + +TEST_F(AuthorizationSessionTest, DuplicateRolesOK) { + // Add a user with doubled-up readWrite and single dbAdmin on the test DB + ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "readWrite" + << "db" + << "test") + << BSON("role" + << "dbAdmin" + << "db" + << "test") + << BSON("role" + << "readWrite" + << "db" + << "test"))), + BSONObj())); + ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); + + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testDBResource, ActionType::dbStats)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert)); +} + +TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { + ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "rw" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "readWrite" + << "db" + << "test") + << BSON("role" + << "dbAdmin" + << "db" + << "test"))), + BSONObj())); + ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "useradmin" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "userAdmin" + << "db" + << "test"))), + BSONObj())); + ASSERT_OK( + managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "rwany" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "readWriteAnyDatabase" + << "db" + << "admin") + << BSON("role" + << "dbAdminAnyDatabase" + << "db" + << "admin"))), + BSONObj())); + ASSERT_OK( + managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "useradminany" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "userAdminAnyDatabase" + << "db" + << "admin"))), + BSONObj())); + + ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("rwany", "test"))); + + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::find)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testIndexesCollResource, ActionType::find)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testProfileCollResource, ActionType::find)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(otherIndexesCollResource, ActionType::find)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); + + // Logging in as useradminany@test implicitly logs out rwany@test. + ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("useradminany", "test"))); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::insert)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testIndexesCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testProfileCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherIndexesCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); + + // Logging in as rw@test implicitly logs out useradminany@test. + ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("rw", "test"))); + + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::find)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testIndexesCollResource, ActionType::find)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testProfileCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherIndexesCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); + + + // Logging in as useradmin@test implicitly logs out rw@test. + ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("useradmin", "test"))); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::insert)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testIndexesCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testProfileCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherIndexesCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); +} + +TEST_F(AuthorizationSessionTest, InvalidateUser) { + // Add a readWrite user + ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "readWrite" + << "db" + << "test"))), + BSONObj())); + ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); + + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + + User* user = authzSession->lookupUser(UserName("spencer", "test")); + ASSERT(user->isValid()); + + // Change the user to be read-only + int ignored; + managerState->remove( + &_txn, AuthorizationManager::usersCollectionNamespace, BSONObj(), BSONObj(), &ignored); + ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "read" + << "db" + << "test"))), + BSONObj())); + + // Make sure that invalidating the user causes the session to reload its privileges. + authzManager->invalidateUserByName(user->getName()); + authzSession->startRequest(&_txn); // Refreshes cached data for invalid users + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + + user = authzSession->lookupUser(UserName("spencer", "test")); + ASSERT(user->isValid()); + + // Delete the user. + managerState->remove( + &_txn, AuthorizationManager::usersCollectionNamespace, BSONObj(), BSONObj(), &ignored); + // Make sure that invalidating the user causes the session to reload its privileges. + authzManager->invalidateUserByName(user->getName()); + authzSession->startRequest(&_txn); // Refreshes cached data for invalid users + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + ASSERT_FALSE(authzSession->lookupUser(UserName("spencer", "test"))); +} + +TEST_F(AuthorizationSessionTest, UseOldUserInfoInFaceOfConnectivityProblems) { + // Add a readWrite user + ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "readWrite" + << "db" + << "test"))), + BSONObj())); + ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test"))); + + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + + User* user = authzSession->lookupUser(UserName("spencer", "test")); + ASSERT(user->isValid()); + + // Change the user to be read-only + int ignored; + managerState->setFindsShouldFail(true); + managerState->remove( + &_txn, AuthorizationManager::usersCollectionNamespace, BSONObj(), BSONObj(), &ignored); + ASSERT_OK(managerState->insertPrivilegeDocument(&_txn, + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "read" + << "db" + << "test"))), + BSONObj())); + + // Even though the user's privileges have been reduced, since we've configured user + // document lookup to fail, the authz session should continue to use its known out-of-date + // privilege data. + authzManager->invalidateUserByName(user->getName()); + authzSession->startRequest(&_txn); // Refreshes cached data for invalid users + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); + + // Once we configure document lookup to succeed again, authorization checks should + // observe the new values. + managerState->setFindsShouldFail(false); + authzSession->startRequest(&_txn); // Refreshes cached data for invalid users + ASSERT_TRUE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); + ASSERT_FALSE( + authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); +} } // namespace } // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state.cpp b/src/mongo/db/auth/authz_manager_external_state.cpp index 62d6683b78b..fae54cbf5e9 100644 --- a/src/mongo/db/auth/authz_manager_external_state.cpp +++ b/src/mongo/db/auth/authz_manager_external_state.cpp @@ -32,9 +32,9 @@ namespace mongo { - stdx::function<std::unique_ptr<AuthzManagerExternalState>()> AuthzManagerExternalState::create; +stdx::function<std::unique_ptr<AuthzManagerExternalState>()> AuthzManagerExternalState::create; - AuthzManagerExternalState::AuthzManagerExternalState() = default; - AuthzManagerExternalState::~AuthzManagerExternalState() = default; +AuthzManagerExternalState::AuthzManagerExternalState() = default; +AuthzManagerExternalState::~AuthzManagerExternalState() = default; } // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h index 6fd94effb69..e3bdcdb8c43 100644 --- a/src/mongo/db/auth/authz_manager_external_state.h +++ b/src/mongo/db/auth/authz_manager_external_state.h @@ -41,105 +41,101 @@ namespace mongo { - class AuthorizationManager; - class AuthzSessionExternalState; - class OperationContext; +class AuthorizationManager; +class AuthzSessionExternalState; +class OperationContext; + +/** + * Public interface for a class that encapsulates all the information related to system + * state not stored in AuthorizationManager. This is primarily to make AuthorizationManager + * easier to test as well as to allow different implementations for mongos and mongod. + */ +class AuthzManagerExternalState { + MONGO_DISALLOW_COPYING(AuthzManagerExternalState); + +public: + static stdx::function<std::unique_ptr<AuthzManagerExternalState>()> create; + + virtual ~AuthzManagerExternalState(); /** - * Public interface for a class that encapsulates all the information related to system - * state not stored in AuthorizationManager. This is primarily to make AuthorizationManager - * easier to test as well as to allow different implementations for mongos and mongod. + * Initializes the external state object. Must be called after construction and before + * calling other methods. Object may not be used after this method returns something other + * than Status::OK(). */ - class AuthzManagerExternalState { - MONGO_DISALLOW_COPYING(AuthzManagerExternalState); - - public: - - static stdx::function<std::unique_ptr<AuthzManagerExternalState>()> create; - - virtual ~AuthzManagerExternalState(); - - /** - * Initializes the external state object. Must be called after construction and before - * calling other methods. Object may not be used after this method returns something other - * than Status::OK(). - */ - virtual Status initialize(OperationContext* txn) = 0; - - /** - * Creates an external state manipulator for an AuthorizationSession whose - * AuthorizationManager uses this object as its own external state manipulator. - */ - virtual std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( - AuthorizationManager* authzManager) = 0; - - /** - * Retrieves the schema version of the persistent data describing users and roles. - * Will leave *outVersion unmodified on non-OK status return values. - */ - virtual Status getStoredAuthorizationVersion(OperationContext* txn, int* outVersion) = 0; - - /** - * Writes into "result" a document describing the named user and returns Status::OK(). The - * description includes the user credentials, if present, the user's role membership and - * delegation information, a full list of the user's privileges, and a full list of the - * user's roles, including those roles held implicitly through other roles (indirect roles). - * In the event that some of this information is inconsistent, the document will contain a - * "warnings" array, with std::string messages describing inconsistencies. - * - * If the user does not exist, returns ErrorCodes::UserNotFound. - */ - virtual Status getUserDescription( - OperationContext* txn, const UserName& userName, BSONObj* result) = 0; - - /** - * Writes into "result" a document describing the named role and returns Status::OK(). The - * description includes the roles in which the named role has membership and a full list of - * the roles of which the named role is a member, including those roles memberships held - * implicitly through other roles (indirect roles). If "showPrivileges" is true, then the - * description documents will also include a full list of the role's privileges. - * In the event that some of this information is inconsistent, the document will contain a - * "warnings" array, with std::string messages describing inconsistencies. - * - * If the role does not exist, returns ErrorCodes::RoleNotFound. - */ - virtual Status getRoleDescription(const RoleName& roleName, - bool showPrivileges, - BSONObj* result) = 0; - - /** - * Writes into "result" documents describing the roles that are defined on the given - * database. Each role description document includes the other roles in which the role has - * membership and a full list of the roles of which the named role is a member, - * including those roles memberships held implicitly through other roles (indirect roles). - * If showPrivileges is true, then the description documents will also include a full list - * of the role's privileges. If showBuiltinRoles is true, then the result array will - * contain description documents for all the builtin roles for the given database, if it - * is false the result will just include user defined roles. - * In the event that some of the information in a given role description is inconsistent, - * the document will contain a "warnings" array, with std::string messages describing - * inconsistencies. - */ - virtual Status getRoleDescriptionsForDB(const std::string dbname, - bool showPrivileges, - bool showBuiltinRoles, - std::vector<BSONObj>* result) = 0; - - /** - * Returns true if there exists at least one privilege document in the system. - */ - virtual bool hasAnyPrivilegeDocuments(OperationContext* txn) = 0; - - virtual void logOp( - OperationContext* txn, - const char* op, - const char* ns, - const BSONObj& o, - BSONObj* o2) {} - - - protected: - AuthzManagerExternalState(); // This class should never be instantiated directly. - }; - -} // namespace mongo + virtual Status initialize(OperationContext* txn) = 0; + + /** + * Creates an external state manipulator for an AuthorizationSession whose + * AuthorizationManager uses this object as its own external state manipulator. + */ + virtual std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( + AuthorizationManager* authzManager) = 0; + + /** + * Retrieves the schema version of the persistent data describing users and roles. + * Will leave *outVersion unmodified on non-OK status return values. + */ + virtual Status getStoredAuthorizationVersion(OperationContext* txn, int* outVersion) = 0; + + /** + * Writes into "result" a document describing the named user and returns Status::OK(). The + * description includes the user credentials, if present, the user's role membership and + * delegation information, a full list of the user's privileges, and a full list of the + * user's roles, including those roles held implicitly through other roles (indirect roles). + * In the event that some of this information is inconsistent, the document will contain a + * "warnings" array, with std::string messages describing inconsistencies. + * + * If the user does not exist, returns ErrorCodes::UserNotFound. + */ + virtual Status getUserDescription(OperationContext* txn, + const UserName& userName, + BSONObj* result) = 0; + + /** + * Writes into "result" a document describing the named role and returns Status::OK(). The + * description includes the roles in which the named role has membership and a full list of + * the roles of which the named role is a member, including those roles memberships held + * implicitly through other roles (indirect roles). If "showPrivileges" is true, then the + * description documents will also include a full list of the role's privileges. + * In the event that some of this information is inconsistent, the document will contain a + * "warnings" array, with std::string messages describing inconsistencies. + * + * If the role does not exist, returns ErrorCodes::RoleNotFound. + */ + virtual Status getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result) = 0; + + /** + * Writes into "result" documents describing the roles that are defined on the given + * database. Each role description document includes the other roles in which the role has + * membership and a full list of the roles of which the named role is a member, + * including those roles memberships held implicitly through other roles (indirect roles). + * If showPrivileges is true, then the description documents will also include a full list + * of the role's privileges. If showBuiltinRoles is true, then the result array will + * contain description documents for all the builtin roles for the given database, if it + * is false the result will just include user defined roles. + * In the event that some of the information in a given role description is inconsistent, + * the document will contain a "warnings" array, with std::string messages describing + * inconsistencies. + */ + virtual Status getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + std::vector<BSONObj>* result) = 0; + + /** + * Returns true if there exists at least one privilege document in the system. + */ + virtual bool hasAnyPrivilegeDocuments(OperationContext* txn) = 0; + + virtual void logOp( + OperationContext* txn, const char* op, const char* ns, const BSONObj& o, BSONObj* o2) {} + + +protected: + AuthzManagerExternalState(); // This class should never be instantiated directly. +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_d.cpp b/src/mongo/db/auth/authz_manager_external_state_d.cpp index 1c529f037d3..601c14decff 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_d.cpp @@ -51,49 +51,43 @@ namespace mongo { - AuthzManagerExternalStateMongod::AuthzManagerExternalStateMongod() = default; - AuthzManagerExternalStateMongod::~AuthzManagerExternalStateMongod() = default; +AuthzManagerExternalStateMongod::AuthzManagerExternalStateMongod() = default; +AuthzManagerExternalStateMongod::~AuthzManagerExternalStateMongod() = default; - std::unique_ptr<AuthzSessionExternalState> - AuthzManagerExternalStateMongod::makeAuthzSessionExternalState( - AuthorizationManager* authzManager) { +std::unique_ptr<AuthzSessionExternalState> +AuthzManagerExternalStateMongod::makeAuthzSessionExternalState(AuthorizationManager* authzManager) { + return stdx::make_unique<AuthzSessionExternalStateMongod>(authzManager); +} - return stdx::make_unique<AuthzSessionExternalStateMongod>(authzManager); +Status AuthzManagerExternalStateMongod::query( + OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& projection, + const stdx::function<void(const BSONObj&)>& resultProcessor) { + try { + DBDirectClient client(txn); + client.query(resultProcessor, collectionName.ns(), query, &projection); + return Status::OK(); + } catch (const DBException& e) { + return e.toStatus(); } +} - Status AuthzManagerExternalStateMongod::query( - OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& projection, - const stdx::function<void(const BSONObj&)>& resultProcessor) { - try { - DBDirectClient client(txn); - client.query(resultProcessor, collectionName.ns(), query, &projection); - return Status::OK(); - } catch (const DBException& e) { - return e.toStatus(); - } - } - - Status AuthzManagerExternalStateMongod::findOne( - OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - BSONObj* result) { - - AutoGetCollectionForRead ctx(txn, collectionName); +Status AuthzManagerExternalStateMongod::findOne(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + BSONObj* result) { + AutoGetCollectionForRead ctx(txn, collectionName); - BSONObj found; - if (Helpers::findOne(txn, - ctx.getCollection(), - query, - found)) { - *result = found.getOwned(); - return Status::OK(); - } - return Status(ErrorCodes::NoMatchingDocument, mongoutils::str::stream() << - "No document in " << collectionName.ns() << " matches " << query); + BSONObj found; + if (Helpers::findOne(txn, ctx.getCollection(), query, found)) { + *result = found.getOwned(); + return Status::OK(); } + return Status(ErrorCodes::NoMatchingDocument, + mongoutils::str::stream() << "No document in " << collectionName.ns() + << " matches " << query); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_d.h b/src/mongo/db/auth/authz_manager_external_state_d.h index fd2c6537166..f0fb7f91568 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.h +++ b/src/mongo/db/auth/authz_manager_external_state_d.h @@ -39,28 +39,28 @@ namespace mongo { - /** - * The implementation of AuthzManagerExternalState functionality for mongod. - */ - class AuthzManagerExternalStateMongod : public AuthzManagerExternalStateLocal { - MONGO_DISALLOW_COPYING(AuthzManagerExternalStateMongod); +/** + * The implementation of AuthzManagerExternalState functionality for mongod. + */ +class AuthzManagerExternalStateMongod : public AuthzManagerExternalStateLocal { + MONGO_DISALLOW_COPYING(AuthzManagerExternalStateMongod); - public: - AuthzManagerExternalStateMongod(); - virtual ~AuthzManagerExternalStateMongod(); +public: + AuthzManagerExternalStateMongod(); + virtual ~AuthzManagerExternalStateMongod(); - std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( - AuthorizationManager* authzManager) override; + std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( + AuthorizationManager* authzManager) override; - virtual Status findOne(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - BSONObj* result); - virtual Status query(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& projection, - const stdx::function<void(const BSONObj&)>& resultProcessor); - }; + virtual Status findOne(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + BSONObj* result); + virtual Status query(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& projection, + const stdx::function<void(const BSONObj&)>& resultProcessor); +}; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_local.cpp b/src/mongo/db/auth/authz_manager_external_state_local.cpp index e1f4b8e0301..ed0c8e560a5 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp @@ -42,422 +42,385 @@ namespace mongo { - using std::vector; +using std::vector; - Status AuthzManagerExternalStateLocal::initialize(OperationContext* txn) { - Status status = _initializeRoleGraph(txn); - if (!status.isOK()) { - if (status == ErrorCodes::GraphContainsCycle) { - error() << "Cycle detected in admin.system.roles; role inheritance disabled. " - "Remove the listed cycle and any others to re-enable role inheritance. " << - status.reason(); - } - else { - error() << "Could not generate role graph from admin.system.roles; " - "only system roles available: " << status; - } +Status AuthzManagerExternalStateLocal::initialize(OperationContext* txn) { + Status status = _initializeRoleGraph(txn); + if (!status.isOK()) { + if (status == ErrorCodes::GraphContainsCycle) { + error() << "Cycle detected in admin.system.roles; role inheritance disabled. " + "Remove the listed cycle and any others to re-enable role inheritance. " + << status.reason(); + } else { + error() << "Could not generate role graph from admin.system.roles; " + "only system roles available: " << status; } - - return Status::OK(); } - Status AuthzManagerExternalStateLocal::getStoredAuthorizationVersion( - OperationContext* txn, int* outVersion) { - BSONObj versionDoc; - Status status = findOne(txn, - AuthorizationManager::versionCollectionNamespace, - AuthorizationManager::versionDocumentQuery, - &versionDoc); - if (status.isOK()) { - BSONElement versionElement = versionDoc[AuthorizationManager::schemaVersionFieldName]; - if (versionElement.isNumber()) { - *outVersion = versionElement.numberInt(); - return Status::OK(); - } - else if (versionElement.eoo()) { - return Status(ErrorCodes::NoSuchKey, mongoutils::str::stream() << - "No " << AuthorizationManager::schemaVersionFieldName << - " field in version document."); - } - else { - return Status(ErrorCodes::TypeMismatch, mongoutils::str::stream() << - "Could not determine schema version of authorization data. " - "Bad (non-numeric) type " << typeName(versionElement.type()) << - " (" << versionElement.type() << ") for " << - AuthorizationManager::schemaVersionFieldName << - " field in version document"); - } - } - else if (status == ErrorCodes::NoMatchingDocument) { - *outVersion = AuthorizationManager::schemaVersion28SCRAM; + return Status::OK(); +} + +Status AuthzManagerExternalStateLocal::getStoredAuthorizationVersion(OperationContext* txn, + int* outVersion) { + BSONObj versionDoc; + Status status = findOne(txn, + AuthorizationManager::versionCollectionNamespace, + AuthorizationManager::versionDocumentQuery, + &versionDoc); + if (status.isOK()) { + BSONElement versionElement = versionDoc[AuthorizationManager::schemaVersionFieldName]; + if (versionElement.isNumber()) { + *outVersion = versionElement.numberInt(); return Status::OK(); + } else if (versionElement.eoo()) { + return Status(ErrorCodes::NoSuchKey, + mongoutils::str::stream() << "No " + << AuthorizationManager::schemaVersionFieldName + << " field in version document."); + } else { + return Status(ErrorCodes::TypeMismatch, + mongoutils::str::stream() + << "Could not determine schema version of authorization data. " + "Bad (non-numeric) type " << typeName(versionElement.type()) + << " (" << versionElement.type() << ") for " + << AuthorizationManager::schemaVersionFieldName + << " field in version document"); } - else { - return status; - } + } else if (status == ErrorCodes::NoMatchingDocument) { + *outVersion = AuthorizationManager::schemaVersion28SCRAM; + return Status::OK(); + } else { + return status; } +} namespace { - void addRoleNameToObjectElement(mutablebson::Element object, const RoleName& role) { - fassert(17153, object.appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, - role.getRole())); - fassert(17154, object.appendString(AuthorizationManager::ROLE_DB_FIELD_NAME, - role.getDB())); - } - - void addRoleNameObjectsToArrayElement(mutablebson::Element array, RoleNameIterator roles) { - for (; roles.more(); roles.next()) { - mutablebson::Element roleElement = array.getDocument().makeElementObject(""); - addRoleNameToObjectElement(roleElement, roles.get()); - fassert(17155, array.pushBack(roleElement)); - } +void addRoleNameToObjectElement(mutablebson::Element object, const RoleName& role) { + fassert(17153, object.appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, role.getRole())); + fassert(17154, object.appendString(AuthorizationManager::ROLE_DB_FIELD_NAME, role.getDB())); +} + +void addRoleNameObjectsToArrayElement(mutablebson::Element array, RoleNameIterator roles) { + for (; roles.more(); roles.next()) { + mutablebson::Element roleElement = array.getDocument().makeElementObject(""); + addRoleNameToObjectElement(roleElement, roles.get()); + fassert(17155, array.pushBack(roleElement)); } - - void addPrivilegeObjectsOrWarningsToArrayElement(mutablebson::Element privilegesElement, - mutablebson::Element warningsElement, - const PrivilegeVector& privileges) { - std::string errmsg; - for (size_t i = 0; i < privileges.size(); ++i) { - ParsedPrivilege pp; - if (ParsedPrivilege::privilegeToParsedPrivilege(privileges[i], &pp, &errmsg)) { - fassert(17156, privilegesElement.appendObject("", pp.toBSON())); - } else { - fassert(17157, - warningsElement.appendString( +} + +void addPrivilegeObjectsOrWarningsToArrayElement(mutablebson::Element privilegesElement, + mutablebson::Element warningsElement, + const PrivilegeVector& privileges) { + std::string errmsg; + for (size_t i = 0; i < privileges.size(); ++i) { + ParsedPrivilege pp; + if (ParsedPrivilege::privilegeToParsedPrivilege(privileges[i], &pp, &errmsg)) { + fassert(17156, privilegesElement.appendObject("", pp.toBSON())); + } else { + fassert(17157, + warningsElement.appendString( "", - std::string(mongoutils::str::stream() << - "Skipped privileges on resource " << - privileges[i].getResourcePattern().toString() << - ". Reason: " << errmsg))); - } + std::string(mongoutils::str::stream() + << "Skipped privileges on resource " + << privileges[i].getResourcePattern().toString() + << ". Reason: " << errmsg))); } } +} } // namespace - bool AuthzManagerExternalStateLocal::hasAnyPrivilegeDocuments(OperationContext* txn) { - BSONObj userBSONObj; - Status status = findOne( - txn, - AuthorizationManager::usersCollectionNamespace, - BSONObj(), - &userBSONObj); - // If we were unable to complete the query, - // it's best to assume that there _are_ privilege documents. - return status != ErrorCodes::NoMatchingDocument; - } - - Status AuthzManagerExternalStateLocal::getUserDescription( - OperationContext* txn, - const UserName& userName, - BSONObj* result) { - - BSONObj userDoc; - Status status = _getUserDocument(txn, userName, &userDoc); - if (!status.isOK()) - return status; +bool AuthzManagerExternalStateLocal::hasAnyPrivilegeDocuments(OperationContext* txn) { + BSONObj userBSONObj; + Status status = + findOne(txn, AuthorizationManager::usersCollectionNamespace, BSONObj(), &userBSONObj); + // If we were unable to complete the query, + // it's best to assume that there _are_ privilege documents. + return status != ErrorCodes::NoMatchingDocument; +} + +Status AuthzManagerExternalStateLocal::getUserDescription(OperationContext* txn, + const UserName& userName, + BSONObj* result) { + BSONObj userDoc; + Status status = _getUserDocument(txn, userName, &userDoc); + if (!status.isOK()) + return status; - BSONElement directRolesElement; - status = bsonExtractTypedField(userDoc, "roles", Array, &directRolesElement); - if (!status.isOK()) - return status; - std::vector<RoleName> directRoles; - status = V2UserDocumentParser::parseRoleVector(BSONArray(directRolesElement.Obj()), - &directRoles); - if (!status.isOK()) - return status; + BSONElement directRolesElement; + status = bsonExtractTypedField(userDoc, "roles", Array, &directRolesElement); + if (!status.isOK()) + return status; + std::vector<RoleName> directRoles; + status = + V2UserDocumentParser::parseRoleVector(BSONArray(directRolesElement.Obj()), &directRoles); + if (!status.isOK()) + return status; - unordered_set<RoleName> indirectRoles; - PrivilegeVector allPrivileges; - bool isRoleGraphInconsistent; - { - stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex); - isRoleGraphInconsistent = _roleGraphState == roleGraphStateConsistent; - for (size_t i = 0; i < directRoles.size(); ++i) { - const RoleName& role(directRoles[i]); - indirectRoles.insert(role); - if (isRoleGraphInconsistent) { - for (RoleNameIterator subordinates = _roleGraph.getIndirectSubordinates(role); - subordinates.more(); - subordinates.next()) { - - indirectRoles.insert(subordinates.get()); - } - } - const PrivilegeVector& rolePrivileges( - isRoleGraphInconsistent ? - _roleGraph.getAllPrivileges(role) : - _roleGraph.getDirectPrivileges(role)); - for (PrivilegeVector::const_iterator priv = rolePrivileges.begin(), - end = rolePrivileges.end(); - priv != end; - ++priv) { - - Privilege::addPrivilegeToPrivilegeVector(&allPrivileges, *priv); + unordered_set<RoleName> indirectRoles; + PrivilegeVector allPrivileges; + bool isRoleGraphInconsistent; + { + stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex); + isRoleGraphInconsistent = _roleGraphState == roleGraphStateConsistent; + for (size_t i = 0; i < directRoles.size(); ++i) { + const RoleName& role(directRoles[i]); + indirectRoles.insert(role); + if (isRoleGraphInconsistent) { + for (RoleNameIterator subordinates = _roleGraph.getIndirectSubordinates(role); + subordinates.more(); + subordinates.next()) { + indirectRoles.insert(subordinates.get()); } } + const PrivilegeVector& rolePrivileges(isRoleGraphInconsistent + ? _roleGraph.getAllPrivileges(role) + : _roleGraph.getDirectPrivileges(role)); + for (PrivilegeVector::const_iterator priv = rolePrivileges.begin(), + end = rolePrivileges.end(); + priv != end; + ++priv) { + Privilege::addPrivilegeToPrivilegeVector(&allPrivileges, *priv); + } } - - mutablebson::Document resultDoc(userDoc, mutablebson::Document::kInPlaceDisabled); - mutablebson::Element inheritedRolesElement = resultDoc.makeElementArray("inheritedRoles"); - mutablebson::Element privilegesElement = resultDoc.makeElementArray("inheritedPrivileges"); - mutablebson::Element warningsElement = resultDoc.makeElementArray("warnings"); - fassert(17159, resultDoc.root().pushBack(inheritedRolesElement)); - fassert(17158, resultDoc.root().pushBack(privilegesElement)); - if (!isRoleGraphInconsistent) { - fassert(17160, warningsElement.appendString( - "", "Role graph inconsistent, only direct privileges available.")); - } - addRoleNameObjectsToArrayElement(inheritedRolesElement, - makeRoleNameIteratorForContainer(indirectRoles)); - addPrivilegeObjectsOrWarningsToArrayElement( - privilegesElement, warningsElement, allPrivileges); - if (warningsElement.hasChildren()) { - fassert(17161, resultDoc.root().pushBack(warningsElement)); - } - *result = resultDoc.getObject(); - return Status::OK(); } - Status AuthzManagerExternalStateLocal::_getUserDocument(OperationContext* txn, - const UserName& userName, - BSONObj* userDoc) { - Status status = findOne( - txn, - AuthorizationManager::usersCollectionNamespace, - BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << - AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), - userDoc); - if (status == ErrorCodes::NoMatchingDocument) { - status = Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << - "Could not find user " << userName.getFullName()); - } - return status; + mutablebson::Document resultDoc(userDoc, mutablebson::Document::kInPlaceDisabled); + mutablebson::Element inheritedRolesElement = resultDoc.makeElementArray("inheritedRoles"); + mutablebson::Element privilegesElement = resultDoc.makeElementArray("inheritedPrivileges"); + mutablebson::Element warningsElement = resultDoc.makeElementArray("warnings"); + fassert(17159, resultDoc.root().pushBack(inheritedRolesElement)); + fassert(17158, resultDoc.root().pushBack(privilegesElement)); + if (!isRoleGraphInconsistent) { + fassert(17160, + warningsElement.appendString( + "", "Role graph inconsistent, only direct privileges available.")); } - - Status AuthzManagerExternalStateLocal::getRoleDescription(const RoleName& roleName, - bool showPrivileges, - BSONObj* result) { - stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex); - return _getRoleDescription_inlock(roleName, showPrivileges, result); + addRoleNameObjectsToArrayElement(inheritedRolesElement, + makeRoleNameIteratorForContainer(indirectRoles)); + addPrivilegeObjectsOrWarningsToArrayElement(privilegesElement, warningsElement, allPrivileges); + if (warningsElement.hasChildren()) { + fassert(17161, resultDoc.root().pushBack(warningsElement)); + } + *result = resultDoc.getObject(); + return Status::OK(); +} + +Status AuthzManagerExternalStateLocal::_getUserDocument(OperationContext* txn, + const UserName& userName, + BSONObj* userDoc) { + Status status = findOne(txn, + AuthorizationManager::usersCollectionNamespace, + BSON(AuthorizationManager::USER_NAME_FIELD_NAME + << userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME + << userName.getDB()), + userDoc); + if (status == ErrorCodes::NoMatchingDocument) { + status = + Status(ErrorCodes::UserNotFound, + mongoutils::str::stream() << "Could not find user " << userName.getFullName()); + } + return status; +} + +Status AuthzManagerExternalStateLocal::getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result) { + stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex); + return _getRoleDescription_inlock(roleName, showPrivileges, result); +} + +Status AuthzManagerExternalStateLocal::_getRoleDescription_inlock(const RoleName& roleName, + bool showPrivileges, + BSONObj* result) { + if (!_roleGraph.roleExists(roleName)) + return Status(ErrorCodes::RoleNotFound, "No role named " + roleName.toString()); + + mutablebson::Document resultDoc; + fassert(17162, + resultDoc.root().appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, + roleName.getRole())); + fassert( + 17163, + resultDoc.root().appendString(AuthorizationManager::ROLE_DB_FIELD_NAME, roleName.getDB())); + fassert(17267, resultDoc.root().appendBool("isBuiltin", _roleGraph.isBuiltinRole(roleName))); + mutablebson::Element rolesElement = resultDoc.makeElementArray("roles"); + fassert(17164, resultDoc.root().pushBack(rolesElement)); + mutablebson::Element inheritedRolesElement = resultDoc.makeElementArray("inheritedRoles"); + fassert(17165, resultDoc.root().pushBack(inheritedRolesElement)); + mutablebson::Element privilegesElement = resultDoc.makeElementArray("privileges"); + mutablebson::Element inheritedPrivilegesElement = + resultDoc.makeElementArray("inheritedPrivileges"); + if (showPrivileges) { + fassert(17166, resultDoc.root().pushBack(privilegesElement)); } + mutablebson::Element warningsElement = resultDoc.makeElementArray("warnings"); - Status AuthzManagerExternalStateLocal::_getRoleDescription_inlock(const RoleName& roleName, - bool showPrivileges, - BSONObj* result) { - if (!_roleGraph.roleExists(roleName)) - return Status(ErrorCodes::RoleNotFound, "No role named " + roleName.toString()); - - mutablebson::Document resultDoc; - fassert(17162, resultDoc.root().appendString( - AuthorizationManager::ROLE_NAME_FIELD_NAME, roleName.getRole())); - fassert(17163, resultDoc.root().appendString( - AuthorizationManager::ROLE_DB_FIELD_NAME, roleName.getDB())); - fassert(17267, - resultDoc.root().appendBool("isBuiltin", _roleGraph.isBuiltinRole(roleName))); - mutablebson::Element rolesElement = resultDoc.makeElementArray("roles"); - fassert(17164, resultDoc.root().pushBack(rolesElement)); - mutablebson::Element inheritedRolesElement = resultDoc.makeElementArray("inheritedRoles"); - fassert(17165, resultDoc.root().pushBack(inheritedRolesElement)); - mutablebson::Element privilegesElement = resultDoc.makeElementArray("privileges"); - mutablebson::Element inheritedPrivilegesElement = - resultDoc.makeElementArray("inheritedPrivileges"); + addRoleNameObjectsToArrayElement(rolesElement, _roleGraph.getDirectSubordinates(roleName)); + if (_roleGraphState == roleGraphStateConsistent) { + addRoleNameObjectsToArrayElement(inheritedRolesElement, + _roleGraph.getIndirectSubordinates(roleName)); if (showPrivileges) { - fassert(17166, resultDoc.root().pushBack(privilegesElement)); - } - mutablebson::Element warningsElement = resultDoc.makeElementArray("warnings"); - - addRoleNameObjectsToArrayElement(rolesElement, _roleGraph.getDirectSubordinates(roleName)); - if (_roleGraphState == roleGraphStateConsistent) { - addRoleNameObjectsToArrayElement( - inheritedRolesElement, _roleGraph.getIndirectSubordinates(roleName)); - if (showPrivileges) { - addPrivilegeObjectsOrWarningsToArrayElement( - privilegesElement, - warningsElement, - _roleGraph.getDirectPrivileges(roleName)); - - addPrivilegeObjectsOrWarningsToArrayElement( - inheritedPrivilegesElement, - warningsElement, - _roleGraph.getAllPrivileges(roleName)); - - fassert(17323, resultDoc.root().pushBack(inheritedPrivilegesElement)); - } - } - else if (showPrivileges) { - warningsElement.appendString( - "", "Role graph state inconsistent; only direct privileges available."); addPrivilegeObjectsOrWarningsToArrayElement( - privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName)); - } - if (warningsElement.hasChildren()) { - fassert(17167, resultDoc.root().pushBack(warningsElement)); - } - *result = resultDoc.getObject(); - return Status::OK(); - } + privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName)); - Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(const std::string dbname, - bool showPrivileges, - bool showBuiltinRoles, - vector<BSONObj>* result) { - stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex); + addPrivilegeObjectsOrWarningsToArrayElement( + inheritedPrivilegesElement, warningsElement, _roleGraph.getAllPrivileges(roleName)); - for (RoleNameIterator it = _roleGraph.getRolesForDatabase(dbname); - it.more(); it.next()) { - if (!showBuiltinRoles && _roleGraph.isBuiltinRole(it.get())) { - continue; - } - BSONObj roleDoc; - Status status = _getRoleDescription_inlock(it.get(), showPrivileges, &roleDoc); - if (!status.isOK()) { - return status; - } - result->push_back(roleDoc); + fassert(17323, resultDoc.root().pushBack(inheritedPrivilegesElement)); } - return Status::OK(); + } else if (showPrivileges) { + warningsElement.appendString( + "", "Role graph state inconsistent; only direct privileges available."); + addPrivilegeObjectsOrWarningsToArrayElement( + privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName)); } + if (warningsElement.hasChildren()) { + fassert(17167, resultDoc.root().pushBack(warningsElement)); + } + *result = resultDoc.getObject(); + return Status::OK(); +} + +Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + vector<BSONObj>* result) { + stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex); + + for (RoleNameIterator it = _roleGraph.getRolesForDatabase(dbname); it.more(); it.next()) { + if (!showBuiltinRoles && _roleGraph.isBuiltinRole(it.get())) { + continue; + } + BSONObj roleDoc; + Status status = _getRoleDescription_inlock(it.get(), showPrivileges, &roleDoc); + if (!status.isOK()) { + return status; + } + result->push_back(roleDoc); + } + return Status::OK(); +} namespace { - /** - * Adds the role described in "doc" to "roleGraph". If the role cannot be added, due to - * some error in "doc", logs a warning. - */ - void addRoleFromDocumentOrWarn(RoleGraph* roleGraph, const BSONObj& doc) { - Status status = roleGraph->addRoleFromDocument(doc); - if (!status.isOK()) { - warning() << "Skipping invalid admin.system.roles document while calculating privileges" - " for user-defined roles: " << status << "; document " << doc; - } +/** + * Adds the role described in "doc" to "roleGraph". If the role cannot be added, due to + * some error in "doc", logs a warning. + */ +void addRoleFromDocumentOrWarn(RoleGraph* roleGraph, const BSONObj& doc) { + Status status = roleGraph->addRoleFromDocument(doc); + if (!status.isOK()) { + warning() << "Skipping invalid admin.system.roles document while calculating privileges" + " for user-defined roles: " << status << "; document " << doc; } +} } // namespace - Status AuthzManagerExternalStateLocal::_initializeRoleGraph(OperationContext* txn) { - stdx::lock_guard<stdx::mutex> lkInitialzeRoleGraph(_roleGraphMutex); +Status AuthzManagerExternalStateLocal::_initializeRoleGraph(OperationContext* txn) { + stdx::lock_guard<stdx::mutex> lkInitialzeRoleGraph(_roleGraphMutex); - _roleGraphState = roleGraphStateInitial; - _roleGraph = RoleGraph(); + _roleGraphState = roleGraphStateInitial; + _roleGraph = RoleGraph(); - RoleGraph newRoleGraph; - Status status = query( - txn, - AuthorizationManager::rolesCollectionNamespace, - BSONObj(), - BSONObj(), - stdx::bind(addRoleFromDocumentOrWarn, &newRoleGraph, stdx::placeholders::_1)); - if (!status.isOK()) - return status; + RoleGraph newRoleGraph; + Status status = + query(txn, + AuthorizationManager::rolesCollectionNamespace, + BSONObj(), + BSONObj(), + stdx::bind(addRoleFromDocumentOrWarn, &newRoleGraph, stdx::placeholders::_1)); + if (!status.isOK()) + return status; - status = newRoleGraph.recomputePrivilegeData(); + status = newRoleGraph.recomputePrivilegeData(); + + RoleGraphState newState; + if (status == ErrorCodes::GraphContainsCycle) { + error() << "Inconsistent role graph during authorization manager initialization. Only " + "direct privileges available. " << status.reason(); + newState = roleGraphStateHasCycle; + status = Status::OK(); + } else if (status.isOK()) { + newState = roleGraphStateConsistent; + } else { + newState = roleGraphStateInitial; + } - RoleGraphState newState; - if (status == ErrorCodes::GraphContainsCycle) { - error() << "Inconsistent role graph during authorization manager initialization. Only " - "direct privileges available. " << status.reason(); - newState = roleGraphStateHasCycle; - status = Status::OK(); - } - else if (status.isOK()) { - newState = roleGraphStateConsistent; - } - else { - newState = roleGraphStateInitial; + if (status.isOK()) { + _roleGraph.swap(newRoleGraph); + _roleGraphState = newState; + } + return status; +} + +class AuthzManagerExternalStateLocal::AuthzManagerLogOpHandler : public RecoveryUnit::Change { +public: + // None of the parameters below (except externalState) need to live longer than + // the instantiations of this class + AuthzManagerLogOpHandler(AuthzManagerExternalStateLocal* externalState, + const char* op, + const char* ns, + const BSONObj& o, + const BSONObj* o2) + : _externalState(externalState), + _op(op), + _ns(ns), + _o(o.getOwned()), + + _isO2Set(o2 ? true : false), + _o2(_isO2Set ? o2->getOwned() : BSONObj()) {} + + virtual void commit() { + stdx::lock_guard<stdx::mutex> lk(_externalState->_roleGraphMutex); + Status status = _externalState->_roleGraph.handleLogOp( + _op.c_str(), NamespaceString(_ns.c_str()), _o, _isO2Set ? &_o2 : NULL); + + if (status == ErrorCodes::OplogOperationUnsupported) { + _externalState->_roleGraph = RoleGraph(); + _externalState->_roleGraphState = _externalState->roleGraphStateInitial; + BSONObjBuilder oplogEntryBuilder; + oplogEntryBuilder << "op" << _op << "ns" << _ns << "o" << _o; + if (_isO2Set) + oplogEntryBuilder << "o2" << _o2; + error() << "Unsupported modification to roles collection in oplog; " + "restart this process to reenable user-defined roles; " << status.reason() + << "; Oplog entry: " << oplogEntryBuilder.done(); + } else if (!status.isOK()) { + warning() << "Skipping bad update to roles collection in oplog. " << status + << " Oplog entry: " << _op; } - - if (status.isOK()) { - _roleGraph.swap(newRoleGraph); - _roleGraphState = newState; + status = _externalState->_roleGraph.recomputePrivilegeData(); + if (status == ErrorCodes::GraphContainsCycle) { + _externalState->_roleGraphState = _externalState->roleGraphStateHasCycle; + error() << "Inconsistent role graph during authorization manager initialization. " + "Only direct privileges available. " << status.reason() + << " after applying oplog entry " << _op; + } else { + fassert(17183, status); + _externalState->_roleGraphState = _externalState->roleGraphStateConsistent; } - return status; } - class AuthzManagerExternalStateLocal::AuthzManagerLogOpHandler : public RecoveryUnit::Change { - public: + virtual void rollback() {} - // None of the parameters below (except externalState) need to live longer than - // the instantiations of this class - AuthzManagerLogOpHandler(AuthzManagerExternalStateLocal* externalState, - const char* op, - const char* ns, - const BSONObj& o, - const BSONObj* o2): - _externalState(externalState), - _op(op), - _ns(ns), - _o(o.getOwned()), +private: + AuthzManagerExternalStateLocal* _externalState; + const std::string _op; + const std::string _ns; + const BSONObj _o; - _isO2Set(o2 ? true : false), - _o2(_isO2Set ? o2->getOwned() : BSONObj()) { + const bool _isO2Set; + const BSONObj _o2; +}; - } - - virtual void commit() { - stdx::lock_guard<stdx::mutex> lk(_externalState->_roleGraphMutex); - Status status = _externalState->_roleGraph.handleLogOp(_op.c_str(), - NamespaceString(_ns.c_str()), - _o, - _isO2Set ? &_o2 : NULL); - - if (status == ErrorCodes::OplogOperationUnsupported) { - _externalState->_roleGraph = RoleGraph(); - _externalState->_roleGraphState = _externalState->roleGraphStateInitial; - BSONObjBuilder oplogEntryBuilder; - oplogEntryBuilder << "op" << _op << "ns" << _ns << "o" << _o; - if (_isO2Set) - oplogEntryBuilder << "o2" << _o2; - error() << "Unsupported modification to roles collection in oplog; " - "restart this process to reenable user-defined roles; " << status.reason() << - "; Oplog entry: " << oplogEntryBuilder.done(); - } - else if (!status.isOK()) { - warning() << "Skipping bad update to roles collection in oplog. " << status << - " Oplog entry: " << _op; - } - status = _externalState->_roleGraph.recomputePrivilegeData(); - if (status == ErrorCodes::GraphContainsCycle) { - _externalState->_roleGraphState = _externalState->roleGraphStateHasCycle; - error() << "Inconsistent role graph during authorization manager initialization. " - "Only direct privileges available. " << status.reason() << - " after applying oplog entry " << _op; - } - else { - fassert(17183, status); - _externalState->_roleGraphState = _externalState->roleGraphStateConsistent; - } - - } - - virtual void rollback() { } - - private: - AuthzManagerExternalStateLocal* _externalState; - const std::string _op; - const std::string _ns; - const BSONObj _o; - - const bool _isO2Set; - const BSONObj _o2; - }; - - void AuthzManagerExternalStateLocal::logOp( - OperationContext* txn, - const char* op, - const char* ns, - const BSONObj& o, - BSONObj* o2) { - - if (ns == AuthorizationManager::rolesCollectionNamespace.ns() || - ns == AuthorizationManager::adminCommandNamespace.ns()) { - - txn->recoveryUnit()->registerChange(new AuthzManagerLogOpHandler(this, - op, - ns, - o, - o2)); - } +void AuthzManagerExternalStateLocal::logOp( + OperationContext* txn, const char* op, const char* ns, const BSONObj& o, BSONObj* o2) { + if (ns == AuthorizationManager::rolesCollectionNamespace.ns() || + ns == AuthorizationManager::adminCommandNamespace.ns()) { + txn->recoveryUnit()->registerChange(new AuthzManagerLogOpHandler(this, op, ns, o, o2)); } +} } // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_local.h b/src/mongo/db/auth/authz_manager_external_state_local.h index f8243aff00e..fe4a90ed1cd 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.h +++ b/src/mongo/db/auth/authz_manager_external_state_local.h @@ -40,105 +40,103 @@ namespace mongo { +/** + * Common implementation of AuthzManagerExternalState for systems where role + * and user information are stored locally. + */ +class AuthzManagerExternalStateLocal : public AuthzManagerExternalState { + MONGO_DISALLOW_COPYING(AuthzManagerExternalStateLocal); + +public: + virtual ~AuthzManagerExternalStateLocal() = default; + + virtual Status initialize(OperationContext* txn); + + virtual Status getStoredAuthorizationVersion(OperationContext* txn, int* outVersion); + virtual Status getUserDescription(OperationContext* txn, + const UserName& userName, + BSONObj* result); + virtual Status getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result); + virtual Status getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + std::vector<BSONObj>* result); + + bool hasAnyPrivilegeDocuments(OperationContext* txn) override; + /** - * Common implementation of AuthzManagerExternalState for systems where role - * and user information are stored locally. + * Finds a document matching "query" in "collectionName", and store a shared-ownership + * copy into "result". + * + * Returns Status::OK() on success. If no match is found, returns + * ErrorCodes::NoMatchingDocument. Other errors returned as appropriate. */ - class AuthzManagerExternalStateLocal : public AuthzManagerExternalState { - MONGO_DISALLOW_COPYING(AuthzManagerExternalStateLocal); - - public: - virtual ~AuthzManagerExternalStateLocal() = default; - - virtual Status initialize(OperationContext* txn); - - virtual Status getStoredAuthorizationVersion(OperationContext* txn, int* outVersion); - virtual Status getUserDescription( - OperationContext* txn, const UserName& userName, BSONObj* result); - virtual Status getRoleDescription(const RoleName& roleName, - bool showPrivileges, - BSONObj* result); - virtual Status getRoleDescriptionsForDB(const std::string dbname, - bool showPrivileges, - bool showBuiltinRoles, - std::vector<BSONObj>* result); - - bool hasAnyPrivilegeDocuments(OperationContext* txn) override; - - /** - * Finds a document matching "query" in "collectionName", and store a shared-ownership - * copy into "result". - * - * Returns Status::OK() on success. If no match is found, returns - * ErrorCodes::NoMatchingDocument. Other errors returned as appropriate. - */ - virtual Status findOne(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - BSONObj* result) = 0; - - /** - * Finds all documents matching "query" in "collectionName". For each document returned, - * calls the function resultProcessor on it. - */ - virtual Status query(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& projection, - const stdx::function<void(const BSONObj&)>& resultProcessor) = 0; - - virtual void logOp( - OperationContext* txn, - const char* op, - const char* ns, - const BSONObj& o, - BSONObj* o2); - - protected: - AuthzManagerExternalStateLocal() = default; - - /** - * Fetches the user document for "userName" from local storage, and stores it into "result". - */ - virtual Status _getUserDocument(OperationContext* txn, - const UserName& userName, - BSONObj* result); - private: - enum RoleGraphState { - roleGraphStateInitial = 0, - roleGraphStateConsistent, - roleGraphStateHasCycle - }; - - /** - * RecoveryUnit::Change subclass used to commit work for AuthzManager logOp listener. - */ - class AuthzManagerLogOpHandler; - - /** - * Initializes the role graph from the contents of the admin.system.roles collection. - */ - Status _initializeRoleGraph(OperationContext* txn); - - Status _getRoleDescription_inlock(const RoleName& roleName, - bool showPrivileges, - BSONObj* result); - /** - * Eventually consistent, in-memory representation of all roles in the system (both - * user-defined and built-in). Synchronized via _roleGraphMutex. - */ - RoleGraph _roleGraph; - - /** - * State of _roleGraph, one of "initial", "consistent" and "has cycle". Synchronized via - * _roleGraphMutex. - */ - RoleGraphState _roleGraphState = roleGraphStateInitial; - - /** - * Guards _roleGraphState and _roleGraph. - */ - stdx::mutex _roleGraphMutex; + virtual Status findOne(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + BSONObj* result) = 0; + + /** + * Finds all documents matching "query" in "collectionName". For each document returned, + * calls the function resultProcessor on it. + */ + virtual Status query(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& projection, + const stdx::function<void(const BSONObj&)>& resultProcessor) = 0; + + virtual void logOp( + OperationContext* txn, const char* op, const char* ns, const BSONObj& o, BSONObj* o2); + +protected: + AuthzManagerExternalStateLocal() = default; + + /** + * Fetches the user document for "userName" from local storage, and stores it into "result". + */ + virtual Status _getUserDocument(OperationContext* txn, + const UserName& userName, + BSONObj* result); + +private: + enum RoleGraphState { + roleGraphStateInitial = 0, + roleGraphStateConsistent, + roleGraphStateHasCycle }; -} // namespace mongo + /** + * RecoveryUnit::Change subclass used to commit work for AuthzManager logOp listener. + */ + class AuthzManagerLogOpHandler; + + /** + * Initializes the role graph from the contents of the admin.system.roles collection. + */ + Status _initializeRoleGraph(OperationContext* txn); + + Status _getRoleDescription_inlock(const RoleName& roleName, + bool showPrivileges, + BSONObj* result); + /** + * Eventually consistent, in-memory representation of all roles in the system (both + * user-defined and built-in). Synchronized via _roleGraphMutex. + */ + RoleGraph _roleGraph; + + /** + * State of _roleGraph, one of "initial", "consistent" and "has cycle". Synchronized via + * _roleGraphMutex. + */ + RoleGraphState _roleGraphState = roleGraphStateInitial; + + /** + * Guards _roleGraphState and _roleGraph. + */ + stdx::mutex _roleGraphMutex; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.cpp b/src/mongo/db/auth/authz_manager_external_state_mock.cpp index dc15b46f58d..6dc24b6a8d6 100644 --- a/src/mongo/db/auth/authz_manager_external_state_mock.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_mock.cpp @@ -48,282 +48,249 @@ namespace mongo { namespace { - void addRoleNameToObjectElement(mutablebson::Element object, const RoleName& role) { - fassert(17175, object.appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, role.getRole())); - fassert(17176, object.appendString(AuthorizationManager::ROLE_DB_FIELD_NAME, role.getDB())); +void addRoleNameToObjectElement(mutablebson::Element object, const RoleName& role) { + fassert(17175, object.appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, role.getRole())); + fassert(17176, object.appendString(AuthorizationManager::ROLE_DB_FIELD_NAME, role.getDB())); +} + +void addRoleNameObjectsToArrayElement(mutablebson::Element array, RoleNameIterator roles) { + for (; roles.more(); roles.next()) { + mutablebson::Element roleElement = array.getDocument().makeElementObject(""); + addRoleNameToObjectElement(roleElement, roles.get()); + fassert(17177, array.pushBack(roleElement)); } - - void addRoleNameObjectsToArrayElement(mutablebson::Element array, RoleNameIterator roles) { - for (; roles.more(); roles.next()) { - mutablebson::Element roleElement = array.getDocument().makeElementObject(""); - addRoleNameToObjectElement(roleElement, roles.get()); - fassert(17177, array.pushBack(roleElement)); - } - } - - void addPrivilegeObjectsOrWarningsToArrayElement(mutablebson::Element privilegesElement, - mutablebson::Element warningsElement, - const PrivilegeVector& privileges) { - std::string errmsg; - for (size_t i = 0; i < privileges.size(); ++i) { - ParsedPrivilege pp; - if (ParsedPrivilege::privilegeToParsedPrivilege(privileges[i], &pp, &errmsg)) { - fassert(17178, privilegesElement.appendObject("", pp.toBSON())); - } else { - fassert(17179, - warningsElement.appendString( +} + +void addPrivilegeObjectsOrWarningsToArrayElement(mutablebson::Element privilegesElement, + mutablebson::Element warningsElement, + const PrivilegeVector& privileges) { + std::string errmsg; + for (size_t i = 0; i < privileges.size(); ++i) { + ParsedPrivilege pp; + if (ParsedPrivilege::privilegeToParsedPrivilege(privileges[i], &pp, &errmsg)) { + fassert(17178, privilegesElement.appendObject("", pp.toBSON())); + } else { + fassert(17179, + warningsElement.appendString( "", - std::string(mongoutils::str::stream() << - "Skipped privileges on resource " << - privileges[i].getResourcePattern().toString() << - ". Reason: " << errmsg))); - } + std::string(mongoutils::str::stream() + << "Skipped privileges on resource " + << privileges[i].getResourcePattern().toString() + << ". Reason: " << errmsg))); } } +} } // namespace - AuthzManagerExternalStateMock::AuthzManagerExternalStateMock() : _authzManager(NULL) {} - AuthzManagerExternalStateMock::~AuthzManagerExternalStateMock() {} - - void AuthzManagerExternalStateMock::setAuthorizationManager( - AuthorizationManager* authzManager) { - _authzManager = authzManager; +AuthzManagerExternalStateMock::AuthzManagerExternalStateMock() : _authzManager(NULL) {} +AuthzManagerExternalStateMock::~AuthzManagerExternalStateMock() {} + +void AuthzManagerExternalStateMock::setAuthorizationManager(AuthorizationManager* authzManager) { + _authzManager = authzManager; +} + +void AuthzManagerExternalStateMock::setAuthzVersion(int version) { + OperationContextNoop opCtx; + uassertStatusOK( + updateOne(&opCtx, + AuthorizationManager::versionCollectionNamespace, + AuthorizationManager::versionDocumentQuery, + BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName << version)), + true, + BSONObj())); +} + +std::unique_ptr<AuthzSessionExternalState> +AuthzManagerExternalStateMock::makeAuthzSessionExternalState(AuthorizationManager* authzManager) { + return stdx::make_unique<AuthzSessionExternalStateMock>(authzManager); +} + +Status AuthzManagerExternalStateMock::findOne(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + BSONObj* result) { + BSONObjCollection::iterator iter; + Status status = _findOneIter(collectionName, query, &iter); + if (!status.isOK()) + return status; + *result = iter->copy(); + return Status::OK(); +} + +Status AuthzManagerExternalStateMock::query( + OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj&, + const stdx::function<void(const BSONObj&)>& resultProcessor) { + std::vector<BSONObjCollection::iterator> iterVector; + Status status = _queryVector(collectionName, query, &iterVector); + if (!status.isOK()) { + return status; } - - void AuthzManagerExternalStateMock::setAuthzVersion(int version) { - OperationContextNoop opCtx; - uassertStatusOK( - updateOne(&opCtx, - AuthorizationManager::versionCollectionNamespace, - AuthorizationManager::versionDocumentQuery, - BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName << - version)), - true, - BSONObj())); + try { + for (std::vector<BSONObjCollection::iterator>::iterator it = iterVector.begin(); + it != iterVector.end(); + ++it) { + resultProcessor(**it); + } + } catch (const DBException& ex) { + status = ex.toStatus(); } - - std::unique_ptr<AuthzSessionExternalState> - AuthzManagerExternalStateMock::makeAuthzSessionExternalState( - AuthorizationManager* authzManager) { - - return stdx::make_unique<AuthzSessionExternalStateMock>(authzManager); + return status; +} + +Status AuthzManagerExternalStateMock::insert(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& document, + const BSONObj&) { + BSONObj toInsert; + if (document["_id"].eoo()) { + BSONObjBuilder docWithIdBuilder; + docWithIdBuilder.append("_id", OID::gen()); + docWithIdBuilder.appendElements(document); + toInsert = docWithIdBuilder.obj(); + } else { + toInsert = document.copy(); } + _documents[collectionName].push_back(toInsert); - Status AuthzManagerExternalStateMock::findOne( - OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - BSONObj* result) { - BSONObjCollection::iterator iter; - Status status = _findOneIter(collectionName, query, &iter); - if (!status.isOK()) - return status; - *result = iter->copy(); - return Status::OK(); + if (_authzManager) { + _authzManager->logOp(txn, "i", collectionName.ns().c_str(), toInsert, NULL); } - Status AuthzManagerExternalStateMock::query( - OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj&, - const stdx::function<void(const BSONObj&)>& resultProcessor) { - std::vector<BSONObjCollection::iterator> iterVector; - Status status = _queryVector(collectionName, query, &iterVector); - if (!status.isOK()) { - return status; - } - try { - for (std::vector<BSONObjCollection::iterator>::iterator it = iterVector.begin(); - it != iterVector.end(); ++it) { - resultProcessor(**it); - } - } - catch (const DBException& ex) { - status = ex.toStatus(); - } + return Status::OK(); +} + +Status AuthzManagerExternalStateMock::insertPrivilegeDocument(OperationContext* txn, + const BSONObj& userObj, + const BSONObj& writeConcern) { + return insert(txn, AuthorizationManager::usersCollectionNamespace, userObj, writeConcern); +} + +Status AuthzManagerExternalStateMock::updateOne(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& updatePattern, + bool upsert, + const BSONObj& writeConcern) { + namespace mmb = mutablebson; + UpdateDriver::Options updateOptions; + UpdateDriver driver(updateOptions); + Status status = driver.parse(updatePattern); + if (!status.isOK()) return status; - } - Status AuthzManagerExternalStateMock::insert( - OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& document, - const BSONObj&) { - BSONObj toInsert; - if (document["_id"].eoo()) { - BSONObjBuilder docWithIdBuilder; - docWithIdBuilder.append("_id", OID::gen()); - docWithIdBuilder.appendElements(document); - toInsert = docWithIdBuilder.obj(); - } - else { - toInsert = document.copy(); - } - _documents[collectionName].push_back(toInsert); + BSONObjCollection::iterator iter; + status = _findOneIter(collectionName, query, &iter); + mmb::Document document; + if (status.isOK()) { + document.reset(*iter, mmb::Document::kInPlaceDisabled); + BSONObj logObj; + status = driver.update(StringData(), &document, &logObj); + if (!status.isOK()) + return status; + BSONObj newObj = document.getObject().copy(); + *iter = newObj; + BSONObj idQuery = driver.makeOplogEntryQuery(newObj, false); if (_authzManager) { - _authzManager->logOp( - txn, - "i", - collectionName.ns().c_str(), - toInsert, - NULL); + _authzManager->logOp(txn, "u", collectionName.ns().c_str(), logObj, &idQuery); } return Status::OK(); - } - - Status AuthzManagerExternalStateMock::insertPrivilegeDocument( - OperationContext* txn, - const BSONObj& userObj, - const BSONObj& writeConcern) { - return insert(txn, AuthorizationManager::usersCollectionNamespace, userObj, writeConcern); - } - - Status AuthzManagerExternalStateMock::updateOne( - OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& updatePattern, - bool upsert, - const BSONObj& writeConcern) { - - namespace mmb = mutablebson; - UpdateDriver::Options updateOptions; - UpdateDriver driver(updateOptions); - Status status = driver.parse(updatePattern); - if (!status.isOK()) - return status; - - BSONObjCollection::iterator iter; - status = _findOneIter(collectionName, query, &iter); - mmb::Document document; - if (status.isOK()) { - document.reset(*iter, mmb::Document::kInPlaceDisabled); - BSONObj logObj; - status = driver.update(StringData(), &document, &logObj); - if (!status.isOK()) - return status; - BSONObj newObj = document.getObject().copy(); - *iter = newObj; - BSONObj idQuery = driver.makeOplogEntryQuery(newObj, false); - - if (_authzManager) { - _authzManager->logOp( - txn, - "u", - collectionName.ns().c_str(), - logObj, - &idQuery); - } - - return Status::OK(); + } else if (status == ErrorCodes::NoMatchingDocument && upsert) { + if (query.hasField("_id")) { + document.root().appendElement(query["_id"]); } - else if (status == ErrorCodes::NoMatchingDocument && upsert) { - if (query.hasField("_id")) { - document.root().appendElement(query["_id"]); - } - status = driver.populateDocumentWithQueryFields(query, NULL, document); - if (!status.isOK()) { - return status; - } - status = driver.update(StringData(), &document); - if (!status.isOK()) { - return status; - } - return insert(txn, collectionName, document.getObject(), writeConcern); + status = driver.populateDocumentWithQueryFields(query, NULL, document); + if (!status.isOK()) { + return status; } - else { + status = driver.update(StringData(), &document); + if (!status.isOK()) { return status; } + return insert(txn, collectionName, document.getObject(), writeConcern); + } else { + return status; } +} + +Status AuthzManagerExternalStateMock::update(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& updatePattern, + bool upsert, + bool multi, + const BSONObj& writeConcern, + int* nMatched) { + return Status(ErrorCodes::InternalError, + "AuthzManagerExternalStateMock::update not implemented in mock."); +} + +Status AuthzManagerExternalStateMock::remove(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj&, + int* numRemoved) { + int n = 0; + BSONObjCollection::iterator iter; + while (_findOneIter(collectionName, query, &iter).isOK()) { + BSONObj idQuery = (*iter)["_id"].wrap(); + _documents[collectionName].erase(iter); + ++n; - Status AuthzManagerExternalStateMock::update(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& updatePattern, - bool upsert, - bool multi, - const BSONObj& writeConcern, - int* nMatched) { - return Status(ErrorCodes::InternalError, - "AuthzManagerExternalStateMock::update not implemented in mock."); - } - - Status AuthzManagerExternalStateMock::remove( - OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj&, - int* numRemoved) { - int n = 0; - BSONObjCollection::iterator iter; - while (_findOneIter(collectionName, query, &iter).isOK()) { - BSONObj idQuery = (*iter)["_id"].wrap(); - _documents[collectionName].erase(iter); - ++n; - - if (_authzManager) { - _authzManager->logOp( - txn, - "d", - collectionName.ns().c_str(), - idQuery, - NULL); - } - + if (_authzManager) { + _authzManager->logOp(txn, "d", collectionName.ns().c_str(), idQuery, NULL); } - *numRemoved = n; - return Status::OK(); } - - std::vector<BSONObj> AuthzManagerExternalStateMock::getCollectionContents( - const NamespaceString& collectionName) { - return mapFindWithDefault(_documents, collectionName, std::vector<BSONObj>()); + *numRemoved = n; + return Status::OK(); +} + +std::vector<BSONObj> AuthzManagerExternalStateMock::getCollectionContents( + const NamespaceString& collectionName) { + return mapFindWithDefault(_documents, collectionName, std::vector<BSONObj>()); +} + +Status AuthzManagerExternalStateMock::_findOneIter(const NamespaceString& collectionName, + const BSONObj& query, + BSONObjCollection::iterator* result) { + std::vector<BSONObjCollection::iterator> iterVector; + Status status = _queryVector(collectionName, query, &iterVector); + if (!status.isOK()) { + return status; } - - Status AuthzManagerExternalStateMock::_findOneIter( - const NamespaceString& collectionName, - const BSONObj& query, - BSONObjCollection::iterator* result) { - std::vector<BSONObjCollection::iterator> iterVector; - Status status = _queryVector(collectionName, query, &iterVector); - if (!status.isOK()) { - return status; - } - if (!iterVector.size()) { - return Status(ErrorCodes::NoMatchingDocument, "No matching document"); - } - *result = iterVector.front(); - return Status::OK(); + if (!iterVector.size()) { + return Status(ErrorCodes::NoMatchingDocument, "No matching document"); } + *result = iterVector.front(); + return Status::OK(); +} + +Status AuthzManagerExternalStateMock::_queryVector( + const NamespaceString& collectionName, + const BSONObj& query, + std::vector<BSONObjCollection::iterator>* result) { + StatusWithMatchExpression parseResult = + MatchExpressionParser::parse(query, MatchExpressionParser::WhereCallback()); + if (!parseResult.isOK()) { + return parseResult.getStatus(); + } + const std::unique_ptr<MatchExpression> matcher(parseResult.getValue()); - Status AuthzManagerExternalStateMock::_queryVector( - const NamespaceString& collectionName, - const BSONObj& query, - std::vector<BSONObjCollection::iterator>* result) { - - StatusWithMatchExpression parseResult = - MatchExpressionParser::parse(query, MatchExpressionParser::WhereCallback()); - if (!parseResult.isOK()) { - return parseResult.getStatus(); - } - const std::unique_ptr<MatchExpression> matcher(parseResult.getValue()); - - NamespaceDocumentMap::iterator mapIt = _documents.find(collectionName); - if (mapIt == _documents.end()) - return Status::OK(); - - for (BSONObjCollection::iterator vecIt = mapIt->second.begin(); - vecIt != mapIt->second.end(); - ++vecIt) { + NamespaceDocumentMap::iterator mapIt = _documents.find(collectionName); + if (mapIt == _documents.end()) + return Status::OK(); - if (matcher->matchesBSON(*vecIt)) { - result->push_back(vecIt); - } + for (BSONObjCollection::iterator vecIt = mapIt->second.begin(); vecIt != mapIt->second.end(); + ++vecIt) { + if (matcher->matchesBSON(*vecIt)) { + result->push_back(vecIt); } - return Status::OK(); } + return Status::OK(); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.h b/src/mongo/db/auth/authz_manager_external_state_mock.h index 43f3abb2546..d6b457e0de9 100644 --- a/src/mongo/db/auth/authz_manager_external_state_mock.h +++ b/src/mongo/db/auth/authz_manager_external_state_mock.h @@ -42,88 +42,87 @@ namespace mongo { - class AuthorizationManager; +class AuthorizationManager; - /** - * Mock of the AuthzManagerExternalState class used only for testing. - */ - class AuthzManagerExternalStateMock : public AuthzManagerExternalStateLocal { - MONGO_DISALLOW_COPYING(AuthzManagerExternalStateMock); +/** + * Mock of the AuthzManagerExternalState class used only for testing. + */ +class AuthzManagerExternalStateMock : public AuthzManagerExternalStateLocal { + MONGO_DISALLOW_COPYING(AuthzManagerExternalStateMock); - public: +public: + AuthzManagerExternalStateMock(); + virtual ~AuthzManagerExternalStateMock(); - AuthzManagerExternalStateMock(); - virtual ~AuthzManagerExternalStateMock(); + void setAuthorizationManager(AuthorizationManager* authzManager); + void setAuthzVersion(int version); - void setAuthorizationManager(AuthorizationManager* authzManager); - void setAuthzVersion(int version); + std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( + AuthorizationManager* authzManager) override; - std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( - AuthorizationManager* authzManager) override; + virtual Status findOne(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + BSONObj* result); - virtual Status findOne(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - BSONObj* result); + virtual Status query(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& projection, // Currently unused in mock + const stdx::function<void(const BSONObj&)>& resultProcessor); - virtual Status query(OperationContext* txn, + /** + * Inserts the given user object into the "admin" database. + */ + Status insertPrivilegeDocument(OperationContext* txn, + const BSONObj& userObj, + const BSONObj& writeConcern); + + // This implementation does not understand uniqueness constraints. + virtual Status insert(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& document, + const BSONObj& writeConcern); + + // This implementation does not understand uniqueness constraints, ignores writeConcern, + // and only correctly handles some upsert behaviors. + virtual Status updateOne(OperationContext* txn, const NamespaceString& collectionName, const BSONObj& query, - const BSONObj& projection, // Currently unused in mock - const stdx::function<void(const BSONObj&)>& resultProcessor); - - /** - * Inserts the given user object into the "admin" database. - */ - Status insertPrivilegeDocument(OperationContext* txn, - const BSONObj& userObj, - const BSONObj& writeConcern); - - // This implementation does not understand uniqueness constraints. - virtual Status insert(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& document, - const BSONObj& writeConcern); - - // This implementation does not understand uniqueness constraints, ignores writeConcern, - // and only correctly handles some upsert behaviors. - virtual Status updateOne(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& updatePattern, - bool upsert, - const BSONObj& writeConcern); - virtual Status update(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& updatePattern, - bool upsert, - bool multi, - const BSONObj& writeConcern, - int* nMatched); - virtual Status remove(OperationContext* txn, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& writeConcern, - int* numRemoved); - - std::vector<BSONObj> getCollectionContents(const NamespaceString& collectionName); - - private: - typedef std::vector<BSONObj> BSONObjCollection; - typedef std::map<NamespaceString, BSONObjCollection> NamespaceDocumentMap; - - Status _findOneIter(const NamespaceString& collectionName, - const BSONObj& query, - BSONObjCollection::iterator* result); - - Status _queryVector(const NamespaceString& collectionName, - const BSONObj& query, - std::vector<BSONObjCollection::iterator>* result); - - - AuthorizationManager* _authzManager; // For reporting logOps. - NamespaceDocumentMap _documents; // Mock database. - }; - -} // namespace mongo + const BSONObj& updatePattern, + bool upsert, + const BSONObj& writeConcern); + virtual Status update(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& updatePattern, + bool upsert, + bool multi, + const BSONObj& writeConcern, + int* nMatched); + virtual Status remove(OperationContext* txn, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& writeConcern, + int* numRemoved); + + std::vector<BSONObj> getCollectionContents(const NamespaceString& collectionName); + +private: + typedef std::vector<BSONObj> BSONObjCollection; + typedef std::map<NamespaceString, BSONObjCollection> NamespaceDocumentMap; + + Status _findOneIter(const NamespaceString& collectionName, + const BSONObj& query, + BSONObjCollection::iterator* result); + + Status _queryVector(const NamespaceString& collectionName, + const BSONObj& query, + std::vector<BSONObjCollection::iterator>* result); + + + AuthorizationManager* _authzManager; // For reporting logOps. + NamespaceDocumentMap _documents; // Mock database. +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_s.cpp b/src/mongo/db/auth/authz_manager_external_state_s.cpp index 11d0bf3720f..91ca85ee3ef 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp @@ -49,153 +49,139 @@ namespace mongo { - AuthzManagerExternalStateMongos::AuthzManagerExternalStateMongos() = default; - - AuthzManagerExternalStateMongos::~AuthzManagerExternalStateMongos() = default; - - Status AuthzManagerExternalStateMongos::initialize(OperationContext* txn) { - return Status::OK(); +AuthzManagerExternalStateMongos::AuthzManagerExternalStateMongos() = default; + +AuthzManagerExternalStateMongos::~AuthzManagerExternalStateMongos() = default; + +Status AuthzManagerExternalStateMongos::initialize(OperationContext* txn) { + return Status::OK(); +} + +std::unique_ptr<AuthzSessionExternalState> +AuthzManagerExternalStateMongos::makeAuthzSessionExternalState(AuthorizationManager* authzManager) { + return stdx::make_unique<AuthzSessionExternalStateMongos>(authzManager); +} + +Status AuthzManagerExternalStateMongos::getStoredAuthorizationVersion(OperationContext* txn, + int* outVersion) { + // Note: we are treating + // { 'getParameter' : 1, <authSchemaVersionServerParameter> : 1 } + // as a user management command since this is the *only* part of mongos + // that runs this command + BSONObj getParameterCmd = BSON("getParameter" << 1 << authSchemaVersionServerParameter << 1); + BSONObjBuilder builder; + const bool ok = + grid.catalogManager()->runUserManagementReadCommand("admin", getParameterCmd, &builder); + BSONObj cmdResult = builder.obj(); + if (!ok) { + return Command::getStatusFromCommandResult(cmdResult); } - std::unique_ptr<AuthzSessionExternalState> - AuthzManagerExternalStateMongos::makeAuthzSessionExternalState( - AuthorizationManager* authzManager) { - - return stdx::make_unique<AuthzSessionExternalStateMongos>(authzManager); + BSONElement versionElement = cmdResult[authSchemaVersionServerParameter]; + if (versionElement.eoo()) { + return Status(ErrorCodes::UnknownError, "getParameter misbehaved."); } - - Status AuthzManagerExternalStateMongos::getStoredAuthorizationVersion( - OperationContext* txn, int* outVersion) { - // Note: we are treating - // { 'getParameter' : 1, <authSchemaVersionServerParameter> : 1 } - // as a user management command since this is the *only* part of mongos - // that runs this command - BSONObj getParameterCmd = BSON("getParameter" << 1 << - authSchemaVersionServerParameter << 1); - BSONObjBuilder builder; - const bool ok = grid.catalogManager()->runUserManagementReadCommand("admin", - getParameterCmd, - &builder); - BSONObj cmdResult = builder.obj(); - if (!ok) { - return Command::getStatusFromCommandResult(cmdResult); - } - - BSONElement versionElement = cmdResult[authSchemaVersionServerParameter]; - if (versionElement.eoo()) { - return Status(ErrorCodes::UnknownError, "getParameter misbehaved."); - } - *outVersion = versionElement.numberInt(); - - return Status::OK(); + *outVersion = versionElement.numberInt(); + + return Status::OK(); +} + +Status AuthzManagerExternalStateMongos::getUserDescription(OperationContext* txn, + const UserName& userName, + BSONObj* result) { + BSONObj usersInfoCmd = + BSON("usersInfo" << BSON_ARRAY(BSON(AuthorizationManager::USER_NAME_FIELD_NAME + << userName.getUser() + << AuthorizationManager::USER_DB_FIELD_NAME + << userName.getDB())) << "showPrivileges" << true + << "showCredentials" << true); + BSONObjBuilder builder; + const bool ok = + grid.catalogManager()->runUserManagementReadCommand("admin", usersInfoCmd, &builder); + BSONObj cmdResult = builder.obj(); + if (!ok) { + return Command::getStatusFromCommandResult(cmdResult); } - Status AuthzManagerExternalStateMongos::getUserDescription( - OperationContext* txn, const UserName& userName, BSONObj* result) { - BSONObj usersInfoCmd = BSON("usersInfo" << - BSON_ARRAY(BSON(AuthorizationManager::USER_NAME_FIELD_NAME << - userName.getUser() << - AuthorizationManager::USER_DB_FIELD_NAME << - userName.getDB())) << - "showPrivileges" << true << - "showCredentials" << true); - BSONObjBuilder builder; - const bool ok = grid.catalogManager()->runUserManagementReadCommand("admin", - usersInfoCmd, - &builder); - BSONObj cmdResult = builder.obj(); - if (!ok) { - return Command::getStatusFromCommandResult(cmdResult); - } - - std::vector<BSONElement> foundUsers = cmdResult["users"].Array(); - if (foundUsers.size() == 0) { - return Status(ErrorCodes::UserNotFound, - "User \"" + userName.toString() + "\" not found"); - } - - if (foundUsers.size() > 1) { - return Status(ErrorCodes::UserDataInconsistent, - str::stream() << "Found multiple users on the \"" - << userName.getDB() << "\" database with name \"" - << userName.getUser() << "\""); - } - *result = foundUsers[0].Obj().getOwned(); - return Status::OK(); + std::vector<BSONElement> foundUsers = cmdResult["users"].Array(); + if (foundUsers.size() == 0) { + return Status(ErrorCodes::UserNotFound, "User \"" + userName.toString() + "\" not found"); } - Status AuthzManagerExternalStateMongos::getRoleDescription(const RoleName& roleName, - bool showPrivileges, - BSONObj* result) { - BSONObj rolesInfoCmd = BSON("rolesInfo" << - BSON_ARRAY(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << - roleName.getRole() << - AuthorizationManager::ROLE_DB_FIELD_NAME << - roleName.getDB())) << - "showPrivileges" << showPrivileges); - BSONObjBuilder builder; - const bool ok = grid.catalogManager()->runUserManagementReadCommand("admin", - rolesInfoCmd, - &builder); - BSONObj cmdResult = builder.obj(); - if (!ok) { - return Command::getStatusFromCommandResult(cmdResult); - } - - std::vector<BSONElement> foundRoles = cmdResult["roles"].Array(); - if (foundRoles.size() == 0) { - return Status(ErrorCodes::RoleNotFound, - "Role \"" + roleName.toString() + "\" not found"); - } - - if (foundRoles.size() > 1) { - return Status(ErrorCodes::RoleDataInconsistent, - str::stream() << "Found multiple roles on the \"" - << roleName.getDB() << "\" database with name \"" - << roleName.getRole() << "\""); - } - *result = foundRoles[0].Obj().getOwned(); - return Status::OK(); + if (foundUsers.size() > 1) { + return Status(ErrorCodes::UserDataInconsistent, + str::stream() << "Found multiple users on the \"" << userName.getDB() + << "\" database with name \"" << userName.getUser() << "\""); + } + *result = foundUsers[0].Obj().getOwned(); + return Status::OK(); +} + +Status AuthzManagerExternalStateMongos::getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result) { + BSONObj rolesInfoCmd = + BSON("rolesInfo" << BSON_ARRAY(BSON( + AuthorizationManager::ROLE_NAME_FIELD_NAME + << roleName.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME + << roleName.getDB())) << "showPrivileges" << showPrivileges); + BSONObjBuilder builder; + const bool ok = + grid.catalogManager()->runUserManagementReadCommand("admin", rolesInfoCmd, &builder); + BSONObj cmdResult = builder.obj(); + if (!ok) { + return Command::getStatusFromCommandResult(cmdResult); } - Status AuthzManagerExternalStateMongos::getRoleDescriptionsForDB(const std::string dbname, - bool showPrivileges, - bool showBuiltinRoles, - std::vector<BSONObj>* result) { - BSONObj rolesInfoCmd = BSON("rolesInfo" << 1 << - "showPrivileges" << showPrivileges << - "showBuiltinRoles" << showBuiltinRoles); - BSONObjBuilder builder; - const bool ok = grid.catalogManager()->runUserManagementReadCommand(dbname, - rolesInfoCmd, - &builder); - BSONObj cmdResult = builder.obj(); - if (!ok) { - return Command::getStatusFromCommandResult(cmdResult); - } - for (BSONObjIterator it(cmdResult["roles"].Obj()); it.more(); it.next()) { - result->push_back((*it).Obj().getOwned()); - } - return Status::OK(); + std::vector<BSONElement> foundRoles = cmdResult["roles"].Array(); + if (foundRoles.size() == 0) { + return Status(ErrorCodes::RoleNotFound, "Role \"" + roleName.toString() + "\" not found"); } - bool AuthzManagerExternalStateMongos::hasAnyPrivilegeDocuments(OperationContext* txn) { - BSONObj usersInfoCmd = BSON("usersInfo" << 1); - BSONObjBuilder builder; - const bool ok = grid.catalogManager()->runUserManagementReadCommand("admin", - usersInfoCmd, - &builder); - if (!ok) { - // If we were unable to complete the query, - // it's best to assume that there _are_ privilege documents. This might happen - // if the node contaning the users collection becomes transiently unavailable. - // See SERVER-12616, for example. - return true; - } - - BSONObj cmdResult = builder.obj(); - std::vector<BSONElement> foundUsers = cmdResult["users"].Array(); - return foundUsers.size() > 0; + if (foundRoles.size() > 1) { + return Status(ErrorCodes::RoleDataInconsistent, + str::stream() << "Found multiple roles on the \"" << roleName.getDB() + << "\" database with name \"" << roleName.getRole() << "\""); } + *result = foundRoles[0].Obj().getOwned(); + return Status::OK(); +} + +Status AuthzManagerExternalStateMongos::getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + std::vector<BSONObj>* result) { + BSONObj rolesInfoCmd = BSON("rolesInfo" << 1 << "showPrivileges" << showPrivileges + << "showBuiltinRoles" << showBuiltinRoles); + BSONObjBuilder builder; + const bool ok = + grid.catalogManager()->runUserManagementReadCommand(dbname, rolesInfoCmd, &builder); + BSONObj cmdResult = builder.obj(); + if (!ok) { + return Command::getStatusFromCommandResult(cmdResult); + } + for (BSONObjIterator it(cmdResult["roles"].Obj()); it.more(); it.next()) { + result->push_back((*it).Obj().getOwned()); + } + return Status::OK(); +} + +bool AuthzManagerExternalStateMongos::hasAnyPrivilegeDocuments(OperationContext* txn) { + BSONObj usersInfoCmd = BSON("usersInfo" << 1); + BSONObjBuilder builder; + const bool ok = + grid.catalogManager()->runUserManagementReadCommand("admin", usersInfoCmd, &builder); + if (!ok) { + // If we were unable to complete the query, + // it's best to assume that there _are_ privilege documents. This might happen + // if the node contaning the users collection becomes transiently unavailable. + // See SERVER-12616, for example. + return true; + } + + BSONObj cmdResult = builder.obj(); + std::vector<BSONElement> foundUsers = cmdResult["users"].Array(); + return foundUsers.size() > 0; +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_s.h b/src/mongo/db/auth/authz_manager_external_state_s.h index 047ff018006..8de98a53552 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.h +++ b/src/mongo/db/auth/authz_manager_external_state_s.h @@ -40,31 +40,32 @@ namespace mongo { - /** - * The implementation of AuthzManagerExternalState functionality for mongos. - */ - class AuthzManagerExternalStateMongos : public AuthzManagerExternalState{ - MONGO_DISALLOW_COPYING(AuthzManagerExternalStateMongos); +/** + * The implementation of AuthzManagerExternalState functionality for mongos. + */ +class AuthzManagerExternalStateMongos : public AuthzManagerExternalState { + MONGO_DISALLOW_COPYING(AuthzManagerExternalStateMongos); - public: - AuthzManagerExternalStateMongos(); - virtual ~AuthzManagerExternalStateMongos(); +public: + AuthzManagerExternalStateMongos(); + virtual ~AuthzManagerExternalStateMongos(); - virtual Status initialize(OperationContext* txn); - std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( - AuthorizationManager* authzManager) override; - virtual Status getStoredAuthorizationVersion(OperationContext* txn, int* outVersion); - virtual Status getUserDescription( - OperationContext* txn, const UserName& userName, BSONObj* result); - virtual Status getRoleDescription(const RoleName& roleName, - bool showPrivileges, - BSONObj* result); - virtual Status getRoleDescriptionsForDB(const std::string dbname, - bool showPrivileges, - bool showBuiltinRoles, - std::vector<BSONObj>* result); + virtual Status initialize(OperationContext* txn); + std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( + AuthorizationManager* authzManager) override; + virtual Status getStoredAuthorizationVersion(OperationContext* txn, int* outVersion); + virtual Status getUserDescription(OperationContext* txn, + const UserName& userName, + BSONObj* result); + virtual Status getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result); + virtual Status getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + std::vector<BSONObj>* result); - bool hasAnyPrivilegeDocuments(OperationContext* txn) override; - }; + bool hasAnyPrivilegeDocuments(OperationContext* txn) override; +}; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_session_external_state.cpp b/src/mongo/db/auth/authz_session_external_state.cpp index 2a7db8c1ff1..c0dceb9617b 100644 --- a/src/mongo/db/auth/authz_session_external_state.cpp +++ b/src/mongo/db/auth/authz_session_external_state.cpp @@ -35,12 +35,12 @@ namespace mongo { - AuthzSessionExternalState::AuthzSessionExternalState(AuthorizationManager* authzManager) : - _authzManager(authzManager) {} - AuthzSessionExternalState::~AuthzSessionExternalState() {} +AuthzSessionExternalState::AuthzSessionExternalState(AuthorizationManager* authzManager) + : _authzManager(authzManager) {} +AuthzSessionExternalState::~AuthzSessionExternalState() {} - AuthorizationManager& AuthzSessionExternalState::getAuthorizationManager() { - return *_authzManager; - } +AuthorizationManager& AuthzSessionExternalState::getAuthorizationManager() { + return *_authzManager; +} } // namespace mongo diff --git a/src/mongo/db/auth/authz_session_external_state.h b/src/mongo/db/auth/authz_session_external_state.h index d49e5eb35bf..5ce7ab155ed 100644 --- a/src/mongo/db/auth/authz_session_external_state.h +++ b/src/mongo/db/auth/authz_session_external_state.h @@ -38,53 +38,52 @@ namespace mongo { - class Principal; - class OperationContext; +class Principal; +class OperationContext; - /** - * Public interface for a class that encapsulates all the session information related to system - * state not stored in AuthorizationSession. This is primarily to make AuthorizationSession - * easier to test as well as to allow different implementations in mongos and mongod. - */ - class AuthzSessionExternalState { - MONGO_DISALLOW_COPYING(AuthzSessionExternalState); - - public: - - virtual ~AuthzSessionExternalState(); - - AuthorizationManager& getAuthorizationManager(); - - // Returns true if this connection should be treated as if it has full access to do - // anything, regardless of the current auth state. Currently the reasons why this could be - // are that auth isn't enabled or the connection is a "god" connection. - virtual bool shouldIgnoreAuthChecks() const = 0; - - // Returns true if this connection should be treated as a localhost connection with no - // admin authentication users created. This condition is used to allow the creation of - // the first user on a server with authorization enabled. - // NOTE: _checkShouldAllowLocalhost MUST be called at least once before any call to - // shouldAllowLocalhost or we could ignore auth checks incorrectly. - virtual bool shouldAllowLocalhost() const = 0; - - // Returns true if this connection should allow extra server configuration actions under - // the localhost exception. This condition is used to allow special privileges on arbiters. - // See SERVER-5479 for details on when this may be removed. - virtual bool serverIsArbiter() const = 0; - - // Should be called at the beginning of every new request. This performs the checks - // necessary to determine if localhost connections should be given full access. - virtual void startRequest(OperationContext* txn) = 0; - - protected: - // This class should never be instantiated directly. - AuthzSessionExternalState(AuthorizationManager* authzManager); - - // Pointer to the authorization manager associated with the authorization session - // that owns this object. - // - // TODO(schwerin): Eliminate this back pointer. - AuthorizationManager* _authzManager; - }; - -} // namespace mongo +/** + * Public interface for a class that encapsulates all the session information related to system + * state not stored in AuthorizationSession. This is primarily to make AuthorizationSession + * easier to test as well as to allow different implementations in mongos and mongod. + */ +class AuthzSessionExternalState { + MONGO_DISALLOW_COPYING(AuthzSessionExternalState); + +public: + virtual ~AuthzSessionExternalState(); + + AuthorizationManager& getAuthorizationManager(); + + // Returns true if this connection should be treated as if it has full access to do + // anything, regardless of the current auth state. Currently the reasons why this could be + // are that auth isn't enabled or the connection is a "god" connection. + virtual bool shouldIgnoreAuthChecks() const = 0; + + // Returns true if this connection should be treated as a localhost connection with no + // admin authentication users created. This condition is used to allow the creation of + // the first user on a server with authorization enabled. + // NOTE: _checkShouldAllowLocalhost MUST be called at least once before any call to + // shouldAllowLocalhost or we could ignore auth checks incorrectly. + virtual bool shouldAllowLocalhost() const = 0; + + // Returns true if this connection should allow extra server configuration actions under + // the localhost exception. This condition is used to allow special privileges on arbiters. + // See SERVER-5479 for details on when this may be removed. + virtual bool serverIsArbiter() const = 0; + + // Should be called at the beginning of every new request. This performs the checks + // necessary to determine if localhost connections should be given full access. + virtual void startRequest(OperationContext* txn) = 0; + +protected: + // This class should never be instantiated directly. + AuthzSessionExternalState(AuthorizationManager* authzManager); + + // Pointer to the authorization manager associated with the authorization session + // that owns this object. + // + // TODO(schwerin): Eliminate this back pointer. + AuthorizationManager* _authzManager; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/authz_session_external_state_d.cpp b/src/mongo/db/auth/authz_session_external_state_d.cpp index 65f30b5ce75..dee99046240 100644 --- a/src/mongo/db/auth/authz_session_external_state_d.cpp +++ b/src/mongo/db/auth/authz_session_external_state_d.cpp @@ -43,29 +43,28 @@ namespace mongo { - AuthzSessionExternalStateMongod::AuthzSessionExternalStateMongod( - AuthorizationManager* authzManager) : - AuthzSessionExternalStateServerCommon(authzManager) {} - AuthzSessionExternalStateMongod::~AuthzSessionExternalStateMongod() {} +AuthzSessionExternalStateMongod::AuthzSessionExternalStateMongod(AuthorizationManager* authzManager) + : AuthzSessionExternalStateServerCommon(authzManager) {} +AuthzSessionExternalStateMongod::~AuthzSessionExternalStateMongod() {} - void AuthzSessionExternalStateMongod::startRequest(OperationContext* txn) { - // No locks should be held as this happens before any database accesses occur - dassert(!txn->lockState()->isLocked()); +void AuthzSessionExternalStateMongod::startRequest(OperationContext* txn) { + // No locks should be held as this happens before any database accesses occur + dassert(!txn->lockState()->isLocked()); - _checkShouldAllowLocalhost(txn); - } + _checkShouldAllowLocalhost(txn); +} - bool AuthzSessionExternalStateMongod::shouldIgnoreAuthChecks() const { - // TODO(spencer): get "isInDirectClient" from OperationContext - return cc().isInDirectClient() || - AuthzSessionExternalStateServerCommon::shouldIgnoreAuthChecks(); - } +bool AuthzSessionExternalStateMongod::shouldIgnoreAuthChecks() const { + // TODO(spencer): get "isInDirectClient" from OperationContext + return cc().isInDirectClient() || + AuthzSessionExternalStateServerCommon::shouldIgnoreAuthChecks(); +} - bool AuthzSessionExternalStateMongod::serverIsArbiter() const { - // Arbiters have access to extra privileges under localhost. See SERVER-5479. - return (repl::getGlobalReplicationCoordinator()->getReplicationMode() == +bool AuthzSessionExternalStateMongod::serverIsArbiter() const { + // Arbiters have access to extra privileges under localhost. See SERVER-5479. + return (repl::getGlobalReplicationCoordinator()->getReplicationMode() == repl::ReplicationCoordinator::modeReplSet && - repl::getGlobalReplicationCoordinator()->getMemberState().arbiter()); - } + repl::getGlobalReplicationCoordinator()->getMemberState().arbiter()); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_session_external_state_d.h b/src/mongo/db/auth/authz_session_external_state_d.h index 8af5cc41e61..0761ad5ea39 100644 --- a/src/mongo/db/auth/authz_session_external_state_d.h +++ b/src/mongo/db/auth/authz_session_external_state_d.h @@ -35,21 +35,21 @@ namespace mongo { - /** - * The implementation of AuthzSessionExternalState functionality for mongod. - */ - class AuthzSessionExternalStateMongod : public AuthzSessionExternalStateServerCommon { - MONGO_DISALLOW_COPYING(AuthzSessionExternalStateMongod); +/** + * The implementation of AuthzSessionExternalState functionality for mongod. + */ +class AuthzSessionExternalStateMongod : public AuthzSessionExternalStateServerCommon { + MONGO_DISALLOW_COPYING(AuthzSessionExternalStateMongod); - public: - AuthzSessionExternalStateMongod(AuthorizationManager* authzManager); - virtual ~AuthzSessionExternalStateMongod(); +public: + AuthzSessionExternalStateMongod(AuthorizationManager* authzManager); + virtual ~AuthzSessionExternalStateMongod(); - virtual bool shouldIgnoreAuthChecks() const; + virtual bool shouldIgnoreAuthChecks() const; - virtual bool serverIsArbiter() const; + virtual bool serverIsArbiter() const; - virtual void startRequest(OperationContext* txn); - }; + virtual void startRequest(OperationContext* txn); +}; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_session_external_state_mock.h b/src/mongo/db/auth/authz_session_external_state_mock.h index 6e26c1bbceb..f1aa1d7a166 100644 --- a/src/mongo/db/auth/authz_session_external_state_mock.h +++ b/src/mongo/db/auth/authz_session_external_state_mock.h @@ -35,43 +35,45 @@ namespace mongo { - /** - * Mock of the AuthzSessionExternalState class used only for testing. - */ - class AuthzSessionExternalStateMock : public AuthzSessionExternalState { - MONGO_DISALLOW_COPYING(AuthzSessionExternalStateMock); +/** + * Mock of the AuthzSessionExternalState class used only for testing. + */ +class AuthzSessionExternalStateMock : public AuthzSessionExternalState { + MONGO_DISALLOW_COPYING(AuthzSessionExternalStateMock); - public: - AuthzSessionExternalStateMock(AuthorizationManager* authzManager) : - AuthzSessionExternalState(authzManager), _ignoreAuthChecksReturnValue(false), - _allowLocalhostReturnValue(false), _serverIsArbiterReturnValue(false) {} +public: + AuthzSessionExternalStateMock(AuthorizationManager* authzManager) + : AuthzSessionExternalState(authzManager), + _ignoreAuthChecksReturnValue(false), + _allowLocalhostReturnValue(false), + _serverIsArbiterReturnValue(false) {} - virtual bool shouldIgnoreAuthChecks() const { - return _ignoreAuthChecksReturnValue; - } + virtual bool shouldIgnoreAuthChecks() const { + return _ignoreAuthChecksReturnValue; + } - virtual bool shouldAllowLocalhost() const { - return _allowLocalhostReturnValue; - } + virtual bool shouldAllowLocalhost() const { + return _allowLocalhostReturnValue; + } - virtual bool serverIsArbiter() const { - return _serverIsArbiterReturnValue; - } + virtual bool serverIsArbiter() const { + return _serverIsArbiterReturnValue; + } - void setReturnValueForShouldIgnoreAuthChecks(bool returnValue) { - _ignoreAuthChecksReturnValue = returnValue; - } + void setReturnValueForShouldIgnoreAuthChecks(bool returnValue) { + _ignoreAuthChecksReturnValue = returnValue; + } - void setReturnValueForShouldAllowLocalhost(bool returnValue) { - _allowLocalhostReturnValue = returnValue; - } + void setReturnValueForShouldAllowLocalhost(bool returnValue) { + _allowLocalhostReturnValue = returnValue; + } - virtual void startRequest(OperationContext* txn) {} + virtual void startRequest(OperationContext* txn) {} - private: - bool _ignoreAuthChecksReturnValue; - bool _allowLocalhostReturnValue; - bool _serverIsArbiterReturnValue; - }; +private: + bool _ignoreAuthChecksReturnValue; + bool _allowLocalhostReturnValue; + bool _serverIsArbiterReturnValue; +}; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_session_external_state_s.cpp b/src/mongo/db/auth/authz_session_external_state_s.cpp index 4009670c6c4..9576bc79a2d 100644 --- a/src/mongo/db/auth/authz_session_external_state_s.cpp +++ b/src/mongo/db/auth/authz_session_external_state_s.cpp @@ -38,13 +38,12 @@ namespace mongo { - AuthzSessionExternalStateMongos::AuthzSessionExternalStateMongos( - AuthorizationManager* authzManager) : - AuthzSessionExternalStateServerCommon(authzManager) {} - AuthzSessionExternalStateMongos::~AuthzSessionExternalStateMongos() {} +AuthzSessionExternalStateMongos::AuthzSessionExternalStateMongos(AuthorizationManager* authzManager) + : AuthzSessionExternalStateServerCommon(authzManager) {} +AuthzSessionExternalStateMongos::~AuthzSessionExternalStateMongos() {} - void AuthzSessionExternalStateMongos::startRequest(OperationContext* txn) { - _checkShouldAllowLocalhost(txn); - } +void AuthzSessionExternalStateMongos::startRequest(OperationContext* txn) { + _checkShouldAllowLocalhost(txn); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_session_external_state_s.h b/src/mongo/db/auth/authz_session_external_state_s.h index 777082faadc..7db5078db2a 100644 --- a/src/mongo/db/auth/authz_session_external_state_s.h +++ b/src/mongo/db/auth/authz_session_external_state_s.h @@ -35,17 +35,17 @@ namespace mongo { - /** - * The implementation of AuthzSessionExternalState functionality for mongos. - */ - class AuthzSessionExternalStateMongos : public AuthzSessionExternalStateServerCommon { - MONGO_DISALLOW_COPYING(AuthzSessionExternalStateMongos); +/** + * The implementation of AuthzSessionExternalState functionality for mongos. + */ +class AuthzSessionExternalStateMongos : public AuthzSessionExternalStateServerCommon { + MONGO_DISALLOW_COPYING(AuthzSessionExternalStateMongos); - public: - AuthzSessionExternalStateMongos(AuthorizationManager* authzManager); - virtual ~AuthzSessionExternalStateMongos(); +public: + AuthzSessionExternalStateMongos(AuthorizationManager* authzManager); + virtual ~AuthzSessionExternalStateMongos(); - virtual void startRequest(OperationContext* txn); - }; + virtual void startRequest(OperationContext* txn); +}; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_session_external_state_server_common.cpp b/src/mongo/db/auth/authz_session_external_state_server_common.cpp index 453980e19f7..a85ab1c5ac2 100644 --- a/src/mongo/db/auth/authz_session_external_state_server_common.cpp +++ b/src/mongo/db/auth/authz_session_external_state_server_common.cpp @@ -42,50 +42,49 @@ namespace mongo { namespace { - MONGO_EXPORT_STARTUP_SERVER_PARAMETER(enableLocalhostAuthBypass, bool, true); -} // namespace +MONGO_EXPORT_STARTUP_SERVER_PARAMETER(enableLocalhostAuthBypass, bool, true); +} // namespace - // NOTE: we default _allowLocalhost to true under the assumption that _checkShouldAllowLocalhost - // will always be called before any calls to shouldAllowLocalhost. If this is not the case, - // it could cause a security hole. - AuthzSessionExternalStateServerCommon::AuthzSessionExternalStateServerCommon( - AuthorizationManager* authzManager) : - AuthzSessionExternalState(authzManager), - _allowLocalhost(enableLocalhostAuthBypass) {} - AuthzSessionExternalStateServerCommon::~AuthzSessionExternalStateServerCommon() {} +// NOTE: we default _allowLocalhost to true under the assumption that _checkShouldAllowLocalhost +// will always be called before any calls to shouldAllowLocalhost. If this is not the case, +// it could cause a security hole. +AuthzSessionExternalStateServerCommon::AuthzSessionExternalStateServerCommon( + AuthorizationManager* authzManager) + : AuthzSessionExternalState(authzManager), _allowLocalhost(enableLocalhostAuthBypass) {} +AuthzSessionExternalStateServerCommon::~AuthzSessionExternalStateServerCommon() {} - void AuthzSessionExternalStateServerCommon::_checkShouldAllowLocalhost(OperationContext* txn) { - if (!_authzManager->isAuthEnabled()) - return; - // If we know that an admin user exists, don't re-check. - if (!_allowLocalhost) - return; - // Don't bother checking if we're not on a localhost connection - if (!ClientBasic::getCurrent()->getIsLocalHostConnection()) { - _allowLocalhost = false; - return; - } +void AuthzSessionExternalStateServerCommon::_checkShouldAllowLocalhost(OperationContext* txn) { + if (!_authzManager->isAuthEnabled()) + return; + // If we know that an admin user exists, don't re-check. + if (!_allowLocalhost) + return; + // Don't bother checking if we're not on a localhost connection + if (!ClientBasic::getCurrent()->getIsLocalHostConnection()) { + _allowLocalhost = false; + return; + } - _allowLocalhost = !_authzManager->hasAnyPrivilegeDocuments(txn); - if (_allowLocalhost) { - ONCE { - log() << "note: no users configured in admin.system.users, allowing localhost " - "access" << std::endl; - } + _allowLocalhost = !_authzManager->hasAnyPrivilegeDocuments(txn); + if (_allowLocalhost) { + ONCE { + log() << "note: no users configured in admin.system.users, allowing localhost " + "access" << std::endl; } } +} - bool AuthzSessionExternalStateServerCommon::serverIsArbiter() const { - return false; - } +bool AuthzSessionExternalStateServerCommon::serverIsArbiter() const { + return false; +} - bool AuthzSessionExternalStateServerCommon::shouldAllowLocalhost() const { - ClientBasic* client = ClientBasic::getCurrent(); - return _allowLocalhost && client->getIsLocalHostConnection(); - } +bool AuthzSessionExternalStateServerCommon::shouldAllowLocalhost() const { + ClientBasic* client = ClientBasic::getCurrent(); + return _allowLocalhost && client->getIsLocalHostConnection(); +} - bool AuthzSessionExternalStateServerCommon::shouldIgnoreAuthChecks() const { - return !_authzManager->isAuthEnabled(); - } +bool AuthzSessionExternalStateServerCommon::shouldIgnoreAuthChecks() const { + return !_authzManager->isAuthEnabled(); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/authz_session_external_state_server_common.h b/src/mongo/db/auth/authz_session_external_state_server_common.h index f96035dcade..df7ceb6c9f4 100644 --- a/src/mongo/db/auth/authz_session_external_state_server_common.h +++ b/src/mongo/db/auth/authz_session_external_state_server_common.h @@ -35,31 +35,29 @@ namespace mongo { - /** - * The implementation of AuthzSessionExternalState functionality common to mongod and mongos. - */ - class AuthzSessionExternalStateServerCommon : public AuthzSessionExternalState { - MONGO_DISALLOW_COPYING(AuthzSessionExternalStateServerCommon); - - public: - virtual ~AuthzSessionExternalStateServerCommon(); - - virtual bool shouldAllowLocalhost() const; - virtual bool shouldIgnoreAuthChecks() const; - virtual bool serverIsArbiter() const; +/** + * The implementation of AuthzSessionExternalState functionality common to mongod and mongos. + */ +class AuthzSessionExternalStateServerCommon : public AuthzSessionExternalState { + MONGO_DISALLOW_COPYING(AuthzSessionExternalStateServerCommon); - protected: - AuthzSessionExternalStateServerCommon(AuthorizationManager* authzManager); +public: + virtual ~AuthzSessionExternalStateServerCommon(); - // Checks whether or not localhost connections should be given full access and stores the - // result in _allowLocalhost. Currently localhost connections are only given full access - // if there are no users in the admin database. - void _checkShouldAllowLocalhost(OperationContext* txn); + virtual bool shouldAllowLocalhost() const; + virtual bool shouldIgnoreAuthChecks() const; + virtual bool serverIsArbiter() const; - private: +protected: + AuthzSessionExternalStateServerCommon(AuthorizationManager* authzManager); - bool _allowLocalhost; + // Checks whether or not localhost connections should be given full access and stores the + // result in _allowLocalhost. Currently localhost connections are only given full access + // if there are no users in the admin database. + void _checkShouldAllowLocalhost(OperationContext* txn); - }; +private: + bool _allowLocalhost; +}; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/impersonation_session.cpp b/src/mongo/db/auth/impersonation_session.cpp index eb8bf54a6f9..228d5567807 100644 --- a/src/mongo/db/auth/impersonation_session.cpp +++ b/src/mongo/db/auth/impersonation_session.cpp @@ -44,36 +44,29 @@ namespace mongo { - ImpersonationSessionGuard::ImpersonationSessionGuard(OperationContext* txn) - : _txn(txn) { +ImpersonationSessionGuard::ImpersonationSessionGuard(OperationContext* txn) : _txn(txn) { + auto authSession = AuthorizationSession::get(_txn->getClient()); - auto authSession = AuthorizationSession::get(_txn->getClient()); + const auto& impersonatedUsersAndRoles = + rpc::AuditMetadata::get(txn).getImpersonatedUsersAndRoles(); - const auto& impersonatedUsersAndRoles = - rpc::AuditMetadata::get(txn).getImpersonatedUsersAndRoles(); + if (impersonatedUsersAndRoles != boost::none) { + uassert(ErrorCodes::Unauthorized, + "Unauthorized use of impersonation metadata.", + authSession->isAuthorizedForPrivilege( + Privilege(ResourcePattern::forClusterResource(), ActionType::impersonate))); - if (impersonatedUsersAndRoles != boost::none) { + fassert(ErrorCodes::InternalError, !authSession->isImpersonating()); - uassert(ErrorCodes::Unauthorized, - "Unauthorized use of impersonation metadata.", - authSession->isAuthorizedForPrivilege( - Privilege(ResourcePattern::forClusterResource(), - ActionType::impersonate))); - - fassert(ErrorCodes::InternalError, !authSession->isImpersonating()); - - authSession->setImpersonatedUserData(std::get<0>(*impersonatedUsersAndRoles), - std::get<1>(*impersonatedUsersAndRoles)); - _active = true; - } + authSession->setImpersonatedUserData(std::get<0>(*impersonatedUsersAndRoles), + std::get<1>(*impersonatedUsersAndRoles)); + _active = true; } +} - ImpersonationSessionGuard::~ImpersonationSessionGuard() { - DESTRUCTOR_GUARD( - if (_active) { - AuthorizationSession::get(_txn->getClient())->clearImpersonatedUserData(); - } - ) - } +ImpersonationSessionGuard::~ImpersonationSessionGuard() { + DESTRUCTOR_GUARD( + if (_active) { AuthorizationSession::get(_txn->getClient())->clearImpersonatedUserData(); }) +} } // namespace mongo diff --git a/src/mongo/db/auth/impersonation_session.h b/src/mongo/db/auth/impersonation_session.h index ab05bf1e5c3..af2dab2f774 100644 --- a/src/mongo/db/auth/impersonation_session.h +++ b/src/mongo/db/auth/impersonation_session.h @@ -29,20 +29,22 @@ #include "mongo/base/disallow_copying.h" namespace mongo { - class OperationContext; +class OperationContext; - /** - * RAII class to optionally set an impersonated username list into the authorization session - * for the duration of the life of this object. - */ - class ImpersonationSessionGuard { - MONGO_DISALLOW_COPYING(ImpersonationSessionGuard); - public: - ImpersonationSessionGuard(OperationContext* txn); - ~ImpersonationSessionGuard(); - private: - OperationContext* _txn; - bool _active{false}; - }; +/** + * RAII class to optionally set an impersonated username list into the authorization session + * for the duration of the life of this object. + */ +class ImpersonationSessionGuard { + MONGO_DISALLOW_COPYING(ImpersonationSessionGuard); + +public: + ImpersonationSessionGuard(OperationContext* txn); + ~ImpersonationSessionGuard(); + +private: + OperationContext* _txn; + bool _active{false}; +}; } // namespace mongo diff --git a/src/mongo/db/auth/internal_user_auth.cpp b/src/mongo/db/auth/internal_user_auth.cpp index 6c8190845ea..5cc90c29ea9 100644 --- a/src/mongo/db/auth/internal_user_auth.cpp +++ b/src/mongo/db/auth/internal_user_auth.cpp @@ -39,78 +39,78 @@ #include "mongo/util/log.h" namespace mongo { - namespace mmb = mongo::mutablebson; +namespace mmb = mongo::mutablebson; - // not guarded by the authParams mutex never changed in - // multi-threaded operation - static bool authParamsSet = false; +// not guarded by the authParams mutex never changed in +// multi-threaded operation +static bool authParamsSet = false; - // Store default authentication parameters for internal authentication to cluster members, - // guarded by the authParams mutex - static BSONObj authParams; +// Store default authentication parameters for internal authentication to cluster members, +// guarded by the authParams mutex +static BSONObj authParams; - static stdx::mutex authParamMutex; +static stdx::mutex authParamMutex; - bool isInternalAuthSet() { - return authParamsSet; - } - - void setInternalUserAuthParams(const BSONObj& authParamsIn) { - if (!isInternalAuthSet()) { - authParamsSet = true; - } - stdx::lock_guard<stdx::mutex> lk(authParamMutex); +bool isInternalAuthSet() { + return authParamsSet; +} - if (authParamsIn["mechanism"].String() != "SCRAM-SHA-1") { - authParams = authParamsIn.copy(); - return; - } +void setInternalUserAuthParams(const BSONObj& authParamsIn) { + if (!isInternalAuthSet()) { + authParamsSet = true; + } + stdx::lock_guard<stdx::mutex> lk(authParamMutex); - // Create authParams for legacy MONGODB-CR authentication for 2.6/3.0 mixed - // mode if applicable. - mmb::Document fallback(authParamsIn); - fallback.root().findFirstChildNamed("mechanism").setValueString("MONGODB-CR"); + if (authParamsIn["mechanism"].String() != "SCRAM-SHA-1") { + authParams = authParamsIn.copy(); + return; + } - mmb::Document doc(authParamsIn); - mmb::Element fallbackEl = doc.makeElementObject("fallbackParams"); - fallbackEl.setValueObject(fallback.getObject()); - doc.root().pushBack(fallbackEl); - authParams = doc.getObject().copy(); + // Create authParams for legacy MONGODB-CR authentication for 2.6/3.0 mixed + // mode if applicable. + mmb::Document fallback(authParamsIn); + fallback.root().findFirstChildNamed("mechanism").setValueString("MONGODB-CR"); + + mmb::Document doc(authParamsIn); + mmb::Element fallbackEl = doc.makeElementObject("fallbackParams"); + fallbackEl.setValueObject(fallback.getObject()); + doc.root().pushBack(fallbackEl); + authParams = doc.getObject().copy(); +} + +BSONObj getInternalUserAuthParamsWithFallback() { + if (!authParamsSet) { + return BSONObj(); } - BSONObj getInternalUserAuthParamsWithFallback() { - if (!authParamsSet) { - return BSONObj(); - } + stdx::lock_guard<stdx::mutex> lk(authParamMutex); + return authParams.copy(); +} - stdx::lock_guard<stdx::mutex> lk(authParamMutex); - return authParams.copy(); +BSONObj getFallbackAuthParams(BSONObj params) { + if (params["fallbackParams"].type() != Object) { + return BSONObj(); } + return params["fallbackParams"].Obj(); +} - BSONObj getFallbackAuthParams(BSONObj params) { - if (params["fallbackParams"].type() != Object) { - return BSONObj(); +bool authenticateInternalUser(DBClientWithCommands* conn) { + if (!isInternalAuthSet()) { + if (!serverGlobalParams.quiet) { + log() << "ERROR: No authentication parameters set for internal user"; } - return params["fallbackParams"].Obj(); + return false; } - bool authenticateInternalUser(DBClientWithCommands* conn) { - if (!isInternalAuthSet()) { - if (!serverGlobalParams.quiet) { - log() << "ERROR: No authentication parameters set for internal user"; - } - return false; - } - - try { - conn->auth(getInternalUserAuthParamsWithFallback()); - return true; - } catch(const UserException& ex) { - if (!serverGlobalParams.quiet) { - log() << "can't authenticate to " << conn->toString() - << " as internal user, error: "<< ex.what(); - } - return false; + try { + conn->auth(getInternalUserAuthParamsWithFallback()); + return true; + } catch (const UserException& ex) { + if (!serverGlobalParams.quiet) { + log() << "can't authenticate to " << conn->toString() + << " as internal user, error: " << ex.what(); } + return false; } -} // namespace mongo +} +} // namespace mongo diff --git a/src/mongo/db/auth/internal_user_auth.h b/src/mongo/db/auth/internal_user_auth.h index 547c9ba76a8..772eef1b322 100644 --- a/src/mongo/db/auth/internal_user_auth.h +++ b/src/mongo/db/auth/internal_user_auth.h @@ -29,40 +29,40 @@ #pragma once namespace mongo { - class BSONObj; - class DBClientWithCommands; +class BSONObj; +class DBClientWithCommands; - /** - * @return true if internal authentication parameters has been set up - */ - bool isInternalAuthSet(); +/** + * @return true if internal authentication parameters has been set up + */ +bool isInternalAuthSet(); - /** - * This method initializes the authParams object with authentication - * credentials to be used by authenticateInternalUser. - */ - void setInternalUserAuthParams(const BSONObj& authParamsIn); +/** + * This method initializes the authParams object with authentication + * credentials to be used by authenticateInternalUser. + */ +void setInternalUserAuthParams(const BSONObj& authParamsIn); - /** - * Returns a copy of the authParams object to be used by authenticateInternalUser - * - * The format of the return object is { authparams, fallbackParams:params} - * - * If SCRAM-SHA-1 is the internal auth mechanism the fallbackParams sub document is - * for MONGODB-CR auth is included. For MONGODB-XC509 no fallbackParams document is - * returned. - **/ - BSONObj getInternalUserAuthParamsWithFallback(); +/** + * Returns a copy of the authParams object to be used by authenticateInternalUser + * + * The format of the return object is { authparams, fallbackParams:params} + * + * If SCRAM-SHA-1 is the internal auth mechanism the fallbackParams sub document is + * for MONGODB-CR auth is included. For MONGODB-XC509 no fallbackParams document is + * returned. + **/ +BSONObj getInternalUserAuthParamsWithFallback(); - /** - * Returns a copy of the fallback parameter portion of an internal auth parameter object - **/ - BSONObj getFallbackAuthParams(BSONObj params); +/** + * Returns a copy of the fallback parameter portion of an internal auth parameter object + **/ +BSONObj getFallbackAuthParams(BSONObj params); - /** - * Authenticates to another cluster member using appropriate authentication data. - * Uses getInternalUserAuthParams() to retrive authentication parameters. - * @return true if the authentication was succesful - */ - bool authenticateInternalUser(DBClientWithCommands* conn); -} // namespace mongo +/** +* Authenticates to another cluster member using appropriate authentication data. +* Uses getInternalUserAuthParams() to retrive authentication parameters. +* @return true if the authentication was succesful +*/ +bool authenticateInternalUser(DBClientWithCommands* conn); +} // namespace mongo diff --git a/src/mongo/db/auth/mongo_authentication_session.cpp b/src/mongo/db/auth/mongo_authentication_session.cpp index 117f3a047f2..2e25b4f561c 100644 --- a/src/mongo/db/auth/mongo_authentication_session.cpp +++ b/src/mongo/db/auth/mongo_authentication_session.cpp @@ -29,11 +29,9 @@ namespace mongo { - MongoAuthenticationSession::MongoAuthenticationSession(nonce64 nonce) : - AuthenticationSession(AuthenticationSession::SESSION_TYPE_MONGO), - _nonce(nonce) { - } +MongoAuthenticationSession::MongoAuthenticationSession(nonce64 nonce) + : AuthenticationSession(AuthenticationSession::SESSION_TYPE_MONGO), _nonce(nonce) {} - MongoAuthenticationSession::~MongoAuthenticationSession() {} +MongoAuthenticationSession::~MongoAuthenticationSession() {} } // namespace mongo diff --git a/src/mongo/db/auth/mongo_authentication_session.h b/src/mongo/db/auth/mongo_authentication_session.h index 19d2c44d387..e60bcf6ac85 100644 --- a/src/mongo/db/auth/mongo_authentication_session.h +++ b/src/mongo/db/auth/mongo_authentication_session.h @@ -31,24 +31,27 @@ namespace mongo { - typedef unsigned long long nonce64; - - /** - * Authentication session data for a nonce-challenge-response authentication of the - * type used in the Mongo nonce-authenticate protocol. - * - * The only session data is the nonce sent to the client. - */ - class MongoAuthenticationSession : public AuthenticationSession { - MONGO_DISALLOW_COPYING(MongoAuthenticationSession); - public: - explicit MongoAuthenticationSession(nonce64 nonce); - virtual ~MongoAuthenticationSession(); - - nonce64 getNonce() const { return _nonce; } - - private: - const nonce64 _nonce; - }; +typedef unsigned long long nonce64; + +/** + * Authentication session data for a nonce-challenge-response authentication of the + * type used in the Mongo nonce-authenticate protocol. + * + * The only session data is the nonce sent to the client. + */ +class MongoAuthenticationSession : public AuthenticationSession { + MONGO_DISALLOW_COPYING(MongoAuthenticationSession); + +public: + explicit MongoAuthenticationSession(nonce64 nonce); + virtual ~MongoAuthenticationSession(); + + nonce64 getNonce() const { + return _nonce; + } + +private: + const nonce64 _nonce; +}; } // namespace mongo diff --git a/src/mongo/db/auth/native_sasl_authentication_session.cpp b/src/mongo/db/auth/native_sasl_authentication_session.cpp index e2392beacd3..9566ba37487 100644 --- a/src/mongo/db/auth/native_sasl_authentication_session.cpp +++ b/src/mongo/db/auth/native_sasl_authentication_session.cpp @@ -52,122 +52,108 @@ namespace mongo { - using std::unique_ptr; +using std::unique_ptr; namespace { - SaslAuthenticationSession* createNativeSaslAuthenticationSession( - AuthorizationSession* authzSession, - const std::string& mechanism) { - return new NativeSaslAuthenticationSession(authzSession); - } - - MONGO_INITIALIZER(NativeSaslServerCore)(InitializerContext* context) { - if (saslGlobalParams.hostName.empty()) - saslGlobalParams.hostName = getHostNameCached(); - if (saslGlobalParams.serviceName.empty()) - saslGlobalParams.serviceName = "mongodb"; - - SaslAuthenticationSession::create = createNativeSaslAuthenticationSession; - return Status::OK(); - } - - // PostSaslCommands is reversely dependent on CyrusSaslCommands having been run - MONGO_INITIALIZER_WITH_PREREQUISITES(PostSaslCommands, - ("NativeSaslServerCore")) - (InitializerContext*) { - - AuthorizationManager authzManager(stdx::make_unique<AuthzManagerExternalStateMock>()); - std::unique_ptr<AuthorizationSession> authzSession = - authzManager.makeAuthorizationSession(); - - for (size_t i = 0; i < saslGlobalParams.authenticationMechanisms.size(); ++i) { - const std::string& mechanism = saslGlobalParams.authenticationMechanisms[i]; - if (mechanism == "MONGODB-CR" || mechanism == "MONGODB-X509") { - // Not a SASL mechanism; no need to smoke test built-in mechanisms. - continue; - } - unique_ptr<SaslAuthenticationSession> - session(SaslAuthenticationSession::create(authzSession.get(), mechanism)); - Status status = session->start("test", - mechanism, - saslGlobalParams.serviceName, - saslGlobalParams.hostName, - 1, - true); - if (!status.isOK()) - return status; +SaslAuthenticationSession* createNativeSaslAuthenticationSession(AuthorizationSession* authzSession, + const std::string& mechanism) { + return new NativeSaslAuthenticationSession(authzSession); +} + +MONGO_INITIALIZER(NativeSaslServerCore)(InitializerContext* context) { + if (saslGlobalParams.hostName.empty()) + saslGlobalParams.hostName = getHostNameCached(); + if (saslGlobalParams.serviceName.empty()) + saslGlobalParams.serviceName = "mongodb"; + + SaslAuthenticationSession::create = createNativeSaslAuthenticationSession; + return Status::OK(); +} + +// PostSaslCommands is reversely dependent on CyrusSaslCommands having been run +MONGO_INITIALIZER_WITH_PREREQUISITES(PostSaslCommands, ("NativeSaslServerCore")) +(InitializerContext*) { + AuthorizationManager authzManager(stdx::make_unique<AuthzManagerExternalStateMock>()); + std::unique_ptr<AuthorizationSession> authzSession = authzManager.makeAuthorizationSession(); + + for (size_t i = 0; i < saslGlobalParams.authenticationMechanisms.size(); ++i) { + const std::string& mechanism = saslGlobalParams.authenticationMechanisms[i]; + if (mechanism == "MONGODB-CR" || mechanism == "MONGODB-X509") { + // Not a SASL mechanism; no need to smoke test built-in mechanisms. + continue; } - - return Status::OK(); - } -} //namespace - - NativeSaslAuthenticationSession::NativeSaslAuthenticationSession( - AuthorizationSession* authzSession) : - SaslAuthenticationSession(authzSession), - _mechanism("") { + unique_ptr<SaslAuthenticationSession> session( + SaslAuthenticationSession::create(authzSession.get(), mechanism)); + Status status = session->start( + "test", mechanism, saslGlobalParams.serviceName, saslGlobalParams.hostName, 1, true); + if (!status.isOK()) + return status; } - NativeSaslAuthenticationSession::~NativeSaslAuthenticationSession() {} + return Status::OK(); +} +} // namespace - Status NativeSaslAuthenticationSession::start(StringData authenticationDatabase, - StringData mechanism, - StringData serviceName, - StringData serviceHostname, - int64_t conversationId, - bool autoAuthorize) { - fassert(18626, conversationId > 0); +NativeSaslAuthenticationSession::NativeSaslAuthenticationSession(AuthorizationSession* authzSession) + : SaslAuthenticationSession(authzSession), _mechanism("") {} - if (_conversationId != 0) { - return Status(ErrorCodes::AlreadyInitialized, - "Cannot call start() twice on same NativeSaslAuthenticationSession."); - } +NativeSaslAuthenticationSession::~NativeSaslAuthenticationSession() {} - _authenticationDatabase = authenticationDatabase.toString(); - _mechanism = mechanism.toString(); - _serviceName = serviceName.toString(); - _serviceHostname = serviceHostname.toString(); - _conversationId = conversationId; - _autoAuthorize = autoAuthorize; +Status NativeSaslAuthenticationSession::start(StringData authenticationDatabase, + StringData mechanism, + StringData serviceName, + StringData serviceHostname, + int64_t conversationId, + bool autoAuthorize) { + fassert(18626, conversationId > 0); - if (mechanism == "PLAIN") { - _saslConversation.reset(new SaslPLAINServerConversation(this)); - } - else if (mechanism == "SCRAM-SHA-1") { - _saslConversation.reset(new SaslSCRAMSHA1ServerConversation(this)); - } - else { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << "SASL mechanism " << mechanism << - " is not supported"); - } + if (_conversationId != 0) { + return Status(ErrorCodes::AlreadyInitialized, + "Cannot call start() twice on same NativeSaslAuthenticationSession."); + } - return Status::OK(); + _authenticationDatabase = authenticationDatabase.toString(); + _mechanism = mechanism.toString(); + _serviceName = serviceName.toString(); + _serviceHostname = serviceHostname.toString(); + _conversationId = conversationId; + _autoAuthorize = autoAuthorize; + + if (mechanism == "PLAIN") { + _saslConversation.reset(new SaslPLAINServerConversation(this)); + } else if (mechanism == "SCRAM-SHA-1") { + _saslConversation.reset(new SaslSCRAMSHA1ServerConversation(this)); + } else { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "SASL mechanism " << mechanism + << " is not supported"); } - Status NativeSaslAuthenticationSession::step(StringData inputData, - std::string* outputData) { - if (!_saslConversation) { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << - "The authentication session has not been properly initialized"); - } + return Status::OK(); +} - StatusWith<bool> status = _saslConversation->step(inputData, outputData); - if (status.isOK()) { - _done = status.getValue(); - } else { - _done = true; - } - return status.getStatus(); +Status NativeSaslAuthenticationSession::step(StringData inputData, std::string* outputData) { + if (!_saslConversation) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() + << "The authentication session has not been properly initialized"); } - std::string NativeSaslAuthenticationSession::getPrincipalId() const { - return _saslConversation->getPrincipalId(); + StatusWith<bool> status = _saslConversation->step(inputData, outputData); + if (status.isOK()) { + _done = status.getValue(); + } else { + _done = true; } + return status.getStatus(); +} - const char* NativeSaslAuthenticationSession::getMechanism() const { - return _mechanism.c_str(); - } +std::string NativeSaslAuthenticationSession::getPrincipalId() const { + return _saslConversation->getPrincipalId(); +} + +const char* NativeSaslAuthenticationSession::getMechanism() const { + return _mechanism.c_str(); +} } // namespace mongo diff --git a/src/mongo/db/auth/native_sasl_authentication_session.h b/src/mongo/db/auth/native_sasl_authentication_session.h index 57b90a145be..156ffe685f3 100644 --- a/src/mongo/db/auth/native_sasl_authentication_session.h +++ b/src/mongo/db/auth/native_sasl_authentication_session.h @@ -40,31 +40,31 @@ namespace mongo { - /** - * Authentication session data for the server side of SASL authentication. - */ - class NativeSaslAuthenticationSession : public SaslAuthenticationSession { - MONGO_DISALLOW_COPYING(NativeSaslAuthenticationSession); - public: +/** + * Authentication session data for the server side of SASL authentication. + */ +class NativeSaslAuthenticationSession : public SaslAuthenticationSession { + MONGO_DISALLOW_COPYING(NativeSaslAuthenticationSession); - explicit NativeSaslAuthenticationSession(AuthorizationSession* authSession); - virtual ~NativeSaslAuthenticationSession(); +public: + explicit NativeSaslAuthenticationSession(AuthorizationSession* authSession); + virtual ~NativeSaslAuthenticationSession(); - virtual Status start(StringData authenticationDatabase, - StringData mechanism, - StringData serviceName, - StringData serviceHostname, - int64_t conversationId, - bool autoAuthorize); + virtual Status start(StringData authenticationDatabase, + StringData mechanism, + StringData serviceName, + StringData serviceHostname, + int64_t conversationId, + bool autoAuthorize); - virtual Status step(StringData inputData, std::string* outputData); + virtual Status step(StringData inputData, std::string* outputData); - virtual std::string getPrincipalId() const; + virtual std::string getPrincipalId() const; - virtual const char* getMechanism() const; + virtual const char* getMechanism() const; - private: - std::string _mechanism; - std::unique_ptr<SaslServerConversation> _saslConversation; - }; +private: + std::string _mechanism; + std::unique_ptr<SaslServerConversation> _saslConversation; +}; } // namespace mongo diff --git a/src/mongo/db/auth/privilege.cpp b/src/mongo/db/auth/privilege.cpp index c98385f2bf6..25cd3141151 100644 --- a/src/mongo/db/auth/privilege.cpp +++ b/src/mongo/db/auth/privilege.cpp @@ -32,47 +32,46 @@ namespace mongo { - void Privilege::addPrivilegeToPrivilegeVector(PrivilegeVector* privileges, - const Privilege& privilegeToAdd) { - for (PrivilegeVector::iterator it = privileges->begin(); it != privileges->end(); ++it) { - if (it->getResourcePattern() == privilegeToAdd.getResourcePattern()) { - it->addActions(privilegeToAdd.getActions()); - return; - } +void Privilege::addPrivilegeToPrivilegeVector(PrivilegeVector* privileges, + const Privilege& privilegeToAdd) { + for (PrivilegeVector::iterator it = privileges->begin(); it != privileges->end(); ++it) { + if (it->getResourcePattern() == privilegeToAdd.getResourcePattern()) { + it->addActions(privilegeToAdd.getActions()); + return; } - // No privilege exists yet for this resource - privileges->push_back(privilegeToAdd); } + // No privilege exists yet for this resource + privileges->push_back(privilegeToAdd); +} - Privilege::Privilege(const ResourcePattern& resource, const ActionType& action) : - _resource(resource) { +Privilege::Privilege(const ResourcePattern& resource, const ActionType& action) + : _resource(resource) { + _actions.addAction(action); +} +Privilege::Privilege(const ResourcePattern& resource, const ActionSet& actions) + : _resource(resource), _actions(actions) {} - _actions.addAction(action); - } - Privilege::Privilege(const ResourcePattern& resource, const ActionSet& actions) : - _resource(resource), _actions(actions) {} +void Privilege::addActions(const ActionSet& actionsToAdd) { + _actions.addAllActionsFromSet(actionsToAdd); +} - void Privilege::addActions(const ActionSet& actionsToAdd) { - _actions.addAllActionsFromSet(actionsToAdd); - } +void Privilege::removeActions(const ActionSet& actionsToRemove) { + _actions.removeAllActionsFromSet(actionsToRemove); +} - void Privilege::removeActions(const ActionSet& actionsToRemove) { - _actions.removeAllActionsFromSet(actionsToRemove); - } +bool Privilege::includesAction(const ActionType& action) const { + return _actions.contains(action); +} - bool Privilege::includesAction(const ActionType& action) const { - return _actions.contains(action); - } +bool Privilege::includesActions(const ActionSet& actions) const { + return _actions.isSupersetOf(actions); +} - bool Privilege::includesActions(const ActionSet& actions) const { - return _actions.isSupersetOf(actions); - } - - BSONObj Privilege::toBSON() const { - ParsedPrivilege pp; - std::string errmsg; - invariant(ParsedPrivilege::privilegeToParsedPrivilege(*this, &pp, &errmsg)); - return pp.toBSON(); - } +BSONObj Privilege::toBSON() const { + ParsedPrivilege pp; + std::string errmsg; + invariant(ParsedPrivilege::privilegeToParsedPrivilege(*this, &pp, &errmsg)); + return pp.toBSON(); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/privilege.h b/src/mongo/db/auth/privilege.h index 1009172a30c..da6b054e012 100644 --- a/src/mongo/db/auth/privilege.h +++ b/src/mongo/db/auth/privilege.h @@ -36,47 +36,50 @@ namespace mongo { - class Privilege; - typedef std::vector<Privilege> PrivilegeVector; +class Privilege; +typedef std::vector<Privilege> PrivilegeVector; +/** + * A representation of the permission to perform a set of actions on a resource. + */ +class Privilege { +public: /** - * A representation of the permission to perform a set of actions on a resource. + * Adds "privilegeToAdd" to "privileges", de-duping "privilegeToAdd" if the vector already + * contains a privilege on the same resource. + * + * This method is the preferred way to add privileges to privilege vectors. */ - class Privilege { - public: - /** - * Adds "privilegeToAdd" to "privileges", de-duping "privilegeToAdd" if the vector already - * contains a privilege on the same resource. - * - * This method is the preferred way to add privileges to privilege vectors. - */ - static void addPrivilegeToPrivilegeVector(PrivilegeVector* privileges, - const Privilege& privilegeToAdd); - + static void addPrivilegeToPrivilegeVector(PrivilegeVector* privileges, + const Privilege& privilegeToAdd); - Privilege() {}; - Privilege(const ResourcePattern& resource, const ActionType& action); - Privilege(const ResourcePattern& resource, const ActionSet& actions); - ~Privilege() {} - const ResourcePattern& getResourcePattern() const { return _resource; } + Privilege(){}; + Privilege(const ResourcePattern& resource, const ActionType& action); + Privilege(const ResourcePattern& resource, const ActionSet& actions); + ~Privilege() {} - const ActionSet& getActions() const { return _actions; } + const ResourcePattern& getResourcePattern() const { + return _resource; + } - void addActions(const ActionSet& actionsToAdd); - void removeActions(const ActionSet& actionsToRemove); + const ActionSet& getActions() const { + return _actions; + } - // Checks if the given action is present in the Privilege. - bool includesAction(const ActionType& action) const; - // Checks if the given actions are present in the Privilege. - bool includesActions(const ActionSet& actions) const; + void addActions(const ActionSet& actionsToAdd); + void removeActions(const ActionSet& actionsToRemove); - BSONObj toBSON() const; + // Checks if the given action is present in the Privilege. + bool includesAction(const ActionType& action) const; + // Checks if the given actions are present in the Privilege. + bool includesActions(const ActionSet& actions) const; - private: + BSONObj toBSON() const; - ResourcePattern _resource; - ActionSet _actions; // bitmask of actions this privilege grants - }; +private: + ResourcePattern _resource; + ActionSet _actions; // bitmask of actions this privilege grants +}; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/privilege_parser.cpp b/src/mongo/db/auth/privilege_parser.cpp index f1b4777a74c..140fbde2d7f 100644 --- a/src/mongo/db/auth/privilege_parser.cpp +++ b/src/mongo/db/auth/privilege_parser.cpp @@ -37,420 +37,429 @@ namespace mongo { - using std::string; - using std::vector; +using std::string; +using std::vector; - using mongoutils::str::stream; +using mongoutils::str::stream; - const BSONField<bool> ParsedResource::anyResource("anyResource"); - const BSONField<bool> ParsedResource::cluster("cluster"); - const BSONField<string> ParsedResource::db("db"); - const BSONField<string> ParsedResource::collection("collection"); +const BSONField<bool> ParsedResource::anyResource("anyResource"); +const BSONField<bool> ParsedResource::cluster("cluster"); +const BSONField<string> ParsedResource::db("db"); +const BSONField<string> ParsedResource::collection("collection"); - ParsedResource::ParsedResource() { - clear(); - } - - ParsedResource::~ParsedResource() { - } - - bool ParsedResource::isValid(std::string* errMsg) const { - std::string dummy; - if (errMsg == NULL) { - errMsg = &dummy; - } +ParsedResource::ParsedResource() { + clear(); +} - int numCandidateTypes = 0; - if (isAnyResourceSet()) ++numCandidateTypes; - if (isClusterSet()) ++numCandidateTypes; - if (isDbSet() || isCollectionSet()) ++numCandidateTypes; +ParsedResource::~ParsedResource() {} - if (isDbSet() != isCollectionSet()) { - *errMsg = stream() << "resource must set both " << db.name() << " and " << - collection.name() << " or neither, but not exactly one."; - return false; - } - if (numCandidateTypes != 1) { - *errMsg = stream() << "resource must have exactly " << db.name() << " and " << - collection.name() << " set, or have only " << cluster.name() << " set " << - " or have only " << anyResource.name() << " set"; - return false; - } - if (isAnyResourceSet() && !getAnyResource()) { - *errMsg = stream() << anyResource.name() << " must be true when specified"; - return false; - } - if (isClusterSet() && !getCluster()) { - *errMsg = stream() << cluster.name() << " must be true when specified"; - return false; - } - if (isDbSet() && (!NamespaceString::validDBName(getDb()) && !getDb().empty())) { - *errMsg = stream() << getDb() << " is not a valid database name"; - return false; - } - if (isCollectionSet() && (!NamespaceString::validCollectionName(getCollection()) && - !getCollection().empty())) { - *errMsg = stream() << getCollection() << " is not a valid collection name"; - return false; - } - return true; +bool ParsedResource::isValid(std::string* errMsg) const { + std::string dummy; + if (errMsg == NULL) { + errMsg = &dummy; } - BSONObj ParsedResource::toBSON() const { - BSONObjBuilder builder; - - if (_isAnyResourceSet) builder.append(anyResource(), _anyResource); - - if (_isClusterSet) builder.append(cluster(), _cluster); - - if (_isDbSet) builder.append(db(), _db); - - if (_isCollectionSet) builder.append(collection(), _collection); + int numCandidateTypes = 0; + if (isAnyResourceSet()) + ++numCandidateTypes; + if (isClusterSet()) + ++numCandidateTypes; + if (isDbSet() || isCollectionSet()) + ++numCandidateTypes; - return builder.obj(); + if (isDbSet() != isCollectionSet()) { + *errMsg = stream() << "resource must set both " << db.name() << " and " << collection.name() + << " or neither, but not exactly one."; + return false; } - - bool ParsedResource::parseBSON(const BSONObj& source, string* errMsg) { - clear(); - - std::string dummy; - if (!errMsg) errMsg = &dummy; - - FieldParser::FieldState fieldState; - fieldState = FieldParser::extract(source, anyResource, &_anyResource, errMsg); - if (fieldState == FieldParser::FIELD_INVALID) return false; - _isAnyResourceSet = fieldState == FieldParser::FIELD_SET; - - fieldState = FieldParser::extract(source, cluster, &_cluster, errMsg); - if (fieldState == FieldParser::FIELD_INVALID) return false; - _isClusterSet = fieldState == FieldParser::FIELD_SET; - - fieldState = FieldParser::extract(source, db, &_db, errMsg); - if (fieldState == FieldParser::FIELD_INVALID) return false; - _isDbSet = fieldState == FieldParser::FIELD_SET; - - fieldState = FieldParser::extract(source, collection, &_collection, errMsg); - if (fieldState == FieldParser::FIELD_INVALID) return false; - _isCollectionSet = fieldState == FieldParser::FIELD_SET; - - return true; + if (numCandidateTypes != 1) { + *errMsg = stream() << "resource must have exactly " << db.name() << " and " + << collection.name() << " set, or have only " << cluster.name() + << " set " + << " or have only " << anyResource.name() << " set"; + return false; } - - void ParsedResource::clear() { - _anyResource = false; - _isAnyResourceSet = false; - - _cluster = false; - _isClusterSet = false; - - _db.clear(); - _isDbSet = false; - - _collection.clear(); - _isCollectionSet = false; - + if (isAnyResourceSet() && !getAnyResource()) { + *errMsg = stream() << anyResource.name() << " must be true when specified"; + return false; } - - void ParsedResource::cloneTo(ParsedResource* other) const { - other->clear(); - - other->_anyResource = _anyResource; - other->_isAnyResourceSet = _isAnyResourceSet; - - other->_cluster = _cluster; - other->_isClusterSet = _isClusterSet; - - other->_db = _db; - other->_isDbSet = _isDbSet; - - other->_collection = _collection; - other->_isCollectionSet = _isCollectionSet; + if (isClusterSet() && !getCluster()) { + *errMsg = stream() << cluster.name() << " must be true when specified"; + return false; } - - std::string ParsedResource::toString() const { - return toBSON().toString(); + if (isDbSet() && (!NamespaceString::validDBName(getDb()) && !getDb().empty())) { + *errMsg = stream() << getDb() << " is not a valid database name"; + return false; } - - void ParsedResource::setAnyResource(bool anyResource) { - _anyResource = anyResource; - _isAnyResourceSet = true; + if (isCollectionSet() && + (!NamespaceString::validCollectionName(getCollection()) && !getCollection().empty())) { + *errMsg = stream() << getCollection() << " is not a valid collection name"; + return false; } + return true; +} - void ParsedResource::unsetAnyResource() { - _isAnyResourceSet = false; - } +BSONObj ParsedResource::toBSON() const { + BSONObjBuilder builder; - bool ParsedResource::isAnyResourceSet() const { - return _isAnyResourceSet; - } + if (_isAnyResourceSet) + builder.append(anyResource(), _anyResource); - bool ParsedResource::getAnyResource() const { - dassert(_isAnyResourceSet); - return _anyResource; - } - - void ParsedResource::setCluster(bool cluster) { - _cluster = cluster; - _isClusterSet = true; - } + if (_isClusterSet) + builder.append(cluster(), _cluster); - void ParsedResource::unsetCluster() { - _isClusterSet = false; - } + if (_isDbSet) + builder.append(db(), _db); - bool ParsedResource::isClusterSet() const { - return _isClusterSet; - } + if (_isCollectionSet) + builder.append(collection(), _collection); - bool ParsedResource::getCluster() const { - dassert(_isClusterSet); - return _cluster; - } + return builder.obj(); +} - void ParsedResource::setDb(StringData db) { - _db = db.toString(); - _isDbSet = true; - } +bool ParsedResource::parseBSON(const BSONObj& source, string* errMsg) { + clear(); - void ParsedResource::unsetDb() { - _isDbSet = false; - } + std::string dummy; + if (!errMsg) + errMsg = &dummy; - bool ParsedResource::isDbSet() const { - return _isDbSet; - } + FieldParser::FieldState fieldState; + fieldState = FieldParser::extract(source, anyResource, &_anyResource, errMsg); + if (fieldState == FieldParser::FIELD_INVALID) + return false; + _isAnyResourceSet = fieldState == FieldParser::FIELD_SET; - const std::string& ParsedResource::getDb() const { - dassert(_isDbSet); - return _db; - } + fieldState = FieldParser::extract(source, cluster, &_cluster, errMsg); + if (fieldState == FieldParser::FIELD_INVALID) + return false; + _isClusterSet = fieldState == FieldParser::FIELD_SET; - void ParsedResource::setCollection(StringData collection) { - _collection = collection.toString(); - _isCollectionSet = true; - } + fieldState = FieldParser::extract(source, db, &_db, errMsg); + if (fieldState == FieldParser::FIELD_INVALID) + return false; + _isDbSet = fieldState == FieldParser::FIELD_SET; - void ParsedResource::unsetCollection() { - _isCollectionSet = false; - } + fieldState = FieldParser::extract(source, collection, &_collection, errMsg); + if (fieldState == FieldParser::FIELD_INVALID) + return false; + _isCollectionSet = fieldState == FieldParser::FIELD_SET; - bool ParsedResource::isCollectionSet() const { - return _isCollectionSet; - } + return true; +} + +void ParsedResource::clear() { + _anyResource = false; + _isAnyResourceSet = false; + + _cluster = false; + _isClusterSet = false; + + _db.clear(); + _isDbSet = false; - const std::string& ParsedResource::getCollection() const { - dassert(_isCollectionSet); - return _collection; - } + _collection.clear(); + _isCollectionSet = false; +} + +void ParsedResource::cloneTo(ParsedResource* other) const { + other->clear(); - const BSONField<std::vector<string> > ParsedPrivilege::actions("actions"); - const BSONField<ParsedResource> ParsedPrivilege::resource("resource"); + other->_anyResource = _anyResource; + other->_isAnyResourceSet = _isAnyResourceSet; - ParsedPrivilege::ParsedPrivilege() { - clear(); - } + other->_cluster = _cluster; + other->_isClusterSet = _isClusterSet; - ParsedPrivilege::~ParsedPrivilege() { - } + other->_db = _db; + other->_isDbSet = _isDbSet; - bool ParsedPrivilege::isValid(std::string* errMsg) const { - std::string dummy; - if (errMsg == NULL) { - errMsg = &dummy; - } + other->_collection = _collection; + other->_isCollectionSet = _isCollectionSet; +} - // All the mandatory fields must be present. - if (!_isActionsSet || !_actions.size()) { - *errMsg = stream() << "missing " << actions.name() << " field"; - return false; - } +std::string ParsedResource::toString() const { + return toBSON().toString(); +} - if (!_isResourceSet) { - *errMsg = stream() << "missing " << resource.name() << " field"; - return false; - } +void ParsedResource::setAnyResource(bool anyResource) { + _anyResource = anyResource; + _isAnyResourceSet = true; +} - return getResource().isValid(errMsg); - } +void ParsedResource::unsetAnyResource() { + _isAnyResourceSet = false; +} - BSONObj ParsedPrivilege::toBSON() const { - BSONObjBuilder builder; +bool ParsedResource::isAnyResourceSet() const { + return _isAnyResourceSet; +} - if (_isResourceSet) builder.append(resource(), _resource.toBSON()); +bool ParsedResource::getAnyResource() const { + dassert(_isAnyResourceSet); + return _anyResource; +} - if (_isActionsSet) { - BSONArrayBuilder actionsBuilder(builder.subarrayStart(actions())); - for (std::vector<string>::const_iterator it = _actions.begin(); - it != _actions.end(); - ++it) { - actionsBuilder.append(*it); - } - actionsBuilder.doneFast(); - } +void ParsedResource::setCluster(bool cluster) { + _cluster = cluster; + _isClusterSet = true; +} - return builder.obj().getOwned(); - } +void ParsedResource::unsetCluster() { + _isClusterSet = false; +} - bool ParsedPrivilege::parseBSON(const BSONObj& source, string* errMsg) { - clear(); +bool ParsedResource::isClusterSet() const { + return _isClusterSet; +} - std::string dummy; - if (!errMsg) errMsg = &dummy; +bool ParsedResource::getCluster() const { + dassert(_isClusterSet); + return _cluster; +} - FieldParser::FieldState fieldState; - fieldState = FieldParser::extract(source, actions, &_actions, errMsg); - if (fieldState == FieldParser::FIELD_INVALID) return false; - _isActionsSet = fieldState == FieldParser::FIELD_SET; +void ParsedResource::setDb(StringData db) { + _db = db.toString(); + _isDbSet = true; +} - fieldState = FieldParser::extract(source, resource, &_resource, errMsg); - if (fieldState == FieldParser::FIELD_INVALID) return false; - _isResourceSet = fieldState == FieldParser::FIELD_SET; +void ParsedResource::unsetDb() { + _isDbSet = false; +} - return true; - } +bool ParsedResource::isDbSet() const { + return _isDbSet; +} - void ParsedPrivilege::clear() { - _actions.clear(); - _isActionsSet = false; - _resource.clear(); - _isResourceSet = false; +const std::string& ParsedResource::getDb() const { + dassert(_isDbSet); + return _db; +} - } +void ParsedResource::setCollection(StringData collection) { + _collection = collection.toString(); + _isCollectionSet = true; +} - std::string ParsedPrivilege::toString() const { - return toBSON().toString(); - } +void ParsedResource::unsetCollection() { + _isCollectionSet = false; +} - void ParsedPrivilege::setActions(const std::vector<string>& actions) { - for (std::vector<string>::const_iterator it = actions.begin(); - it != actions.end(); - ++it) { - addToActions((*it)); - } - _isActionsSet = actions.size() > 0; - } +bool ParsedResource::isCollectionSet() const { + return _isCollectionSet; +} - void ParsedPrivilege::addToActions(const string& actions) { - _actions.push_back(actions); - _isActionsSet = true; - } +const std::string& ParsedResource::getCollection() const { + dassert(_isCollectionSet); + return _collection; +} - void ParsedPrivilege::unsetActions() { - _actions.clear(); - _isActionsSet = false; - } +const BSONField<std::vector<string>> ParsedPrivilege::actions("actions"); +const BSONField<ParsedResource> ParsedPrivilege::resource("resource"); - bool ParsedPrivilege::isActionsSet() const { - return _isActionsSet; - } +ParsedPrivilege::ParsedPrivilege() { + clear(); +} - size_t ParsedPrivilege::sizeActions() const { - return _actions.size(); - } +ParsedPrivilege::~ParsedPrivilege() {} - const std::vector<string>& ParsedPrivilege::getActions() const { - dassert(_isActionsSet); - return _actions; +bool ParsedPrivilege::isValid(std::string* errMsg) const { + std::string dummy; + if (errMsg == NULL) { + errMsg = &dummy; } - const string& ParsedPrivilege::getActionsAt(size_t pos) const { - dassert(_isActionsSet); - dassert(_actions.size() > pos); - return _actions.at(pos); + // All the mandatory fields must be present. + if (!_isActionsSet || !_actions.size()) { + *errMsg = stream() << "missing " << actions.name() << " field"; + return false; } - void ParsedPrivilege::setResource(const ParsedResource& resource) { - resource.cloneTo(&_resource); - _isResourceSet = true; + if (!_isResourceSet) { + *errMsg = stream() << "missing " << resource.name() << " field"; + return false; } - void ParsedPrivilege::unsetResource() { - _isResourceSet = false; - } + return getResource().isValid(errMsg); +} - bool ParsedPrivilege::isResourceSet() const { - return _isResourceSet; - } +BSONObj ParsedPrivilege::toBSON() const { + BSONObjBuilder builder; - const ParsedResource& ParsedPrivilege::getResource() const { - dassert(_isResourceSet); - return _resource; - } - - bool ParsedPrivilege::parsedPrivilegeToPrivilege(const ParsedPrivilege& parsedPrivilege, - Privilege* result, - std::string* errmsg) { - if (!parsedPrivilege.isValid(errmsg)) { - return false; - } + if (_isResourceSet) + builder.append(resource(), _resource.toBSON()); - // Build actions - ActionSet actions; - const vector<std::string>& parsedActions = parsedPrivilege.getActions(); - Status status = ActionSet::parseActionSetFromStringVector(parsedActions, &actions); - if (!status.isOK()) { - *errmsg = status.reason(); - return false; + if (_isActionsSet) { + BSONArrayBuilder actionsBuilder(builder.subarrayStart(actions())); + for (std::vector<string>::const_iterator it = _actions.begin(); it != _actions.end(); + ++it) { + actionsBuilder.append(*it); } - - // Build resource - ResourcePattern resource; - const ParsedResource& parsedResource = parsedPrivilege.getResource(); - if (parsedResource.isAnyResourceSet() && parsedResource.getAnyResource()) { - resource = ResourcePattern::forAnyResource(); - } else if (parsedResource.isClusterSet() && parsedResource.getCluster()) { - resource = ResourcePattern::forClusterResource(); + actionsBuilder.doneFast(); + } + + return builder.obj().getOwned(); +} + +bool ParsedPrivilege::parseBSON(const BSONObj& source, string* errMsg) { + clear(); + + std::string dummy; + if (!errMsg) + errMsg = &dummy; + + FieldParser::FieldState fieldState; + fieldState = FieldParser::extract(source, actions, &_actions, errMsg); + if (fieldState == FieldParser::FIELD_INVALID) + return false; + _isActionsSet = fieldState == FieldParser::FIELD_SET; + + fieldState = FieldParser::extract(source, resource, &_resource, errMsg); + if (fieldState == FieldParser::FIELD_INVALID) + return false; + _isResourceSet = fieldState == FieldParser::FIELD_SET; + + return true; +} + +void ParsedPrivilege::clear() { + _actions.clear(); + _isActionsSet = false; + _resource.clear(); + _isResourceSet = false; +} + +std::string ParsedPrivilege::toString() const { + return toBSON().toString(); +} + +void ParsedPrivilege::setActions(const std::vector<string>& actions) { + for (std::vector<string>::const_iterator it = actions.begin(); it != actions.end(); ++it) { + addToActions((*it)); + } + _isActionsSet = actions.size() > 0; +} + +void ParsedPrivilege::addToActions(const string& actions) { + _actions.push_back(actions); + _isActionsSet = true; +} + +void ParsedPrivilege::unsetActions() { + _actions.clear(); + _isActionsSet = false; +} + +bool ParsedPrivilege::isActionsSet() const { + return _isActionsSet; +} + +size_t ParsedPrivilege::sizeActions() const { + return _actions.size(); +} + +const std::vector<string>& ParsedPrivilege::getActions() const { + dassert(_isActionsSet); + return _actions; +} + +const string& ParsedPrivilege::getActionsAt(size_t pos) const { + dassert(_isActionsSet); + dassert(_actions.size() > pos); + return _actions.at(pos); +} + +void ParsedPrivilege::setResource(const ParsedResource& resource) { + resource.cloneTo(&_resource); + _isResourceSet = true; +} + +void ParsedPrivilege::unsetResource() { + _isResourceSet = false; +} + +bool ParsedPrivilege::isResourceSet() const { + return _isResourceSet; +} + +const ParsedResource& ParsedPrivilege::getResource() const { + dassert(_isResourceSet); + return _resource; +} + +bool ParsedPrivilege::parsedPrivilegeToPrivilege(const ParsedPrivilege& parsedPrivilege, + Privilege* result, + std::string* errmsg) { + if (!parsedPrivilege.isValid(errmsg)) { + return false; + } + + // Build actions + ActionSet actions; + const vector<std::string>& parsedActions = parsedPrivilege.getActions(); + Status status = ActionSet::parseActionSetFromStringVector(parsedActions, &actions); + if (!status.isOK()) { + *errmsg = status.reason(); + return false; + } + + // Build resource + ResourcePattern resource; + const ParsedResource& parsedResource = parsedPrivilege.getResource(); + if (parsedResource.isAnyResourceSet() && parsedResource.getAnyResource()) { + resource = ResourcePattern::forAnyResource(); + } else if (parsedResource.isClusterSet() && parsedResource.getCluster()) { + resource = ResourcePattern::forClusterResource(); + } else { + if (parsedResource.isDbSet() && !parsedResource.getDb().empty()) { + if (parsedResource.isCollectionSet() && !parsedResource.getCollection().empty()) { + resource = ResourcePattern::forExactNamespace( + NamespaceString(parsedResource.getDb(), parsedResource.getCollection())); + } else { + resource = ResourcePattern::forDatabaseName(parsedResource.getDb()); + } } else { - if (parsedResource.isDbSet() && !parsedResource.getDb().empty()) { - if (parsedResource.isCollectionSet() && !parsedResource.getCollection().empty()) { - resource = ResourcePattern::forExactNamespace( - NamespaceString(parsedResource.getDb(), - parsedResource.getCollection())); - } else { - resource = ResourcePattern::forDatabaseName(parsedResource.getDb()); - } + if (parsedResource.isCollectionSet() && !parsedResource.getCollection().empty()) { + resource = ResourcePattern::forCollectionName(parsedResource.getCollection()); } else { - if (parsedResource.isCollectionSet() && !parsedResource.getCollection().empty()) { - resource = ResourcePattern::forCollectionName(parsedResource.getCollection()); - } else { - resource = ResourcePattern::forAnyNormalResource(); - } + resource = ResourcePattern::forAnyNormalResource(); } } - - *result = Privilege(resource, actions); - return true; } - bool ParsedPrivilege::privilegeToParsedPrivilege(const Privilege& privilege, - ParsedPrivilege* result, - std::string* errmsg) { - ParsedResource parsedResource; - if (privilege.getResourcePattern().isExactNamespacePattern()) { - parsedResource.setDb(privilege.getResourcePattern().databaseToMatch()); - parsedResource.setCollection(privilege.getResourcePattern().collectionToMatch()); - } else if (privilege.getResourcePattern().isDatabasePattern()) { - parsedResource.setDb(privilege.getResourcePattern().databaseToMatch()); - parsedResource.setCollection(""); - } else if (privilege.getResourcePattern().isCollectionPattern()) { - parsedResource.setDb(""); - parsedResource.setCollection(privilege.getResourcePattern().collectionToMatch()); - } else if (privilege.getResourcePattern().isAnyNormalResourcePattern()) { - parsedResource.setDb(""); - parsedResource.setCollection(""); - } else if (privilege.getResourcePattern().isClusterResourcePattern()) { - parsedResource.setCluster(true); - } else if (privilege.getResourcePattern().isAnyResourcePattern()) { - parsedResource.setAnyResource(true); - } else { - *errmsg = stream() << privilege.getResourcePattern().toString() << - " is not a valid user-grantable resource pattern"; - return false; - } - - result->clear(); - result->setResource(parsedResource); - result->setActions(privilege.getActions().getActionsAsStrings()); - return result->isValid(errmsg); - } -} // namespace mongo + *result = Privilege(resource, actions); + return true; +} + +bool ParsedPrivilege::privilegeToParsedPrivilege(const Privilege& privilege, + ParsedPrivilege* result, + std::string* errmsg) { + ParsedResource parsedResource; + if (privilege.getResourcePattern().isExactNamespacePattern()) { + parsedResource.setDb(privilege.getResourcePattern().databaseToMatch()); + parsedResource.setCollection(privilege.getResourcePattern().collectionToMatch()); + } else if (privilege.getResourcePattern().isDatabasePattern()) { + parsedResource.setDb(privilege.getResourcePattern().databaseToMatch()); + parsedResource.setCollection(""); + } else if (privilege.getResourcePattern().isCollectionPattern()) { + parsedResource.setDb(""); + parsedResource.setCollection(privilege.getResourcePattern().collectionToMatch()); + } else if (privilege.getResourcePattern().isAnyNormalResourcePattern()) { + parsedResource.setDb(""); + parsedResource.setCollection(""); + } else if (privilege.getResourcePattern().isClusterResourcePattern()) { + parsedResource.setCluster(true); + } else if (privilege.getResourcePattern().isAnyResourcePattern()) { + parsedResource.setAnyResource(true); + } else { + *errmsg = stream() << privilege.getResourcePattern().toString() + << " is not a valid user-grantable resource pattern"; + return false; + } + + result->clear(); + result->setResource(parsedResource); + result->setActions(privilege.getActions().getActionsAsStrings()); + return result->isValid(errmsg); +} +} // namespace mongo diff --git a/src/mongo/db/auth/privilege_parser.h b/src/mongo/db/auth/privilege_parser.h index aec390e3973..a48297c862f 100644 --- a/src/mongo/db/auth/privilege_parser.h +++ b/src/mongo/db/auth/privilege_parser.h @@ -37,160 +37,160 @@ namespace mongo { - class Privilege; +class Privilege; + +/** + * This class is used to parse documents describing resources as they are represented as part + * of privileges granted to roles in the role management commands. + */ +class ParsedResource : BSONSerializable { + MONGO_DISALLOW_COPYING(ParsedResource); + +public: + // + // schema declarations + // + + static const BSONField<bool> anyResource; + static const BSONField<bool> cluster; + static const BSONField<std::string> db; + static const BSONField<std::string> collection; + + // + // construction / destruction + // + + ParsedResource(); + ~ParsedResource(); + + /** Copies all the fields present in 'this' to 'other'. */ + void cloneTo(ParsedResource* other) const; + + // + // bson serializable interface implementation + // + + bool isValid(std::string* errMsg) const; + BSONObj toBSON() const; + bool parseBSON(const BSONObj& source, std::string* errMsg); + void clear(); + virtual std::string toString() const; + + // + // individual field accessors + // + + void setAnyResource(bool anyResource); + void unsetAnyResource(); + bool isAnyResourceSet() const; + bool getAnyResource() const; + + void setCluster(bool cluster); + void unsetCluster(); + bool isClusterSet() const; + bool getCluster() const; + + void setDb(StringData db); + void unsetDb(); + bool isDbSet() const; + const std::string& getDb() const; + + void setCollection(StringData collection); + void unsetCollection(); + bool isCollectionSet() const; + const std::string& getCollection() const; + +private: + // Convention: (M)andatory, (O)ptional + + // (O) Only present if the resource matches anything. + bool _anyResource; + bool _isAnyResourceSet; + + // (O) Only present if the resource is the cluster + bool _cluster; + bool _isClusterSet; + + // (O) database portion of the resource + std::string _db; + bool _isDbSet; + + // (O) collection portion of the resource + std::string _collection; + bool _isCollectionSet; +}; + +/** + * This class is used to parse documents describing privileges in the role managment commands. + */ +class ParsedPrivilege : BSONSerializable { + MONGO_DISALLOW_COPYING(ParsedPrivilege); + +public: + // + // schema declarations + // + + static const BSONField<std::vector<std::string>> actions; + static const BSONField<ParsedResource> resource; + + // + // construction / destruction + // + + ParsedPrivilege(); + ~ParsedPrivilege(); /** - * This class is used to parse documents describing resources as they are represented as part - * of privileges granted to roles in the role management commands. + * Takes a parsedPrivilege and turns it into a true Privilege object. */ - class ParsedResource : BSONSerializable { - MONGO_DISALLOW_COPYING(ParsedResource); - public: - - // - // schema declarations - // - - static const BSONField<bool> anyResource; - static const BSONField<bool> cluster; - static const BSONField<std::string> db; - static const BSONField<std::string> collection; - - // - // construction / destruction - // - - ParsedResource(); - ~ParsedResource(); - - /** Copies all the fields present in 'this' to 'other'. */ - void cloneTo(ParsedResource* other) const; - - // - // bson serializable interface implementation - // - - bool isValid(std::string* errMsg) const; - BSONObj toBSON() const; - bool parseBSON(const BSONObj& source, std::string* errMsg); - void clear(); - virtual std::string toString() const; - - // - // individual field accessors - // - - void setAnyResource(bool anyResource); - void unsetAnyResource(); - bool isAnyResourceSet() const; - bool getAnyResource() const; - - void setCluster(bool cluster); - void unsetCluster(); - bool isClusterSet() const; - bool getCluster() const; - - void setDb(StringData db); - void unsetDb(); - bool isDbSet() const; - const std::string& getDb() const; - - void setCollection(StringData collection); - void unsetCollection(); - bool isCollectionSet() const; - const std::string& getCollection() const; - - private: - // Convention: (M)andatory, (O)ptional - - // (O) Only present if the resource matches anything. - bool _anyResource; - bool _isAnyResourceSet; - - // (O) Only present if the resource is the cluster - bool _cluster; - bool _isClusterSet; - - // (O) database portion of the resource - std::string _db; - bool _isDbSet; - - // (O) collection portion of the resource - std::string _collection; - bool _isCollectionSet; - }; - + static bool parsedPrivilegeToPrivilege(const ParsedPrivilege& parsedPrivilege, + Privilege* result, + std::string* errmsg); /** - * This class is used to parse documents describing privileges in the role managment commands. + * Takes a Privilege object and turns it into a ParsedPrivilege. */ - class ParsedPrivilege : BSONSerializable { - MONGO_DISALLOW_COPYING(ParsedPrivilege); - public: - - // - // schema declarations - // - - static const BSONField<std::vector<std::string> > actions; - static const BSONField<ParsedResource> resource; - - // - // construction / destruction - // - - ParsedPrivilege(); - ~ParsedPrivilege(); - - /** - * Takes a parsedPrivilege and turns it into a true Privilege object. - */ - static bool parsedPrivilegeToPrivilege(const ParsedPrivilege& parsedPrivilege, - Privilege* result, - std::string* errmsg); - /** - * Takes a Privilege object and turns it into a ParsedPrivilege. - */ - static bool privilegeToParsedPrivilege(const Privilege& privilege, - ParsedPrivilege* result, - std::string* errmsg); - - // - // bson serializable interface implementation - // - - bool isValid(std::string* errMsg) const; - BSONObj toBSON() const; - bool parseBSON(const BSONObj& source, std::string* errMsg); - void clear(); - std::string toString() const; - - // - // individual field accessors - // - - void setActions(const std::vector<std::string>& actions); - void addToActions(const std::string& actions); - void unsetActions(); - bool isActionsSet() const; - size_t sizeActions() const; - const std::vector<std::string>& getActions() const; - const std::string& getActionsAt(size_t pos) const; - - void setResource(const ParsedResource& resource); - void unsetResource(); - bool isResourceSet() const; - const ParsedResource& getResource() const; - - private: - // Convention: (M)andatory, (O)ptional - - // (M) Array of action types - std::vector<std::string> _actions; - bool _isActionsSet; - - // (M) Object describing the resource pattern of this privilege - ParsedResource _resource; - bool _isResourceSet; - }; - -} // namespace mongo + static bool privilegeToParsedPrivilege(const Privilege& privilege, + ParsedPrivilege* result, + std::string* errmsg); + + // + // bson serializable interface implementation + // + + bool isValid(std::string* errMsg) const; + BSONObj toBSON() const; + bool parseBSON(const BSONObj& source, std::string* errMsg); + void clear(); + std::string toString() const; + + // + // individual field accessors + // + + void setActions(const std::vector<std::string>& actions); + void addToActions(const std::string& actions); + void unsetActions(); + bool isActionsSet() const; + size_t sizeActions() const; + const std::vector<std::string>& getActions() const; + const std::string& getActionsAt(size_t pos) const; + + void setResource(const ParsedResource& resource); + void unsetResource(); + bool isResourceSet() const; + const ParsedResource& getResource() const; + +private: + // Convention: (M)andatory, (O)ptional + + // (M) Array of action types + std::vector<std::string> _actions; + bool _isActionsSet; + + // (M) Object describing the resource pattern of this privilege + ParsedResource _resource; + bool _isResourceSet; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/privilege_parser_test.cpp b/src/mongo/db/auth/privilege_parser_test.cpp index 798a197ae91..8885922b1dd 100644 --- a/src/mongo/db/auth/privilege_parser_test.cpp +++ b/src/mongo/db/auth/privilege_parser_test.cpp @@ -38,173 +38,180 @@ namespace mongo { namespace { - TEST(PrivilegeParserTest, IsValidTest) { - ParsedPrivilege parsedPrivilege; - std::string errmsg; - - // must have resource - parsedPrivilege.parseBSON(BSON("actions" << BSON_ARRAY("find")), &errmsg); - ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); - - // must have actions - parsedPrivilege.parseBSON(BSON("resource" << BSON("cluster" << true)), &errmsg); - ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); - - // resource can't have cluster as well as db or collection - parsedPrivilege.parseBSON(BSON("resource" << BSON("cluster" << true << - "db" << "" << - "collection" << "") << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); - - // resource can't have db without collection - parsedPrivilege.parseBSON(BSON("resource" << BSON("db" << "") << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); - - // resource can't have collection without db - parsedPrivilege.parseBSON(BSON("resource" << BSON("collection" << "") << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); - - // Works with wildcard db and resource - parsedPrivilege.parseBSON(BSON("resource" << BSON("db" << "" << "collection" << "") << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT(parsedPrivilege.isValid(&errmsg)); - - // Works with real db and collection - parsedPrivilege.parseBSON(BSON("resource" << BSON("db" << "test" << - "collection" << "foo") << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT(parsedPrivilege.isValid(&errmsg)); - - // Works with cluster resource - parsedPrivilege.parseBSON(BSON("resource" << BSON("cluster" << true) << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT(parsedPrivilege.isValid(&errmsg)); - } - - TEST(PrivilegeParserTest, ConvertBetweenPrivilegeTest) { - ParsedPrivilege parsedPrivilege; - Privilege privilege; - std::string errmsg; - std::vector<std::string> actionsVector; - actionsVector.push_back("find"); - - // Works with wildcard db and resource - parsedPrivilege.parseBSON(BSON("resource" << BSON("db" << "" << "collection" << "") << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); - ASSERT(privilege.getActions().contains(ActionType::find)); - ASSERT(!privilege.getActions().contains(ActionType::insert)); - ASSERT_EQUALS(privilege.getResourcePattern(), ResourcePattern::forAnyNormalResource()); - - ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(parsedPrivilege.isResourceSet()); - ASSERT_FALSE(parsedPrivilege.getResource().isClusterSet()); - ASSERT(parsedPrivilege.getResource().isDbSet()); - ASSERT(parsedPrivilege.getResource().isCollectionSet()); - ASSERT_EQUALS("", parsedPrivilege.getResource().getDb()); - ASSERT_EQUALS("", parsedPrivilege.getResource().getCollection()); - ASSERT(parsedPrivilege.isActionsSet()); - ASSERT(actionsVector == parsedPrivilege.getActions()); - - // Works with exact namespaces - parsedPrivilege.parseBSON(BSON("resource" << BSON("db" << "test" << - "collection" << "foo") << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); - ASSERT(privilege.getActions().contains(ActionType::find)); - ASSERT(!privilege.getActions().contains(ActionType::insert)); - ASSERT_EQUALS(privilege.getResourcePattern(), - ResourcePattern::forExactNamespace(NamespaceString("test.foo"))); - - ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(parsedPrivilege.isResourceSet()); - ASSERT_FALSE(parsedPrivilege.getResource().isClusterSet()); - ASSERT(parsedPrivilege.getResource().isDbSet()); - ASSERT(parsedPrivilege.getResource().isCollectionSet()); - ASSERT_EQUALS("test", parsedPrivilege.getResource().getDb()); - ASSERT_EQUALS("foo", parsedPrivilege.getResource().getCollection()); - ASSERT(parsedPrivilege.isActionsSet()); - ASSERT(actionsVector == parsedPrivilege.getActions()); - - // Works with database resource - parsedPrivilege.parseBSON(BSON("resource" << BSON("db" << "test" << - "collection" << "") << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); - ASSERT(privilege.getActions().contains(ActionType::find)); - ASSERT(!privilege.getActions().contains(ActionType::insert)); - ASSERT_EQUALS(privilege.getResourcePattern(), ResourcePattern::forDatabaseName("test")); - - ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(parsedPrivilege.isResourceSet()); - ASSERT_FALSE(parsedPrivilege.getResource().isClusterSet()); - ASSERT(parsedPrivilege.getResource().isDbSet()); - ASSERT(parsedPrivilege.getResource().isCollectionSet()); - ASSERT_EQUALS("test", parsedPrivilege.getResource().getDb()); - ASSERT_EQUALS("", parsedPrivilege.getResource().getCollection()); - ASSERT(parsedPrivilege.isActionsSet()); - ASSERT(actionsVector == parsedPrivilege.getActions()); - - // Works with collection resource - parsedPrivilege.parseBSON(BSON("resource" << BSON("db" << "" << - "collection" << "foo") << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); - ASSERT(privilege.getActions().contains(ActionType::find)); - ASSERT(!privilege.getActions().contains(ActionType::insert)); - ASSERT_EQUALS(privilege.getResourcePattern(), ResourcePattern::forCollectionName("foo")); - - ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(parsedPrivilege.isResourceSet()); - ASSERT_FALSE(parsedPrivilege.getResource().isClusterSet()); - ASSERT(parsedPrivilege.getResource().isDbSet()); - ASSERT(parsedPrivilege.getResource().isCollectionSet()); - ASSERT_EQUALS("", parsedPrivilege.getResource().getDb()); - ASSERT_EQUALS("foo", parsedPrivilege.getResource().getCollection()); - ASSERT(parsedPrivilege.isActionsSet()); - ASSERT(actionsVector == parsedPrivilege.getActions()); - - // Works with cluster resource - parsedPrivilege.parseBSON(BSON("resource" << BSON("cluster" << true) << - "actions" << BSON_ARRAY("find")), - &errmsg); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); - ASSERT(privilege.getActions().contains(ActionType::find)); - ASSERT(!privilege.getActions().contains(ActionType::insert)); - ASSERT_EQUALS(privilege.getResourcePattern(), ResourcePattern::forClusterResource()); - - ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); - ASSERT(parsedPrivilege.isValid(&errmsg)); - ASSERT(parsedPrivilege.isResourceSet()); - ASSERT(parsedPrivilege.getResource().isClusterSet()); - ASSERT(parsedPrivilege.getResource().getCluster()); - ASSERT_FALSE(parsedPrivilege.getResource().isDbSet()); - ASSERT_FALSE(parsedPrivilege.getResource().isCollectionSet()); - ASSERT(parsedPrivilege.isActionsSet()); - ASSERT(actionsVector == parsedPrivilege.getActions()); - } +TEST(PrivilegeParserTest, IsValidTest) { + ParsedPrivilege parsedPrivilege; + std::string errmsg; + + // must have resource + parsedPrivilege.parseBSON(BSON("actions" << BSON_ARRAY("find")), &errmsg); + ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); + + // must have actions + parsedPrivilege.parseBSON(BSON("resource" << BSON("cluster" << true)), &errmsg); + ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); + + // resource can't have cluster as well as db or collection + parsedPrivilege.parseBSON( + BSON("resource" << BSON("cluster" << true << "db" + << "" + << "collection" + << "") << "actions" << BSON_ARRAY("find")), + &errmsg); + ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); + + // resource can't have db without collection + parsedPrivilege.parseBSON(BSON("resource" << BSON("db" + << "") << "actions" << BSON_ARRAY("find")), + &errmsg); + ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); + + // resource can't have collection without db + parsedPrivilege.parseBSON(BSON("resource" << BSON("collection" + << "") << "actions" << BSON_ARRAY("find")), + &errmsg); + ASSERT_FALSE(parsedPrivilege.isValid(&errmsg)); + + // Works with wildcard db and resource + parsedPrivilege.parseBSON(BSON("resource" << BSON("db" + << "" + << "collection" + << "") << "actions" << BSON_ARRAY("find")), + &errmsg); + ASSERT(parsedPrivilege.isValid(&errmsg)); + + // Works with real db and collection + parsedPrivilege.parseBSON(BSON("resource" << BSON("db" + << "test" + << "collection" + << "foo") << "actions" << BSON_ARRAY("find")), + &errmsg); + ASSERT(parsedPrivilege.isValid(&errmsg)); + + // Works with cluster resource + parsedPrivilege.parseBSON( + BSON("resource" << BSON("cluster" << true) << "actions" << BSON_ARRAY("find")), &errmsg); + ASSERT(parsedPrivilege.isValid(&errmsg)); +} + +TEST(PrivilegeParserTest, ConvertBetweenPrivilegeTest) { + ParsedPrivilege parsedPrivilege; + Privilege privilege; + std::string errmsg; + std::vector<std::string> actionsVector; + actionsVector.push_back("find"); + + // Works with wildcard db and resource + parsedPrivilege.parseBSON(BSON("resource" << BSON("db" + << "" + << "collection" + << "") << "actions" << BSON_ARRAY("find")), + &errmsg); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); + ASSERT(privilege.getActions().contains(ActionType::find)); + ASSERT(!privilege.getActions().contains(ActionType::insert)); + ASSERT_EQUALS(privilege.getResourcePattern(), ResourcePattern::forAnyNormalResource()); + + ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(parsedPrivilege.isResourceSet()); + ASSERT_FALSE(parsedPrivilege.getResource().isClusterSet()); + ASSERT(parsedPrivilege.getResource().isDbSet()); + ASSERT(parsedPrivilege.getResource().isCollectionSet()); + ASSERT_EQUALS("", parsedPrivilege.getResource().getDb()); + ASSERT_EQUALS("", parsedPrivilege.getResource().getCollection()); + ASSERT(parsedPrivilege.isActionsSet()); + ASSERT(actionsVector == parsedPrivilege.getActions()); + + // Works with exact namespaces + parsedPrivilege.parseBSON(BSON("resource" << BSON("db" + << "test" + << "collection" + << "foo") << "actions" << BSON_ARRAY("find")), + &errmsg); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); + ASSERT(privilege.getActions().contains(ActionType::find)); + ASSERT(!privilege.getActions().contains(ActionType::insert)); + ASSERT_EQUALS(privilege.getResourcePattern(), + ResourcePattern::forExactNamespace(NamespaceString("test.foo"))); + + ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(parsedPrivilege.isResourceSet()); + ASSERT_FALSE(parsedPrivilege.getResource().isClusterSet()); + ASSERT(parsedPrivilege.getResource().isDbSet()); + ASSERT(parsedPrivilege.getResource().isCollectionSet()); + ASSERT_EQUALS("test", parsedPrivilege.getResource().getDb()); + ASSERT_EQUALS("foo", parsedPrivilege.getResource().getCollection()); + ASSERT(parsedPrivilege.isActionsSet()); + ASSERT(actionsVector == parsedPrivilege.getActions()); + + // Works with database resource + parsedPrivilege.parseBSON(BSON("resource" << BSON("db" + << "test" + << "collection" + << "") << "actions" << BSON_ARRAY("find")), + &errmsg); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); + ASSERT(privilege.getActions().contains(ActionType::find)); + ASSERT(!privilege.getActions().contains(ActionType::insert)); + ASSERT_EQUALS(privilege.getResourcePattern(), ResourcePattern::forDatabaseName("test")); + + ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(parsedPrivilege.isResourceSet()); + ASSERT_FALSE(parsedPrivilege.getResource().isClusterSet()); + ASSERT(parsedPrivilege.getResource().isDbSet()); + ASSERT(parsedPrivilege.getResource().isCollectionSet()); + ASSERT_EQUALS("test", parsedPrivilege.getResource().getDb()); + ASSERT_EQUALS("", parsedPrivilege.getResource().getCollection()); + ASSERT(parsedPrivilege.isActionsSet()); + ASSERT(actionsVector == parsedPrivilege.getActions()); + + // Works with collection resource + parsedPrivilege.parseBSON(BSON("resource" << BSON("db" + << "" + << "collection" + << "foo") << "actions" << BSON_ARRAY("find")), + &errmsg); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); + ASSERT(privilege.getActions().contains(ActionType::find)); + ASSERT(!privilege.getActions().contains(ActionType::insert)); + ASSERT_EQUALS(privilege.getResourcePattern(), ResourcePattern::forCollectionName("foo")); + + ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(parsedPrivilege.isResourceSet()); + ASSERT_FALSE(parsedPrivilege.getResource().isClusterSet()); + ASSERT(parsedPrivilege.getResource().isDbSet()); + ASSERT(parsedPrivilege.getResource().isCollectionSet()); + ASSERT_EQUALS("", parsedPrivilege.getResource().getDb()); + ASSERT_EQUALS("foo", parsedPrivilege.getResource().getCollection()); + ASSERT(parsedPrivilege.isActionsSet()); + ASSERT(actionsVector == parsedPrivilege.getActions()); + + // Works with cluster resource + parsedPrivilege.parseBSON( + BSON("resource" << BSON("cluster" << true) << "actions" << BSON_ARRAY("find")), &errmsg); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)); + ASSERT(privilege.getActions().contains(ActionType::find)); + ASSERT(!privilege.getActions().contains(ActionType::insert)); + ASSERT_EQUALS(privilege.getResourcePattern(), ResourcePattern::forClusterResource()); + + ASSERT(ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)); + ASSERT(parsedPrivilege.isValid(&errmsg)); + ASSERT(parsedPrivilege.isResourceSet()); + ASSERT(parsedPrivilege.getResource().isClusterSet()); + ASSERT(parsedPrivilege.getResource().getCluster()); + ASSERT_FALSE(parsedPrivilege.getResource().isDbSet()); + ASSERT_FALSE(parsedPrivilege.getResource().isCollectionSet()); + ASSERT(parsedPrivilege.isActionsSet()); + ASSERT(actionsVector == parsedPrivilege.getActions()); +} } // namespace } // namespace mongo diff --git a/src/mongo/db/auth/resource_pattern.cpp b/src/mongo/db/auth/resource_pattern.cpp index 6bf23368327..fe62ced9ca9 100644 --- a/src/mongo/db/auth/resource_pattern.cpp +++ b/src/mongo/db/auth/resource_pattern.cpp @@ -38,8 +38,8 @@ namespace mongo { - std::string ResourcePattern::toString() const { - switch (_matchType) { +std::string ResourcePattern::toString() const { + switch (_matchType) { case matchNever: return "<no resources>"; case matchClusterResource: @@ -56,11 +56,11 @@ namespace mongo { return "<all resources>"; default: return "<unknown resource pattern type>"; - } } +} - std::ostream& operator<<(std::ostream& os, const ResourcePattern& pattern) { - return os << pattern.toString(); - } +std::ostream& operator<<(std::ostream& os, const ResourcePattern& pattern) { + return os << pattern.toString(); +} } // namespace mongo diff --git a/src/mongo/db/auth/resource_pattern.h b/src/mongo/db/auth/resource_pattern.h index c50876b74c5..2896c0aeb24 100644 --- a/src/mongo/db/auth/resource_pattern.h +++ b/src/mongo/db/auth/resource_pattern.h @@ -37,174 +37,181 @@ namespace mongo { +/** + * Representation of names of various kinds of resources targetable by the access control + * system. + * + * Three of the types of name, "forDatabaseName", "forExactNamespace" and "forClusterResource", + * can represent concrete resources targeted for manipulation by database operations. All of + * the types also act as patterns, useful for matching against groups of concrete resources as + * part of the access control system. See buildResourceSearchList() in + * authorization_session.cpp for details. + */ +class ResourcePattern { +public: + /** + * Returns a pattern that matches absolutely any resource. + */ + static ResourcePattern forAnyResource() { + return ResourcePattern(matchAnyResource); + } + + /** + * Returns a pattern that matches any database or collection resource except collections for + * which ns.isSystem(). + */ + static ResourcePattern forAnyNormalResource() { + return ResourcePattern(matchAnyNormalResource); + } + + /** + * Returns a pattern that matches the "cluster" resource. + */ + static ResourcePattern forClusterResource() { + return ResourcePattern(matchClusterResource); + } + + /** + * Returns a pattern that matches the named database, and NamespaceStrings + * "ns" for which ns.isSystem() is false and ns.db() == dbname. + */ + static ResourcePattern forDatabaseName(StringData dbName) { + return ResourcePattern(matchDatabaseName, NamespaceString(dbName, "")); + } + + /** + * Returns a pattern that matches NamespaceStrings "ns" for which ns.coll() == + * collectionName. + */ + static ResourcePattern forCollectionName(StringData collectionName) { + return ResourcePattern(matchCollectionName, NamespaceString("", collectionName)); + } + + /** + * Returns a pattern that matches the given exact namespace string. + */ + static ResourcePattern forExactNamespace(const NamespaceString& ns) { + return ResourcePattern(matchExactNamespace, ns); + } + + /** + * Constructs a pattern that never matches. + */ + ResourcePattern() : _matchType(matchNever) {} + + /** + * Returns true if this pattern matches only exact namespaces. + */ + bool isExactNamespacePattern() const { + return _matchType == matchExactNamespace; + } + /** - * Representation of names of various kinds of resources targetable by the access control - * system. + * Returns true if this pattern matches on the database name only. + */ + bool isDatabasePattern() const { + return _matchType == matchDatabaseName; + } + + /** + * Returns true if this pattern matches on the collection name only. + */ + bool isCollectionPattern() const { + return _matchType == matchCollectionName; + } + + /** + * Returns true if this pattern matches the cluster resource only. + */ + bool isClusterResourcePattern() const { + return _matchType == matchClusterResource; + } + + /** + * Returns true if this pattern matches only any normal resource. + */ + bool isAnyNormalResourcePattern() const { + return _matchType == matchAnyNormalResource; + } + + /** + * Returns true if this pattern matches any resource. + */ + bool isAnyResourcePattern() const { + return _matchType == matchAnyResource; + } + + /** + * Returns the namespace that this pattern matches. * - * Three of the types of name, "forDatabaseName", "forExactNamespace" and "forClusterResource", - * can represent concrete resources targeted for manipulation by database operations. All of - * the types also act as patterns, useful for matching against groups of concrete resources as - * part of the access control system. See buildResourceSearchList() in - * authorization_session.cpp for details. - */ - class ResourcePattern { - public: - /** - * Returns a pattern that matches absolutely any resource. - */ - static ResourcePattern forAnyResource() { - return ResourcePattern(matchAnyResource); - } - - /** - * Returns a pattern that matches any database or collection resource except collections for - * which ns.isSystem(). - */ - static ResourcePattern forAnyNormalResource() { - return ResourcePattern(matchAnyNormalResource); - } - - /** - * Returns a pattern that matches the "cluster" resource. - */ - static ResourcePattern forClusterResource() { - return ResourcePattern(matchClusterResource); - } - - /** - * Returns a pattern that matches the named database, and NamespaceStrings - * "ns" for which ns.isSystem() is false and ns.db() == dbname. - */ - static ResourcePattern forDatabaseName(StringData dbName) { - return ResourcePattern(matchDatabaseName, NamespaceString(dbName, "")); - } - - /** - * Returns a pattern that matches NamespaceStrings "ns" for which ns.coll() == - * collectionName. - */ - static ResourcePattern forCollectionName(StringData collectionName) { - return ResourcePattern(matchCollectionName, NamespaceString("", collectionName)); - } - - /** - * Returns a pattern that matches the given exact namespace string. - */ - static ResourcePattern forExactNamespace(const NamespaceString& ns) { - return ResourcePattern(matchExactNamespace, ns); - } - - /** - * Constructs a pattern that never matches. - */ - ResourcePattern() : _matchType(matchNever) {} - - /** - * Returns true if this pattern matches only exact namespaces. - */ - bool isExactNamespacePattern() const { - return _matchType == matchExactNamespace; - } - - /** - * Returns true if this pattern matches on the database name only. - */ - bool isDatabasePattern() const { - return _matchType == matchDatabaseName; - } - - /** - * Returns true if this pattern matches on the collection name only. - */ - bool isCollectionPattern() const { - return _matchType == matchCollectionName; - } - - /** - * Returns true if this pattern matches the cluster resource only. - */ - bool isClusterResourcePattern() const { - return _matchType == matchClusterResource; - } - - /** - * Returns true if this pattern matches only any normal resource. - */ - bool isAnyNormalResourcePattern() const { - return _matchType == matchAnyNormalResource; - } - - /** - * Returns true if this pattern matches any resource. - */ - bool isAnyResourcePattern() const { - return _matchType == matchAnyResource; - } - - /** - * Returns the namespace that this pattern matches. - * - * Behavior is undefined unless isExactNamespacePattern() is true. - */ - const NamespaceString& ns() const { return _ns; } - - /** - * Returns the database that this pattern matches. - * - * Behavior is undefined unless the pattern is of type matchDatabaseName or - * matchExactNamespace - */ - StringData databaseToMatch() const { return _ns.db(); } - - /** - * Returns the collection that this pattern matches. - * - * Behavior is undefined unless the pattern is of type matchCollectionName or - * matchExactNamespace - */ - StringData collectionToMatch() const { return _ns.coll(); } - - std::string toString() const; - - inline size_t hash() const { - // TODO: Choose a better hash function. - return MONGO_HASH_NAMESPACE::hash<std::string>()(_ns.ns()) ^ _matchType; - } - - bool operator==(const ResourcePattern& other) const { - if (_matchType != other._matchType) - return false; - if (_ns != other._ns) - return false; - return true; - } - - private: - enum MatchType { - matchNever = 0, /// Matches no resource. - matchClusterResource = 1, /// Matches if the resource is the cluster resource. - matchDatabaseName = 2, /// Matches if the resource's database name is _ns.db(). - matchCollectionName = 3, /// Matches if the resource's collection name is _ns.coll(). - matchExactNamespace = 4, /// Matches if the resource's namespace name is _ns. - matchAnyNormalResource = 5, /// Matches all databases and non-system collections. - matchAnyResource = 6 /// Matches absolutely anything. - }; - - explicit ResourcePattern(MatchType type) : _matchType(type) {} - ResourcePattern(MatchType type, const NamespaceString& ns) : _matchType(type), _ns(ns) {} - - MatchType _matchType; - NamespaceString _ns; + * Behavior is undefined unless isExactNamespacePattern() is true. + */ + const NamespaceString& ns() const { + return _ns; + } + + /** + * Returns the database that this pattern matches. + * + * Behavior is undefined unless the pattern is of type matchDatabaseName or + * matchExactNamespace + */ + StringData databaseToMatch() const { + return _ns.db(); + } + + /** + * Returns the collection that this pattern matches. + * + * Behavior is undefined unless the pattern is of type matchCollectionName or + * matchExactNamespace + */ + StringData collectionToMatch() const { + return _ns.coll(); + } + + std::string toString() const; + + inline size_t hash() const { + // TODO: Choose a better hash function. + return MONGO_HASH_NAMESPACE::hash<std::string>()(_ns.ns()) ^ _matchType; + } + + bool operator==(const ResourcePattern& other) const { + if (_matchType != other._matchType) + return false; + if (_ns != other._ns) + return false; + return true; + } + +private: + enum MatchType { + matchNever = 0, /// Matches no resource. + matchClusterResource = 1, /// Matches if the resource is the cluster resource. + matchDatabaseName = 2, /// Matches if the resource's database name is _ns.db(). + matchCollectionName = 3, /// Matches if the resource's collection name is _ns.coll(). + matchExactNamespace = 4, /// Matches if the resource's namespace name is _ns. + matchAnyNormalResource = 5, /// Matches all databases and non-system collections. + matchAnyResource = 6 /// Matches absolutely anything. }; - std::ostream& operator<<(std::ostream& os, const ResourcePattern& pattern); + explicit ResourcePattern(MatchType type) : _matchType(type) {} + ResourcePattern(MatchType type, const NamespaceString& ns) : _matchType(type), _ns(ns) {} + + MatchType _matchType; + NamespaceString _ns; +}; + +std::ostream& operator<<(std::ostream& os, const ResourcePattern& pattern); } // namespace mongo MONGO_HASH_NAMESPACE_START - template <> struct hash<mongo::ResourcePattern> { - size_t operator()(const mongo::ResourcePattern& resource) const { - return resource.hash(); - } - }; +template <> +struct hash<mongo::ResourcePattern> { + size_t operator()(const mongo::ResourcePattern& resource) const { + return resource.hash(); + } +}; MONGO_HASH_NAMESPACE_END diff --git a/src/mongo/db/auth/role_graph.cpp b/src/mongo/db/auth/role_graph.cpp index 98ea177cc43..a0861b98236 100644 --- a/src/mongo/db/auth/role_graph.cpp +++ b/src/mongo/db/auth/role_graph.cpp @@ -40,522 +40,520 @@ namespace mongo { namespace { - PrivilegeVector emptyPrivilegeVector; -} // namespace - - RoleGraph::RoleGraph() {}; - RoleGraph::RoleGraph(const RoleGraph& other) : _roleToSubordinates(other._roleToSubordinates), - _roleToIndirectSubordinates(other._roleToIndirectSubordinates), - _roleToMembers(other._roleToMembers), - _directPrivilegesForRole(other._directPrivilegesForRole), - _allPrivilegesForRole(other._allPrivilegesForRole), - _allRoles(other._allRoles) {} - RoleGraph::~RoleGraph() {}; - - void RoleGraph::swap(RoleGraph& other) { - using std::swap; - swap(this->_roleToSubordinates, other._roleToSubordinates); - swap(this->_roleToIndirectSubordinates, other._roleToIndirectSubordinates); - swap(this->_roleToMembers, other._roleToMembers); - swap(this->_directPrivilegesForRole, other._directPrivilegesForRole); - swap(this->_allPrivilegesForRole, other._allPrivilegesForRole); - swap(this->_allRoles, other._allRoles); - } - - void swap(RoleGraph& lhs, RoleGraph& rhs) { - lhs.swap(rhs); - } - - bool RoleGraph::roleExists(const RoleName& role) { - _createBuiltinRoleIfNeeded(role); - return _roleExistsDontCreateBuiltin(role); - } - - bool RoleGraph::_roleExistsDontCreateBuiltin(const RoleName& role) { - EdgeSet::const_iterator edgeIt = _roleToSubordinates.find(role); - if (edgeIt == _roleToSubordinates.end()) - return false; - edgeIt = _roleToMembers.find(role); - fassert(16825, edgeIt != _roleToMembers.end()); - - RolePrivilegeMap::const_iterator strIt = _directPrivilegesForRole.find(role); - if (strIt == _directPrivilegesForRole.end()) - return false; - strIt = _allPrivilegesForRole.find(role); - fassert(16826, strIt != _allPrivilegesForRole.end()); - return true; - } - - Status RoleGraph::createRole(const RoleName& role) { - if (roleExists(role)) { - return Status(ErrorCodes::DuplicateKey, - mongoutils::str::stream() << "Role: " << role.getFullName() << - " already exists", - 0); - } - - _createRoleDontCheckIfRoleExists(role); - return Status::OK(); +PrivilegeVector emptyPrivilegeVector; +} // namespace + +RoleGraph::RoleGraph(){}; +RoleGraph::RoleGraph(const RoleGraph& other) + : _roleToSubordinates(other._roleToSubordinates), + _roleToIndirectSubordinates(other._roleToIndirectSubordinates), + _roleToMembers(other._roleToMembers), + _directPrivilegesForRole(other._directPrivilegesForRole), + _allPrivilegesForRole(other._allPrivilegesForRole), + _allRoles(other._allRoles) {} +RoleGraph::~RoleGraph(){}; + +void RoleGraph::swap(RoleGraph& other) { + using std::swap; + swap(this->_roleToSubordinates, other._roleToSubordinates); + swap(this->_roleToIndirectSubordinates, other._roleToIndirectSubordinates); + swap(this->_roleToMembers, other._roleToMembers); + swap(this->_directPrivilegesForRole, other._directPrivilegesForRole); + swap(this->_allPrivilegesForRole, other._allPrivilegesForRole); + swap(this->_allRoles, other._allRoles); +} + +void swap(RoleGraph& lhs, RoleGraph& rhs) { + lhs.swap(rhs); +} + +bool RoleGraph::roleExists(const RoleName& role) { + _createBuiltinRoleIfNeeded(role); + return _roleExistsDontCreateBuiltin(role); +} + +bool RoleGraph::_roleExistsDontCreateBuiltin(const RoleName& role) { + EdgeSet::const_iterator edgeIt = _roleToSubordinates.find(role); + if (edgeIt == _roleToSubordinates.end()) + return false; + edgeIt = _roleToMembers.find(role); + fassert(16825, edgeIt != _roleToMembers.end()); + + RolePrivilegeMap::const_iterator strIt = _directPrivilegesForRole.find(role); + if (strIt == _directPrivilegesForRole.end()) + return false; + strIt = _allPrivilegesForRole.find(role); + fassert(16826, strIt != _allPrivilegesForRole.end()); + return true; +} + +Status RoleGraph::createRole(const RoleName& role) { + if (roleExists(role)) { + return Status(ErrorCodes::DuplicateKey, + mongoutils::str::stream() << "Role: " << role.getFullName() + << " already exists", + 0); } - void RoleGraph::_createRoleDontCheckIfRoleExists(const RoleName& role) { - // Just reference the role in all the maps so that an entry gets created with empty - // containers for the value. - _roleToSubordinates[role]; - _roleToIndirectSubordinates[role]; - _roleToMembers[role]; - _directPrivilegesForRole[role]; - _allPrivilegesForRole[role]; - _allRoles.insert(role); + _createRoleDontCheckIfRoleExists(role); + return Status::OK(); +} + +void RoleGraph::_createRoleDontCheckIfRoleExists(const RoleName& role) { + // Just reference the role in all the maps so that an entry gets created with empty + // containers for the value. + _roleToSubordinates[role]; + _roleToIndirectSubordinates[role]; + _roleToMembers[role]; + _directPrivilegesForRole[role]; + _allPrivilegesForRole[role]; + _allRoles.insert(role); +} + +Status RoleGraph::deleteRole(const RoleName& role) { + if (!roleExists(role)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << role.getFullName() + << " does not exist", + 0); } - - Status RoleGraph::deleteRole(const RoleName& role) { - if (!roleExists(role)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << role.getFullName() << - " does not exist", - 0); - } - if (isBuiltinRole(role)) { - return Status(ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Cannot delete built-in role: " << - role.getFullName(), - 0); - } - - for (std::vector<RoleName>::iterator it = _roleToSubordinates[role].begin(); - it != _roleToSubordinates[role].end(); ++it) { - _roleToMembers[*it].erase(std::find(_roleToMembers[*it].begin(), - _roleToMembers[*it].end(), - role)); - } - for (std::vector<RoleName>::iterator it = _roleToMembers[role].begin(); - it != _roleToMembers[role].end(); ++it) { - _roleToSubordinates[*it].erase(std::find(_roleToSubordinates[*it].begin(), - _roleToSubordinates[*it].end(), - role)); - } - _roleToSubordinates.erase(role); - _roleToIndirectSubordinates.erase(role); - _roleToMembers.erase(role); - _directPrivilegesForRole.erase(role); - _allPrivilegesForRole.erase(role); - _allRoles.erase(role); - return Status::OK(); + if (isBuiltinRole(role)) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() + << "Cannot delete built-in role: " << role.getFullName(), + 0); } - RoleNameIterator RoleGraph::getDirectSubordinates(const RoleName& role) { - if (!roleExists(role)) - return RoleNameIterator(NULL); - return makeRoleNameIteratorForContainer(_roleToSubordinates[role]); + for (std::vector<RoleName>::iterator it = _roleToSubordinates[role].begin(); + it != _roleToSubordinates[role].end(); + ++it) { + _roleToMembers[*it].erase( + std::find(_roleToMembers[*it].begin(), _roleToMembers[*it].end(), role)); } - - RoleNameIterator RoleGraph::getIndirectSubordinates(const RoleName& role) { - if (!roleExists(role)) - return RoleNameIterator(NULL); - return makeRoleNameIteratorForContainer(_roleToIndirectSubordinates[role]); + for (std::vector<RoleName>::iterator it = _roleToMembers[role].begin(); + it != _roleToMembers[role].end(); + ++it) { + _roleToSubordinates[*it].erase( + std::find(_roleToSubordinates[*it].begin(), _roleToSubordinates[*it].end(), role)); } - - RoleNameIterator RoleGraph::getDirectMembers(const RoleName& role) { - if (!roleExists(role)) - return RoleNameIterator(NULL); - return makeRoleNameIteratorForContainer(_roleToMembers[role]); + _roleToSubordinates.erase(role); + _roleToIndirectSubordinates.erase(role); + _roleToMembers.erase(role); + _directPrivilegesForRole.erase(role); + _allPrivilegesForRole.erase(role); + _allRoles.erase(role); + return Status::OK(); +} + +RoleNameIterator RoleGraph::getDirectSubordinates(const RoleName& role) { + if (!roleExists(role)) + return RoleNameIterator(NULL); + return makeRoleNameIteratorForContainer(_roleToSubordinates[role]); +} + +RoleNameIterator RoleGraph::getIndirectSubordinates(const RoleName& role) { + if (!roleExists(role)) + return RoleNameIterator(NULL); + return makeRoleNameIteratorForContainer(_roleToIndirectSubordinates[role]); +} + +RoleNameIterator RoleGraph::getDirectMembers(const RoleName& role) { + if (!roleExists(role)) + return RoleNameIterator(NULL); + return makeRoleNameIteratorForContainer(_roleToMembers[role]); +} + +const PrivilegeVector& RoleGraph::getDirectPrivileges(const RoleName& role) { + if (!roleExists(role)) + return emptyPrivilegeVector; + return _directPrivilegesForRole.find(role)->second; +} + +const PrivilegeVector& RoleGraph::getAllPrivileges(const RoleName& role) { + if (!roleExists(role)) + return emptyPrivilegeVector; + return _allPrivilegesForRole.find(role)->second; +} + +Status RoleGraph::addRoleToRole(const RoleName& recipient, const RoleName& role) { + if (!roleExists(recipient)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << recipient.getFullName() + << " does not exist"); } - - const PrivilegeVector& RoleGraph::getDirectPrivileges(const RoleName& role) { - if (!roleExists(role)) - return emptyPrivilegeVector; - return _directPrivilegesForRole.find(role)->second; + if (isBuiltinRole(recipient)) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() + << "Cannot grant roles to built-in role: " << role.getFullName()); } - - const PrivilegeVector& RoleGraph::getAllPrivileges(const RoleName& role) { - if (!roleExists(role)) - return emptyPrivilegeVector; - return _allPrivilegesForRole.find(role)->second; + if (!roleExists(role)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << role.getFullName() + << " does not exist"); } - Status RoleGraph::addRoleToRole(const RoleName& recipient, const RoleName& role) { - if (!roleExists(recipient)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << recipient.getFullName() << - " does not exist"); - } - if (isBuiltinRole(recipient)) { - return Status(ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Cannot grant roles to built-in role: " << - role.getFullName()); - } - if (!roleExists(role)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << role.getFullName() << - " does not exist"); - } - - if (std::find(_roleToSubordinates[recipient].begin(), - _roleToSubordinates[recipient].end(), - role) == - _roleToSubordinates[recipient].end()) { - // Only add role if it's not already present - _roleToSubordinates[recipient].push_back(role); - _roleToMembers[role].push_back(recipient); - } - - return Status::OK(); + if (std::find(_roleToSubordinates[recipient].begin(), + _roleToSubordinates[recipient].end(), + role) == _roleToSubordinates[recipient].end()) { + // Only add role if it's not already present + _roleToSubordinates[recipient].push_back(role); + _roleToMembers[role].push_back(recipient); } - Status RoleGraph::removeRoleFromRole(const RoleName& recipient, const RoleName& role) { - if (!roleExists(recipient)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << recipient.getFullName() << - " does not exist", - 0); - } - if (isBuiltinRole(recipient)) { - return Status(ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Cannot remove roles from built-in role: " << - role.getFullName(), - 0); - } - if (!roleExists(role)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << role.getFullName() << - " does not exist", - 0); - } + return Status::OK(); +} - std::vector<RoleName>::iterator itToRm = std::find(_roleToMembers[role].begin(), - _roleToMembers[role].end(), - recipient); - if (itToRm != _roleToMembers[role].end()) { - _roleToMembers[role].erase(itToRm); - } else { - return Status(ErrorCodes::RolesNotRelated, - mongoutils::str::stream() << recipient.getFullName() << " is not a member" - " of " << role.getFullName(), - 0); - } - - itToRm = std::find(_roleToSubordinates[recipient].begin(), - _roleToSubordinates[recipient].end(), - role); - fassert(16827, itToRm != _roleToSubordinates[recipient].end()); - _roleToSubordinates[recipient].erase(itToRm); - return Status::OK(); +Status RoleGraph::removeRoleFromRole(const RoleName& recipient, const RoleName& role) { + if (!roleExists(recipient)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << recipient.getFullName() + << " does not exist", + 0); } - - Status RoleGraph::removeAllRolesFromRole(const RoleName& victim) { - typedef std::vector<RoleName> RoleNameVector; - if (!roleExists(victim)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << victim.getFullName() << - " does not exist", - 0); - } - if (isBuiltinRole(victim)) { - return Status(ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Cannot remove roles from built-in role: " << - victim.getFullName(), - 0); - } - - RoleNameVector& subordinatesOfVictim = _roleToSubordinates[victim]; - for (RoleNameVector::const_iterator subordinateRole = subordinatesOfVictim.begin(), - end = subordinatesOfVictim.end(); - subordinateRole != end; - ++subordinateRole) { - - RoleNameVector& membersOfSubordinate = _roleToMembers[*subordinateRole]; - RoleNameVector::iterator toErase = std::find( - membersOfSubordinate.begin(), membersOfSubordinate.end(), victim); - fassert(17173, toErase != membersOfSubordinate.end()); - membersOfSubordinate.erase(toErase); - } - subordinatesOfVictim.clear(); - return Status::OK(); + if (isBuiltinRole(recipient)) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() + << "Cannot remove roles from built-in role: " << role.getFullName(), + 0); } - - Status RoleGraph::addPrivilegeToRole(const RoleName& role, const Privilege& privilegeToAdd) { - if (!roleExists(role)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << role.getFullName() << - " does not exist", - 0); - } - if (isBuiltinRole(role)) { - return Status(ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Cannot grant privileges to built-in role: " - << role.getFullName(), - 0); - } - - _addPrivilegeToRoleNoChecks(role, privilegeToAdd); - return Status::OK(); + if (!roleExists(role)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << role.getFullName() + << " does not exist", + 0); } - void RoleGraph::_addPrivilegeToRoleNoChecks(const RoleName& role, - const Privilege& privilegeToAdd) { - Privilege::addPrivilegeToPrivilegeVector(&_directPrivilegesForRole[role], privilegeToAdd); + std::vector<RoleName>::iterator itToRm = + std::find(_roleToMembers[role].begin(), _roleToMembers[role].end(), recipient); + if (itToRm != _roleToMembers[role].end()) { + _roleToMembers[role].erase(itToRm); + } else { + return Status(ErrorCodes::RolesNotRelated, + mongoutils::str::stream() << recipient.getFullName() << " is not a member" + " of " + << role.getFullName(), + 0); } - // NOTE: Current runtime of this is O(n*m) where n is the size of the current PrivilegeVector - // for the given role, and m is the size of the privilegesToAdd vector. - // If this was a PrivilegeSet (sorted on resource) rather than a PrivilegeVector, we - // could do this in O(n+m) instead. - Status RoleGraph::addPrivilegesToRole(const RoleName& role, - const PrivilegeVector& privilegesToAdd) { - if (!roleExists(role)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << role.getFullName() << - " does not exist", - 0); - } - if (isBuiltinRole(role)) { - return Status(ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Cannot grant privileges to built-in role: " - << role.getFullName(), - 0); - } - - for (PrivilegeVector::const_iterator it = privilegesToAdd.begin(); - it != privilegesToAdd.end(); ++it) { - _addPrivilegeToRoleNoChecks(role, *it); - } - return Status::OK(); + itToRm = std::find( + _roleToSubordinates[recipient].begin(), _roleToSubordinates[recipient].end(), role); + fassert(16827, itToRm != _roleToSubordinates[recipient].end()); + _roleToSubordinates[recipient].erase(itToRm); + return Status::OK(); +} + +Status RoleGraph::removeAllRolesFromRole(const RoleName& victim) { + typedef std::vector<RoleName> RoleNameVector; + if (!roleExists(victim)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << victim.getFullName() + << " does not exist", + 0); + } + if (isBuiltinRole(victim)) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() + << "Cannot remove roles from built-in role: " << victim.getFullName(), + 0); } - Status RoleGraph::removePrivilegeFromRole(const RoleName& role, - const Privilege& privilegeToRemove) { - if (!roleExists(role)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << role.getFullName() << - " does not exist", - 0); - } - if (isBuiltinRole(role)) { - return Status( - ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Cannot remove privileges from built-in role: " << - role.getFullName()); - } + RoleNameVector& subordinatesOfVictim = _roleToSubordinates[victim]; + for (RoleNameVector::const_iterator subordinateRole = subordinatesOfVictim.begin(), + end = subordinatesOfVictim.end(); + subordinateRole != end; + ++subordinateRole) { + RoleNameVector& membersOfSubordinate = _roleToMembers[*subordinateRole]; + RoleNameVector::iterator toErase = + std::find(membersOfSubordinate.begin(), membersOfSubordinate.end(), victim); + fassert(17173, toErase != membersOfSubordinate.end()); + membersOfSubordinate.erase(toErase); + } + subordinatesOfVictim.clear(); + return Status::OK(); +} + +Status RoleGraph::addPrivilegeToRole(const RoleName& role, const Privilege& privilegeToAdd) { + if (!roleExists(role)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << role.getFullName() + << " does not exist", + 0); + } + if (isBuiltinRole(role)) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() + << "Cannot grant privileges to built-in role: " << role.getFullName(), + 0); + } - PrivilegeVector& currentPrivileges = _directPrivilegesForRole[role]; - for (PrivilegeVector::iterator it = currentPrivileges.begin(); - it != currentPrivileges.end(); ++it) { - - Privilege& curPrivilege = *it; - if (curPrivilege.getResourcePattern() == privilegeToRemove.getResourcePattern()) { - ActionSet curActions = curPrivilege.getActions(); - - if (!curActions.isSupersetOf(privilegeToRemove.getActions())) { - // Didn't possess all the actions being removed. - return Status(ErrorCodes::PrivilegeNotFound, - mongoutils::str::stream() << "Role: " << role.getFullName() << - " does not contain a privilege on " << - privilegeToRemove.getResourcePattern().toString() << - " with actions: " << - privilegeToRemove.getActions().toString(), - 0); - } + _addPrivilegeToRoleNoChecks(role, privilegeToAdd); + return Status::OK(); +} + +void RoleGraph::_addPrivilegeToRoleNoChecks(const RoleName& role, const Privilege& privilegeToAdd) { + Privilege::addPrivilegeToPrivilegeVector(&_directPrivilegesForRole[role], privilegeToAdd); +} + +// NOTE: Current runtime of this is O(n*m) where n is the size of the current PrivilegeVector +// for the given role, and m is the size of the privilegesToAdd vector. +// If this was a PrivilegeSet (sorted on resource) rather than a PrivilegeVector, we +// could do this in O(n+m) instead. +Status RoleGraph::addPrivilegesToRole(const RoleName& role, + const PrivilegeVector& privilegesToAdd) { + if (!roleExists(role)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << role.getFullName() + << " does not exist", + 0); + } + if (isBuiltinRole(role)) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() + << "Cannot grant privileges to built-in role: " << role.getFullName(), + 0); + } - curPrivilege.removeActions(privilegeToRemove.getActions()); - if (curPrivilege.getActions().empty()) { - currentPrivileges.erase(it); - } - return Status::OK(); - } - } - return Status(ErrorCodes::PrivilegeNotFound, - mongoutils::str::stream() << "Role: " << role.getFullName() << " does not " - "contain any privileges on " << - privilegeToRemove.getResourcePattern().toString(), + for (PrivilegeVector::const_iterator it = privilegesToAdd.begin(); it != privilegesToAdd.end(); + ++it) { + _addPrivilegeToRoleNoChecks(role, *it); + } + return Status::OK(); +} + +Status RoleGraph::removePrivilegeFromRole(const RoleName& role, + const Privilege& privilegeToRemove) { + if (!roleExists(role)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << role.getFullName() + << " does not exist", 0); } + if (isBuiltinRole(role)) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() + << "Cannot remove privileges from built-in role: " << role.getFullName()); + } - Status RoleGraph::removePrivilegesFromRole(const RoleName& role, - const PrivilegeVector& privilegesToRemove) { - for (PrivilegeVector::const_iterator it = privilegesToRemove.begin(); - it != privilegesToRemove.end(); ++it) { - Status status = removePrivilegeFromRole(role, *it); - if (!status.isOK()) { - return status; + PrivilegeVector& currentPrivileges = _directPrivilegesForRole[role]; + for (PrivilegeVector::iterator it = currentPrivileges.begin(); it != currentPrivileges.end(); + ++it) { + Privilege& curPrivilege = *it; + if (curPrivilege.getResourcePattern() == privilegeToRemove.getResourcePattern()) { + ActionSet curActions = curPrivilege.getActions(); + + if (!curActions.isSupersetOf(privilegeToRemove.getActions())) { + // Didn't possess all the actions being removed. + return Status(ErrorCodes::PrivilegeNotFound, + mongoutils::str::stream() + << "Role: " << role.getFullName() + << " does not contain a privilege on " + << privilegeToRemove.getResourcePattern().toString() + << " with actions: " << privilegeToRemove.getActions().toString(), + 0); } - } - return Status::OK(); - } - Status RoleGraph::removeAllPrivilegesFromRole(const RoleName& role) { - if (!roleExists(role)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << role.getFullName() << - " does not exist", - 0); - } - if (isBuiltinRole(role)) { - return Status( - ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Cannot remove privileges from built-in role: " << - role.getFullName()); + curPrivilege.removeActions(privilegeToRemove.getActions()); + if (curPrivilege.getActions().empty()) { + currentPrivileges.erase(it); + } + return Status::OK(); } - _directPrivilegesForRole[role].clear(); - return Status::OK(); } - - Status RoleGraph::replaceRole(const RoleName& roleName, - const std::vector<RoleName>& roles, - const PrivilegeVector& privileges) { - Status status = removeAllPrivilegesFromRole(roleName); - if (status == ErrorCodes::RoleNotFound) { - fassert(17168, createRole(roleName)); - } - else if (!status.isOK()) { + return Status(ErrorCodes::PrivilegeNotFound, + mongoutils::str::stream() << "Role: " << role.getFullName() + << " does not " + "contain any privileges on " + << privilegeToRemove.getResourcePattern().toString(), + 0); +} + +Status RoleGraph::removePrivilegesFromRole(const RoleName& role, + const PrivilegeVector& privilegesToRemove) { + for (PrivilegeVector::const_iterator it = privilegesToRemove.begin(); + it != privilegesToRemove.end(); + ++it) { + Status status = removePrivilegeFromRole(role, *it); + if (!status.isOK()) { return status; } - fassert(17169, removeAllRolesFromRole(roleName)); - for (size_t i = 0; i < roles.size(); ++i) { - const RoleName& grantedRole = roles[i]; - status = createRole(grantedRole); - fassert(17170, status.isOK() || status == ErrorCodes::DuplicateKey); - fassert(17171, addRoleToRole(roleName, grantedRole)); + } + return Status::OK(); +} + +Status RoleGraph::removeAllPrivilegesFromRole(const RoleName& role) { + if (!roleExists(role)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << role.getFullName() + << " does not exist", + 0); + } + if (isBuiltinRole(role)) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() + << "Cannot remove privileges from built-in role: " << role.getFullName()); + } + _directPrivilegesForRole[role].clear(); + return Status::OK(); +} + +Status RoleGraph::replaceRole(const RoleName& roleName, + const std::vector<RoleName>& roles, + const PrivilegeVector& privileges) { + Status status = removeAllPrivilegesFromRole(roleName); + if (status == ErrorCodes::RoleNotFound) { + fassert(17168, createRole(roleName)); + } else if (!status.isOK()) { + return status; + } + fassert(17169, removeAllRolesFromRole(roleName)); + for (size_t i = 0; i < roles.size(); ++i) { + const RoleName& grantedRole = roles[i]; + status = createRole(grantedRole); + fassert(17170, status.isOK() || status == ErrorCodes::DuplicateKey); + fassert(17171, addRoleToRole(roleName, grantedRole)); + } + fassert(17172, addPrivilegesToRole(roleName, privileges)); + return Status::OK(); +} + +Status RoleGraph::recomputePrivilegeData() { + /* + * This method is used to recompute the "allPrivileges" vector for each node in the graph, + * as well as look for cycles. It is implemented by performing a depth-first traversal of + * the dependency graph, once for each node. "visitedRoles" tracks the set of role names + * ever visited, and it is used to prune each DFS. A node that has been visited once on any + * DFS is never visited again. Complexity of this implementation is O(n+m) where "n" is the + * number of nodes and "m" is the number of prerequisite edges. Space complexity is O(n), + * in both stack space and size of the "visitedRoles" set. + * + * "inProgressRoles" is used to detect and report cycles, as well as to keep track of roles + * we started visiting before realizing they had children that needed visiting first, so + * we can get back to them after visiting their children. + */ + + unordered_set<RoleName> visitedRoles; + for (EdgeSet::const_iterator it = _roleToSubordinates.begin(); it != _roleToSubordinates.end(); + ++it) { + Status status = _recomputePrivilegeDataHelper(it->first, visitedRoles); + if (!status.isOK()) { + return status; } - fassert(17172, addPrivilegesToRole(roleName, privileges)); - return Status::OK(); } + return Status::OK(); +} - Status RoleGraph::recomputePrivilegeData() { - /* - * This method is used to recompute the "allPrivileges" vector for each node in the graph, - * as well as look for cycles. It is implemented by performing a depth-first traversal of - * the dependency graph, once for each node. "visitedRoles" tracks the set of role names - * ever visited, and it is used to prune each DFS. A node that has been visited once on any - * DFS is never visited again. Complexity of this implementation is O(n+m) where "n" is the - * number of nodes and "m" is the number of prerequisite edges. Space complexity is O(n), - * in both stack space and size of the "visitedRoles" set. - * - * "inProgressRoles" is used to detect and report cycles, as well as to keep track of roles - * we started visiting before realizing they had children that needed visiting first, so - * we can get back to them after visiting their children. - */ - - unordered_set<RoleName> visitedRoles; - for (EdgeSet::const_iterator it = _roleToSubordinates.begin(); - it != _roleToSubordinates.end(); ++it) { - Status status = _recomputePrivilegeDataHelper(it->first, visitedRoles); - if (!status.isOK()) { - return status; - } - } +Status RoleGraph::_recomputePrivilegeDataHelper(const RoleName& startingRole, + unordered_set<RoleName>& visitedRoles) { + if (visitedRoles.count(startingRole)) { return Status::OK(); } - Status RoleGraph::_recomputePrivilegeDataHelper(const RoleName& startingRole, - unordered_set<RoleName>& visitedRoles) { - if (visitedRoles.count(startingRole)) { - return Status::OK(); - } - - std::vector<RoleName> inProgressRoles; - inProgressRoles.push_back(startingRole); - while (inProgressRoles.size()) { - const RoleName currentRole = inProgressRoles.back(); - fassert(17277, !visitedRoles.count(currentRole)); + std::vector<RoleName> inProgressRoles; + inProgressRoles.push_back(startingRole); + while (inProgressRoles.size()) { + const RoleName currentRole = inProgressRoles.back(); + fassert(17277, !visitedRoles.count(currentRole)); - if (!roleExists(currentRole)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << "Role: " << currentRole.getFullName() << - " does not exist", - 0); - } + if (!roleExists(currentRole)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << "Role: " << currentRole.getFullName() + << " does not exist", + 0); + } - // Check for cycles - { - const std::vector<RoleName>::const_iterator begin = inProgressRoles.begin(); - // The currentRole will always be last so don't look there. - const std::vector<RoleName>::const_iterator end = --inProgressRoles.end(); - const std::vector<RoleName>::const_iterator firstOccurence = - std::find(begin, end, currentRole); - if (firstOccurence != end) { - std::ostringstream os; - os << "Cycle in dependency graph: "; - for (std::vector<RoleName>::const_iterator it = firstOccurence; - it != end; ++it) { - os << it->getFullName() << " -> "; - } - os << currentRole.getFullName(); - return Status(ErrorCodes::GraphContainsCycle, os.str()); + // Check for cycles + { + const std::vector<RoleName>::const_iterator begin = inProgressRoles.begin(); + // The currentRole will always be last so don't look there. + const std::vector<RoleName>::const_iterator end = --inProgressRoles.end(); + const std::vector<RoleName>::const_iterator firstOccurence = + std::find(begin, end, currentRole); + if (firstOccurence != end) { + std::ostringstream os; + os << "Cycle in dependency graph: "; + for (std::vector<RoleName>::const_iterator it = firstOccurence; it != end; ++it) { + os << it->getFullName() << " -> "; } + os << currentRole.getFullName(); + return Status(ErrorCodes::GraphContainsCycle, os.str()); } + } - // Make sure we've already visited all subordinate roles before worrying about this one. - const std::vector<RoleName>& currentRoleDirectRoles = _roleToSubordinates[currentRole]; - std::vector<RoleName>::const_iterator roleIt; - for (roleIt = currentRoleDirectRoles.begin(); - roleIt != currentRoleDirectRoles.end(); ++roleIt) { - const RoleName& childRole = *roleIt; - if (!visitedRoles.count(childRole)) { - inProgressRoles.push_back(childRole); - break; - } - } - // If roleIt didn't reach the end of currentRoleDirectRoles that means we found a child - // of currentRole that we haven't visited yet. - if (roleIt != currentRoleDirectRoles.end()) { - continue; + // Make sure we've already visited all subordinate roles before worrying about this one. + const std::vector<RoleName>& currentRoleDirectRoles = _roleToSubordinates[currentRole]; + std::vector<RoleName>::const_iterator roleIt; + for (roleIt = currentRoleDirectRoles.begin(); roleIt != currentRoleDirectRoles.end(); + ++roleIt) { + const RoleName& childRole = *roleIt; + if (!visitedRoles.count(childRole)) { + inProgressRoles.push_back(childRole); + break; } - // At this point, we know that we've already visited all child roles of currentRole - // and thus their "all privileges" sets are correct and can be added to currentRole's - // "all privileges" set - - // Need to clear out the "all privileges" vector for the current role, and re-fill it - // with just the direct privileges for this role. - PrivilegeVector& currentRoleAllPrivileges = _allPrivilegesForRole[currentRole]; - currentRoleAllPrivileges = _directPrivilegesForRole[currentRole]; - - // Need to do the same thing for the indirect roles - unordered_set<RoleName>& currentRoleIndirectRoles = - _roleToIndirectSubordinates[currentRole]; - currentRoleIndirectRoles.clear(); - for (std::vector<RoleName>::const_iterator it = currentRoleDirectRoles.begin(); - it != currentRoleDirectRoles.end(); ++it) { - currentRoleIndirectRoles.insert(*it); + } + // If roleIt didn't reach the end of currentRoleDirectRoles that means we found a child + // of currentRole that we haven't visited yet. + if (roleIt != currentRoleDirectRoles.end()) { + continue; + } + // At this point, we know that we've already visited all child roles of currentRole + // and thus their "all privileges" sets are correct and can be added to currentRole's + // "all privileges" set + + // Need to clear out the "all privileges" vector for the current role, and re-fill it + // with just the direct privileges for this role. + PrivilegeVector& currentRoleAllPrivileges = _allPrivilegesForRole[currentRole]; + currentRoleAllPrivileges = _directPrivilegesForRole[currentRole]; + + // Need to do the same thing for the indirect roles + unordered_set<RoleName>& currentRoleIndirectRoles = + _roleToIndirectSubordinates[currentRole]; + currentRoleIndirectRoles.clear(); + for (std::vector<RoleName>::const_iterator it = currentRoleDirectRoles.begin(); + it != currentRoleDirectRoles.end(); + ++it) { + currentRoleIndirectRoles.insert(*it); + } + + // Recursively add children's privileges to current role's "all privileges" vector, and + // children's roles to current roles's "indirect roles" vector. + for (std::vector<RoleName>::const_iterator roleIt = currentRoleDirectRoles.begin(); + roleIt != currentRoleDirectRoles.end(); + ++roleIt) { + // At this point, we already know that the "all privilege" set for the child is + // correct, so add those privileges to our "all privilege" set. + const RoleName& childRole = *roleIt; + + const PrivilegeVector& childsPrivileges = _allPrivilegesForRole[childRole]; + for (PrivilegeVector::const_iterator privIt = childsPrivileges.begin(); + privIt != childsPrivileges.end(); + ++privIt) { + Privilege::addPrivilegeToPrivilegeVector(¤tRoleAllPrivileges, *privIt); } - // Recursively add children's privileges to current role's "all privileges" vector, and - // children's roles to current roles's "indirect roles" vector. - for (std::vector<RoleName>::const_iterator roleIt = currentRoleDirectRoles.begin(); - roleIt != currentRoleDirectRoles.end(); ++roleIt) { - // At this point, we already know that the "all privilege" set for the child is - // correct, so add those privileges to our "all privilege" set. - const RoleName& childRole = *roleIt; - - const PrivilegeVector& childsPrivileges = _allPrivilegesForRole[childRole]; - for (PrivilegeVector::const_iterator privIt = childsPrivileges.begin(); - privIt != childsPrivileges.end(); ++privIt) { - Privilege::addPrivilegeToPrivilegeVector(¤tRoleAllPrivileges, *privIt); - } - - // We also know that the "indirect roles" for the child is also correct, so we can - // add those roles to our "indirect roles" set. - const unordered_set<RoleName>& childsRoles = _roleToIndirectSubordinates[childRole]; - for (unordered_set<RoleName>::const_iterator childsRoleIt = childsRoles.begin(); - childsRoleIt != childsRoles.end(); ++childsRoleIt) { - currentRoleIndirectRoles.insert(*childsRoleIt); - } + // We also know that the "indirect roles" for the child is also correct, so we can + // add those roles to our "indirect roles" set. + const unordered_set<RoleName>& childsRoles = _roleToIndirectSubordinates[childRole]; + for (unordered_set<RoleName>::const_iterator childsRoleIt = childsRoles.begin(); + childsRoleIt != childsRoles.end(); + ++childsRoleIt) { + currentRoleIndirectRoles.insert(*childsRoleIt); } - - visitedRoles.insert(currentRole); - inProgressRoles.pop_back(); } - return Status::OK(); + + visitedRoles.insert(currentRole); + inProgressRoles.pop_back(); } + return Status::OK(); +} - RoleNameIterator RoleGraph::getRolesForDatabase(const std::string& dbname) { - _createBuiltinRolesForDBIfNeeded(dbname); +RoleNameIterator RoleGraph::getRolesForDatabase(const std::string& dbname) { + _createBuiltinRolesForDBIfNeeded(dbname); - std::set<RoleName>::const_iterator lower = _allRoles.lower_bound(RoleName("", dbname)); - std::string afterDB = dbname; - afterDB.push_back('\0'); - std::set<RoleName>::const_iterator upper = _allRoles.lower_bound(RoleName("", afterDB)); - return makeRoleNameIterator(lower, upper); - } + std::set<RoleName>::const_iterator lower = _allRoles.lower_bound(RoleName("", dbname)); + std::string afterDB = dbname; + afterDB.push_back('\0'); + std::set<RoleName>::const_iterator upper = _allRoles.lower_bound(RoleName("", afterDB)); + return makeRoleNameIterator(lower, upper); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/role_graph.h b/src/mongo/db/auth/role_graph.h index bec12b6d1e2..50a0c47a857 100644 --- a/src/mongo/db/auth/role_graph.h +++ b/src/mongo/db/auth/role_graph.h @@ -41,273 +41,271 @@ namespace mongo { +/** + * A graph of role and privilege relationships. + * + * This structure is used to store an in-memory representation of the admin.system.roledata + * collection, specifically the graph of which roles are members of other roles and what + * privileges each role has, both directly and transitively through membership in other roles. + * There are some restrictions on calls to getAllPrivileges(), specifically, one must call + * recomputePrivilegeData() before calling getAllPrivileges() if any of the mutation methods + * have been called on the instance since the later of its construction or the last call to + * recomputePrivilegeData() on the object. + */ +class RoleGraph { +public: + /** + * Adds to "privileges" the privileges associated with the named built-in role, and returns + * true. Returns false if "role" does not name a built-in role, and does not modify + * "privileges". Addition of new privileges is done as with + * Privilege::addPrivilegeToPrivilegeVector. + */ + static bool addPrivilegesForBuiltinRole(const RoleName& role, PrivilegeVector* privileges); + + RoleGraph(); + RoleGraph(const RoleGraph& other); + ~RoleGraph(); + + // Built-in roles for backwards compatibility with 2.2 and prior + static const std::string BUILTIN_ROLE_V0_READ; + static const std::string BUILTIN_ROLE_V0_READ_WRITE; + static const std::string BUILTIN_ROLE_V0_ADMIN_READ; + static const std::string BUILTIN_ROLE_V0_ADMIN_READ_WRITE; + + // Swaps the contents of this RoleGraph with those of "other" + void swap(RoleGraph& other); + + /** + * Adds to "privileges" the necessary privileges to do absolutely anything on the system. + */ + static void generateUniversalPrivileges(PrivilegeVector* privileges); + + /** + * Returns an iterator over the RoleNames of the "members" of the given role. + * Members of a role are roles that have been granted this role directly (roles that are + * members transitively through another role are not included). These are the "parents" of + * this node in the graph. + */ + RoleNameIterator getDirectMembers(const RoleName& role); + + /** + * Returns an iterator over the RoleNames of the "subordinates" of the given role. + * Subordinate roles are the roles that this role has been granted directly (roles + * that have been granted transitively through another role are not included). These are + * the "children" of this node in the graph. + */ + RoleNameIterator getDirectSubordinates(const RoleName& role); + + /** + * Returns an iterator that can be used to get a full list of roles that this role inherits + * privileges from. This includes its direct subordinate roles as well as the subordinates + * of its subordinates, and so on. + */ + RoleNameIterator getIndirectSubordinates(const RoleName& role); + + /** + * Returns an iterator that can be used to get a full list of roles (in lexicographical + * order) that are defined on the given database. + */ + RoleNameIterator getRolesForDatabase(const std::string& dbname); + + /** + * Returns a vector of the privileges that the given role has been directly granted. + * Privileges that have been granted transitively through this role's subordinate roles are + * not included. + */ + const PrivilegeVector& getDirectPrivileges(const RoleName& role); + /** - * A graph of role and privilege relationships. + * Returns a vector of all privileges that the given role contains. This includes both the + * privileges that have been granted to this role directly, as well as any privileges + * inherited from the role's subordinate roles. + */ + const PrivilegeVector& getAllPrivileges(const RoleName& role); + + /** + * Returns whether or not the given role exists in the role graph. Will implicitly + * add the role to the graph if it is a built-in role and isn't already in the graph. + */ + bool roleExists(const RoleName& role); + + /** + * Returns whether the given role corresponds to a built-in role. + */ + static bool isBuiltinRole(const RoleName& role); + + // Mutation functions + + /** + * Puts an entry into the RoleGraph for the given RoleName. + * Returns DuplicateKey if the role already exists. + */ + Status createRole(const RoleName& role); + + /** + * Deletes the given role by first removing it from the members/subordinates arrays for + * all other roles, and then by removing its own entries in the 4 member maps. + * Returns RoleNotFound if the role doesn't exist. + * Returns InvalidRoleModification if "role" is a built-in role. + */ + Status deleteRole(const RoleName& role); + + /** + * Grants "role" to "recipient". This leaves "recipient" as a member of "role" and "role" + * as a subordinate of "recipient". + * Returns RoleNotFound if either of "role" or "recipient" doesn't exist in + * the RoleGraph. + * Returns InvalidRoleModification if "recipient" is a built-in role. + */ + Status addRoleToRole(const RoleName& recipient, const RoleName& role); + + /** + * Revokes "role" from "recipient". + * Returns RoleNotFound if either of "role" or "recipient" doesn't exist in + * the RoleGraph. Returns RolesNotRelated if "recipient" is not currently a + * member of "role". + * Returns InvalidRoleModification if "role" is a built-in role. + */ + Status removeRoleFromRole(const RoleName& recipient, const RoleName& role); + + /** + * Removes all roles held by "victim". + * Returns RoleNotFound if "victim" doesn't exist in the role graph. + * Returns InvalidRoleModification if "victim" is a built-in role. + */ + Status removeAllRolesFromRole(const RoleName& victim); + + /** + * Grants "privilegeToAdd" to "role". + * Returns RoleNotFound if "role" doesn't exist in the role graph. + * Returns InvalidRoleModification if "role" is a built-in role. + */ + Status addPrivilegeToRole(const RoleName& role, const Privilege& privilegeToAdd); + + /** + * Grants Privileges from "privilegesToAdd" to "role". + * Returns RoleNotFound if "role" doesn't exist in the role graph. + * Returns InvalidRoleModification if "role" is a built-in role. + */ + Status addPrivilegesToRole(const RoleName& role, const PrivilegeVector& privilegesToAdd); + + /** + * Removes "privilegeToRemove" from "role". + * Returns RoleNotFound if "role" doesn't exist in the role graph. + * Returns PrivilegeNotFound if "role" doesn't contain the full privilege being removed. + * Returns InvalidRoleModification if "role" is a built-in role. + */ + Status removePrivilegeFromRole(const RoleName& role, const Privilege& privilegeToRemove); + + /** + * Removes all privileges in the "privilegesToRemove" vector from "role". + * Returns RoleNotFound if "role" doesn't exist in the role graph. + * Returns InvalidRoleModification if "role" is a built-in role. + * Returns PrivilegeNotFound if "role" is missing any of the privileges being removed. If + * PrivilegeNotFound is returned then the graph may be in an inconsistent state and needs to + * be abandoned. + */ + Status removePrivilegesFromRole(const RoleName& role, + const PrivilegeVector& privilegesToRemove); + + /** + * Removes all privileges from "role". + * Returns RoleNotFound if "role" doesn't exist in the role graph. + * Returns InvalidRoleModification if "role" is a built-in role. + */ + Status removeAllPrivilegesFromRole(const RoleName& role); + + /** + * Updates the RoleGraph by adding the role named "roleName", with the given role + * memberships and privileges. If the name "roleName" already exists, it is replaced. Any + * subordinate roles mentioned in role.roles are created, if needed, with empty privilege + * and subordinate role lists. + * + * Should _only_ fail if the role to replace is a builtin role, in which + * case it will return ErrorCodes::InvalidRoleModification. + */ + Status replaceRole(const RoleName& roleName, + const std::vector<RoleName>& roles, + const PrivilegeVector& privileges); + + /** + * Adds the role described in "doc" the role graph. + */ + Status addRoleFromDocument(const BSONObj& doc); + + /** + * Applies to the RoleGraph the oplog operation described by the parameters. * - * This structure is used to store an in-memory representation of the admin.system.roledata - * collection, specifically the graph of which roles are members of other roles and what - * privileges each role has, both directly and transitively through membership in other roles. - * There are some restrictions on calls to getAllPrivileges(), specifically, one must call - * recomputePrivilegeData() before calling getAllPrivileges() if any of the mutation methods - * have been called on the instance since the later of its construction or the last call to - * recomputePrivilegeData() on the object. + * Returns Status::OK() on success, ErrorCodes::OplogOperationUnsupported if the oplog + * operation is not supported, and other codes (typically BadValue) if the oplog operation + * is ill-described. */ - class RoleGraph { - public: - /** - * Adds to "privileges" the privileges associated with the named built-in role, and returns - * true. Returns false if "role" does not name a built-in role, and does not modify - * "privileges". Addition of new privileges is done as with - * Privilege::addPrivilegeToPrivilegeVector. - */ - static bool addPrivilegesForBuiltinRole(const RoleName& role, PrivilegeVector* privileges); - - RoleGraph(); - RoleGraph(const RoleGraph& other); - ~RoleGraph(); - - // Built-in roles for backwards compatibility with 2.2 and prior - static const std::string BUILTIN_ROLE_V0_READ; - static const std::string BUILTIN_ROLE_V0_READ_WRITE; - static const std::string BUILTIN_ROLE_V0_ADMIN_READ; - static const std::string BUILTIN_ROLE_V0_ADMIN_READ_WRITE; - - // Swaps the contents of this RoleGraph with those of "other" - void swap(RoleGraph& other); - - /** - * Adds to "privileges" the necessary privileges to do absolutely anything on the system. - */ - static void generateUniversalPrivileges(PrivilegeVector* privileges); - - /** - * Returns an iterator over the RoleNames of the "members" of the given role. - * Members of a role are roles that have been granted this role directly (roles that are - * members transitively through another role are not included). These are the "parents" of - * this node in the graph. - */ - RoleNameIterator getDirectMembers(const RoleName& role); - - /** - * Returns an iterator over the RoleNames of the "subordinates" of the given role. - * Subordinate roles are the roles that this role has been granted directly (roles - * that have been granted transitively through another role are not included). These are - * the "children" of this node in the graph. - */ - RoleNameIterator getDirectSubordinates(const RoleName& role); - - /** - * Returns an iterator that can be used to get a full list of roles that this role inherits - * privileges from. This includes its direct subordinate roles as well as the subordinates - * of its subordinates, and so on. - */ - RoleNameIterator getIndirectSubordinates(const RoleName& role); - - /** - * Returns an iterator that can be used to get a full list of roles (in lexicographical - * order) that are defined on the given database. - */ - RoleNameIterator getRolesForDatabase(const std::string& dbname); - - /** - * Returns a vector of the privileges that the given role has been directly granted. - * Privileges that have been granted transitively through this role's subordinate roles are - * not included. - */ - const PrivilegeVector& getDirectPrivileges(const RoleName& role); - - /** - * Returns a vector of all privileges that the given role contains. This includes both the - * privileges that have been granted to this role directly, as well as any privileges - * inherited from the role's subordinate roles. - */ - const PrivilegeVector& getAllPrivileges(const RoleName& role); - - /** - * Returns whether or not the given role exists in the role graph. Will implicitly - * add the role to the graph if it is a built-in role and isn't already in the graph. - */ - bool roleExists(const RoleName& role); - - /** - * Returns whether the given role corresponds to a built-in role. - */ - static bool isBuiltinRole(const RoleName& role); - - // Mutation functions - - /** - * Puts an entry into the RoleGraph for the given RoleName. - * Returns DuplicateKey if the role already exists. - */ - Status createRole(const RoleName& role); - - /** - * Deletes the given role by first removing it from the members/subordinates arrays for - * all other roles, and then by removing its own entries in the 4 member maps. - * Returns RoleNotFound if the role doesn't exist. - * Returns InvalidRoleModification if "role" is a built-in role. - */ - Status deleteRole(const RoleName& role); - - /** - * Grants "role" to "recipient". This leaves "recipient" as a member of "role" and "role" - * as a subordinate of "recipient". - * Returns RoleNotFound if either of "role" or "recipient" doesn't exist in - * the RoleGraph. - * Returns InvalidRoleModification if "recipient" is a built-in role. - */ - Status addRoleToRole(const RoleName& recipient, const RoleName& role); - - /** - * Revokes "role" from "recipient". - * Returns RoleNotFound if either of "role" or "recipient" doesn't exist in - * the RoleGraph. Returns RolesNotRelated if "recipient" is not currently a - * member of "role". - * Returns InvalidRoleModification if "role" is a built-in role. - */ - Status removeRoleFromRole(const RoleName& recipient, const RoleName& role); - - /** - * Removes all roles held by "victim". - * Returns RoleNotFound if "victim" doesn't exist in the role graph. - * Returns InvalidRoleModification if "victim" is a built-in role. - */ - Status removeAllRolesFromRole(const RoleName& victim); - - /** - * Grants "privilegeToAdd" to "role". - * Returns RoleNotFound if "role" doesn't exist in the role graph. - * Returns InvalidRoleModification if "role" is a built-in role. - */ - Status addPrivilegeToRole(const RoleName& role, const Privilege& privilegeToAdd); - - /** - * Grants Privileges from "privilegesToAdd" to "role". - * Returns RoleNotFound if "role" doesn't exist in the role graph. - * Returns InvalidRoleModification if "role" is a built-in role. - */ - Status addPrivilegesToRole(const RoleName& role, const PrivilegeVector& privilegesToAdd); - - /** - * Removes "privilegeToRemove" from "role". - * Returns RoleNotFound if "role" doesn't exist in the role graph. - * Returns PrivilegeNotFound if "role" doesn't contain the full privilege being removed. - * Returns InvalidRoleModification if "role" is a built-in role. - */ - Status removePrivilegeFromRole(const RoleName& role, - const Privilege& privilegeToRemove); - - /** - * Removes all privileges in the "privilegesToRemove" vector from "role". - * Returns RoleNotFound if "role" doesn't exist in the role graph. - * Returns InvalidRoleModification if "role" is a built-in role. - * Returns PrivilegeNotFound if "role" is missing any of the privileges being removed. If - * PrivilegeNotFound is returned then the graph may be in an inconsistent state and needs to - * be abandoned. - */ - Status removePrivilegesFromRole(const RoleName& role, - const PrivilegeVector& privilegesToRemove); - - /** - * Removes all privileges from "role". - * Returns RoleNotFound if "role" doesn't exist in the role graph. - * Returns InvalidRoleModification if "role" is a built-in role. - */ - Status removeAllPrivilegesFromRole(const RoleName& role); - - /** - * Updates the RoleGraph by adding the role named "roleName", with the given role - * memberships and privileges. If the name "roleName" already exists, it is replaced. Any - * subordinate roles mentioned in role.roles are created, if needed, with empty privilege - * and subordinate role lists. - * - * Should _only_ fail if the role to replace is a builtin role, in which - * case it will return ErrorCodes::InvalidRoleModification. - */ - Status replaceRole(const RoleName& roleName, - const std::vector<RoleName>& roles, - const PrivilegeVector& privileges); - - /** - * Adds the role described in "doc" the role graph. - */ - Status addRoleFromDocument(const BSONObj& doc); - - /** - * Applies to the RoleGraph the oplog operation described by the parameters. - * - * Returns Status::OK() on success, ErrorCodes::OplogOperationUnsupported if the oplog - * operation is not supported, and other codes (typically BadValue) if the oplog operation - * is ill-described. - */ - Status handleLogOp( - const char* op, - const NamespaceString& ns, - const BSONObj& o, - const BSONObj* o2); - - /** - * Recomputes the indirect (getAllPrivileges) data for this graph. - * - * Must be called between calls to any of the mutation functions and calls - * to getAllPrivileges(). - * - * Returns Status::OK() on success. If a cycle is detected, returns - * ErrorCodes::GraphContainsCycle, and the status message reveals the cycle. - */ - Status recomputePrivilegeData(); - - private: - // Helper method doing a topological DFS to compute the indirect privilege - // data and look for cycles - Status _recomputePrivilegeDataHelper(const RoleName& currentRole, - unordered_set<RoleName>& visitedRoles); - - /** - * If the role name given is not a built-in role, or it is but it's already in the role - * graph, then this does nothing. If it *is* a built-in role and this is the first time - * this function has been called for this role, it will add the role into the role graph. - */ - void _createBuiltinRoleIfNeeded(const RoleName& role); - - /** - * Adds the built-in roles for the given database name to the role graph if they aren't - * already present. - */ - void _createBuiltinRolesForDBIfNeeded(const std::string& dbname); - - /** - * Returns whether or not the given role exists strictly within the role graph. - */ - bool _roleExistsDontCreateBuiltin(const RoleName& role); - - /** - * Just creates the role in the role graph, without checking whether or not the role already - * exists. - */ - void _createRoleDontCheckIfRoleExists(const RoleName& role); - - /** - * Grants "privilegeToAdd" to "role". - * Doesn't do any checking as to whether the role exists or is a built-in role. - */ - void _addPrivilegeToRoleNoChecks(const RoleName& role, const Privilege& privilegeToAdd); - - - // Represents all the outgoing edges to other roles from any given role. - typedef unordered_map<RoleName, std::vector<RoleName> > EdgeSet; - // Maps a role name to a list of privileges associated with that role. - typedef unordered_map<RoleName, PrivilegeVector> RolePrivilegeMap; - - EdgeSet _roleToSubordinates; - unordered_map<RoleName, unordered_set<RoleName> > _roleToIndirectSubordinates; - EdgeSet _roleToMembers; - RolePrivilegeMap _directPrivilegesForRole; - RolePrivilegeMap _allPrivilegesForRole; - std::set<RoleName> _allRoles; - }; - - void swap(RoleGraph& lhs, RoleGraph& rhs); + Status handleLogOp(const char* op, + const NamespaceString& ns, + const BSONObj& o, + const BSONObj* o2); + + /** + * Recomputes the indirect (getAllPrivileges) data for this graph. + * + * Must be called between calls to any of the mutation functions and calls + * to getAllPrivileges(). + * + * Returns Status::OK() on success. If a cycle is detected, returns + * ErrorCodes::GraphContainsCycle, and the status message reveals the cycle. + */ + Status recomputePrivilegeData(); + +private: + // Helper method doing a topological DFS to compute the indirect privilege + // data and look for cycles + Status _recomputePrivilegeDataHelper(const RoleName& currentRole, + unordered_set<RoleName>& visitedRoles); + + /** + * If the role name given is not a built-in role, or it is but it's already in the role + * graph, then this does nothing. If it *is* a built-in role and this is the first time + * this function has been called for this role, it will add the role into the role graph. + */ + void _createBuiltinRoleIfNeeded(const RoleName& role); + + /** + * Adds the built-in roles for the given database name to the role graph if they aren't + * already present. + */ + void _createBuiltinRolesForDBIfNeeded(const std::string& dbname); + + /** + * Returns whether or not the given role exists strictly within the role graph. + */ + bool _roleExistsDontCreateBuiltin(const RoleName& role); + + /** + * Just creates the role in the role graph, without checking whether or not the role already + * exists. + */ + void _createRoleDontCheckIfRoleExists(const RoleName& role); + + /** + * Grants "privilegeToAdd" to "role". + * Doesn't do any checking as to whether the role exists or is a built-in role. + */ + void _addPrivilegeToRoleNoChecks(const RoleName& role, const Privilege& privilegeToAdd); + + + // Represents all the outgoing edges to other roles from any given role. + typedef unordered_map<RoleName, std::vector<RoleName>> EdgeSet; + // Maps a role name to a list of privileges associated with that role. + typedef unordered_map<RoleName, PrivilegeVector> RolePrivilegeMap; + + EdgeSet _roleToSubordinates; + unordered_map<RoleName, unordered_set<RoleName>> _roleToIndirectSubordinates; + EdgeSet _roleToMembers; + RolePrivilegeMap _directPrivilegesForRole; + RolePrivilegeMap _allPrivilegesForRole; + std::set<RoleName> _allRoles; +}; + +void swap(RoleGraph& lhs, RoleGraph& rhs); } // namespace mongo diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp index 479cc0de9fa..6b8a1762bce 100644 --- a/src/mongo/db/auth/role_graph_builtin_roles.cpp +++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp @@ -36,784 +36,648 @@ namespace mongo { - const std::string RoleGraph::BUILTIN_ROLE_V0_READ = "read"; - const std::string RoleGraph::BUILTIN_ROLE_V0_READ_WRITE= "dbOwner"; - const std::string RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ = "readAnyDatabase"; - const std::string RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ_WRITE= "root"; +const std::string RoleGraph::BUILTIN_ROLE_V0_READ = "read"; +const std::string RoleGraph::BUILTIN_ROLE_V0_READ_WRITE = "dbOwner"; +const std::string RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ = "readAnyDatabase"; +const std::string RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ_WRITE = "root"; namespace { - const std::string ADMIN_DBNAME = "admin"; - - const std::string BUILTIN_ROLE_READ = "read"; - const std::string BUILTIN_ROLE_READ_WRITE = "readWrite"; - const std::string BUILTIN_ROLE_USER_ADMIN = "userAdmin"; - const std::string BUILTIN_ROLE_DB_ADMIN = "dbAdmin"; - const std::string BUILTIN_ROLE_CLUSTER_ADMIN = "clusterAdmin"; - const std::string BUILTIN_ROLE_READ_ANY_DB = "readAnyDatabase"; - const std::string BUILTIN_ROLE_READ_WRITE_ANY_DB = "readWriteAnyDatabase"; - const std::string BUILTIN_ROLE_USER_ADMIN_ANY_DB = "userAdminAnyDatabase"; - const std::string BUILTIN_ROLE_DB_ADMIN_ANY_DB = "dbAdminAnyDatabase"; - const std::string BUILTIN_ROLE_ROOT = "root"; - const std::string BUILTIN_ROLE_INTERNAL = "__system"; - const std::string BUILTIN_ROLE_DB_OWNER = "dbOwner"; - const std::string BUILTIN_ROLE_CLUSTER_MONITOR = "clusterMonitor"; - const std::string BUILTIN_ROLE_HOST_MANAGEMENT = "hostManager"; - const std::string BUILTIN_ROLE_CLUSTER_MANAGEMENT = "clusterManager"; - const std::string BUILTIN_ROLE_BACKUP = "backup"; - const std::string BUILTIN_ROLE_RESTORE = "restore"; - - /// Actions that the "read" role may perform on a normal resources of a specific database, and - /// that the "readAnyDatabase" role may perform on normal resources of any database. - ActionSet readRoleActions; - - /// Actions that the "readWrite" role may perform on a normal resources of a specific database, - /// and that the "readWriteAnyDatabase" role may perform on normal resources of any database. - ActionSet readWriteRoleActions; - - /// Actions that the "userAdmin" role may perform on normal resources of a specific database, - /// and that the "userAdminAnyDatabase" role may perform on normal resources of any database. - ActionSet userAdminRoleActions; - - /// Actions that the "dbAdmin" role may perform on normal resources of a specific database, - // and that the "dbAdminAnyDatabase" role may perform on normal resources of any database. - ActionSet dbAdminRoleActions; - - /// Actions that the "clusterMonitor" role may perform on the cluster resource. - ActionSet clusterMonitorRoleClusterActions; - - /// Actions that the "clusterMonitor" role may perform on any database. - ActionSet clusterMonitorRoleDatabaseActions; - - /// Actions that the "hostManager" role may perform on the cluster resource. - ActionSet hostManagerRoleClusterActions; - - /// Actions that the "hostManager" role may perform on any database. - ActionSet hostManagerRoleDatabaseActions; - - /// Actions that the "clusterManager" role may perform on the cluster resource. - ActionSet clusterManagerRoleClusterActions; - - /// Actions that the "clusterManager" role may perform on any database - ActionSet clusterManagerRoleDatabaseActions; - - ActionSet& operator<<(ActionSet& target, ActionType source) { - target.addAction(source); - return target; - } - - void operator+=(ActionSet& target, const ActionSet& source) { - target.addAllActionsFromSet(source); - } - - // This sets up the built-in role ActionSets. This is what determines what actions each role - // is authorized to perform - MONGO_INITIALIZER(AuthorizationBuiltinRoles)(InitializerContext* context) { - // Read role - readRoleActions - << ActionType::collStats - << ActionType::dbHash - << ActionType::dbStats - << ActionType::find - << ActionType::killCursors - << ActionType::listCollections - << ActionType::listIndexes - << ActionType::planCacheRead; - - // Read-write role - readWriteRoleActions += readRoleActions; - readWriteRoleActions - << ActionType::convertToCapped // db admin gets this also - << ActionType::createCollection // db admin gets this also - << ActionType::dropCollection - << ActionType::dropIndex - << ActionType::emptycapped - << ActionType::createIndex - << ActionType::insert - << ActionType::remove - << ActionType::renameCollectionSameDB // db admin gets this also - << ActionType::update; - - // User admin role - userAdminRoleActions - << ActionType::changeCustomData - << ActionType::changePassword - << ActionType::createUser - << ActionType::createRole - << ActionType::dropUser - << ActionType::dropRole - << ActionType::grantRole - << ActionType::revokeRole - << ActionType::viewUser - << ActionType::viewRole; - - - // DB admin role - dbAdminRoleActions - << ActionType::bypassDocumentValidation - << ActionType::collMod - << ActionType::collStats // clusterMonitor gets this also - << ActionType::compact - << ActionType::convertToCapped // read_write gets this also - << ActionType::createCollection // read_write gets this also - << ActionType::dbStats // clusterMonitor gets this also - << ActionType::dropCollection - << ActionType::dropDatabase // clusterAdmin gets this also TODO(spencer): should readWriteAnyDatabase? - << ActionType::dropIndex - << ActionType::createIndex - << ActionType::indexStats - << ActionType::enableProfiler - << ActionType::listCollections - << ActionType::listIndexes - << ActionType::planCacheIndexFilter - << ActionType::planCacheRead - << ActionType::planCacheWrite - << ActionType::reIndex - << ActionType::renameCollectionSameDB // read_write gets this also - << ActionType::repairDatabase - << ActionType::storageDetails - << ActionType::validate; - - // clusterMonitor role actions that target the cluster resource - clusterMonitorRoleClusterActions - << ActionType::connPoolStats - << ActionType::getCmdLineOpts - << ActionType::getLog - << ActionType::getParameter - << ActionType::getShardMap - << ActionType::hostInfo - << ActionType::listDatabases - << ActionType::listShards // clusterManager gets this also - << ActionType::netstat - << ActionType::replSetGetConfig // clusterManager gets this also - << ActionType::replSetGetStatus // clusterManager gets this also - << ActionType::serverStatus - << ActionType::top - << ActionType::cursorInfo - << ActionType::inprog - << ActionType::shardingState; - - // clusterMonitor role actions that target a database (or collection) resource - clusterMonitorRoleDatabaseActions - << ActionType::collStats // dbAdmin gets this also - << ActionType::dbStats // dbAdmin gets this also - << ActionType::getShardVersion; - - // hostManager role actions that target the cluster resource - hostManagerRoleClusterActions - << ActionType::applicationMessage // clusterManager gets this also - << ActionType::connPoolSync - << ActionType::cpuProfiler - << ActionType::logRotate - << ActionType::setParameter - << ActionType::shutdown - << ActionType::touch - << ActionType::unlock - << ActionType::diagLogging - << ActionType::flushRouterConfig // clusterManager gets this also - << ActionType::fsync - << ActionType::invalidateUserCache // userAdminAnyDatabase gets this also - << ActionType::killop - << ActionType::resync; // clusterManager gets this also - - // hostManager role actions that target the database resource - hostManagerRoleDatabaseActions - << ActionType::killCursors - << ActionType::repairDatabase; - - - // clusterManager role actions that target the cluster resource - clusterManagerRoleClusterActions - << ActionType::appendOplogNote // backup gets this also - << ActionType::applicationMessage // hostManager gets this also - << ActionType::replSetConfigure - << ActionType::replSetGetConfig // clusterMonitor gets this also - << ActionType::replSetGetStatus // clusterMonitor gets this also - << ActionType::replSetStateChange - << ActionType::resync // hostManager gets this also - << ActionType::addShard - << ActionType::removeShard - << ActionType::listShards // clusterMonitor gets this also - << ActionType::flushRouterConfig // hostManager gets this also - << ActionType::cleanupOrphaned; - - clusterManagerRoleDatabaseActions - << ActionType::splitChunk - << ActionType::moveChunk - << ActionType::enableSharding - << ActionType::splitVector; - - return Status::OK(); - } - - void addReadOnlyDbPrivileges(PrivilegeVector* privileges, StringData dbName) { - Privilege::addPrivilegeToPrivilegeVector( - privileges, Privilege(ResourcePattern::forDatabaseName(dbName), readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyResource(), ActionType::listCollections)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - NamespaceString(dbName, "system.indexes")), - readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString(dbName, "system.js")), - readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - NamespaceString(dbName, "system.namespaces")), - readRoleActions)); - } - - void addReadWriteDbPrivileges(PrivilegeVector* privileges, StringData dbName) { - addReadOnlyDbPrivileges(privileges, dbName); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forDatabaseName(dbName), readWriteRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString(dbName, "system.js")), - readWriteRoleActions)); - } - - void addUserAdminDbPrivileges(PrivilegeVector* privileges, StringData dbName) { - privileges->push_back( - Privilege(ResourcePattern::forDatabaseName(dbName), userAdminRoleActions)); - } - - void addDbAdminDbPrivileges(PrivilegeVector* privileges, StringData dbName) { - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forDatabaseName(dbName), dbAdminRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - NamespaceString(dbName, "system.indexes")), - readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - NamespaceString(dbName, "system.namespaces")), - readRoleActions)); - - ActionSet profileActions = readRoleActions; - profileActions.addAction(ActionType::convertToCapped); - profileActions.addAction(ActionType::createCollection); - profileActions.addAction(ActionType::dropCollection); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - NamespaceString(dbName, "system.profile")), - profileActions)); - } - - void addDbOwnerPrivileges(PrivilegeVector* privileges, StringData dbName) { - addReadWriteDbPrivileges(privileges, dbName); - addDbAdminDbPrivileges(privileges, dbName); - addUserAdminDbPrivileges(privileges, dbName); - } - - - void addReadOnlyAnyDbPrivileges(PrivilegeVector* privileges) { - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forClusterResource(), ActionType::listDatabases)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.indexes"), - readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.js"), - readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.namespaces"), - readRoleActions)); - } - - void addReadWriteAnyDbPrivileges(PrivilegeVector* privileges) { - addReadOnlyAnyDbPrivileges(privileges); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), readWriteRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.js"), readWriteRoleActions)); - } - - void addUserAdminAnyDbPrivileges(PrivilegeVector* privileges) { - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), userAdminRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forClusterResource(), ActionType::listDatabases)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forClusterResource(), ActionType::invalidateUserCache)); - - - ActionSet readRoleAndIndexActions; - readRoleAndIndexActions += readRoleActions; - readRoleAndIndexActions << ActionType::createIndex << ActionType::dropIndex; - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.users"), - readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::usersCollectionNamespace), - readRoleAndIndexActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::rolesCollectionNamespace), - readRoleAndIndexActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::versionCollectionNamespace), - readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::usersAltCollectionNamespace), - readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::usersBackupCollectionNamespace), - readRoleActions)); - } - - void addDbAdminAnyDbPrivileges(PrivilegeVector* privileges) { - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forClusterResource(), ActionType::listDatabases)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), dbAdminRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.indexes"), - readRoleActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.namespaces"), - readRoleActions)); - ActionSet profileActions = readRoleActions; - profileActions.addAction(ActionType::convertToCapped); - profileActions.addAction(ActionType::createCollection); - profileActions.addAction(ActionType::dropCollection); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.profile"), - profileActions)); - } - - void addClusterMonitorPrivileges(PrivilegeVector* privileges) { - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forClusterResource(), clusterMonitorRoleClusterActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), - clusterMonitorRoleDatabaseActions)); - addReadOnlyDbPrivileges(privileges, "config"); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - NamespaceString("local.system.replset")), - ActionType::find)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.profile"), ActionType::find)); - } - - void addHostManagerPrivileges(PrivilegeVector* privileges) { - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forClusterResource(), hostManagerRoleClusterActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), - hostManagerRoleDatabaseActions)); - } - - void addClusterManagerPrivileges(PrivilegeVector* privileges) { - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forClusterResource(), clusterManagerRoleClusterActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), - clusterManagerRoleDatabaseActions)); - addReadOnlyDbPrivileges(privileges, "config"); - - ActionSet configSettingsActions; - configSettingsActions << ActionType::insert << ActionType::update << ActionType::remove; - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString("config", - "settings")), - configSettingsActions)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString("local", - "system.replset")), - readRoleActions)); - } +const std::string ADMIN_DBNAME = "admin"; + +const std::string BUILTIN_ROLE_READ = "read"; +const std::string BUILTIN_ROLE_READ_WRITE = "readWrite"; +const std::string BUILTIN_ROLE_USER_ADMIN = "userAdmin"; +const std::string BUILTIN_ROLE_DB_ADMIN = "dbAdmin"; +const std::string BUILTIN_ROLE_CLUSTER_ADMIN = "clusterAdmin"; +const std::string BUILTIN_ROLE_READ_ANY_DB = "readAnyDatabase"; +const std::string BUILTIN_ROLE_READ_WRITE_ANY_DB = "readWriteAnyDatabase"; +const std::string BUILTIN_ROLE_USER_ADMIN_ANY_DB = "userAdminAnyDatabase"; +const std::string BUILTIN_ROLE_DB_ADMIN_ANY_DB = "dbAdminAnyDatabase"; +const std::string BUILTIN_ROLE_ROOT = "root"; +const std::string BUILTIN_ROLE_INTERNAL = "__system"; +const std::string BUILTIN_ROLE_DB_OWNER = "dbOwner"; +const std::string BUILTIN_ROLE_CLUSTER_MONITOR = "clusterMonitor"; +const std::string BUILTIN_ROLE_HOST_MANAGEMENT = "hostManager"; +const std::string BUILTIN_ROLE_CLUSTER_MANAGEMENT = "clusterManager"; +const std::string BUILTIN_ROLE_BACKUP = "backup"; +const std::string BUILTIN_ROLE_RESTORE = "restore"; + +/// Actions that the "read" role may perform on a normal resources of a specific database, and +/// that the "readAnyDatabase" role may perform on normal resources of any database. +ActionSet readRoleActions; + +/// Actions that the "readWrite" role may perform on a normal resources of a specific database, +/// and that the "readWriteAnyDatabase" role may perform on normal resources of any database. +ActionSet readWriteRoleActions; + +/// Actions that the "userAdmin" role may perform on normal resources of a specific database, +/// and that the "userAdminAnyDatabase" role may perform on normal resources of any database. +ActionSet userAdminRoleActions; + +/// Actions that the "dbAdmin" role may perform on normal resources of a specific database, +// and that the "dbAdminAnyDatabase" role may perform on normal resources of any database. +ActionSet dbAdminRoleActions; + +/// Actions that the "clusterMonitor" role may perform on the cluster resource. +ActionSet clusterMonitorRoleClusterActions; + +/// Actions that the "clusterMonitor" role may perform on any database. +ActionSet clusterMonitorRoleDatabaseActions; + +/// Actions that the "hostManager" role may perform on the cluster resource. +ActionSet hostManagerRoleClusterActions; + +/// Actions that the "hostManager" role may perform on any database. +ActionSet hostManagerRoleDatabaseActions; + +/// Actions that the "clusterManager" role may perform on the cluster resource. +ActionSet clusterManagerRoleClusterActions; + +/// Actions that the "clusterManager" role may perform on any database +ActionSet clusterManagerRoleDatabaseActions; + +ActionSet& operator<<(ActionSet& target, ActionType source) { + target.addAction(source); + return target; +} + +void operator+=(ActionSet& target, const ActionSet& source) { + target.addAllActionsFromSet(source); +} + +// This sets up the built-in role ActionSets. This is what determines what actions each role +// is authorized to perform +MONGO_INITIALIZER(AuthorizationBuiltinRoles)(InitializerContext* context) { + // Read role + readRoleActions << ActionType::collStats << ActionType::dbHash << ActionType::dbStats + << ActionType::find << ActionType::killCursors << ActionType::listCollections + << ActionType::listIndexes << ActionType::planCacheRead; + + // Read-write role + readWriteRoleActions += readRoleActions; + readWriteRoleActions << ActionType::convertToCapped // db admin gets this also + << ActionType::createCollection // db admin gets this also + << ActionType::dropCollection << ActionType::dropIndex + << ActionType::emptycapped << ActionType::createIndex << ActionType::insert + << ActionType::remove + << ActionType::renameCollectionSameDB // db admin gets this also + << ActionType::update; + + // User admin role + userAdminRoleActions << ActionType::changeCustomData << ActionType::changePassword + << ActionType::createUser << ActionType::createRole << ActionType::dropUser + << ActionType::dropRole << ActionType::grantRole << ActionType::revokeRole + << ActionType::viewUser << ActionType::viewRole; + + + // DB admin role + dbAdminRoleActions + << ActionType::bypassDocumentValidation << ActionType::collMod + << ActionType::collStats // clusterMonitor gets this also + << ActionType::compact << ActionType::convertToCapped // read_write gets this also + << ActionType::createCollection // read_write gets this also + << ActionType::dbStats // clusterMonitor gets this also + << ActionType::dropCollection + << ActionType:: + dropDatabase // clusterAdmin gets this also TODO(spencer): should readWriteAnyDatabase? + << ActionType::dropIndex << ActionType::createIndex << ActionType::indexStats + << ActionType::enableProfiler << ActionType::listCollections << ActionType::listIndexes + << ActionType::planCacheIndexFilter << ActionType::planCacheRead + << ActionType::planCacheWrite << ActionType::reIndex + << ActionType::renameCollectionSameDB // read_write gets this also + << ActionType::repairDatabase << ActionType::storageDetails << ActionType::validate; + + // clusterMonitor role actions that target the cluster resource + clusterMonitorRoleClusterActions + << ActionType::connPoolStats << ActionType::getCmdLineOpts << ActionType::getLog + << ActionType::getParameter << ActionType::getShardMap << ActionType::hostInfo + << ActionType::listDatabases << ActionType::listShards // clusterManager gets this also + << ActionType::netstat << ActionType::replSetGetConfig // clusterManager gets this also + << ActionType::replSetGetStatus // clusterManager gets this also + << ActionType::serverStatus << ActionType::top << ActionType::cursorInfo + << ActionType::inprog << ActionType::shardingState; + + // clusterMonitor role actions that target a database (or collection) resource + clusterMonitorRoleDatabaseActions << ActionType::collStats // dbAdmin gets this also + << ActionType::dbStats // dbAdmin gets this also + << ActionType::getShardVersion; + + // hostManager role actions that target the cluster resource + hostManagerRoleClusterActions + << ActionType::applicationMessage // clusterManager gets this also + << ActionType::connPoolSync << ActionType::cpuProfiler << ActionType::logRotate + << ActionType::setParameter << ActionType::shutdown << ActionType::touch + << ActionType::unlock << ActionType::diagLogging + << ActionType::flushRouterConfig // clusterManager gets this also + << ActionType::fsync + << ActionType::invalidateUserCache // userAdminAnyDatabase gets this also + << ActionType::killop << ActionType::resync; // clusterManager gets this also + + // hostManager role actions that target the database resource + hostManagerRoleDatabaseActions << ActionType::killCursors << ActionType::repairDatabase; + + + // clusterManager role actions that target the cluster resource + clusterManagerRoleClusterActions + << ActionType::appendOplogNote // backup gets this also + << ActionType::applicationMessage // hostManager gets this also + << ActionType::replSetConfigure + << ActionType::replSetGetConfig // clusterMonitor gets this also + << ActionType::replSetGetStatus // clusterMonitor gets this also + << ActionType::replSetStateChange << ActionType::resync // hostManager gets this also + << ActionType::addShard << ActionType::removeShard + << ActionType::listShards // clusterMonitor gets this also + << ActionType::flushRouterConfig // hostManager gets this also + << ActionType::cleanupOrphaned; + + clusterManagerRoleDatabaseActions << ActionType::splitChunk << ActionType::moveChunk + << ActionType::enableSharding << ActionType::splitVector; + + return Status::OK(); +} + +void addReadOnlyDbPrivileges(PrivilegeVector* privileges, StringData dbName) { + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forDatabaseName(dbName), readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyResource(), ActionType::listCollections)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString(dbName, "system.indexes")), + readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString(dbName, "system.js")), + readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString(dbName, "system.namespaces")), + readRoleActions)); +} + +void addReadWriteDbPrivileges(PrivilegeVector* privileges, StringData dbName) { + addReadOnlyDbPrivileges(privileges, dbName); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forDatabaseName(dbName), readWriteRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString(dbName, "system.js")), + readWriteRoleActions)); +} + +void addUserAdminDbPrivileges(PrivilegeVector* privileges, StringData dbName) { + privileges->push_back( + Privilege(ResourcePattern::forDatabaseName(dbName), userAdminRoleActions)); +} + +void addDbAdminDbPrivileges(PrivilegeVector* privileges, StringData dbName) { + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forDatabaseName(dbName), dbAdminRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString(dbName, "system.indexes")), + readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString(dbName, "system.namespaces")), + readRoleActions)); + + ActionSet profileActions = readRoleActions; + profileActions.addAction(ActionType::convertToCapped); + profileActions.addAction(ActionType::createCollection); + profileActions.addAction(ActionType::dropCollection); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString(dbName, "system.profile")), + profileActions)); +} + +void addDbOwnerPrivileges(PrivilegeVector* privileges, StringData dbName) { + addReadWriteDbPrivileges(privileges, dbName); + addDbAdminDbPrivileges(privileges, dbName); + addUserAdminDbPrivileges(privileges, dbName); +} + + +void addReadOnlyAnyDbPrivileges(PrivilegeVector* privileges) { + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyNormalResource(), readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forClusterResource(), ActionType::listDatabases)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.indexes"), readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forCollectionName("system.js"), readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.namespaces"), readRoleActions)); +} + +void addReadWriteAnyDbPrivileges(PrivilegeVector* privileges) { + addReadOnlyAnyDbPrivileges(privileges); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyNormalResource(), readWriteRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.js"), readWriteRoleActions)); +} + +void addUserAdminAnyDbPrivileges(PrivilegeVector* privileges) { + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyNormalResource(), userAdminRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forClusterResource(), ActionType::listDatabases)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forClusterResource(), ActionType::invalidateUserCache)); + + + ActionSet readRoleAndIndexActions; + readRoleAndIndexActions += readRoleActions; + readRoleAndIndexActions << ActionType::createIndex << ActionType::dropIndex; + + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forCollectionName("system.users"), readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::usersCollectionNamespace), + readRoleAndIndexActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::rolesCollectionNamespace), + readRoleAndIndexActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::versionCollectionNamespace), + readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::usersAltCollectionNamespace), + readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace( + AuthorizationManager::usersBackupCollectionNamespace), + readRoleActions)); +} + +void addDbAdminAnyDbPrivileges(PrivilegeVector* privileges) { + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forClusterResource(), ActionType::listDatabases)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyNormalResource(), dbAdminRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.indexes"), readRoleActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.namespaces"), readRoleActions)); + ActionSet profileActions = readRoleActions; + profileActions.addAction(ActionType::convertToCapped); + profileActions.addAction(ActionType::createCollection); + profileActions.addAction(ActionType::dropCollection); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.profile"), profileActions)); +} + +void addClusterMonitorPrivileges(PrivilegeVector* privileges) { + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forClusterResource(), clusterMonitorRoleClusterActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forAnyNormalResource(), clusterMonitorRoleDatabaseActions)); + addReadOnlyDbPrivileges(privileges, "config"); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString("local.system.replset")), + ActionType::find)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.profile"), ActionType::find)); +} + +void addHostManagerPrivileges(PrivilegeVector* privileges) { + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forClusterResource(), hostManagerRoleClusterActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forAnyNormalResource(), hostManagerRoleDatabaseActions)); +} + +void addClusterManagerPrivileges(PrivilegeVector* privileges) { + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forClusterResource(), clusterManagerRoleClusterActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forAnyNormalResource(), clusterManagerRoleDatabaseActions)); + addReadOnlyDbPrivileges(privileges, "config"); + + ActionSet configSettingsActions; + configSettingsActions << ActionType::insert << ActionType::update << ActionType::remove; + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString("config", "settings")), + configSettingsActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString("local", "system.replset")), + readRoleActions)); +} + +void addClusterAdminPrivileges(PrivilegeVector* privileges) { + addClusterMonitorPrivileges(privileges); + addHostManagerPrivileges(privileges); + addClusterManagerPrivileges(privileges); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyNormalResource(), ActionType::dropDatabase)); +} + +void addBackupPrivileges(PrivilegeVector* privileges) { + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyResource(), ActionType::collStats)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyNormalResource(), ActionType::find)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyResource(), ActionType::listCollections)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyResource(), ActionType::listIndexes)); + + ActionSet clusterActions; + clusterActions << ActionType::getParameter // To check authSchemaVersion + << ActionType::listDatabases << ActionType::appendOplogNote; // For BRS + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forClusterResource(), clusterActions)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.indexes"), ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.namespaces"), ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forCollectionName("system.js"), ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.users"), ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::usersAltCollectionNamespace), + ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace( + AuthorizationManager::usersBackupCollectionNamespace), + ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::rolesCollectionNamespace), + ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::versionCollectionNamespace), + ActionType::find)); + + ActionSet configSettingsActions; + configSettingsActions << ActionType::insert << ActionType::update << ActionType::find; + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace(NamespaceString("config", "settings")), + configSettingsActions)); +} + +void addRestorePrivileges(PrivilegeVector* privileges) { + ActionSet actions; + actions << ActionType::bypassDocumentValidation << ActionType::collMod + << ActionType::createCollection << ActionType::createIndex << ActionType::dropCollection + << ActionType::insert; + + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyNormalResource(), actions)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forCollectionName("system.js"), actions)); + + // Need to be able to query system.namespaces to check existing collection options. + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forCollectionName("system.namespaces"), ActionType::find)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyResource(), ActionType::listCollections)); + + // Privileges for user/role management + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forAnyNormalResource(), userAdminRoleActions)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace( + AuthorizationManager::defaultTempUsersCollectionNamespace), + ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace( + AuthorizationManager::defaultTempRolesCollectionNamespace), + ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::usersAltCollectionNamespace), + actions)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace( + AuthorizationManager::usersBackupCollectionNamespace), + actions)); + + actions << ActionType::find; + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::versionCollectionNamespace), + actions)); + + // Need additional actions on system.users. + actions << ActionType::update << ActionType::remove; + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forCollectionName("system.users"), actions)); + + // Need to be able to run getParameter to check authSchemaVersion + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forClusterResource(), ActionType::getParameter)); + + // Need to be able to create an index on the system.roles collection. + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege( + ResourcePattern::forExactNamespace(AuthorizationManager::rolesCollectionNamespace), + ActionType::createIndex)); +} + +void addRootRolePrivileges(PrivilegeVector* privileges) { + addClusterAdminPrivileges(privileges); + addUserAdminAnyDbPrivileges(privileges); + addDbAdminAnyDbPrivileges(privileges); + addReadWriteAnyDbPrivileges(privileges); +} + +void addInternalRolePrivileges(PrivilegeVector* privileges) { + RoleGraph::generateUniversalPrivileges(privileges); +} - void addClusterAdminPrivileges(PrivilegeVector* privileges) { - addClusterMonitorPrivileges(privileges); - addHostManagerPrivileges(privileges); - addClusterManagerPrivileges(privileges); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), - ActionType::dropDatabase)); - } +} // namespace - void addBackupPrivileges(PrivilegeVector* privileges) { - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyResource(), ActionType::collStats)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), ActionType::find)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyResource(), ActionType::listCollections)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyResource(), ActionType::listIndexes)); - - ActionSet clusterActions; - clusterActions << ActionType::getParameter // To check authSchemaVersion - << ActionType::listDatabases - << ActionType::appendOplogNote; // For BRS - Privilege::addPrivilegeToPrivilegeVector( - privileges, Privilege(ResourcePattern::forClusterResource(), clusterActions)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.indexes"), ActionType::find)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.namespaces"), - ActionType::find)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.js"), ActionType::find)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.users"), ActionType::find)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::usersAltCollectionNamespace), - ActionType::find)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::usersBackupCollectionNamespace), - ActionType::find)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::rolesCollectionNamespace), - ActionType::find)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::versionCollectionNamespace), - ActionType::find)); - - ActionSet configSettingsActions; - configSettingsActions << ActionType::insert << ActionType::update << ActionType::find; - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString("config", - "settings")), - configSettingsActions)); +bool RoleGraph::addPrivilegesForBuiltinRole(const RoleName& roleName, PrivilegeVector* result) { + const bool isAdminDB = (roleName.getDB() == ADMIN_DBNAME); + + if (roleName.getRole() == BUILTIN_ROLE_READ) { + addReadOnlyDbPrivileges(result, roleName.getDB()); + } else if (roleName.getRole() == BUILTIN_ROLE_READ_WRITE) { + addReadWriteDbPrivileges(result, roleName.getDB()); + } else if (roleName.getRole() == BUILTIN_ROLE_USER_ADMIN) { + addUserAdminDbPrivileges(result, roleName.getDB()); + } else if (roleName.getRole() == BUILTIN_ROLE_DB_ADMIN) { + addDbAdminDbPrivileges(result, roleName.getDB()); + } else if (roleName.getRole() == BUILTIN_ROLE_DB_OWNER) { + addDbOwnerPrivileges(result, roleName.getDB()); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_READ_ANY_DB) { + addReadOnlyAnyDbPrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_READ_WRITE_ANY_DB) { + addReadWriteAnyDbPrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_USER_ADMIN_ANY_DB) { + addUserAdminAnyDbPrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_DB_ADMIN_ANY_DB) { + addDbAdminAnyDbPrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_CLUSTER_MONITOR) { + addClusterMonitorPrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_HOST_MANAGEMENT) { + addHostManagerPrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_CLUSTER_MANAGEMENT) { + addClusterManagerPrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_CLUSTER_ADMIN) { + addClusterAdminPrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_BACKUP) { + addBackupPrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_RESTORE) { + addRestorePrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_ROOT) { + addRootRolePrivileges(result); + } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_INTERNAL) { + addInternalRolePrivileges(result); + } else { + return false; } + return true; +} - void addRestorePrivileges(PrivilegeVector* privileges) { - ActionSet actions; - actions - << ActionType::bypassDocumentValidation - << ActionType::collMod - << ActionType::createCollection - << ActionType::createIndex - << ActionType::dropCollection - << ActionType::insert - ; - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), actions)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.js"), actions)); - - // Need to be able to query system.namespaces to check existing collection options. - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.namespaces"), - ActionType::find)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyResource(), ActionType::listCollections)); - - // Privileges for user/role management - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), userAdminRoleActions)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::defaultTempUsersCollectionNamespace), - ActionType::find)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::defaultTempRolesCollectionNamespace), - ActionType::find)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::usersAltCollectionNamespace), - actions)); - - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::usersBackupCollectionNamespace), - actions)); - - actions << ActionType::find; - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege( - ResourcePattern::forExactNamespace( - AuthorizationManager::versionCollectionNamespace), - actions)); - - // Need additional actions on system.users. - actions << ActionType::update << ActionType::remove; - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forCollectionName("system.users"), actions)); - - // Need to be able to run getParameter to check authSchemaVersion - Privilege::addPrivilegeToPrivilegeVector( - privileges, Privilege(ResourcePattern::forClusterResource(), - ActionType::getParameter)); - - // Need to be able to create an index on the system.roles collection. - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace( - AuthorizationManager::rolesCollectionNamespace), - ActionType::createIndex)); - } +void RoleGraph::generateUniversalPrivileges(PrivilegeVector* privileges) { + ActionSet allActions; + allActions.addAllActions(); + privileges->push_back(Privilege(ResourcePattern::forAnyResource(), allActions)); +} - void addRootRolePrivileges(PrivilegeVector* privileges) { - addClusterAdminPrivileges(privileges); - addUserAdminAnyDbPrivileges(privileges); - addDbAdminAnyDbPrivileges(privileges); - addReadWriteAnyDbPrivileges(privileges); +bool RoleGraph::isBuiltinRole(const RoleName& role) { + if (!NamespaceString::validDBName(role.getDB()) || role.getDB() == "$external") { + return false; } - void addInternalRolePrivileges(PrivilegeVector* privileges) { - RoleGraph::generateUniversalPrivileges(privileges); - } + bool isAdminDB = role.getDB() == ADMIN_DBNAME; -} // namespace - - bool RoleGraph::addPrivilegesForBuiltinRole(const RoleName& roleName, - PrivilegeVector* result) { - const bool isAdminDB = (roleName.getDB() == ADMIN_DBNAME); - - if (roleName.getRole() == BUILTIN_ROLE_READ) { - addReadOnlyDbPrivileges(result, roleName.getDB()); - } - else if (roleName.getRole() == BUILTIN_ROLE_READ_WRITE) { - addReadWriteDbPrivileges(result, roleName.getDB()); - } - else if (roleName.getRole() == BUILTIN_ROLE_USER_ADMIN) { - addUserAdminDbPrivileges(result, roleName.getDB()); - } - else if (roleName.getRole() == BUILTIN_ROLE_DB_ADMIN) { - addDbAdminDbPrivileges(result, roleName.getDB()); - } - else if (roleName.getRole() == BUILTIN_ROLE_DB_OWNER) { - addDbOwnerPrivileges(result, roleName.getDB()); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_READ_ANY_DB) { - addReadOnlyAnyDbPrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_READ_WRITE_ANY_DB) { - addReadWriteAnyDbPrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_USER_ADMIN_ANY_DB) { - addUserAdminAnyDbPrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_DB_ADMIN_ANY_DB) { - addDbAdminAnyDbPrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_CLUSTER_MONITOR) { - addClusterMonitorPrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_HOST_MANAGEMENT) { - addHostManagerPrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_CLUSTER_MANAGEMENT) { - addClusterManagerPrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_CLUSTER_ADMIN) { - addClusterAdminPrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_BACKUP) { - addBackupPrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_RESTORE) { - addRestorePrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_ROOT) { - addRootRolePrivileges(result); - } - else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_INTERNAL) { - addInternalRolePrivileges(result); - } - else { - return false; - } + if (role.getRole() == BUILTIN_ROLE_READ) { + return true; + } else if (role.getRole() == BUILTIN_ROLE_READ_WRITE) { + return true; + } else if (role.getRole() == BUILTIN_ROLE_USER_ADMIN) { + return true; + } else if (role.getRole() == BUILTIN_ROLE_DB_ADMIN) { + return true; + } else if (role.getRole() == BUILTIN_ROLE_DB_OWNER) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_READ_ANY_DB) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_READ_WRITE_ANY_DB) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_USER_ADMIN_ANY_DB) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_DB_ADMIN_ANY_DB) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_CLUSTER_MONITOR) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_HOST_MANAGEMENT) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_CLUSTER_MANAGEMENT) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_CLUSTER_ADMIN) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_BACKUP) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_RESTORE) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_ROOT) { + return true; + } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_INTERNAL) { return true; } - - void RoleGraph::generateUniversalPrivileges(PrivilegeVector* privileges) { - ActionSet allActions; - allActions.addAllActions(); - privileges->push_back(Privilege(ResourcePattern::forAnyResource(), allActions)); - } - - bool RoleGraph::isBuiltinRole(const RoleName& role) { - if (!NamespaceString::validDBName(role.getDB()) || role.getDB() == "$external") { - return false; - } - - bool isAdminDB = role.getDB() == ADMIN_DBNAME; - - if (role.getRole() == BUILTIN_ROLE_READ) { - return true; - } - else if (role.getRole() == BUILTIN_ROLE_READ_WRITE) { - return true; - } - else if (role.getRole() == BUILTIN_ROLE_USER_ADMIN) { - return true; - } - else if (role.getRole() == BUILTIN_ROLE_DB_ADMIN) { - return true; - } - else if (role.getRole() == BUILTIN_ROLE_DB_OWNER) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_READ_ANY_DB) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_READ_WRITE_ANY_DB) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_USER_ADMIN_ANY_DB) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_DB_ADMIN_ANY_DB) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_CLUSTER_MONITOR) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_HOST_MANAGEMENT) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_CLUSTER_MANAGEMENT) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_CLUSTER_ADMIN) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_BACKUP) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_RESTORE) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_ROOT) { - return true; - } - else if (isAdminDB && role.getRole() == BUILTIN_ROLE_INTERNAL) { - return true; - } - return false; + return false; +} + +void RoleGraph::_createBuiltinRolesForDBIfNeeded(const std::string& dbname) { + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_WRITE, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_USER_ADMIN, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_ADMIN, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_OWNER, dbname)); + + if (dbname == "admin") { + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_ANY_DB, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_WRITE_ANY_DB, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_USER_ADMIN_ANY_DB, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_ADMIN_ANY_DB, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_MONITOR, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_HOST_MANAGEMENT, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_MANAGEMENT, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_ADMIN, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_BACKUP, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_RESTORE, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_ROOT, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_INTERNAL, dbname)); } +} - void RoleGraph::_createBuiltinRolesForDBIfNeeded(const std::string& dbname) { - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_WRITE, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_USER_ADMIN, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_ADMIN, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_OWNER, dbname)); - - if (dbname == "admin") { - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_ANY_DB, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_WRITE_ANY_DB, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_USER_ADMIN_ANY_DB, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_ADMIN_ANY_DB, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_MONITOR, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_HOST_MANAGEMENT, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_MANAGEMENT, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_ADMIN, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_BACKUP, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_RESTORE, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_ROOT, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_INTERNAL, dbname)); - } +void RoleGraph::_createBuiltinRoleIfNeeded(const RoleName& role) { + if (!isBuiltinRole(role) || _roleExistsDontCreateBuiltin(role)) { + return; } - void RoleGraph::_createBuiltinRoleIfNeeded(const RoleName& role) { - if (!isBuiltinRole(role) || _roleExistsDontCreateBuiltin(role)) { - return; - } - - _createRoleDontCheckIfRoleExists(role); - PrivilegeVector privileges; - fassert(17145, addPrivilegesForBuiltinRole(role, &privileges)); - for (size_t i = 0; i < privileges.size(); ++i) { - _addPrivilegeToRoleNoChecks(role, privileges[i]); - _allPrivilegesForRole[role].push_back(privileges[i]); - } + _createRoleDontCheckIfRoleExists(role); + PrivilegeVector privileges; + fassert(17145, addPrivilegesForBuiltinRole(role, &privileges)); + for (size_t i = 0; i < privileges.size(); ++i) { + _addPrivilegeToRoleNoChecks(role, privileges[i]); + _allPrivilegesForRole[role].push_back(privileges[i]); } +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/role_graph_test.cpp b/src/mongo/db/auth/role_graph_test.cpp index 4e99f584276..745207c4b6d 100644 --- a/src/mongo/db/auth/role_graph_test.cpp +++ b/src/mongo/db/auth/role_graph_test.cpp @@ -40,693 +40,696 @@ namespace mongo { namespace { - // Tests adding and removing roles from other roles, the RoleNameIterator, and the - // getDirectMembers and getDirectSubordinates methods - TEST(RoleGraphTest, AddRemoveRoles) { - RoleName roleA("roleA", "dbA"); - RoleName roleB("roleB", "dbB"); - RoleName roleC("roleC", "dbC"); - RoleName roleD("readWrite", "dbD"); // built-in role - - RoleGraph graph; - ASSERT_OK(graph.createRole(roleA)); - ASSERT_OK(graph.createRole(roleB)); - ASSERT_OK(graph.createRole(roleC)); - - RoleNameIterator it; - it = graph.getDirectSubordinates(roleA); - ASSERT_FALSE(it.more()); - it = graph.getDirectMembers(roleA); - ASSERT_FALSE(it.more()); - - ASSERT_OK(graph.addRoleToRole(roleA, roleB)); - - // A -> B - it = graph.getDirectSubordinates(roleA); - ASSERT_TRUE(it.more()); - // should not advance the iterator - ASSERT_EQUALS(it.get().getFullName(), roleB.getFullName()); - ASSERT_EQUALS(it.get().getFullName(), roleB.getFullName()); +// Tests adding and removing roles from other roles, the RoleNameIterator, and the +// getDirectMembers and getDirectSubordinates methods +TEST(RoleGraphTest, AddRemoveRoles) { + RoleName roleA("roleA", "dbA"); + RoleName roleB("roleB", "dbB"); + RoleName roleC("roleC", "dbC"); + RoleName roleD("readWrite", "dbD"); // built-in role + + RoleGraph graph; + ASSERT_OK(graph.createRole(roleA)); + ASSERT_OK(graph.createRole(roleB)); + ASSERT_OK(graph.createRole(roleC)); + + RoleNameIterator it; + it = graph.getDirectSubordinates(roleA); + ASSERT_FALSE(it.more()); + it = graph.getDirectMembers(roleA); + ASSERT_FALSE(it.more()); + + ASSERT_OK(graph.addRoleToRole(roleA, roleB)); + + // A -> B + it = graph.getDirectSubordinates(roleA); + ASSERT_TRUE(it.more()); + // should not advance the iterator + ASSERT_EQUALS(it.get().getFullName(), roleB.getFullName()); + ASSERT_EQUALS(it.get().getFullName(), roleB.getFullName()); + ASSERT_EQUALS(it.next().getFullName(), roleB.getFullName()); + ASSERT_FALSE(it.more()); + + it = graph.getDirectMembers(roleA); + ASSERT_FALSE(it.more()); + + it = graph.getDirectMembers(roleB); + ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); + ASSERT_FALSE(it.more()); + + it = graph.getDirectSubordinates(roleB); + ASSERT_FALSE(it.more()); + + ASSERT_OK(graph.addRoleToRole(roleA, roleC)); + ASSERT_OK(graph.addRoleToRole(roleB, roleC)); + ASSERT_OK(graph.addRoleToRole(roleB, roleD)); + // Adding the same role twice should be a no-op, duplicate roles should be de-duped. + ASSERT_OK(graph.addRoleToRole(roleB, roleD)); + + /* + * Graph now looks like: + * A + * / \ + * v v + * B -> C + * | + * v + * D + */ + + + it = graph.getDirectSubordinates(roleA); // should be roleB and roleC, order doesn't matter + RoleName cur = it.next(); + if (cur == roleB) { + ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); + } else if (cur == roleC) { ASSERT_EQUALS(it.next().getFullName(), roleB.getFullName()); - ASSERT_FALSE(it.more()); - - it = graph.getDirectMembers(roleA); - ASSERT_FALSE(it.more()); - - it = graph.getDirectMembers(roleB); - ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); - ASSERT_FALSE(it.more()); - - it = graph.getDirectSubordinates(roleB); - ASSERT_FALSE(it.more()); - - ASSERT_OK(graph.addRoleToRole(roleA, roleC)); - ASSERT_OK(graph.addRoleToRole(roleB, roleC)); - ASSERT_OK(graph.addRoleToRole(roleB, roleD)); - // Adding the same role twice should be a no-op, duplicate roles should be de-duped. - ASSERT_OK(graph.addRoleToRole(roleB, roleD)); - - /* - * Graph now looks like: - * A - * / \ - * v v - * B -> C - * | - * v - * D - */ - - - it = graph.getDirectSubordinates(roleA); // should be roleB and roleC, order doesn't matter + } else { + FAIL(mongoutils::str::stream() << "unexpected role returned: " << cur.getFullName()); + } + ASSERT_FALSE(it.more()); + + ASSERT_OK(graph.recomputePrivilegeData()); + it = graph.getIndirectSubordinates(roleA); // should have roleB, roleC and roleD + bool hasB = false; + bool hasC = false; + bool hasD = false; + int num = 0; + while (it.more()) { + ++num; RoleName cur = it.next(); if (cur == roleB) { - ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); + hasB = true; } else if (cur == roleC) { - ASSERT_EQUALS(it.next().getFullName(), roleB.getFullName()); - } else { - FAIL(mongoutils::str::stream() << "unexpected role returned: " << cur.getFullName()); - } - ASSERT_FALSE(it.more()); - - ASSERT_OK(graph.recomputePrivilegeData()); - it = graph.getIndirectSubordinates(roleA); // should have roleB, roleC and roleD - bool hasB = false; - bool hasC = false; - bool hasD = false; - int num = 0; - while (it.more()) { - ++num; - RoleName cur = it.next(); - if (cur == roleB) { - hasB = true; - } else if (cur == roleC) { - hasC = true; - } else if (cur == roleD) { - hasD = true; - } else { - FAIL(mongoutils::str::stream() << "unexpected role returned: " << - cur.getFullName()); - } - } - ASSERT_EQUALS(3, num); - ASSERT(hasB); - ASSERT(hasC); - ASSERT(hasD); - - it = graph.getDirectSubordinates(roleB); // should be roleC and roleD, order doesn't matter - cur = it.next(); - if (cur == roleC) { - ASSERT_EQUALS(it.next().getFullName(), roleD.getFullName()); + hasC = true; } else if (cur == roleD) { - ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); - } else { - FAIL(mongoutils::str::stream() << "unexpected role returned: " << cur.getFullName()); - } - ASSERT_FALSE(it.more()); - - it = graph.getDirectSubordinates(roleC); - ASSERT_FALSE(it.more()); - - it = graph.getDirectMembers(roleA); - ASSERT_FALSE(it.more()); - - it = graph.getDirectMembers(roleB); - ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); - ASSERT_FALSE(it.more()); - - it = graph.getDirectMembers(roleC); // should be role A and role B, order doesn't matter - cur = it.next(); - if (cur == roleA) { - ASSERT_EQUALS(it.next().getFullName(), roleB.getFullName()); - } else if (cur == roleB) { - ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); + hasD = true; } else { FAIL(mongoutils::str::stream() << "unexpected role returned: " << cur.getFullName()); } - ASSERT_FALSE(it.more()); - - // Now remove roleD from roleB and make sure graph is update correctly - ASSERT_OK(graph.removeRoleFromRole(roleB, roleD)); - - /* - * Graph now looks like: - * A - * / \ - * v v - * B -> C - */ - it = graph.getDirectSubordinates(roleB); // should be just roleC - ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); - ASSERT_FALSE(it.more()); - - it = graph.getDirectSubordinates(roleD); // should be empty - ASSERT_FALSE(it.more()); - - - // Now delete roleB entirely and make sure that the other roles are updated properly - ASSERT_OK(graph.deleteRole(roleB)); - ASSERT_NOT_OK(graph.deleteRole(roleB)); - it = graph.getDirectSubordinates(roleA); - ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); - ASSERT_FALSE(it.more()); - it = graph.getDirectMembers(roleC); - ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); - ASSERT_FALSE(it.more()); } - - const ResourcePattern collectionAFooResource(ResourcePattern::forExactNamespace( - NamespaceString("dbA.foo"))); - const ResourcePattern db1Resource(ResourcePattern::forDatabaseName("db1")); - const ResourcePattern db2Resource(ResourcePattern::forDatabaseName("db2")); - const ResourcePattern dbAResource(ResourcePattern::forDatabaseName("dbA")); - const ResourcePattern dbBResource(ResourcePattern::forDatabaseName("dbB")); - const ResourcePattern dbCResource(ResourcePattern::forDatabaseName("dbC")); - const ResourcePattern dbDResource(ResourcePattern::forDatabaseName("dbD")); - const ResourcePattern dbResource(ResourcePattern::forDatabaseName("db")); - - // Tests that adding multiple privileges on the same resource correctly collapses those to one - // privilege - TEST(RoleGraphTest, AddPrivileges) { - RoleName roleA("roleA", "dbA"); - - RoleGraph graph; - ASSERT_OK(graph.createRole(roleA)); - - // Test adding a single privilege - ActionSet actions; - actions.addAction(ActionType::find); - ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(dbAResource, actions))); - - PrivilegeVector privileges = graph.getDirectPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - ASSERT_EQUALS(actions.toString(), privileges[0].getActions().toString()); - - // Add a privilege on a different resource - ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(collectionAFooResource, actions))); - privileges = graph.getDirectPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - ASSERT_EQUALS(actions.toString(), privileges[0].getActions().toString()); - ASSERT_EQUALS(collectionAFooResource, privileges[1].getResourcePattern()); - ASSERT_EQUALS(actions.toString(), privileges[1].getActions().toString()); - - - // Add different privileges on an existing resource and make sure they get de-duped - actions.removeAllActions(); - actions.addAction(ActionType::insert); - - PrivilegeVector privilegesToAdd; - privilegesToAdd.push_back(Privilege(dbAResource, actions)); - - actions.removeAllActions(); - actions.addAction(ActionType::update); - privilegesToAdd.push_back(Privilege(dbAResource, actions)); - - ASSERT_OK(graph.addPrivilegesToRole(roleA, privilegesToAdd)); - - privileges = graph.getDirectPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - ASSERT_NOT_EQUALS(actions.toString(), privileges[0].getActions().toString()); - actions.addAction(ActionType::find); - actions.addAction(ActionType::insert); - ASSERT_EQUALS(actions.toString(), privileges[0].getActions().toString()); - actions.removeAction(ActionType::insert); - actions.removeAction(ActionType::update); - ASSERT_EQUALS(collectionAFooResource, privileges[1].getResourcePattern()); - ASSERT_EQUALS(actions.toString(), privileges[1].getActions().toString()); - } - - // Tests that recomputePrivilegeData correctly detects cycles in the graph. - TEST(RoleGraphTest, DetectCycles) { - RoleName roleA("roleA", "dbA"); - RoleName roleB("roleB", "dbB"); - RoleName roleC("roleC", "dbC"); - RoleName roleD("roleD", "dbD"); - - RoleGraph graph; - ASSERT_OK(graph.createRole(roleA)); - ASSERT_OK(graph.createRole(roleB)); - ASSERT_OK(graph.createRole(roleC)); - ASSERT_OK(graph.createRole(roleD)); - - // Add a role to itself - ASSERT_OK(graph.recomputePrivilegeData()); - ASSERT_OK(graph.addRoleToRole(roleA, roleA)); - ASSERT_NOT_OK(graph.recomputePrivilegeData()); - ASSERT_OK(graph.removeRoleFromRole(roleA, roleA)); - ASSERT_OK(graph.recomputePrivilegeData()); - - ASSERT_OK(graph.addRoleToRole(roleA, roleB)); - ASSERT_OK(graph.recomputePrivilegeData()); - ASSERT_OK(graph.addRoleToRole(roleA, roleC)); - ASSERT_OK(graph.addRoleToRole(roleB, roleC)); - ASSERT_OK(graph.recomputePrivilegeData()); - /* - * Graph now looks like: - * A - * / \ - * v v - * B -> C - */ - ASSERT_OK(graph.addRoleToRole(roleC, roleD)); - ASSERT_OK(graph.addRoleToRole(roleD, roleB)); // Add a cycle - /* - * Graph now looks like: - * A - * / \ - * v v - * B -> C - * ^ / - * \ v - * D - */ - ASSERT_NOT_OK(graph.recomputePrivilegeData()); - ASSERT_OK(graph.removeRoleFromRole(roleD, roleB)); - ASSERT_OK(graph.recomputePrivilegeData()); - } - - // Tests that recomputePrivilegeData correctly updates transitive privilege data for all roles. - TEST(RoleGraphTest, RecomputePrivilegeData) { - // We create 4 roles and give each of them a unique privilege. After that the direct - // privileges for all the roles are not touched. The only thing that is changed is the - // role membership graph, and we test how that affects the set of all transitive privileges - // for each role. - RoleName roleA("roleA", "dbA"); - RoleName roleB("roleB", "dbB"); - RoleName roleC("roleC", "dbC"); - RoleName roleD("readWrite", "dbD"); // built-in role - - ActionSet actions; - actions.addAllActions(); - - RoleGraph graph; - ASSERT_OK(graph.createRole(roleA)); - ASSERT_OK(graph.createRole(roleB)); - ASSERT_OK(graph.createRole(roleC)); - - ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(dbAResource, actions))); - ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbBResource, actions))); - ASSERT_OK(graph.addPrivilegeToRole(roleC, Privilege(dbCResource, actions))); - - ASSERT_OK(graph.recomputePrivilegeData()); - - PrivilegeVector privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - - // At this point we have all 4 roles set up, each with their own privilege, but no - // roles have been granted to each other. - - ASSERT_OK(graph.addRoleToRole(roleA, roleB)); - // Role graph: A->B - ASSERT_OK(graph.recomputePrivilegeData()); - privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); - - // Add's roleC's privileges to roleB and make sure roleA gets them as well. - ASSERT_OK(graph.addRoleToRole(roleB, roleC)); - // Role graph: A->B->C - ASSERT_OK(graph.recomputePrivilegeData()); - privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(3), privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); - ASSERT_EQUALS(dbCResource, privileges[2].getResourcePattern()); - privileges = graph.getAllPrivileges(roleB); - ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); - ASSERT_EQUALS(dbBResource, privileges[0].getResourcePattern()); - ASSERT_EQUALS(dbCResource, privileges[1].getResourcePattern()); - - // Add's roleD's privileges to roleC and make sure that roleA and roleB get them as well. - ASSERT_OK(graph.addRoleToRole(roleC, roleD)); - // Role graph: A->B->C->D - ASSERT_OK(graph.recomputePrivilegeData()); - privileges = graph.getAllPrivileges(roleA); - const size_t readWriteRolePrivilegeCount = graph.getAllPrivileges(roleD).size(); - ASSERT_EQUALS(readWriteRolePrivilegeCount + 3, privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); - ASSERT_EQUALS(dbCResource, privileges[2].getResourcePattern()); - privileges = graph.getAllPrivileges(roleB); - ASSERT_EQUALS(readWriteRolePrivilegeCount + 2, privileges.size()); - ASSERT_EQUALS(dbBResource, privileges[0].getResourcePattern()); - ASSERT_EQUALS(dbCResource, privileges[1].getResourcePattern()); - privileges = graph.getAllPrivileges(roleC); - ASSERT_EQUALS(readWriteRolePrivilegeCount + 1, privileges.size()); - ASSERT_EQUALS(dbCResource, privileges[0].getResourcePattern()); - - // Remove roleC from roleB, make sure that roleA then loses both roleC's and roleD's - // privileges - ASSERT_OK(graph.removeRoleFromRole(roleB, roleC)); - // Role graph: A->B C->D - ASSERT_OK(graph.recomputePrivilegeData()); - privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); - privileges = graph.getAllPrivileges(roleB); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_EQUALS(dbBResource, privileges[0].getResourcePattern()); - privileges = graph.getAllPrivileges(roleC); - ASSERT_EQUALS(readWriteRolePrivilegeCount + 1, privileges.size()); - ASSERT_EQUALS(dbCResource, privileges[0].getResourcePattern()); - privileges = graph.getAllPrivileges(roleD); - ASSERT_EQUALS(readWriteRolePrivilegeCount, privileges.size()); - - // Make sure direct privileges were untouched - privileges = graph.getDirectPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - privileges = graph.getDirectPrivileges(roleB); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_EQUALS(dbBResource, privileges[0].getResourcePattern()); - privileges = graph.getDirectPrivileges(roleC); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_EQUALS(dbCResource, privileges[0].getResourcePattern()); - privileges = graph.getDirectPrivileges(roleD); - ASSERT_EQUALS(readWriteRolePrivilegeCount, privileges.size()); - } - - // Test that if you grant 1 role to another, then remove it and change it's privileges, then - // re-grant it, the receiving role sees the new privileges and not the old ones. - TEST(RoleGraphTest, ReAddRole) { - RoleName roleA("roleA", "dbA"); - RoleName roleB("roleB", "dbB"); - RoleName roleC("roleC", "dbC"); - - ActionSet actionsA, actionsB, actionsC; - actionsA.addAction(ActionType::find); - actionsB.addAction(ActionType::insert); - actionsC.addAction(ActionType::update); - - RoleGraph graph; - ASSERT_OK(graph.createRole(roleA)); - ASSERT_OK(graph.createRole(roleB)); - ASSERT_OK(graph.createRole(roleC)); - - ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(dbResource, actionsA))); - ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbResource, actionsB))); - ASSERT_OK(graph.addPrivilegeToRole(roleC, Privilege(dbResource, actionsC))); - - ASSERT_OK(graph.addRoleToRole(roleA, roleB)); - ASSERT_OK(graph.addRoleToRole(roleB, roleC)); // graph: A <- B <- C - - ASSERT_OK(graph.recomputePrivilegeData()); - - // roleA should have privileges from roleB and roleC - PrivilegeVector privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::insert)); - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::update)); - - // Now remove roleB from roleA. B still is a member of C, but A no longer should have - // privileges from B or C. - ASSERT_OK(graph.removeRoleFromRole(roleA, roleB)); - ASSERT_OK(graph.recomputePrivilegeData()); - - // roleA should no longer have the privileges from roleB or roleC - privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); - ASSERT_FALSE(privileges[0].getActions().contains(ActionType::insert)); - ASSERT_FALSE(privileges[0].getActions().contains(ActionType::update)); - - // Change the privileges that roleB grants - ASSERT_OK(graph.removeAllPrivilegesFromRole(roleB)); - ActionSet newActionsB; - newActionsB.addAction(ActionType::remove); - ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbResource, newActionsB))); - - // Grant roleB back to roleA, make sure roleA has roleB's new privilege but not its old one. - ASSERT_OK(graph.addRoleToRole(roleA, roleB)); - ASSERT_OK(graph.recomputePrivilegeData()); - - privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::update)); // should get roleC's actions again - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::remove)); // roleB should grant this to roleA - ASSERT_FALSE(privileges[0].getActions().contains(ActionType::insert)); // no roles have this action anymore - - // Now delete roleB completely. A should once again lose the privileges from both B and C. - ASSERT_OK(graph.deleteRole(roleB)); - ASSERT_OK(graph.recomputePrivilegeData()); - - // roleA should no longer have the privileges from roleB or roleC - privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); - ASSERT_FALSE(privileges[0].getActions().contains(ActionType::update)); - ASSERT_FALSE(privileges[0].getActions().contains(ActionType::remove)); - ASSERT_FALSE(privileges[0].getActions().contains(ActionType::insert)); - - // Now re-create roleB and give it a new privilege, then grant it back to roleA. - // RoleA should get its new privilege but not roleC's privilege this time nor either of - // roleB's old privileges. - ASSERT_OK(graph.createRole(roleB)); - actionsB.removeAllActions(); - actionsB.addAction(ActionType::shutdown); - ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbResource, actionsB))); - ASSERT_OK(graph.addRoleToRole(roleA, roleB)); - ASSERT_OK(graph.recomputePrivilegeData()); - - privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); - ASSERT_TRUE(privileges[0].getActions().contains(ActionType::shutdown)); - ASSERT_FALSE(privileges[0].getActions().contains(ActionType::update)); - ASSERT_FALSE(privileges[0].getActions().contains(ActionType::remove)); - ASSERT_FALSE(privileges[0].getActions().contains(ActionType::insert)); - } - - // Tests copy constructor and swap functionality. - TEST(RoleGraphTest, CopySwap) { - RoleName roleA("roleA", "dbA"); - RoleName roleB("roleB", "dbB"); - RoleName roleC("roleC", "dbC"); - - RoleGraph graph; - ASSERT_OK(graph.createRole(roleA)); - ASSERT_OK(graph.createRole(roleB)); - ASSERT_OK(graph.createRole(roleC)); - - ActionSet actions; - actions.addAction(ActionType::find); - ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(dbAResource, actions))); - ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbBResource, actions))); - ASSERT_OK(graph.addPrivilegeToRole(roleC, Privilege(dbCResource, actions))); - - ASSERT_OK(graph.addRoleToRole(roleA, roleB)); - - // Make a copy of the graph to do further modifications on. - RoleGraph tempGraph(graph); - ASSERT_OK(tempGraph.addRoleToRole(roleB, roleC)); - tempGraph.recomputePrivilegeData(); - - // Now swap the copy back with the original graph and make sure the original was updated - // properly. - swap(tempGraph, graph); - - RoleNameIterator it = graph.getDirectSubordinates(roleB); - ASSERT_TRUE(it.more()); + ASSERT_EQUALS(3, num); + ASSERT(hasB); + ASSERT(hasC); + ASSERT(hasD); + + it = graph.getDirectSubordinates(roleB); // should be roleC and roleD, order doesn't matter + cur = it.next(); + if (cur == roleC) { + ASSERT_EQUALS(it.next().getFullName(), roleD.getFullName()); + } else if (cur == roleD) { ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); - ASSERT_FALSE(it.more()); - - graph.getAllPrivileges(roleA); // should have privileges from roleB *and* role C - PrivilegeVector privileges = graph.getAllPrivileges(roleA); - ASSERT_EQUALS(static_cast<size_t>(3), privileges.size()); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); - ASSERT_EQUALS(dbCResource, privileges[2].getResourcePattern()); + } else { + FAIL(mongoutils::str::stream() << "unexpected role returned: " << cur.getFullName()); } + ASSERT_FALSE(it.more()); - // Tests error handling - TEST(RoleGraphTest, ErrorHandling) { - RoleName roleA("roleA", "dbA"); - RoleName roleB("roleB", "dbB"); - RoleName roleC("roleC", "dbC"); - - ActionSet actions; - actions.addAction(ActionType::find); - Privilege privilege1(db1Resource, actions); - Privilege privilege2(db2Resource, actions); - PrivilegeVector privileges; - privileges.push_back(privilege1); - privileges.push_back(privilege2); - - RoleGraph graph; - // None of the roles exist yet. - ASSERT_NOT_OK(graph.addPrivilegeToRole(roleA, privilege1)); - ASSERT_NOT_OK(graph.addPrivilegesToRole(roleA, privileges)); - ASSERT_NOT_OK(graph.removePrivilegeFromRole(roleA, privilege1)); - ASSERT_NOT_OK(graph.removePrivilegesFromRole(roleA, privileges)); - ASSERT_NOT_OK(graph.removeAllPrivilegesFromRole(roleA)); - ASSERT_NOT_OK(graph.addRoleToRole(roleA, roleB)); - ASSERT_NOT_OK(graph.removeRoleFromRole(roleA, roleB)); - - // One of the roles exists - ASSERT_OK(graph.createRole(roleA)); - ASSERT_NOT_OK(graph.createRole(roleA)); // Can't create same role twice - ASSERT_NOT_OK(graph.addRoleToRole(roleA, roleB)); - ASSERT_NOT_OK(graph.addRoleToRole(roleB, roleA)); - ASSERT_NOT_OK(graph.removeRoleFromRole(roleA, roleB)); - ASSERT_NOT_OK(graph.removeRoleFromRole(roleB, roleA)); - - // Should work now that both exist. - ASSERT_OK(graph.createRole(roleB)); - ASSERT_OK(graph.addRoleToRole(roleA, roleB)); - ASSERT_OK(graph.removeRoleFromRole(roleA, roleB)); - ASSERT_NOT_OK(graph.removeRoleFromRole(roleA, roleB)); // roleA isn't actually a member of roleB - - // Can't remove a privilege from a role that doesn't have it. - ASSERT_NOT_OK(graph.removePrivilegeFromRole(roleA, privilege1)); - ASSERT_OK(graph.addPrivilegeToRole(roleA, privilege1)); - ASSERT_OK(graph.removePrivilegeFromRole(roleA, privilege1)); // now should work - - // Test that removing a vector of privileges fails if *any* of the privileges are missing. - ASSERT_OK(graph.addPrivilegeToRole(roleA, privilege1)); - ASSERT_OK(graph.addPrivilegeToRole(roleA, privilege2)); - // Removing both privileges should work since it has both - ASSERT_OK(graph.removePrivilegesFromRole(roleA, privileges)); - // Now add only 1 back and this time removing both should fail - ASSERT_OK(graph.addPrivilegeToRole(roleA, privilege1)); - ASSERT_NOT_OK(graph.removePrivilegesFromRole(roleA, privileges)); - } + it = graph.getDirectSubordinates(roleC); + ASSERT_FALSE(it.more()); + it = graph.getDirectMembers(roleA); + ASSERT_FALSE(it.more()); - TEST(RoleGraphTest, BuiltinRoles) { - RoleName userRole("userDefined", "dbA"); - RoleName builtinRole("read", "dbA"); - - ActionSet actions; - actions.addAction(ActionType::insert); - Privilege privilege(dbAResource, actions); - - RoleGraph graph; - - ASSERT(graph.roleExists(builtinRole)); - ASSERT_NOT_OK(graph.createRole(builtinRole)); - ASSERT_NOT_OK(graph.deleteRole(builtinRole)); - ASSERT(graph.roleExists(builtinRole)); - ASSERT(!graph.roleExists(userRole)); - ASSERT_OK(graph.createRole(userRole)); - ASSERT(graph.roleExists(userRole)); - - ASSERT_NOT_OK(graph.addPrivilegeToRole(builtinRole, privilege)); - ASSERT_NOT_OK(graph.removePrivilegeFromRole(builtinRole, privilege)); - ASSERT_NOT_OK(graph.addRoleToRole(builtinRole, userRole)); - ASSERT_NOT_OK(graph.removeRoleFromRole(builtinRole, userRole)); - - ASSERT_OK(graph.addPrivilegeToRole(userRole, privilege)); - ASSERT_OK(graph.addRoleToRole(userRole, builtinRole)); - ASSERT_OK(graph.recomputePrivilegeData()); - - PrivilegeVector privileges = graph.getDirectPrivileges(userRole); - ASSERT_EQUALS(1U, privileges.size()); - ASSERT(privileges[0].getActions().equals(actions)); - ASSERT(!privileges[0].getActions().contains(ActionType::find)); - ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); - - privileges = graph.getAllPrivileges(userRole); - size_t i; - for (i = 0; i < privileges.size(); ++i) { - if (dbAResource == privileges[i].getResourcePattern()) - break; - } - ASSERT_NOT_EQUALS(privileges.size(), i); - ASSERT(privileges[i].getActions().isSupersetOf(actions)); - ASSERT(privileges[i].getActions().contains(ActionType::insert)); - ASSERT(privileges[i].getActions().contains(ActionType::find)); + it = graph.getDirectMembers(roleB); + ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); + ASSERT_FALSE(it.more()); - ASSERT_OK(graph.deleteRole(userRole)); - ASSERT(!graph.roleExists(userRole)); - } - - TEST(RoleGraphTest, BuiltinRolesOnlyOnAppropriateDatabases) { - RoleGraph graph; - ASSERT(graph.roleExists(RoleName("read", "test"))); - ASSERT(graph.roleExists(RoleName("readWrite", "test"))); - ASSERT(graph.roleExists(RoleName("userAdmin", "test"))); - ASSERT(graph.roleExists(RoleName("dbAdmin", "test"))); - ASSERT(graph.roleExists(RoleName("dbOwner", "test"))); - ASSERT(!graph.roleExists(RoleName("readAnyDatabase", "test"))); - ASSERT(!graph.roleExists(RoleName("readWriteAnyDatabase", "test"))); - ASSERT(!graph.roleExists(RoleName("userAdminAnyDatabase", "test"))); - ASSERT(!graph.roleExists(RoleName("dbAdminAnyDatabase", "test"))); - ASSERT(!graph.roleExists(RoleName("clusterAdmin", "test"))); - ASSERT(!graph.roleExists(RoleName("root", "test"))); - ASSERT(!graph.roleExists(RoleName("__system", "test"))); - ASSERT(!graph.roleExists(RoleName("MyRole", "test"))); - - ASSERT(graph.roleExists(RoleName("read", "admin"))); - ASSERT(graph.roleExists(RoleName("readWrite", "admin"))); - ASSERT(graph.roleExists(RoleName("userAdmin", "admin"))); - ASSERT(graph.roleExists(RoleName("dbAdmin", "admin"))); - ASSERT(graph.roleExists(RoleName("dbOwner", "admin"))); - ASSERT(graph.roleExists(RoleName("readAnyDatabase", "admin"))); - ASSERT(graph.roleExists(RoleName("readWriteAnyDatabase", "admin"))); - ASSERT(graph.roleExists(RoleName("userAdminAnyDatabase", "admin"))); - ASSERT(graph.roleExists(RoleName("dbAdminAnyDatabase", "admin"))); - ASSERT(graph.roleExists(RoleName("clusterAdmin", "admin"))); - ASSERT(graph.roleExists(RoleName("root", "admin"))); - ASSERT(graph.roleExists(RoleName("__system", "admin"))); - ASSERT(!graph.roleExists(RoleName("MyRole", "admin"))); + it = graph.getDirectMembers(roleC); // should be role A and role B, order doesn't matter + cur = it.next(); + if (cur == roleA) { + ASSERT_EQUALS(it.next().getFullName(), roleB.getFullName()); + } else if (cur == roleB) { + ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); + } else { + FAIL(mongoutils::str::stream() << "unexpected role returned: " << cur.getFullName()); } - - TEST(RoleGraphTest, getRolesForDatabase) { - RoleGraph graph; - graph.createRole(RoleName("myRole", "test")); - // Make sure that a role on "test2" doesn't show up in the roles list for "test" - graph.createRole(RoleName("anotherRole", "test2")); - graph.createRole(RoleName("myAdminRole", "admin")); - - // Non-admin DB with no user-defined roles - RoleNameIterator it = graph.getRolesForDatabase("fakedb"); - ASSERT_EQUALS(RoleName("dbAdmin", "fakedb"), it.next()); - ASSERT_EQUALS(RoleName("dbOwner", "fakedb"), it.next()); - ASSERT_EQUALS(RoleName("read", "fakedb"), it.next()); - ASSERT_EQUALS(RoleName("readWrite", "fakedb"), it.next()); - ASSERT_EQUALS(RoleName("userAdmin", "fakedb"), it.next()); - ASSERT_FALSE(it.more()); - - // Non-admin DB with a user-defined role - it = graph.getRolesForDatabase("test"); - ASSERT_EQUALS(RoleName("dbAdmin", "test"), it.next()); - ASSERT_EQUALS(RoleName("dbOwner", "test"), it.next()); - ASSERT_EQUALS(RoleName("myRole", "test"), it.next()); - ASSERT_EQUALS(RoleName("read", "test"), it.next()); - ASSERT_EQUALS(RoleName("readWrite", "test"), it.next()); - ASSERT_EQUALS(RoleName("userAdmin", "test"), it.next()); - ASSERT_FALSE(it.more()); - - // Admin DB - it = graph.getRolesForDatabase("admin"); - ASSERT_EQUALS(RoleName("__system", "admin"), it.next()); - ASSERT_EQUALS(RoleName("backup", "admin"), it.next()); - ASSERT_EQUALS(RoleName("clusterAdmin", "admin"), it.next()); - ASSERT_EQUALS(RoleName("clusterManager", "admin"), it.next()); - ASSERT_EQUALS(RoleName("clusterMonitor", "admin"), it.next()); - ASSERT_EQUALS(RoleName("dbAdmin", "admin"), it.next()); - ASSERT_EQUALS(RoleName("dbAdminAnyDatabase", "admin"), it.next()); - ASSERT_EQUALS(RoleName("dbOwner", "admin"), it.next()); - ASSERT_EQUALS(RoleName("hostManager", "admin"), it.next()); - ASSERT_EQUALS(RoleName("myAdminRole", "admin"), it.next()); - ASSERT_EQUALS(RoleName("read", "admin"), it.next()); - ASSERT_EQUALS(RoleName("readAnyDatabase", "admin"), it.next()); - ASSERT_EQUALS(RoleName("readWrite", "admin"), it.next()); - ASSERT_EQUALS(RoleName("readWriteAnyDatabase", "admin"), it.next()); - ASSERT_EQUALS(RoleName("restore", "admin"), it.next()); - ASSERT_EQUALS(RoleName("root", "admin"), it.next()); - ASSERT_EQUALS(RoleName("userAdmin", "admin"), it.next()); - ASSERT_EQUALS(RoleName("userAdminAnyDatabase", "admin"), it.next()); - ASSERT_FALSE(it.more()); + ASSERT_FALSE(it.more()); + + // Now remove roleD from roleB and make sure graph is update correctly + ASSERT_OK(graph.removeRoleFromRole(roleB, roleD)); + + /* + * Graph now looks like: + * A + * / \ + * v v + * B -> C + */ + it = graph.getDirectSubordinates(roleB); // should be just roleC + ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); + ASSERT_FALSE(it.more()); + + it = graph.getDirectSubordinates(roleD); // should be empty + ASSERT_FALSE(it.more()); + + + // Now delete roleB entirely and make sure that the other roles are updated properly + ASSERT_OK(graph.deleteRole(roleB)); + ASSERT_NOT_OK(graph.deleteRole(roleB)); + it = graph.getDirectSubordinates(roleA); + ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); + ASSERT_FALSE(it.more()); + it = graph.getDirectMembers(roleC); + ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); + ASSERT_FALSE(it.more()); +} + +const ResourcePattern collectionAFooResource( + ResourcePattern::forExactNamespace(NamespaceString("dbA.foo"))); +const ResourcePattern db1Resource(ResourcePattern::forDatabaseName("db1")); +const ResourcePattern db2Resource(ResourcePattern::forDatabaseName("db2")); +const ResourcePattern dbAResource(ResourcePattern::forDatabaseName("dbA")); +const ResourcePattern dbBResource(ResourcePattern::forDatabaseName("dbB")); +const ResourcePattern dbCResource(ResourcePattern::forDatabaseName("dbC")); +const ResourcePattern dbDResource(ResourcePattern::forDatabaseName("dbD")); +const ResourcePattern dbResource(ResourcePattern::forDatabaseName("db")); + +// Tests that adding multiple privileges on the same resource correctly collapses those to one +// privilege +TEST(RoleGraphTest, AddPrivileges) { + RoleName roleA("roleA", "dbA"); + + RoleGraph graph; + ASSERT_OK(graph.createRole(roleA)); + + // Test adding a single privilege + ActionSet actions; + actions.addAction(ActionType::find); + ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(dbAResource, actions))); + + PrivilegeVector privileges = graph.getDirectPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + ASSERT_EQUALS(actions.toString(), privileges[0].getActions().toString()); + + // Add a privilege on a different resource + ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(collectionAFooResource, actions))); + privileges = graph.getDirectPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + ASSERT_EQUALS(actions.toString(), privileges[0].getActions().toString()); + ASSERT_EQUALS(collectionAFooResource, privileges[1].getResourcePattern()); + ASSERT_EQUALS(actions.toString(), privileges[1].getActions().toString()); + + + // Add different privileges on an existing resource and make sure they get de-duped + actions.removeAllActions(); + actions.addAction(ActionType::insert); + + PrivilegeVector privilegesToAdd; + privilegesToAdd.push_back(Privilege(dbAResource, actions)); + + actions.removeAllActions(); + actions.addAction(ActionType::update); + privilegesToAdd.push_back(Privilege(dbAResource, actions)); + + ASSERT_OK(graph.addPrivilegesToRole(roleA, privilegesToAdd)); + + privileges = graph.getDirectPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + ASSERT_NOT_EQUALS(actions.toString(), privileges[0].getActions().toString()); + actions.addAction(ActionType::find); + actions.addAction(ActionType::insert); + ASSERT_EQUALS(actions.toString(), privileges[0].getActions().toString()); + actions.removeAction(ActionType::insert); + actions.removeAction(ActionType::update); + ASSERT_EQUALS(collectionAFooResource, privileges[1].getResourcePattern()); + ASSERT_EQUALS(actions.toString(), privileges[1].getActions().toString()); +} + +// Tests that recomputePrivilegeData correctly detects cycles in the graph. +TEST(RoleGraphTest, DetectCycles) { + RoleName roleA("roleA", "dbA"); + RoleName roleB("roleB", "dbB"); + RoleName roleC("roleC", "dbC"); + RoleName roleD("roleD", "dbD"); + + RoleGraph graph; + ASSERT_OK(graph.createRole(roleA)); + ASSERT_OK(graph.createRole(roleB)); + ASSERT_OK(graph.createRole(roleC)); + ASSERT_OK(graph.createRole(roleD)); + + // Add a role to itself + ASSERT_OK(graph.recomputePrivilegeData()); + ASSERT_OK(graph.addRoleToRole(roleA, roleA)); + ASSERT_NOT_OK(graph.recomputePrivilegeData()); + ASSERT_OK(graph.removeRoleFromRole(roleA, roleA)); + ASSERT_OK(graph.recomputePrivilegeData()); + + ASSERT_OK(graph.addRoleToRole(roleA, roleB)); + ASSERT_OK(graph.recomputePrivilegeData()); + ASSERT_OK(graph.addRoleToRole(roleA, roleC)); + ASSERT_OK(graph.addRoleToRole(roleB, roleC)); + ASSERT_OK(graph.recomputePrivilegeData()); + /* + * Graph now looks like: + * A + * / \ + * v v + * B -> C + */ + ASSERT_OK(graph.addRoleToRole(roleC, roleD)); + ASSERT_OK(graph.addRoleToRole(roleD, roleB)); // Add a cycle + /* + * Graph now looks like: + * A + * / \ + * v v + * B -> C + * ^ / + * \ v + * D + */ + ASSERT_NOT_OK(graph.recomputePrivilegeData()); + ASSERT_OK(graph.removeRoleFromRole(roleD, roleB)); + ASSERT_OK(graph.recomputePrivilegeData()); +} + +// Tests that recomputePrivilegeData correctly updates transitive privilege data for all roles. +TEST(RoleGraphTest, RecomputePrivilegeData) { + // We create 4 roles and give each of them a unique privilege. After that the direct + // privileges for all the roles are not touched. The only thing that is changed is the + // role membership graph, and we test how that affects the set of all transitive privileges + // for each role. + RoleName roleA("roleA", "dbA"); + RoleName roleB("roleB", "dbB"); + RoleName roleC("roleC", "dbC"); + RoleName roleD("readWrite", "dbD"); // built-in role + + ActionSet actions; + actions.addAllActions(); + + RoleGraph graph; + ASSERT_OK(graph.createRole(roleA)); + ASSERT_OK(graph.createRole(roleB)); + ASSERT_OK(graph.createRole(roleC)); + + ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(dbAResource, actions))); + ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbBResource, actions))); + ASSERT_OK(graph.addPrivilegeToRole(roleC, Privilege(dbCResource, actions))); + + ASSERT_OK(graph.recomputePrivilegeData()); + + PrivilegeVector privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + + // At this point we have all 4 roles set up, each with their own privilege, but no + // roles have been granted to each other. + + ASSERT_OK(graph.addRoleToRole(roleA, roleB)); + // Role graph: A->B + ASSERT_OK(graph.recomputePrivilegeData()); + privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); + + // Add's roleC's privileges to roleB and make sure roleA gets them as well. + ASSERT_OK(graph.addRoleToRole(roleB, roleC)); + // Role graph: A->B->C + ASSERT_OK(graph.recomputePrivilegeData()); + privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(3), privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); + ASSERT_EQUALS(dbCResource, privileges[2].getResourcePattern()); + privileges = graph.getAllPrivileges(roleB); + ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); + ASSERT_EQUALS(dbBResource, privileges[0].getResourcePattern()); + ASSERT_EQUALS(dbCResource, privileges[1].getResourcePattern()); + + // Add's roleD's privileges to roleC and make sure that roleA and roleB get them as well. + ASSERT_OK(graph.addRoleToRole(roleC, roleD)); + // Role graph: A->B->C->D + ASSERT_OK(graph.recomputePrivilegeData()); + privileges = graph.getAllPrivileges(roleA); + const size_t readWriteRolePrivilegeCount = graph.getAllPrivileges(roleD).size(); + ASSERT_EQUALS(readWriteRolePrivilegeCount + 3, privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); + ASSERT_EQUALS(dbCResource, privileges[2].getResourcePattern()); + privileges = graph.getAllPrivileges(roleB); + ASSERT_EQUALS(readWriteRolePrivilegeCount + 2, privileges.size()); + ASSERT_EQUALS(dbBResource, privileges[0].getResourcePattern()); + ASSERT_EQUALS(dbCResource, privileges[1].getResourcePattern()); + privileges = graph.getAllPrivileges(roleC); + ASSERT_EQUALS(readWriteRolePrivilegeCount + 1, privileges.size()); + ASSERT_EQUALS(dbCResource, privileges[0].getResourcePattern()); + + // Remove roleC from roleB, make sure that roleA then loses both roleC's and roleD's + // privileges + ASSERT_OK(graph.removeRoleFromRole(roleB, roleC)); + // Role graph: A->B C->D + ASSERT_OK(graph.recomputePrivilegeData()); + privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(2), privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); + privileges = graph.getAllPrivileges(roleB); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_EQUALS(dbBResource, privileges[0].getResourcePattern()); + privileges = graph.getAllPrivileges(roleC); + ASSERT_EQUALS(readWriteRolePrivilegeCount + 1, privileges.size()); + ASSERT_EQUALS(dbCResource, privileges[0].getResourcePattern()); + privileges = graph.getAllPrivileges(roleD); + ASSERT_EQUALS(readWriteRolePrivilegeCount, privileges.size()); + + // Make sure direct privileges were untouched + privileges = graph.getDirectPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + privileges = graph.getDirectPrivileges(roleB); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_EQUALS(dbBResource, privileges[0].getResourcePattern()); + privileges = graph.getDirectPrivileges(roleC); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_EQUALS(dbCResource, privileges[0].getResourcePattern()); + privileges = graph.getDirectPrivileges(roleD); + ASSERT_EQUALS(readWriteRolePrivilegeCount, privileges.size()); +} + +// Test that if you grant 1 role to another, then remove it and change it's privileges, then +// re-grant it, the receiving role sees the new privileges and not the old ones. +TEST(RoleGraphTest, ReAddRole) { + RoleName roleA("roleA", "dbA"); + RoleName roleB("roleB", "dbB"); + RoleName roleC("roleC", "dbC"); + + ActionSet actionsA, actionsB, actionsC; + actionsA.addAction(ActionType::find); + actionsB.addAction(ActionType::insert); + actionsC.addAction(ActionType::update); + + RoleGraph graph; + ASSERT_OK(graph.createRole(roleA)); + ASSERT_OK(graph.createRole(roleB)); + ASSERT_OK(graph.createRole(roleC)); + + ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(dbResource, actionsA))); + ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbResource, actionsB))); + ASSERT_OK(graph.addPrivilegeToRole(roleC, Privilege(dbResource, actionsC))); + + ASSERT_OK(graph.addRoleToRole(roleA, roleB)); + ASSERT_OK(graph.addRoleToRole(roleB, roleC)); // graph: A <- B <- C + + ASSERT_OK(graph.recomputePrivilegeData()); + + // roleA should have privileges from roleB and roleC + PrivilegeVector privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); + ASSERT_TRUE(privileges[0].getActions().contains(ActionType::insert)); + ASSERT_TRUE(privileges[0].getActions().contains(ActionType::update)); + + // Now remove roleB from roleA. B still is a member of C, but A no longer should have + // privileges from B or C. + ASSERT_OK(graph.removeRoleFromRole(roleA, roleB)); + ASSERT_OK(graph.recomputePrivilegeData()); + + // roleA should no longer have the privileges from roleB or roleC + privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); + ASSERT_FALSE(privileges[0].getActions().contains(ActionType::insert)); + ASSERT_FALSE(privileges[0].getActions().contains(ActionType::update)); + + // Change the privileges that roleB grants + ASSERT_OK(graph.removeAllPrivilegesFromRole(roleB)); + ActionSet newActionsB; + newActionsB.addAction(ActionType::remove); + ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbResource, newActionsB))); + + // Grant roleB back to roleA, make sure roleA has roleB's new privilege but not its old one. + ASSERT_OK(graph.addRoleToRole(roleA, roleB)); + ASSERT_OK(graph.recomputePrivilegeData()); + + privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); + ASSERT_TRUE(privileges[0].getActions().contains( + ActionType::update)); // should get roleC's actions again + ASSERT_TRUE(privileges[0].getActions().contains( + ActionType::remove)); // roleB should grant this to roleA + ASSERT_FALSE(privileges[0].getActions().contains( + ActionType::insert)); // no roles have this action anymore + + // Now delete roleB completely. A should once again lose the privileges from both B and C. + ASSERT_OK(graph.deleteRole(roleB)); + ASSERT_OK(graph.recomputePrivilegeData()); + + // roleA should no longer have the privileges from roleB or roleC + privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); + ASSERT_FALSE(privileges[0].getActions().contains(ActionType::update)); + ASSERT_FALSE(privileges[0].getActions().contains(ActionType::remove)); + ASSERT_FALSE(privileges[0].getActions().contains(ActionType::insert)); + + // Now re-create roleB and give it a new privilege, then grant it back to roleA. + // RoleA should get its new privilege but not roleC's privilege this time nor either of + // roleB's old privileges. + ASSERT_OK(graph.createRole(roleB)); + actionsB.removeAllActions(); + actionsB.addAction(ActionType::shutdown); + ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbResource, actionsB))); + ASSERT_OK(graph.addRoleToRole(roleA, roleB)); + ASSERT_OK(graph.recomputePrivilegeData()); + + privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(1), privileges.size()); + ASSERT_TRUE(privileges[0].getActions().contains(ActionType::find)); + ASSERT_TRUE(privileges[0].getActions().contains(ActionType::shutdown)); + ASSERT_FALSE(privileges[0].getActions().contains(ActionType::update)); + ASSERT_FALSE(privileges[0].getActions().contains(ActionType::remove)); + ASSERT_FALSE(privileges[0].getActions().contains(ActionType::insert)); +} + +// Tests copy constructor and swap functionality. +TEST(RoleGraphTest, CopySwap) { + RoleName roleA("roleA", "dbA"); + RoleName roleB("roleB", "dbB"); + RoleName roleC("roleC", "dbC"); + + RoleGraph graph; + ASSERT_OK(graph.createRole(roleA)); + ASSERT_OK(graph.createRole(roleB)); + ASSERT_OK(graph.createRole(roleC)); + + ActionSet actions; + actions.addAction(ActionType::find); + ASSERT_OK(graph.addPrivilegeToRole(roleA, Privilege(dbAResource, actions))); + ASSERT_OK(graph.addPrivilegeToRole(roleB, Privilege(dbBResource, actions))); + ASSERT_OK(graph.addPrivilegeToRole(roleC, Privilege(dbCResource, actions))); + + ASSERT_OK(graph.addRoleToRole(roleA, roleB)); + + // Make a copy of the graph to do further modifications on. + RoleGraph tempGraph(graph); + ASSERT_OK(tempGraph.addRoleToRole(roleB, roleC)); + tempGraph.recomputePrivilegeData(); + + // Now swap the copy back with the original graph and make sure the original was updated + // properly. + swap(tempGraph, graph); + + RoleNameIterator it = graph.getDirectSubordinates(roleB); + ASSERT_TRUE(it.more()); + ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); + ASSERT_FALSE(it.more()); + + graph.getAllPrivileges(roleA); // should have privileges from roleB *and* role C + PrivilegeVector privileges = graph.getAllPrivileges(roleA); + ASSERT_EQUALS(static_cast<size_t>(3), privileges.size()); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + ASSERT_EQUALS(dbBResource, privileges[1].getResourcePattern()); + ASSERT_EQUALS(dbCResource, privileges[2].getResourcePattern()); +} + +// Tests error handling +TEST(RoleGraphTest, ErrorHandling) { + RoleName roleA("roleA", "dbA"); + RoleName roleB("roleB", "dbB"); + RoleName roleC("roleC", "dbC"); + + ActionSet actions; + actions.addAction(ActionType::find); + Privilege privilege1(db1Resource, actions); + Privilege privilege2(db2Resource, actions); + PrivilegeVector privileges; + privileges.push_back(privilege1); + privileges.push_back(privilege2); + + RoleGraph graph; + // None of the roles exist yet. + ASSERT_NOT_OK(graph.addPrivilegeToRole(roleA, privilege1)); + ASSERT_NOT_OK(graph.addPrivilegesToRole(roleA, privileges)); + ASSERT_NOT_OK(graph.removePrivilegeFromRole(roleA, privilege1)); + ASSERT_NOT_OK(graph.removePrivilegesFromRole(roleA, privileges)); + ASSERT_NOT_OK(graph.removeAllPrivilegesFromRole(roleA)); + ASSERT_NOT_OK(graph.addRoleToRole(roleA, roleB)); + ASSERT_NOT_OK(graph.removeRoleFromRole(roleA, roleB)); + + // One of the roles exists + ASSERT_OK(graph.createRole(roleA)); + ASSERT_NOT_OK(graph.createRole(roleA)); // Can't create same role twice + ASSERT_NOT_OK(graph.addRoleToRole(roleA, roleB)); + ASSERT_NOT_OK(graph.addRoleToRole(roleB, roleA)); + ASSERT_NOT_OK(graph.removeRoleFromRole(roleA, roleB)); + ASSERT_NOT_OK(graph.removeRoleFromRole(roleB, roleA)); + + // Should work now that both exist. + ASSERT_OK(graph.createRole(roleB)); + ASSERT_OK(graph.addRoleToRole(roleA, roleB)); + ASSERT_OK(graph.removeRoleFromRole(roleA, roleB)); + ASSERT_NOT_OK( + graph.removeRoleFromRole(roleA, roleB)); // roleA isn't actually a member of roleB + + // Can't remove a privilege from a role that doesn't have it. + ASSERT_NOT_OK(graph.removePrivilegeFromRole(roleA, privilege1)); + ASSERT_OK(graph.addPrivilegeToRole(roleA, privilege1)); + ASSERT_OK(graph.removePrivilegeFromRole(roleA, privilege1)); // now should work + + // Test that removing a vector of privileges fails if *any* of the privileges are missing. + ASSERT_OK(graph.addPrivilegeToRole(roleA, privilege1)); + ASSERT_OK(graph.addPrivilegeToRole(roleA, privilege2)); + // Removing both privileges should work since it has both + ASSERT_OK(graph.removePrivilegesFromRole(roleA, privileges)); + // Now add only 1 back and this time removing both should fail + ASSERT_OK(graph.addPrivilegeToRole(roleA, privilege1)); + ASSERT_NOT_OK(graph.removePrivilegesFromRole(roleA, privileges)); +} + + +TEST(RoleGraphTest, BuiltinRoles) { + RoleName userRole("userDefined", "dbA"); + RoleName builtinRole("read", "dbA"); + + ActionSet actions; + actions.addAction(ActionType::insert); + Privilege privilege(dbAResource, actions); + + RoleGraph graph; + + ASSERT(graph.roleExists(builtinRole)); + ASSERT_NOT_OK(graph.createRole(builtinRole)); + ASSERT_NOT_OK(graph.deleteRole(builtinRole)); + ASSERT(graph.roleExists(builtinRole)); + ASSERT(!graph.roleExists(userRole)); + ASSERT_OK(graph.createRole(userRole)); + ASSERT(graph.roleExists(userRole)); + + ASSERT_NOT_OK(graph.addPrivilegeToRole(builtinRole, privilege)); + ASSERT_NOT_OK(graph.removePrivilegeFromRole(builtinRole, privilege)); + ASSERT_NOT_OK(graph.addRoleToRole(builtinRole, userRole)); + ASSERT_NOT_OK(graph.removeRoleFromRole(builtinRole, userRole)); + + ASSERT_OK(graph.addPrivilegeToRole(userRole, privilege)); + ASSERT_OK(graph.addRoleToRole(userRole, builtinRole)); + ASSERT_OK(graph.recomputePrivilegeData()); + + PrivilegeVector privileges = graph.getDirectPrivileges(userRole); + ASSERT_EQUALS(1U, privileges.size()); + ASSERT(privileges[0].getActions().equals(actions)); + ASSERT(!privileges[0].getActions().contains(ActionType::find)); + ASSERT_EQUALS(dbAResource, privileges[0].getResourcePattern()); + + privileges = graph.getAllPrivileges(userRole); + size_t i; + for (i = 0; i < privileges.size(); ++i) { + if (dbAResource == privileges[i].getResourcePattern()) + break; } + ASSERT_NOT_EQUALS(privileges.size(), i); + ASSERT(privileges[i].getActions().isSupersetOf(actions)); + ASSERT(privileges[i].getActions().contains(ActionType::insert)); + ASSERT(privileges[i].getActions().contains(ActionType::find)); + + ASSERT_OK(graph.deleteRole(userRole)); + ASSERT(!graph.roleExists(userRole)); +} + +TEST(RoleGraphTest, BuiltinRolesOnlyOnAppropriateDatabases) { + RoleGraph graph; + ASSERT(graph.roleExists(RoleName("read", "test"))); + ASSERT(graph.roleExists(RoleName("readWrite", "test"))); + ASSERT(graph.roleExists(RoleName("userAdmin", "test"))); + ASSERT(graph.roleExists(RoleName("dbAdmin", "test"))); + ASSERT(graph.roleExists(RoleName("dbOwner", "test"))); + ASSERT(!graph.roleExists(RoleName("readAnyDatabase", "test"))); + ASSERT(!graph.roleExists(RoleName("readWriteAnyDatabase", "test"))); + ASSERT(!graph.roleExists(RoleName("userAdminAnyDatabase", "test"))); + ASSERT(!graph.roleExists(RoleName("dbAdminAnyDatabase", "test"))); + ASSERT(!graph.roleExists(RoleName("clusterAdmin", "test"))); + ASSERT(!graph.roleExists(RoleName("root", "test"))); + ASSERT(!graph.roleExists(RoleName("__system", "test"))); + ASSERT(!graph.roleExists(RoleName("MyRole", "test"))); + + ASSERT(graph.roleExists(RoleName("read", "admin"))); + ASSERT(graph.roleExists(RoleName("readWrite", "admin"))); + ASSERT(graph.roleExists(RoleName("userAdmin", "admin"))); + ASSERT(graph.roleExists(RoleName("dbAdmin", "admin"))); + ASSERT(graph.roleExists(RoleName("dbOwner", "admin"))); + ASSERT(graph.roleExists(RoleName("readAnyDatabase", "admin"))); + ASSERT(graph.roleExists(RoleName("readWriteAnyDatabase", "admin"))); + ASSERT(graph.roleExists(RoleName("userAdminAnyDatabase", "admin"))); + ASSERT(graph.roleExists(RoleName("dbAdminAnyDatabase", "admin"))); + ASSERT(graph.roleExists(RoleName("clusterAdmin", "admin"))); + ASSERT(graph.roleExists(RoleName("root", "admin"))); + ASSERT(graph.roleExists(RoleName("__system", "admin"))); + ASSERT(!graph.roleExists(RoleName("MyRole", "admin"))); +} + +TEST(RoleGraphTest, getRolesForDatabase) { + RoleGraph graph; + graph.createRole(RoleName("myRole", "test")); + // Make sure that a role on "test2" doesn't show up in the roles list for "test" + graph.createRole(RoleName("anotherRole", "test2")); + graph.createRole(RoleName("myAdminRole", "admin")); + + // Non-admin DB with no user-defined roles + RoleNameIterator it = graph.getRolesForDatabase("fakedb"); + ASSERT_EQUALS(RoleName("dbAdmin", "fakedb"), it.next()); + ASSERT_EQUALS(RoleName("dbOwner", "fakedb"), it.next()); + ASSERT_EQUALS(RoleName("read", "fakedb"), it.next()); + ASSERT_EQUALS(RoleName("readWrite", "fakedb"), it.next()); + ASSERT_EQUALS(RoleName("userAdmin", "fakedb"), it.next()); + ASSERT_FALSE(it.more()); + + // Non-admin DB with a user-defined role + it = graph.getRolesForDatabase("test"); + ASSERT_EQUALS(RoleName("dbAdmin", "test"), it.next()); + ASSERT_EQUALS(RoleName("dbOwner", "test"), it.next()); + ASSERT_EQUALS(RoleName("myRole", "test"), it.next()); + ASSERT_EQUALS(RoleName("read", "test"), it.next()); + ASSERT_EQUALS(RoleName("readWrite", "test"), it.next()); + ASSERT_EQUALS(RoleName("userAdmin", "test"), it.next()); + ASSERT_FALSE(it.more()); + + // Admin DB + it = graph.getRolesForDatabase("admin"); + ASSERT_EQUALS(RoleName("__system", "admin"), it.next()); + ASSERT_EQUALS(RoleName("backup", "admin"), it.next()); + ASSERT_EQUALS(RoleName("clusterAdmin", "admin"), it.next()); + ASSERT_EQUALS(RoleName("clusterManager", "admin"), it.next()); + ASSERT_EQUALS(RoleName("clusterMonitor", "admin"), it.next()); + ASSERT_EQUALS(RoleName("dbAdmin", "admin"), it.next()); + ASSERT_EQUALS(RoleName("dbAdminAnyDatabase", "admin"), it.next()); + ASSERT_EQUALS(RoleName("dbOwner", "admin"), it.next()); + ASSERT_EQUALS(RoleName("hostManager", "admin"), it.next()); + ASSERT_EQUALS(RoleName("myAdminRole", "admin"), it.next()); + ASSERT_EQUALS(RoleName("read", "admin"), it.next()); + ASSERT_EQUALS(RoleName("readAnyDatabase", "admin"), it.next()); + ASSERT_EQUALS(RoleName("readWrite", "admin"), it.next()); + ASSERT_EQUALS(RoleName("readWriteAnyDatabase", "admin"), it.next()); + ASSERT_EQUALS(RoleName("restore", "admin"), it.next()); + ASSERT_EQUALS(RoleName("root", "admin"), it.next()); + ASSERT_EQUALS(RoleName("userAdmin", "admin"), it.next()); + ASSERT_EQUALS(RoleName("userAdminAnyDatabase", "admin"), it.next()); + ASSERT_FALSE(it.more()); +} } // namespace } // namespace mongo diff --git a/src/mongo/db/auth/role_graph_update.cpp b/src/mongo/db/auth/role_graph_update.cpp index 71e9370ec61..62df4a33a4b 100644 --- a/src/mongo/db/auth/role_graph_update.cpp +++ b/src/mongo/db/auth/role_graph_update.cpp @@ -40,278 +40,266 @@ namespace mongo { namespace { - /** - * Structure representing information parsed out of a role document. - */ - struct RoleInfo { - RoleName name; - std::vector<RoleName> roles; - PrivilegeVector privileges; - }; +/** + * Structure representing information parsed out of a role document. + */ +struct RoleInfo { + RoleName name; + std::vector<RoleName> roles; + PrivilegeVector privileges; +}; - /** - * Parses the role name out of a BSON document. - */ - Status parseRoleNameFromDocument(const BSONObj& doc, RoleName* name) { - BSONElement nameElement; - BSONElement sourceElement; - Status status = bsonExtractTypedField( - doc, AuthorizationManager::ROLE_NAME_FIELD_NAME, String, &nameElement); - if (!status.isOK()) - return status; - status = bsonExtractTypedField( - doc, AuthorizationManager::ROLE_DB_FIELD_NAME, String, &sourceElement); - if (!status.isOK()) - return status; - *name = RoleName(nameElement.valueStringData(), sourceElement.valueStringData()); +/** + * Parses the role name out of a BSON document. + */ +Status parseRoleNameFromDocument(const BSONObj& doc, RoleName* name) { + BSONElement nameElement; + BSONElement sourceElement; + Status status = bsonExtractTypedField( + doc, AuthorizationManager::ROLE_NAME_FIELD_NAME, String, &nameElement); + if (!status.isOK()) return status; - } + status = bsonExtractTypedField( + doc, AuthorizationManager::ROLE_DB_FIELD_NAME, String, &sourceElement); + if (!status.isOK()) + return status; + *name = RoleName(nameElement.valueStringData(), sourceElement.valueStringData()); + return status; +} - /** - * Checks whether the given "roleName" corresponds with the given _id field. - * In admin.system.roles, documents with role name "role@db" must have _id - * "db.role". - * - * Returns Status::OK if the two values are compatible. - */ - Status checkIdMatchesRoleName(const BSONElement& idElement, const RoleName& roleName) { - if (idElement.type() != String) { - return Status(ErrorCodes::TypeMismatch, - "Role document _id fields must be strings."); - } - StringData idField = idElement.valueStringData(); - size_t firstDot = idField.find('.'); - if (firstDot == std::string::npos || - idField.substr(0, firstDot) != roleName.getDB() || - idField.substr(firstDot + 1) != roleName.getRole()) { - return Status(ErrorCodes::FailedToParse, mongoutils::str::stream() << - "Role document _id fields must be encoded as the string " - "dbname.rolename. Found " << idField << " for " << - roleName.getFullName()); - } - return Status::OK(); +/** + * Checks whether the given "roleName" corresponds with the given _id field. + * In admin.system.roles, documents with role name "role@db" must have _id + * "db.role". + * + * Returns Status::OK if the two values are compatible. + */ +Status checkIdMatchesRoleName(const BSONElement& idElement, const RoleName& roleName) { + if (idElement.type() != String) { + return Status(ErrorCodes::TypeMismatch, "Role document _id fields must be strings."); + } + StringData idField = idElement.valueStringData(); + size_t firstDot = idField.find('.'); + if (firstDot == std::string::npos || idField.substr(0, firstDot) != roleName.getDB() || + idField.substr(firstDot + 1) != roleName.getRole()) { + return Status(ErrorCodes::FailedToParse, + mongoutils::str::stream() + << "Role document _id fields must be encoded as the string " + "dbname.rolename. Found " << idField << " for " + << roleName.getFullName()); } + return Status::OK(); +} - /** - * Parses "idElement" to extract the role name, according to the "dbname.role" convention - * used for admin.system.roles documents. - */ - Status getRoleNameFromIdField(const BSONElement& idElement, RoleName* roleName) { - if (idElement.type() != String) { - return Status(ErrorCodes::TypeMismatch, - "Role document _id fields must be strings."); - } - StringData idField = idElement.valueStringData(); - size_t dotPos = idField.find('.'); - if (dotPos == std::string::npos) { - return Status(ErrorCodes::BadValue, - "Role document _id fields must have the form dbname.rolename"); - } - *roleName = RoleName(idField.substr(dotPos + 1), idField.substr(0, dotPos)); - return Status::OK(); +/** + * Parses "idElement" to extract the role name, according to the "dbname.role" convention + * used for admin.system.roles documents. + */ +Status getRoleNameFromIdField(const BSONElement& idElement, RoleName* roleName) { + if (idElement.type() != String) { + return Status(ErrorCodes::TypeMismatch, "Role document _id fields must be strings."); } + StringData idField = idElement.valueStringData(); + size_t dotPos = idField.find('.'); + if (dotPos == std::string::npos) { + return Status(ErrorCodes::BadValue, + "Role document _id fields must have the form dbname.rolename"); + } + *roleName = RoleName(idField.substr(dotPos + 1), idField.substr(0, dotPos)); + return Status::OK(); +} - /** - * Parses information about a role from a BSON document. - */ - Status parseRoleFromDocument(const BSONObj& doc, RoleInfo* role) { - BSONElement rolesElement; - Status status = parseRoleNameFromDocument(doc, &role->name); - if (!status.isOK()) - return status; - status = checkIdMatchesRoleName(doc["_id"], role->name); - if (!status.isOK()) - return status; - status = bsonExtractTypedField(doc, "roles", Array, &rolesElement); - if (!status.isOK()) - return status; - BSONForEach(singleRoleElement, rolesElement.Obj()) { - if (singleRoleElement.type() != Object) { - return Status(ErrorCodes::TypeMismatch, - "Elements of roles array must be objects."); - } - RoleName possessedRoleName; - status = parseRoleNameFromDocument(singleRoleElement.Obj(), &possessedRoleName); - if (!status.isOK()) - return status; - role->roles.push_back(possessedRoleName); +/** + * Parses information about a role from a BSON document. + */ +Status parseRoleFromDocument(const BSONObj& doc, RoleInfo* role) { + BSONElement rolesElement; + Status status = parseRoleNameFromDocument(doc, &role->name); + if (!status.isOK()) + return status; + status = checkIdMatchesRoleName(doc["_id"], role->name); + if (!status.isOK()) + return status; + status = bsonExtractTypedField(doc, "roles", Array, &rolesElement); + if (!status.isOK()) + return status; + BSONForEach(singleRoleElement, rolesElement.Obj()) { + if (singleRoleElement.type() != Object) { + return Status(ErrorCodes::TypeMismatch, "Elements of roles array must be objects."); } - - BSONElement privilegesElement; - status = bsonExtractTypedField(doc, "privileges", Array, &privilegesElement); + RoleName possessedRoleName; + status = parseRoleNameFromDocument(singleRoleElement.Obj(), &possessedRoleName); if (!status.isOK()) return status; - status = auth::parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), - &role->privileges); - return status; + role->roles.push_back(possessedRoleName); } - /** - * Updates roleGraph for an insert-type oplog operation on admin.system.roles. - */ - Status handleOplogInsert(RoleGraph* roleGraph, const BSONObj& insertedObj) { - RoleInfo role; - Status status = parseRoleFromDocument(insertedObj, &role); - if (!status.isOK()) - return status; - status = roleGraph->replaceRole(role.name, role.roles, role.privileges); + BSONElement privilegesElement; + status = bsonExtractTypedField(doc, "privileges", Array, &privilegesElement); + if (!status.isOK()) return status; - } + status = + auth::parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), &role->privileges); + return status; +} - /** - * Updates roleGraph for an update-type oplog operation on admin.system.roles. - * - * Treats all updates as upserts. - */ - Status handleOplogUpdate(RoleGraph* roleGraph, - const BSONObj& updatePattern, - const BSONObj& queryPattern) { - RoleName roleToUpdate; - Status status = getRoleNameFromIdField(queryPattern["_id"], &roleToUpdate); - if (!status.isOK()) - return status; +/** + * Updates roleGraph for an insert-type oplog operation on admin.system.roles. + */ +Status handleOplogInsert(RoleGraph* roleGraph, const BSONObj& insertedObj) { + RoleInfo role; + Status status = parseRoleFromDocument(insertedObj, &role); + if (!status.isOK()) + return status; + status = roleGraph->replaceRole(role.name, role.roles, role.privileges); + return status; +} - UpdateDriver::Options updateOptions; - UpdateDriver driver(updateOptions); - status = driver.parse(updatePattern); - if (!status.isOK()) - return status; +/** + * Updates roleGraph for an update-type oplog operation on admin.system.roles. + * + * Treats all updates as upserts. + */ +Status handleOplogUpdate(RoleGraph* roleGraph, + const BSONObj& updatePattern, + const BSONObj& queryPattern) { + RoleName roleToUpdate; + Status status = getRoleNameFromIdField(queryPattern["_id"], &roleToUpdate); + if (!status.isOK()) + return status; - mutablebson::Document roleDocument; - status = AuthorizationManager::getBSONForRole( - roleGraph, roleToUpdate, roleDocument.root()); - if (status == ErrorCodes::RoleNotFound) { - // The query pattern will only contain _id, no other immutable fields are present - status = driver.populateDocumentWithQueryFields(queryPattern, NULL, roleDocument); - } - if (!status.isOK()) - return status; + UpdateDriver::Options updateOptions; + UpdateDriver driver(updateOptions); + status = driver.parse(updatePattern); + if (!status.isOK()) + return status; - status = driver.update(StringData(), &roleDocument); - if (!status.isOK()) - return status; + mutablebson::Document roleDocument; + status = AuthorizationManager::getBSONForRole(roleGraph, roleToUpdate, roleDocument.root()); + if (status == ErrorCodes::RoleNotFound) { + // The query pattern will only contain _id, no other immutable fields are present + status = driver.populateDocumentWithQueryFields(queryPattern, NULL, roleDocument); + } + if (!status.isOK()) + return status; - // Now use the updated document to totally replace the role in the graph! - RoleInfo role; - status = parseRoleFromDocument(roleDocument.getObject(), &role); - if (!status.isOK()) - return status; - status = roleGraph->replaceRole(role.name, role.roles, role.privileges); + status = driver.update(StringData(), &roleDocument); + if (!status.isOK()) + return status; + // Now use the updated document to totally replace the role in the graph! + RoleInfo role; + status = parseRoleFromDocument(roleDocument.getObject(), &role); + if (!status.isOK()) return status; - } + status = roleGraph->replaceRole(role.name, role.roles, role.privileges); - /** - * Updates roleGraph for a delete-type oplog operation on admin.system.roles. - */ - Status handleOplogDelete( - RoleGraph* roleGraph, - const BSONObj& deletePattern) { + return status; +} - RoleName roleToDelete; - Status status = getRoleNameFromIdField(deletePattern["_id"], &roleToDelete); - if (!status.isOK()) - return status; - status = roleGraph->deleteRole(roleToDelete); - if (ErrorCodes::RoleNotFound == status) { - // Double-delete can happen in oplog application. - status = Status::OK(); - } +/** + * Updates roleGraph for a delete-type oplog operation on admin.system.roles. + */ +Status handleOplogDelete(RoleGraph* roleGraph, const BSONObj& deletePattern) { + RoleName roleToDelete; + Status status = getRoleNameFromIdField(deletePattern["_id"], &roleToDelete); + if (!status.isOK()) return status; + status = roleGraph->deleteRole(roleToDelete); + if (ErrorCodes::RoleNotFound == status) { + // Double-delete can happen in oplog application. + status = Status::OK(); } + return status; +} - /** - * Updates roleGraph for command-type oplog operations on the admin database. - */ - Status handleOplogCommand(RoleGraph* roleGraph, const BSONObj& cmdObj) { - const NamespaceString& rolesCollectionNamespace = - AuthorizationManager::rolesCollectionNamespace; - const StringData cmdName(cmdObj.firstElement().fieldNameStringData()); - if (cmdName == "applyOps") { - // Operations applied by applyOps will be passed into RoleGraph::handleOplog() by the - // implementation of applyOps itself. - return Status::OK(); - } - if (cmdName == "create") { - return Status::OK(); - } - if (cmdName == "drop") { - if (cmdObj.firstElement().str() == rolesCollectionNamespace.coll()) { - *roleGraph = RoleGraph(); - } - return Status::OK(); - } - if (cmdName == "dropDatabase") { +/** + * Updates roleGraph for command-type oplog operations on the admin database. + */ +Status handleOplogCommand(RoleGraph* roleGraph, const BSONObj& cmdObj) { + const NamespaceString& rolesCollectionNamespace = + AuthorizationManager::rolesCollectionNamespace; + const StringData cmdName(cmdObj.firstElement().fieldNameStringData()); + if (cmdName == "applyOps") { + // Operations applied by applyOps will be passed into RoleGraph::handleOplog() by the + // implementation of applyOps itself. + return Status::OK(); + } + if (cmdName == "create") { + return Status::OK(); + } + if (cmdName == "drop") { + if (cmdObj.firstElement().str() == rolesCollectionNamespace.coll()) { *roleGraph = RoleGraph(); - return Status::OK(); } - if (cmdName == "renameCollection") { - if (cmdObj.firstElement().str() == rolesCollectionNamespace.ns()) { - *roleGraph = RoleGraph(); - return Status::OK(); - } - if (cmdObj["to"].str() == rolesCollectionNamespace.ns()) { - *roleGraph = RoleGraph(); - return Status(ErrorCodes::OplogOperationUnsupported, - "Renaming into admin.system.roles produces inconsistent state; " - "must resynchronize role graph."); - } - return Status::OK(); - } - if (cmdName == "dropIndexes" || cmdName == "deleteIndexes") { + return Status::OK(); + } + if (cmdName == "dropDatabase") { + *roleGraph = RoleGraph(); + return Status::OK(); + } + if (cmdName == "renameCollection") { + if (cmdObj.firstElement().str() == rolesCollectionNamespace.ns()) { + *roleGraph = RoleGraph(); return Status::OK(); } - if ((cmdName == "collMod" || cmdName == "emptycapped") && - cmdObj.firstElement().str() != rolesCollectionNamespace.coll()) { - - // We don't care about these if they're not on the roles collection. - return Status::OK(); + if (cmdObj["to"].str() == rolesCollectionNamespace.ns()) { + *roleGraph = RoleGraph(); + return Status(ErrorCodes::OplogOperationUnsupported, + "Renaming into admin.system.roles produces inconsistent state; " + "must resynchronize role graph."); } - // No other commands expected. Warn. - return Status(ErrorCodes::OplogOperationUnsupported, "Unsupported oplog operation"); + return Status::OK(); } + if (cmdName == "dropIndexes" || cmdName == "deleteIndexes") { + return Status::OK(); + } + if ((cmdName == "collMod" || cmdName == "emptycapped") && + cmdObj.firstElement().str() != rolesCollectionNamespace.coll()) { + // We don't care about these if they're not on the roles collection. + return Status::OK(); + } + // No other commands expected. Warn. + return Status(ErrorCodes::OplogOperationUnsupported, "Unsupported oplog operation"); +} } // namespace - Status RoleGraph::addRoleFromDocument(const BSONObj& doc) { - RoleInfo role; - Status status = parseRoleFromDocument(doc, &role); - if (!status.isOK()) - return status; - status = replaceRole(role.name, role.roles, role.privileges); +Status RoleGraph::addRoleFromDocument(const BSONObj& doc) { + RoleInfo role; + Status status = parseRoleFromDocument(doc, &role); + if (!status.isOK()) return status; - } + status = replaceRole(role.name, role.roles, role.privileges); + return status; +} - Status RoleGraph::handleLogOp( - const char* op, - const NamespaceString& ns, - const BSONObj& o, - const BSONObj* o2) { - - if (op == StringData("db", StringData::LiteralTag())) - return Status::OK(); - if (op[0] == '\0' || op[1] != '\0') { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << "Unrecognized \"op\" field value \"" << - op << '"'); - } +Status RoleGraph::handleLogOp(const char* op, + const NamespaceString& ns, + const BSONObj& o, + const BSONObj* o2) { + if (op == StringData("db", StringData::LiteralTag())) + return Status::OK(); + if (op[0] == '\0' || op[1] != '\0') { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "Unrecognized \"op\" field value \"" << op + << '"'); + } - if (ns.db() != AuthorizationManager::rolesCollectionNamespace.db()) - return Status::OK(); + if (ns.db() != AuthorizationManager::rolesCollectionNamespace.db()) + return Status::OK(); - if (ns.isCommand()) { - if (*op == 'c') { - return handleOplogCommand(this, o); - } - else { - return Status(ErrorCodes::BadValue, - "Non-command oplog entry on admin.$cmd namespace"); - } + if (ns.isCommand()) { + if (*op == 'c') { + return handleOplogCommand(this, o); + } else { + return Status(ErrorCodes::BadValue, "Non-command oplog entry on admin.$cmd namespace"); } + } - if (ns.coll() != AuthorizationManager::rolesCollectionNamespace.coll()) - return Status::OK(); + if (ns.coll() != AuthorizationManager::rolesCollectionNamespace.coll()) + return Status::OK(); - switch (*op) { + switch (*op) { case 'i': return handleOplogInsert(this, o); case 'u': @@ -329,9 +317,9 @@ namespace { "Namespace admin.system.roles is not a valid target for commands"); default: return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << "Unrecognized \"op\" field value \"" << - op << '"'); - } + mongoutils::str::stream() << "Unrecognized \"op\" field value \"" << op + << '"'); } +} } // namespace mongo diff --git a/src/mongo/db/auth/role_name.cpp b/src/mongo/db/auth/role_name.cpp index f38bcedeb59..d5f6aecfa48 100644 --- a/src/mongo/db/auth/role_name.cpp +++ b/src/mongo/db/auth/role_name.cpp @@ -36,20 +36,19 @@ namespace mongo { - RoleName::RoleName(StringData role, StringData dbname) { - _fullName.resize(role.size() + dbname.size() + 1); - std::string::iterator iter = std::copy(role.rawData(), - role.rawData() + role.size(), - _fullName.begin()); - *iter = '@'; - ++iter; - iter = std::copy(dbname.rawData(), dbname.rawData() + dbname.size(), iter); - dassert(iter == _fullName.end()); - _splitPoint = role.size(); - } +RoleName::RoleName(StringData role, StringData dbname) { + _fullName.resize(role.size() + dbname.size() + 1); + std::string::iterator iter = + std::copy(role.rawData(), role.rawData() + role.size(), _fullName.begin()); + *iter = '@'; + ++iter; + iter = std::copy(dbname.rawData(), dbname.rawData() + dbname.size(), iter); + dassert(iter == _fullName.end()); + _splitPoint = role.size(); +} - std::ostream& operator<<(std::ostream& os, const RoleName& name) { - return os << name.getFullName(); - } +std::ostream& operator<<(std::ostream& os, const RoleName& name) { + return os << name.getFullName(); +} } // namespace mongo diff --git a/src/mongo/db/auth/role_name.h b/src/mongo/db/auth/role_name.h index 05b5d30e80b..30ef4d50412 100644 --- a/src/mongo/db/auth/role_name.h +++ b/src/mongo/db/auth/role_name.h @@ -40,146 +40,176 @@ namespace mongo { +/** + * Representation of a name of a role in a MongoDB system. + * + * Consists of a "role name" part and a "datbase name" part. + */ +class RoleName { +public: + RoleName() : _splitPoint(0) {} + RoleName(StringData role, StringData dbname); + /** - * Representation of a name of a role in a MongoDB system. - * - * Consists of a "role name" part and a "datbase name" part. + * Gets the name of the role excluding the "@dbname" component. */ - class RoleName { - public: - RoleName() : _splitPoint(0) {} - RoleName(StringData role, StringData dbname); + StringData getRole() const { + return StringData(_fullName).substr(0, _splitPoint); + } - /** - * Gets the name of the role excluding the "@dbname" component. - */ - StringData getRole() const { return StringData(_fullName).substr(0, _splitPoint); } + /** + * Gets the database name part of a role name. + */ + StringData getDB() const { + return StringData(_fullName).substr(_splitPoint + 1); + } - /** - * Gets the database name part of a role name. - */ - StringData getDB() const { return StringData(_fullName).substr(_splitPoint + 1); } + bool empty() const { + return _fullName.empty(); + } - bool empty() const { return _fullName.empty(); } + /** + * Gets the full name of a role as a string, formatted as "role@db". + * + * Allowed for keys in non-persistent data structures, such as std::map. + */ + const std::string& getFullName() const { + return _fullName; + } - /** - * Gets the full name of a role as a string, formatted as "role@db". - * - * Allowed for keys in non-persistent data structures, such as std::map. - */ - const std::string& getFullName() const { return _fullName; } + /** + * Stringifies the object, for logging/debugging. + */ + const std::string& toString() const { + return getFullName(); + } - /** - * Stringifies the object, for logging/debugging. - */ - const std::string& toString() const { return getFullName(); } +private: + std::string _fullName; // The full name, stored as a string. "role@db". + size_t _splitPoint; // The index of the "@" separating the role and db name parts. +}; - private: - std::string _fullName; // The full name, stored as a string. "role@db". - size_t _splitPoint; // The index of the "@" separating the role and db name parts. - }; +static inline bool operator==(const RoleName& lhs, const RoleName& rhs) { + return lhs.getFullName() == rhs.getFullName(); +} - static inline bool operator==(const RoleName& lhs, const RoleName& rhs) { - return lhs.getFullName() == rhs.getFullName(); - } +static inline bool operator!=(const RoleName& lhs, const RoleName& rhs) { + return lhs.getFullName() != rhs.getFullName(); +} - static inline bool operator!=(const RoleName& lhs, const RoleName& rhs) { - return lhs.getFullName() != rhs.getFullName(); +static inline bool operator<(const RoleName& lhs, const RoleName& rhs) { + if (lhs.getDB() == rhs.getDB()) { + return lhs.getRole() < rhs.getRole(); } + return lhs.getDB() < rhs.getDB(); +} - static inline bool operator<(const RoleName& lhs, const RoleName& rhs) { - if (lhs.getDB() == rhs.getDB()) { - return lhs.getRole() < rhs.getRole(); - } - return lhs.getDB() < rhs.getDB(); - } +std::ostream& operator<<(std::ostream& os, const RoleName& name); - std::ostream& operator<<(std::ostream& os, const RoleName& name); +/** + * Iterator over an unspecified container of RoleName objects. + */ +class RoleNameIterator { +public: + class Impl { + MONGO_DISALLOW_COPYING(Impl); - /** - * Iterator over an unspecified container of RoleName objects. - */ - class RoleNameIterator { public: - class Impl { - MONGO_DISALLOW_COPYING(Impl); - public: - Impl() {}; - virtual ~Impl() {}; - static Impl* clone(Impl* orig) { return orig ? orig->doClone(): NULL; } - virtual bool more() const = 0; - virtual const RoleName& get() const = 0; - - virtual const RoleName& next() = 0; - - private: - virtual Impl* doClone() const = 0; - }; - - RoleNameIterator() : _impl(nullptr) {} - RoleNameIterator(const RoleNameIterator& other) : _impl(Impl::clone(other._impl.get())) {} - explicit RoleNameIterator(Impl* impl) : _impl(impl) {} - - RoleNameIterator& operator=(const RoleNameIterator& other) { - _impl.reset(Impl::clone(other._impl.get())); - return *this; + Impl(){}; + virtual ~Impl(){}; + static Impl* clone(Impl* orig) { + return orig ? orig->doClone() : NULL; } + virtual bool more() const = 0; + virtual const RoleName& get() const = 0; - bool more() const { return _impl.get() && _impl->more(); } - const RoleName& get() const { return _impl->get(); } - - const RoleName& next() { return _impl->next(); } - - const RoleName& operator*() const { return get(); } - const RoleName* operator->() const { return &get(); } + virtual const RoleName& next() = 0; private: - std::unique_ptr<Impl> _impl; + virtual Impl* doClone() const = 0; }; -} // namespace mongo + RoleNameIterator() : _impl(nullptr) {} + RoleNameIterator(const RoleNameIterator& other) : _impl(Impl::clone(other._impl.get())) {} + explicit RoleNameIterator(Impl* impl) : _impl(impl) {} + + RoleNameIterator& operator=(const RoleNameIterator& other) { + _impl.reset(Impl::clone(other._impl.get())); + return *this; + } + + bool more() const { + return _impl.get() && _impl->more(); + } + const RoleName& get() const { + return _impl->get(); + } + + const RoleName& next() { + return _impl->next(); + } + + const RoleName& operator*() const { + return get(); + } + const RoleName* operator->() const { + return &get(); + } + +private: + std::unique_ptr<Impl> _impl; +}; + +} // namespace mongo // Define hash function for RoleNames so they can be keys in std::unordered_map MONGO_HASH_NAMESPACE_START - template <> struct hash<mongo::RoleName> { - size_t operator()(const mongo::RoleName& rname) const { - return hash<std::string>()(rname.getFullName()); - } - }; +template <> +struct hash<mongo::RoleName> { + size_t operator()(const mongo::RoleName& rname) const { + return hash<std::string>()(rname.getFullName()); + } +}; MONGO_HASH_NAMESPACE_END namespace mongo { - template <typename ContainerIterator> - class RoleNameContainerIteratorImpl : public RoleNameIterator::Impl { - MONGO_DISALLOW_COPYING(RoleNameContainerIteratorImpl); - public: - RoleNameContainerIteratorImpl(const ContainerIterator& begin, - const ContainerIterator& end) : - _curr(begin), _end(end) {} - virtual ~RoleNameContainerIteratorImpl() {} - virtual bool more() const { return _curr != _end; } - virtual const RoleName& next() { return *(_curr++); } - virtual const RoleName& get() const { return *_curr; } - virtual RoleNameIterator::Impl* doClone() const { - return new RoleNameContainerIteratorImpl(_curr, _end); - } +template <typename ContainerIterator> +class RoleNameContainerIteratorImpl : public RoleNameIterator::Impl { + MONGO_DISALLOW_COPYING(RoleNameContainerIteratorImpl); - private: - ContainerIterator _curr; - ContainerIterator _end; - }; - - template <typename ContainerIterator> - RoleNameIterator makeRoleNameIterator(const ContainerIterator& begin, - const ContainerIterator& end) { - return RoleNameIterator( new RoleNameContainerIteratorImpl<ContainerIterator>(begin, end)); +public: + RoleNameContainerIteratorImpl(const ContainerIterator& begin, const ContainerIterator& end) + : _curr(begin), _end(end) {} + virtual ~RoleNameContainerIteratorImpl() {} + virtual bool more() const { + return _curr != _end; } - - template <typename Container> - RoleNameIterator makeRoleNameIteratorForContainer(const Container& container) { - return makeRoleNameIterator(container.begin(), container.end()); + virtual const RoleName& next() { + return *(_curr++); } + virtual const RoleName& get() const { + return *_curr; + } + virtual RoleNameIterator::Impl* doClone() const { + return new RoleNameContainerIteratorImpl(_curr, _end); + } + +private: + ContainerIterator _curr; + ContainerIterator _end; +}; + +template <typename ContainerIterator> +RoleNameIterator makeRoleNameIterator(const ContainerIterator& begin, + const ContainerIterator& end) { + return RoleNameIterator(new RoleNameContainerIteratorImpl<ContainerIterator>(begin, end)); +} + +template <typename Container> +RoleNameIterator makeRoleNameIteratorForContainer(const Container& container) { + return makeRoleNameIterator(container.begin(), container.end()); +} } // namespace mongo diff --git a/src/mongo/db/auth/sasl_authentication_session.cpp b/src/mongo/db/auth/sasl_authentication_session.cpp index 62abdc8a284..c74bba6fadb 100644 --- a/src/mongo/db/auth/sasl_authentication_session.cpp +++ b/src/mongo/db/auth/sasl_authentication_session.cpp @@ -46,51 +46,47 @@ #include "mongo/util/mongoutils/str.h" namespace mongo { - SaslAuthenticationSession::SaslAuthenticationSessionFactoryFn - SaslAuthenticationSession::create; +SaslAuthenticationSession::SaslAuthenticationSessionFactoryFn SaslAuthenticationSession::create; - // Mechanism name constants. - const char SaslAuthenticationSession::mechanismCRAMMD5[] = "CRAM-MD5"; - const char SaslAuthenticationSession::mechanismDIGESTMD5[] = "DIGEST-MD5"; - const char SaslAuthenticationSession::mechanismSCRAMSHA1[] = "SCRAM-SHA-1"; - const char SaslAuthenticationSession::mechanismGSSAPI[] = "GSSAPI"; - const char SaslAuthenticationSession::mechanismPLAIN[] = "PLAIN"; +// Mechanism name constants. +const char SaslAuthenticationSession::mechanismCRAMMD5[] = "CRAM-MD5"; +const char SaslAuthenticationSession::mechanismDIGESTMD5[] = "DIGEST-MD5"; +const char SaslAuthenticationSession::mechanismSCRAMSHA1[] = "SCRAM-SHA-1"; +const char SaslAuthenticationSession::mechanismGSSAPI[] = "GSSAPI"; +const char SaslAuthenticationSession::mechanismPLAIN[] = "PLAIN"; - /** - * Standard method in mongodb for determining if "authenticatedUser" may act as "requestedUser." - * - * The standard rule in MongoDB is simple. The authenticated user name must be the same as the - * requested user name. - */ - bool isAuthorizedCommon(SaslAuthenticationSession* session, - StringData requestedUser, - StringData authenticatedUser) { - - return requestedUser == authenticatedUser; - } +/** + * Standard method in mongodb for determining if "authenticatedUser" may act as "requestedUser." + * + * The standard rule in MongoDB is simple. The authenticated user name must be the same as the + * requested user name. + */ +bool isAuthorizedCommon(SaslAuthenticationSession* session, + StringData requestedUser, + StringData authenticatedUser) { + return requestedUser == authenticatedUser; +} - SaslAuthenticationSession::SaslAuthenticationSession(AuthorizationSession* authzSession) : - AuthenticationSession(AuthenticationSession::SESSION_TYPE_SASL), - _authzSession(authzSession), - _saslStep(0), - _conversationId(0), - _autoAuthorize(false), - _done(false) { - } +SaslAuthenticationSession::SaslAuthenticationSession(AuthorizationSession* authzSession) + : AuthenticationSession(AuthenticationSession::SESSION_TYPE_SASL), + _authzSession(authzSession), + _saslStep(0), + _conversationId(0), + _autoAuthorize(false), + _done(false) {} - SaslAuthenticationSession::~SaslAuthenticationSession() {}; +SaslAuthenticationSession::~SaslAuthenticationSession(){}; - StringData SaslAuthenticationSession::getAuthenticationDatabase() const { - if (Command::testCommandsEnabled && - _authenticationDatabase == "admin" && - getPrincipalId() == internalSecurity.user->getName().getUser()) { - // Allows authenticating as the internal user against the admin database. This is to - // support the auth passthrough test framework on mongos (since you can't use the local - // database on a mongos, so you can't auth as the internal user without this). - return internalSecurity.user->getName().getDB(); - } else { - return _authenticationDatabase; - } +StringData SaslAuthenticationSession::getAuthenticationDatabase() const { + if (Command::testCommandsEnabled && _authenticationDatabase == "admin" && + getPrincipalId() == internalSecurity.user->getName().getUser()) { + // Allows authenticating as the internal user against the admin database. This is to + // support the auth passthrough test framework on mongos (since you can't use the local + // database on a mongos, so you can't auth as the internal user without this). + return internalSecurity.user->getName().getDB(); + } else { + return _authenticationDatabase; } +} } // namespace mongo diff --git a/src/mongo/db/auth/sasl_authentication_session.h b/src/mongo/db/auth/sasl_authentication_session.h index e1bc7cf7551..ac298dbede6 100644 --- a/src/mongo/db/auth/sasl_authentication_session.h +++ b/src/mongo/db/auth/sasl_authentication_session.h @@ -41,128 +41,140 @@ namespace mongo { - class AuthorizationSession; - class OperationContext; +class AuthorizationSession; +class OperationContext; + +/** + * Authentication session data for the server side of SASL authentication. + */ +class SaslAuthenticationSession : public AuthenticationSession { + MONGO_DISALLOW_COPYING(SaslAuthenticationSession); + +public: + typedef stdx::function<SaslAuthenticationSession*(AuthorizationSession*, const std::string&)> + SaslAuthenticationSessionFactoryFn; + static SaslAuthenticationSessionFactoryFn create; + + // Mechanism name constants. + static const char mechanismCRAMMD5[]; + static const char mechanismDIGESTMD5[]; + static const char mechanismSCRAMSHA1[]; + static const char mechanismGSSAPI[]; + static const char mechanismPLAIN[]; + + explicit SaslAuthenticationSession(AuthorizationSession* authSession); + virtual ~SaslAuthenticationSession(); + + /** + * Start the server side of a SASL authentication. + * + * "authenticationDatabase" is the database against which the user is authenticating. + * "mechanism" is the SASL mechanism to use. + * "serviceName" is the SASL service name to use. + * "serviceHostname" is the FQDN of this server. + * "conversationId" is the conversation identifier to use for this session. + * + * If "autoAuthorize" is set to true, the server will automatically acquire all privileges + * for a successfully authenticated user. If it is false, the client will need to + * explicilty acquire privileges on resources it wishes to access. + * + * Must be called only once on an instance. + */ + virtual Status start(StringData authenticationDatabase, + StringData mechanism, + StringData serviceName, + StringData serviceHostname, + int64_t conversationId, + bool autoAuthorize) = 0; + + /** + * Perform one step of the server side of the authentication session, + * consuming "inputData" and producing "*outputData". + * + * A return of Status::OK() indiciates succesful progress towards authentication. + * Any other return code indicates that authentication has failed. + * + * Must not be called before start(). + */ + virtual Status step(StringData inputData, std::string* outputData) = 0; + + /** + * Returns the the operation context associated with the currently executing command. + * Authentication commands must set this on their associated + * SaslAuthenticationSession. + */ + OperationContext* getOpCtxt() const { + return _txn; + } + void setOpCtxt(OperationContext* txn) { + _txn = txn; + } + + /** + * Gets the name of the database against which this authentication conversation is running. + * + * Not meaningful before a successful call to start(). + */ + StringData getAuthenticationDatabase() const; + + /** + * Get the conversation id for this authentication session. + * + * Must not be called before start(). + */ + int64_t getConversationId() const { + return _conversationId; + } + + /** + * If the last call to step() returned Status::OK(), this method returns true if the + * authentication conversation has completed, from the server's perspective. If it returns + * false, the server expects more input from the client. If the last call to step() did not + * return Status::OK(), returns true. + * + * Behavior is undefined if step() has not been called. + */ + bool isDone() const { + return _done; + } + + /** + * Gets the string identifier of the principal being authenticated. + * + * Returns the empty string if the session does not yet know the identity being + * authenticated. + */ + virtual std::string getPrincipalId() const = 0; + + /** + * Gets the name of the SASL mechanism in use. + * + * Returns "" if start() has not been called or if start() did not return Status::OK(). + */ + virtual const char* getMechanism() const = 0; /** - * Authentication session data for the server side of SASL authentication. + * Returns true if automatic privilege acquisition should be used for this principal, after + * authentication. Not meaningful before a successful call to start(). */ - class SaslAuthenticationSession : public AuthenticationSession { - MONGO_DISALLOW_COPYING(SaslAuthenticationSession); - public: - typedef stdx::function<SaslAuthenticationSession* (AuthorizationSession*, - const std::string&)> - SaslAuthenticationSessionFactoryFn; - static SaslAuthenticationSessionFactoryFn create; - - // Mechanism name constants. - static const char mechanismCRAMMD5[]; - static const char mechanismDIGESTMD5[]; - static const char mechanismSCRAMSHA1[]; - static const char mechanismGSSAPI[]; - static const char mechanismPLAIN[]; - - explicit SaslAuthenticationSession(AuthorizationSession* authSession); - virtual ~SaslAuthenticationSession(); - - /** - * Start the server side of a SASL authentication. - * - * "authenticationDatabase" is the database against which the user is authenticating. - * "mechanism" is the SASL mechanism to use. - * "serviceName" is the SASL service name to use. - * "serviceHostname" is the FQDN of this server. - * "conversationId" is the conversation identifier to use for this session. - * - * If "autoAuthorize" is set to true, the server will automatically acquire all privileges - * for a successfully authenticated user. If it is false, the client will need to - * explicilty acquire privileges on resources it wishes to access. - * - * Must be called only once on an instance. - */ - virtual Status start(StringData authenticationDatabase, - StringData mechanism, - StringData serviceName, - StringData serviceHostname, - int64_t conversationId, - bool autoAuthorize) = 0; - - /** - * Perform one step of the server side of the authentication session, - * consuming "inputData" and producing "*outputData". - * - * A return of Status::OK() indiciates succesful progress towards authentication. - * Any other return code indicates that authentication has failed. - * - * Must not be called before start(). - */ - virtual Status step(StringData inputData, std::string* outputData) = 0; - - /** - * Returns the the operation context associated with the currently executing command.
- * Authentication commands must set this on their associated - * SaslAuthenticationSession. - */ - OperationContext* getOpCtxt() const { return _txn; } - void setOpCtxt(OperationContext* txn) { _txn = txn; } - - /** - * Gets the name of the database against which this authentication conversation is running. - * - * Not meaningful before a successful call to start(). - */ - StringData getAuthenticationDatabase() const; - - /** - * Get the conversation id for this authentication session. - * - * Must not be called before start(). - */ - int64_t getConversationId() const { return _conversationId; } - - /** - * If the last call to step() returned Status::OK(), this method returns true if the - * authentication conversation has completed, from the server's perspective. If it returns - * false, the server expects more input from the client. If the last call to step() did not - * return Status::OK(), returns true. - * - * Behavior is undefined if step() has not been called. - */ - bool isDone() const { return _done; } - - /** - * Gets the string identifier of the principal being authenticated. - * - * Returns the empty string if the session does not yet know the identity being - * authenticated. - */ - virtual std::string getPrincipalId() const = 0; - - /** - * Gets the name of the SASL mechanism in use. - * - * Returns "" if start() has not been called or if start() did not return Status::OK(). - */ - virtual const char* getMechanism() const = 0; - - /** - * Returns true if automatic privilege acquisition should be used for this principal, after - * authentication. Not meaningful before a successful call to start(). - */ - bool shouldAutoAuthorize() const { return _autoAuthorize; } - - AuthorizationSession* getAuthorizationSession() { return _authzSession; } - - protected: - OperationContext* _txn; - AuthorizationSession* _authzSession; - std::string _authenticationDatabase; - std::string _serviceName; - std::string _serviceHostname; - int _saslStep; - int64_t _conversationId; - bool _autoAuthorize; - bool _done; - }; + bool shouldAutoAuthorize() const { + return _autoAuthorize; + } + + AuthorizationSession* getAuthorizationSession() { + return _authzSession; + } + +protected: + OperationContext* _txn; + AuthorizationSession* _authzSession; + std::string _authenticationDatabase; + std::string _serviceName; + std::string _serviceHostname; + int _saslStep; + int64_t _conversationId; + bool _autoAuthorize; + bool _done; +}; } // namespace mongo diff --git a/src/mongo/db/auth/sasl_commands.cpp b/src/mongo/db/auth/sasl_commands.cpp index 6747a35771c..c05eec78539 100644 --- a/src/mongo/db/auth/sasl_commands.cpp +++ b/src/mongo/db/auth/sasl_commands.cpp @@ -56,327 +56,320 @@ namespace mongo { namespace { - using std::stringstream; - - const bool autoAuthorizeDefault = true; - - class CmdSaslStart : public Command { - public: - CmdSaslStart(); - virtual ~CmdSaslStart(); - - virtual void addRequiredPrivileges( - const std::string&, const BSONObj&, std::vector<Privilege>*) {} - - virtual bool run(OperationContext* txn, - const std::string& db, - BSONObj& cmdObj, - int options, - std::string& ignored, - BSONObjBuilder& result); - - virtual void help(stringstream& help) const; - virtual bool isWriteCommandForConfigServer() const { return false; } - virtual bool slaveOk() const { return true; } - virtual bool requiresAuth() { return false; } - - }; - - class CmdSaslContinue : public Command { - public: - CmdSaslContinue(); - virtual ~CmdSaslContinue(); - - virtual void addRequiredPrivileges( - const std::string&, const BSONObj&, std::vector<Privilege>*) {} - - virtual bool run(OperationContext* txn, - const std::string& db, - BSONObj& cmdObj, - int options, - std::string& ignored, - BSONObjBuilder& result); - - virtual void help(stringstream& help) const; - virtual bool isWriteCommandForConfigServer() const { return false; } - virtual bool slaveOk() const { return true; } - virtual bool requiresAuth() { return false; } - }; - - CmdSaslStart cmdSaslStart; - CmdSaslContinue cmdSaslContinue; - Status buildResponse(const SaslAuthenticationSession* session, - const std::string& responsePayload, - BSONType responsePayloadType, - BSONObjBuilder* result) { - result->appendIntOrLL(saslCommandConversationIdFieldName, session->getConversationId()); - result->appendBool(saslCommandDoneFieldName, session->isDone()); - - if (responsePayload.size() > size_t(std::numeric_limits<int>::max())) { - return Status(ErrorCodes::InvalidLength, "Response payload too long"); - } - if (responsePayloadType == BinData) { - result->appendBinData(saslCommandPayloadFieldName, - int(responsePayload.size()), - BinDataGeneral, - responsePayload.data()); - } - else if (responsePayloadType == String) { - result->append(saslCommandPayloadFieldName, base64::encode(responsePayload)); - } - else { - fassertFailed(4003); - } +using std::stringstream; - return Status::OK(); - } +const bool autoAuthorizeDefault = true; - Status extractConversationId(const BSONObj& cmdObj, int64_t* conversationId) { - BSONElement element; - Status status = bsonExtractField(cmdObj, saslCommandConversationIdFieldName, &element); - if (!status.isOK()) - return status; +class CmdSaslStart : public Command { +public: + CmdSaslStart(); + virtual ~CmdSaslStart(); - if (!element.isNumber()) { - return Status(ErrorCodes::TypeMismatch, - str::stream() << "Wrong type for field; expected number for " << element); - } - *conversationId = element.numberLong(); - return Status::OK(); - } + virtual void addRequiredPrivileges(const std::string&, + const BSONObj&, + std::vector<Privilege>*) {} - Status extractMechanism(const BSONObj& cmdObj, std::string* mechanism) { - return bsonExtractStringField(cmdObj, saslCommandMechanismFieldName, mechanism); - } + virtual bool run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& ignored, + BSONObjBuilder& result); - void addStatus(const Status& status, BSONObjBuilder* builder) { - builder->append("ok", status.isOK() ? 1.0: 0.0); - if (!status.isOK()) - builder->append(saslCommandCodeFieldName, status.code()); - if (!status.reason().empty()) - builder->append(saslCommandErrmsgFieldName, status.reason()); + virtual void help(stringstream& help) const; + virtual bool isWriteCommandForConfigServer() const { + return false; } - - Status doSaslStep(const ClientBasic* client, - SaslAuthenticationSession* session, - const BSONObj& cmdObj, - BSONObjBuilder* result) { - - std::string payload; - BSONType type = EOO; - Status status = saslExtractPayload(cmdObj, &payload, &type); - if (!status.isOK()) - return status; - - std::string responsePayload; - // Passing in a payload and extracting a responsePayload - status = session->step(payload, &responsePayload); - - if (!status.isOK()) { - const SockAddr clientAddr = client->port()->localAddr(); - log() << session->getMechanism() << " authentication failed for " << - session->getPrincipalId() << " on " << - session->getAuthenticationDatabase() << " from client " << clientAddr.getAddr() << - " ; " << status.toString() << std::endl; - // All the client needs to know is that authentication has failed. - return Status(ErrorCodes::AuthenticationFailed, "Authentication failed."); - } - - status = buildResponse(session, responsePayload, type, result); - if (!status.isOK()) - return status; - - if (session->isDone()) { - UserName userName(session->getPrincipalId(), session->getAuthenticationDatabase()); - status = session->getAuthorizationSession()->addAndAuthorizeUser( - session->getOpCtxt(), userName); - if (!status.isOK()) { - return status; - } - - if (!serverGlobalParams.quiet) { - log() << "Successfully authenticated as principal " << session->getPrincipalId() - << " on " << session->getAuthenticationDatabase(); - } - } - return Status::OK(); + virtual bool slaveOk() const { + return true; + } + virtual bool requiresAuth() { + return false; + } +}; + +class CmdSaslContinue : public Command { +public: + CmdSaslContinue(); + virtual ~CmdSaslContinue(); + + virtual void addRequiredPrivileges(const std::string&, + const BSONObj&, + std::vector<Privilege>*) {} + + virtual bool run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& ignored, + BSONObjBuilder& result); + + virtual void help(stringstream& help) const; + virtual bool isWriteCommandForConfigServer() const { + return false; + } + virtual bool slaveOk() const { + return true; + } + virtual bool requiresAuth() { + return false; + } +}; + +CmdSaslStart cmdSaslStart; +CmdSaslContinue cmdSaslContinue; +Status buildResponse(const SaslAuthenticationSession* session, + const std::string& responsePayload, + BSONType responsePayloadType, + BSONObjBuilder* result) { + result->appendIntOrLL(saslCommandConversationIdFieldName, session->getConversationId()); + result->appendBool(saslCommandDoneFieldName, session->isDone()); + + if (responsePayload.size() > size_t(std::numeric_limits<int>::max())) { + return Status(ErrorCodes::InvalidLength, "Response payload too long"); + } + if (responsePayloadType == BinData) { + result->appendBinData(saslCommandPayloadFieldName, + int(responsePayload.size()), + BinDataGeneral, + responsePayload.data()); + } else if (responsePayloadType == String) { + result->append(saslCommandPayloadFieldName, base64::encode(responsePayload)); + } else { + fassertFailed(4003); } - Status doSaslStart(const ClientBasic* client, - SaslAuthenticationSession* session, - const std::string& db, - const BSONObj& cmdObj, - BSONObjBuilder* result) { - - bool autoAuthorize = false; - Status status = bsonExtractBooleanFieldWithDefault(cmdObj, - saslCommandAutoAuthorizeFieldName, - autoAuthorizeDefault, - &autoAuthorize); - if (!status.isOK()) - return status; - - std::string mechanism; - status = extractMechanism(cmdObj, &mechanism); - if (!status.isOK()) - return status; - - if (!sequenceContains(saslGlobalParams.authenticationMechanisms, mechanism) && - mechanism != "SCRAM-SHA-1") { - // Always allow SCRAM-SHA-1 to pass to the first sasl step since we need to - // handle internal user authentication, SERVER-16534 - result->append(saslCommandMechanismListFieldName, - saslGlobalParams.authenticationMechanisms); - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << "Unsupported mechanism " << mechanism); - } + return Status::OK(); +} - status = session->start(db, - mechanism, - saslGlobalParams.serviceName, - saslGlobalParams.hostName, - 1, - autoAuthorize); - if (!status.isOK()) - return status; +Status extractConversationId(const BSONObj& cmdObj, int64_t* conversationId) { + BSONElement element; + Status status = bsonExtractField(cmdObj, saslCommandConversationIdFieldName, &element); + if (!status.isOK()) + return status; - return doSaslStep(client, session, cmdObj, result); + if (!element.isNumber()) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "Wrong type for field; expected number for " << element); + } + *conversationId = element.numberLong(); + return Status::OK(); +} + +Status extractMechanism(const BSONObj& cmdObj, std::string* mechanism) { + return bsonExtractStringField(cmdObj, saslCommandMechanismFieldName, mechanism); +} + +void addStatus(const Status& status, BSONObjBuilder* builder) { + builder->append("ok", status.isOK() ? 1.0 : 0.0); + if (!status.isOK()) + builder->append(saslCommandCodeFieldName, status.code()); + if (!status.reason().empty()) + builder->append(saslCommandErrmsgFieldName, status.reason()); +} + +Status doSaslStep(const ClientBasic* client, + SaslAuthenticationSession* session, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + std::string payload; + BSONType type = EOO; + Status status = saslExtractPayload(cmdObj, &payload, &type); + if (!status.isOK()) + return status; + + std::string responsePayload; + // Passing in a payload and extracting a responsePayload + status = session->step(payload, &responsePayload); + + if (!status.isOK()) { + const SockAddr clientAddr = client->port()->localAddr(); + log() << session->getMechanism() << " authentication failed for " + << session->getPrincipalId() << " on " << session->getAuthenticationDatabase() + << " from client " << clientAddr.getAddr() << " ; " << status.toString() << std::endl; + // All the client needs to know is that authentication has failed. + return Status(ErrorCodes::AuthenticationFailed, "Authentication failed."); } - Status doSaslContinue(const ClientBasic* client, - SaslAuthenticationSession* session, - const BSONObj& cmdObj, - BSONObjBuilder* result) { + status = buildResponse(session, responsePayload, type, result); + if (!status.isOK()) + return status; - int64_t conversationId = 0; - Status status = extractConversationId(cmdObj, &conversationId); - if (!status.isOK()) + if (session->isDone()) { + UserName userName(session->getPrincipalId(), session->getAuthenticationDatabase()); + status = + session->getAuthorizationSession()->addAndAuthorizeUser(session->getOpCtxt(), userName); + if (!status.isOK()) { return status; - if (conversationId != session->getConversationId()) - return Status(ErrorCodes::ProtocolError, "sasl: Mismatched conversation id"); + } - return doSaslStep(client, session, cmdObj, result); + if (!serverGlobalParams.quiet) { + log() << "Successfully authenticated as principal " << session->getPrincipalId() + << " on " << session->getAuthenticationDatabase(); + } } - - CmdSaslStart::CmdSaslStart() : Command(saslStartCommandName) {} - CmdSaslStart::~CmdSaslStart() {} - - void CmdSaslStart::help(std::stringstream& os) const { - os << "First step in a SASL authentication conversation."; + return Status::OK(); +} + +Status doSaslStart(const ClientBasic* client, + SaslAuthenticationSession* session, + const std::string& db, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + bool autoAuthorize = false; + Status status = bsonExtractBooleanFieldWithDefault( + cmdObj, saslCommandAutoAuthorizeFieldName, autoAuthorizeDefault, &autoAuthorize); + if (!status.isOK()) + return status; + + std::string mechanism; + status = extractMechanism(cmdObj, &mechanism); + if (!status.isOK()) + return status; + + if (!sequenceContains(saslGlobalParams.authenticationMechanisms, mechanism) && + mechanism != "SCRAM-SHA-1") { + // Always allow SCRAM-SHA-1 to pass to the first sasl step since we need to + // handle internal user authentication, SERVER-16534 + result->append(saslCommandMechanismListFieldName, + saslGlobalParams.authenticationMechanisms); + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "Unsupported mechanism " << mechanism); } - bool CmdSaslStart::run(OperationContext* txn, - const std::string& db, - BSONObj& cmdObj, - int options, - std::string& ignored, - BSONObjBuilder& result) { + status = session->start( + db, mechanism, saslGlobalParams.serviceName, saslGlobalParams.hostName, 1, autoAuthorize); + if (!status.isOK()) + return status; - ClientBasic* client = ClientBasic::getCurrent(); - AuthenticationSession::set(client, std::unique_ptr<AuthenticationSession>()); + return doSaslStep(client, session, cmdObj, result); +} - std::string mechanism; - if (!extractMechanism(cmdObj, &mechanism).isOK()) { - return false; - } +Status doSaslContinue(const ClientBasic* client, + SaslAuthenticationSession* session, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + int64_t conversationId = 0; + Status status = extractConversationId(cmdObj, &conversationId); + if (!status.isOK()) + return status; + if (conversationId != session->getConversationId()) + return Status(ErrorCodes::ProtocolError, "sasl: Mismatched conversation id"); + + return doSaslStep(client, session, cmdObj, result); +} + +CmdSaslStart::CmdSaslStart() : Command(saslStartCommandName) {} +CmdSaslStart::~CmdSaslStart() {} + +void CmdSaslStart::help(std::stringstream& os) const { + os << "First step in a SASL authentication conversation."; +} + +bool CmdSaslStart::run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& ignored, + BSONObjBuilder& result) { + ClientBasic* client = ClientBasic::getCurrent(); + AuthenticationSession::set(client, std::unique_ptr<AuthenticationSession>()); + + std::string mechanism; + if (!extractMechanism(cmdObj, &mechanism).isOK()) { + return false; + } - SaslAuthenticationSession* session = - SaslAuthenticationSession::create(AuthorizationSession::get(client), mechanism); + SaslAuthenticationSession* session = + SaslAuthenticationSession::create(AuthorizationSession::get(client), mechanism); - std::unique_ptr<AuthenticationSession> sessionGuard(session); + std::unique_ptr<AuthenticationSession> sessionGuard(session); - session->setOpCtxt(txn); + session->setOpCtxt(txn); - Status status = doSaslStart(client, session, db, cmdObj, &result); - addStatus(status, &result); + Status status = doSaslStart(client, session, db, cmdObj, &result); + addStatus(status, &result); - if (session->isDone()) { - audit::logAuthentication( - client, - session->getMechanism(), - UserName(session->getPrincipalId(), db), - status.code()); - } - else { - AuthenticationSession::swap(client, sessionGuard); - } - return status.isOK(); + if (session->isDone()) { + audit::logAuthentication(client, + session->getMechanism(), + UserName(session->getPrincipalId(), db), + status.code()); + } else { + AuthenticationSession::swap(client, sessionGuard); } - - CmdSaslContinue::CmdSaslContinue() : Command(saslContinueCommandName) {} - CmdSaslContinue::~CmdSaslContinue() {} - - void CmdSaslContinue::help(std::stringstream& os) const { - os << "Subsequent steps in a SASL authentication conversation."; + return status.isOK(); +} + +CmdSaslContinue::CmdSaslContinue() : Command(saslContinueCommandName) {} +CmdSaslContinue::~CmdSaslContinue() {} + +void CmdSaslContinue::help(std::stringstream& os) const { + os << "Subsequent steps in a SASL authentication conversation."; +} + +bool CmdSaslContinue::run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& ignored, + BSONObjBuilder& result) { + ClientBasic* client = ClientBasic::getCurrent(); + std::unique_ptr<AuthenticationSession> sessionGuard; + AuthenticationSession::swap(client, sessionGuard); + + if (!sessionGuard || sessionGuard->getType() != AuthenticationSession::SESSION_TYPE_SASL) { + addStatus(Status(ErrorCodes::ProtocolError, "No SASL session state found"), &result); + return false; } - bool CmdSaslContinue::run(OperationContext* txn, - const std::string& db, - BSONObj& cmdObj, - int options, - std::string& ignored, - BSONObjBuilder& result) { - - ClientBasic* client = ClientBasic::getCurrent(); - std::unique_ptr<AuthenticationSession> sessionGuard; - AuthenticationSession::swap(client, sessionGuard); - - if (!sessionGuard || sessionGuard->getType() != AuthenticationSession::SESSION_TYPE_SASL) { - addStatus(Status(ErrorCodes::ProtocolError, "No SASL session state found"), &result); - return false; - } - - SaslAuthenticationSession* session = - static_cast<SaslAuthenticationSession*>(sessionGuard.get()); + SaslAuthenticationSession* session = + static_cast<SaslAuthenticationSession*>(sessionGuard.get()); - // Authenticating the __system@local user to the admin database on mongos is required - // by the auth passthrough test suite. - if (session->getAuthenticationDatabase() != db && !Command::testCommandsEnabled) { - addStatus(Status(ErrorCodes::ProtocolError, - "Attempt to switch database target during SASL authentication."), - &result); - return false; - } + // Authenticating the __system@local user to the admin database on mongos is required + // by the auth passthrough test suite. + if (session->getAuthenticationDatabase() != db && !Command::testCommandsEnabled) { + addStatus(Status(ErrorCodes::ProtocolError, + "Attempt to switch database target during SASL authentication."), + &result); + return false; + } - session->setOpCtxt(txn); + session->setOpCtxt(txn); - Status status = doSaslContinue(client, session, cmdObj, &result); - addStatus(status, &result); + Status status = doSaslContinue(client, session, cmdObj, &result); + addStatus(status, &result); - if (session->isDone()) { - audit::logAuthentication( - client, - session->getMechanism(), - UserName(session->getPrincipalId(), db), - status.code()); - } - else { - AuthenticationSession::swap(client, sessionGuard); - } - - return status.isOK(); + if (session->isDone()) { + audit::logAuthentication(client, + session->getMechanism(), + UserName(session->getPrincipalId(), db), + status.code()); + } else { + AuthenticationSession::swap(client, sessionGuard); } - // The CyrusSaslCommands Enterprise initializer is dependent on PreSaslCommands - MONGO_INITIALIZER_WITH_PREREQUISITES(PreSaslCommands, - ("NativeSaslServerCore")) - (InitializerContext*) { + return status.isOK(); +} - if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-CR")) - CmdAuthenticate::disableAuthMechanism("MONGODB-CR"); +// The CyrusSaslCommands Enterprise initializer is dependent on PreSaslCommands +MONGO_INITIALIZER_WITH_PREREQUISITES(PreSaslCommands, ("NativeSaslServerCore")) +(InitializerContext*) { + if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-CR")) + CmdAuthenticate::disableAuthMechanism("MONGODB-CR"); - if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-X509")) - CmdAuthenticate::disableAuthMechanism("MONGODB-X509"); + if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-X509")) + CmdAuthenticate::disableAuthMechanism("MONGODB-X509"); - // For backwards compatibility, in 3.0 we are letting MONGODB-CR imply general - // challenge-response auth and hence SCRAM-SHA-1 is enabled by either specifying - // SCRAM-SHA-1 or MONGODB-CR in the authenticationMechanism server parameter. - if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") && - sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-CR")) - saslGlobalParams.authenticationMechanisms.push_back("SCRAM-SHA-1"); + // For backwards compatibility, in 3.0 we are letting MONGODB-CR imply general + // challenge-response auth and hence SCRAM-SHA-1 is enabled by either specifying + // SCRAM-SHA-1 or MONGODB-CR in the authenticationMechanism server parameter. + if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") && + sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-CR")) + saslGlobalParams.authenticationMechanisms.push_back("SCRAM-SHA-1"); - return Status::OK(); - } + return Status::OK(); +} } // namespace } // namespace mongo diff --git a/src/mongo/db/auth/sasl_options.cpp b/src/mongo/db/auth/sasl_options.cpp index 7261d8b49f0..8ca9e38dd63 100644 --- a/src/mongo/db/auth/sasl_options.cpp +++ b/src/mongo/db/auth/sasl_options.cpp @@ -39,162 +39,160 @@ namespace mongo { - SASLGlobalParams saslGlobalParams; - - const int defaultScramIterationCount = 10000; - const int minimumScramIterationCount = 5000; - - SASLGlobalParams::SASLGlobalParams() { - // Authentication mechanisms supported by default. - authenticationMechanisms.push_back("MONGODB-CR"); - authenticationMechanisms.push_back("MONGODB-X509"); - authenticationMechanisms.push_back("SCRAM-SHA-1"); - // Default iteration count for SCRAM authentication. - scramIterationCount = defaultScramIterationCount; +SASLGlobalParams saslGlobalParams; + +const int defaultScramIterationCount = 10000; +const int minimumScramIterationCount = 5000; + +SASLGlobalParams::SASLGlobalParams() { + // Authentication mechanisms supported by default. + authenticationMechanisms.push_back("MONGODB-CR"); + authenticationMechanisms.push_back("MONGODB-X509"); + authenticationMechanisms.push_back("SCRAM-SHA-1"); + // Default iteration count for SCRAM authentication. + scramIterationCount = defaultScramIterationCount; +} + +Status addSASLOptions(moe::OptionSection* options) { + moe::OptionSection saslOptions("SASL Options"); + + saslOptions.addOptionChaining("security.authenticationMechanisms", + "", + moe::StringVector, + "List of supported authentication mechanisms. " + "Default is MONGODB-CR, SCRAM-SHA-1 and MONGODB-X509.") + .setSources(moe::SourceYAMLConfig); + + saslOptions.addOptionChaining( + "security.sasl.hostName", "", moe::String, "Fully qualified server domain name") + .setSources(moe::SourceYAMLConfig); + + saslOptions.addOptionChaining("security.sasl.serviceName", + "", + moe::String, + "Registered name of the service using SASL") + .setSources(moe::SourceYAMLConfig); + + saslOptions.addOptionChaining("security.sasl.saslauthdSocketPath", + "", + moe::String, + "Path to Unix domain socket file for saslauthd") + .setSources(moe::SourceYAMLConfig); + + Status ret = options->addSection(saslOptions); + if (!ret.isOK()) { + log() << "Failed to add sasl option section: " << ret.toString(); + return ret; } - Status addSASLOptions(moe::OptionSection* options) { - - moe::OptionSection saslOptions("SASL Options"); - - saslOptions.addOptionChaining("security.authenticationMechanisms", "", - moe::StringVector, "List of supported authentication mechanisms. " - "Default is MONGODB-CR, SCRAM-SHA-1 and MONGODB-X509.") - .setSources(moe::SourceYAMLConfig); - - saslOptions.addOptionChaining("security.sasl.hostName", "", moe::String, - "Fully qualified server domain name") - .setSources(moe::SourceYAMLConfig); - - saslOptions.addOptionChaining("security.sasl.serviceName", "", moe::String, - "Registered name of the service using SASL") - .setSources(moe::SourceYAMLConfig); - - saslOptions.addOptionChaining("security.sasl.saslauthdSocketPath", "", moe::String, - "Path to Unix domain socket file for saslauthd") - .setSources(moe::SourceYAMLConfig); - - Status ret = options->addSection(saslOptions); - if (!ret.isOK()) { - log() << "Failed to add sasl option section: " << ret.toString(); - return ret; - } - - return Status::OK(); - } - - Status storeSASLOptions(const moe::Environment& params) { - - bool haveAuthenticationMechanisms = false; - bool haveHostName = false; - bool haveServiceName = false; - bool haveAuthdPath = false; - bool haveScramIterationCount = false; - - // Check our setParameter options first so that these values can be properly overridden via - // the command line even though the options have different names. - if (params.count("setParameter")) { - std::map<std::string, std::string> parameters = - params["setParameter"].as<std::map<std::string, std::string> >(); - for (std::map<std::string, std::string>::iterator parametersIt = parameters.begin(); - parametersIt != parameters.end(); parametersIt++) { - if (parametersIt->first == "authenticationMechanisms") { - haveAuthenticationMechanisms = true; - } - else if (parametersIt->first == "saslHostName") { - haveHostName = true; - } - else if (parametersIt->first == "saslServiceName") { - haveServiceName = true; - } - else if (parametersIt->first == "saslauthdPath") { - haveAuthdPath = true; - } - else if (parametersIt->first == "scramIterationCount") { - haveScramIterationCount = true; - } + return Status::OK(); +} + +Status storeSASLOptions(const moe::Environment& params) { + bool haveAuthenticationMechanisms = false; + bool haveHostName = false; + bool haveServiceName = false; + bool haveAuthdPath = false; + bool haveScramIterationCount = false; + + // Check our setParameter options first so that these values can be properly overridden via + // the command line even though the options have different names. + if (params.count("setParameter")) { + std::map<std::string, std::string> parameters = + params["setParameter"].as<std::map<std::string, std::string>>(); + for (std::map<std::string, std::string>::iterator parametersIt = parameters.begin(); + parametersIt != parameters.end(); + parametersIt++) { + if (parametersIt->first == "authenticationMechanisms") { + haveAuthenticationMechanisms = true; + } else if (parametersIt->first == "saslHostName") { + haveHostName = true; + } else if (parametersIt->first == "saslServiceName") { + haveServiceName = true; + } else if (parametersIt->first == "saslauthdPath") { + haveAuthdPath = true; + } else if (parametersIt->first == "scramIterationCount") { + haveScramIterationCount = true; } } - - if (params.count("security.authenticationMechanisms") && - !haveAuthenticationMechanisms) { - saslGlobalParams.authenticationMechanisms = - params["security.authenticationMechanisms"].as<std::vector<std::string> >(); - } - if (params.count("security.sasl.hostName") && !haveHostName) { - saslGlobalParams.hostName = - params["security.sasl.hostName"].as<std::string>(); - } - if (params.count("security.sasl.serviceName") && !haveServiceName) { - saslGlobalParams.serviceName = - params["security.sasl.serviceName"].as<std::string>(); - } - if (params.count("security.sasl.saslauthdSocketPath") && !haveAuthdPath) { - saslGlobalParams.authdPath = - params["security.sasl.saslauthdSocketPath"].as<std::string>(); - } - if (params.count("security.sasl.scramIterationCount") && !haveScramIterationCount) { - saslGlobalParams.scramIterationCount = - params["security.sasl.scramIterationCount"].as<int>(); - } - - return Status::OK(); } - MONGO_MODULE_STARTUP_OPTIONS_REGISTER(SASLOptions)(InitializerContext* context) { - return addSASLOptions(&moe::startupOptions); + if (params.count("security.authenticationMechanisms") && !haveAuthenticationMechanisms) { + saslGlobalParams.authenticationMechanisms = + params["security.authenticationMechanisms"].as<std::vector<std::string>>(); } - - MONGO_STARTUP_OPTIONS_STORE(SASLOptions)(InitializerContext* context) { - return storeSASLOptions(moe::startupOptionsParsed); + if (params.count("security.sasl.hostName") && !haveHostName) { + saslGlobalParams.hostName = params["security.sasl.hostName"].as<std::string>(); + } + if (params.count("security.sasl.serviceName") && !haveServiceName) { + saslGlobalParams.serviceName = params["security.sasl.serviceName"].as<std::string>(); + } + if (params.count("security.sasl.saslauthdSocketPath") && !haveAuthdPath) { + saslGlobalParams.authdPath = params["security.sasl.saslauthdSocketPath"].as<std::string>(); + } + if (params.count("security.sasl.scramIterationCount") && !haveScramIterationCount) { + saslGlobalParams.scramIterationCount = + params["security.sasl.scramIterationCount"].as<int>(); } - // SASL Startup Parameters, making them settable via setParameter on the command line or in the - // legacy INI config file. None of these parameters are modifiable at runtime. - ExportedServerParameter<std::vector<std::string> > SASLAuthenticationMechanismsSetting( - ServerParameterSet::getGlobal(), - "authenticationMechanisms", - &saslGlobalParams.authenticationMechanisms, - true, // Change at startup - false); // Change at runtime - - ExportedServerParameter<std::string> SASLHostNameSetting(ServerParameterSet::getGlobal(), - "saslHostName", - &saslGlobalParams.hostName, - true, // Change at startup - false); // Change at runtime - - ExportedServerParameter<std::string> SASLServiceNameSetting(ServerParameterSet::getGlobal(), - "saslServiceName", - &saslGlobalParams.serviceName, - true, // Change at startup - false); // Change at runtime - - ExportedServerParameter<std::string> SASLAuthdPathSetting(ServerParameterSet::getGlobal(), - "saslauthdPath", - &saslGlobalParams.authdPath, - true, // Change at startup - false); // Change at runtime - - const std::string scramIterationCountServerParameter = "scramIterationCount"; - class ExportedScramIterationCountParameter : public ExportedServerParameter<int> { - public: - ExportedScramIterationCountParameter(): - ExportedServerParameter<int>(ServerParameterSet::getGlobal(), - scramIterationCountServerParameter, - &saslGlobalParams.scramIterationCount, - true, // Change at startup - true) {} // Change at runtime - - virtual Status validate(const int& newValue) { - if (newValue < minimumScramIterationCount) { - return Status(ErrorCodes::BadValue, mongoutils::str::stream() << - "Invalid value for SCRAM iteration count: " << newValue << - " is less than the minimum SCRAM iteration count, " << - minimumScramIterationCount); - } - return Status::OK(); + return Status::OK(); +} + +MONGO_MODULE_STARTUP_OPTIONS_REGISTER(SASLOptions)(InitializerContext* context) { + return addSASLOptions(&moe::startupOptions); +} + +MONGO_STARTUP_OPTIONS_STORE(SASLOptions)(InitializerContext* context) { + return storeSASLOptions(moe::startupOptionsParsed); +} + +// SASL Startup Parameters, making them settable via setParameter on the command line or in the +// legacy INI config file. None of these parameters are modifiable at runtime. +ExportedServerParameter<std::vector<std::string>> SASLAuthenticationMechanismsSetting( + ServerParameterSet::getGlobal(), + "authenticationMechanisms", + &saslGlobalParams.authenticationMechanisms, + true, // Change at startup + false); // Change at runtime + +ExportedServerParameter<std::string> SASLHostNameSetting(ServerParameterSet::getGlobal(), + "saslHostName", + &saslGlobalParams.hostName, + true, // Change at startup + false); // Change at runtime + +ExportedServerParameter<std::string> SASLServiceNameSetting(ServerParameterSet::getGlobal(), + "saslServiceName", + &saslGlobalParams.serviceName, + true, // Change at startup + false); // Change at runtime + +ExportedServerParameter<std::string> SASLAuthdPathSetting(ServerParameterSet::getGlobal(), + "saslauthdPath", + &saslGlobalParams.authdPath, + true, // Change at startup + false); // Change at runtime + +const std::string scramIterationCountServerParameter = "scramIterationCount"; +class ExportedScramIterationCountParameter : public ExportedServerParameter<int> { +public: + ExportedScramIterationCountParameter() + : ExportedServerParameter<int>(ServerParameterSet::getGlobal(), + scramIterationCountServerParameter, + &saslGlobalParams.scramIterationCount, + true, // Change at startup + true) {} // Change at runtime + + virtual Status validate(const int& newValue) { + if (newValue < minimumScramIterationCount) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() + << "Invalid value for SCRAM iteration count: " << newValue + << " is less than the minimum SCRAM iteration count, " + << minimumScramIterationCount); } - } scramIterationCountParam; + return Status::OK(); + } +} scramIterationCountParam; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_options.h b/src/mongo/db/auth/sasl_options.h index cc649adeeba..f9a4bb15efd 100644 --- a/src/mongo/db/auth/sasl_options.h +++ b/src/mongo/db/auth/sasl_options.h @@ -36,27 +36,26 @@ namespace mongo { namespace optionenvironment { - class OptionSection; - class Environment; -} // namespace optionenvironment +class OptionSection; +class Environment; +} // namespace optionenvironment - namespace moe = optionenvironment; +namespace moe = optionenvironment; - struct SASLGlobalParams { +struct SASLGlobalParams { + std::vector<std::string> authenticationMechanisms; + std::string hostName; + std::string serviceName; + std::string authdPath; + int scramIterationCount; - std::vector<std::string> authenticationMechanisms; - std::string hostName; - std::string serviceName; - std::string authdPath; - int scramIterationCount; + SASLGlobalParams(); +}; - SASLGlobalParams(); - }; +extern SASLGlobalParams saslGlobalParams; - extern SASLGlobalParams saslGlobalParams; +Status addSASLOptions(moe::OptionSection* options); - Status addSASLOptions(moe::OptionSection* options); +Status storeSASLOptions(const moe::Environment& params); - Status storeSASLOptions(const moe::Environment& params); - -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp index ef38762e3a5..b5f0b9e3c8f 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp @@ -36,73 +36,70 @@ namespace mongo { - SaslPLAINServerConversation::SaslPLAINServerConversation( - SaslAuthenticationSession* saslAuthSession) : - SaslServerConversation(saslAuthSession) { +SaslPLAINServerConversation::SaslPLAINServerConversation(SaslAuthenticationSession* saslAuthSession) + : SaslServerConversation(saslAuthSession) {} + +SaslPLAINServerConversation::~SaslPLAINServerConversation(){}; + +StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, std::string* outputData) { + // Expecting user input on the form: user\0user\0pwd + std::string input = inputData.toString(); + std::string pwd = ""; + + try { + _user = input.substr(0, inputData.find('\0')); + pwd = input.substr(inputData.find('\0', _user.size() + 1) + 1); + } catch (std::out_of_range& exception) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + mongoutils::str::stream() + << "Incorrectly formatted PLAIN client message"); } - SaslPLAINServerConversation::~SaslPLAINServerConversation() {}; + User* userObj; + // The authentication database is also the source database for the user. + Status status = + _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().acquireUser( + _saslAuthSession->getOpCtxt(), + UserName(_user, _saslAuthSession->getAuthenticationDatabase()), + &userObj); - StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, - std::string* outputData) { - // Expecting user input on the form: user\0user\0pwd - std::string input = inputData.toString(); - std::string pwd = ""; - - try { - _user = input.substr(0, inputData.find('\0')); - pwd = input.substr(inputData.find('\0', _user.size()+1)+1); - } - catch (std::out_of_range& exception) { - return StatusWith<bool>(ErrorCodes::AuthenticationFailed, - mongoutils::str::stream() << "Incorrectly formatted PLAIN client message"); - } - - User* userObj; - // The authentication database is also the source database for the user. - Status status = _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). - acquireUser(_saslAuthSession->getOpCtxt(), - UserName(_user, _saslAuthSession->getAuthenticationDatabase()), - &userObj); - - if (!status.isOK()) { - return StatusWith<bool>(status); - } + if (!status.isOK()) { + return StatusWith<bool>(status); + } - const User::CredentialData creds = userObj->getCredentials(); - _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). - releaseUser(userObj); + const User::CredentialData creds = userObj->getCredentials(); + _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().releaseUser(userObj); - std::string authDigest = createPasswordDigest(_user, pwd); + std::string authDigest = createPasswordDigest(_user, pwd); - if (!creds.password.empty()) { - // Handle schemaVersion26Final (MONGODB-CR/SCRAM mixed mode) - if (authDigest != creds.password) { - return StatusWith<bool>(ErrorCodes::AuthenticationFailed, - mongoutils::str::stream() << "Incorrect user name or password"); - } + if (!creds.password.empty()) { + // Handle schemaVersion26Final (MONGODB-CR/SCRAM mixed mode) + if (authDigest != creds.password) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + mongoutils::str::stream() << "Incorrect user name or password"); } - else { - // Handle schemaVersion28SCRAM (SCRAM only mode) - unsigned char storedKey[scram::hashSize]; - unsigned char serverKey[scram::hashSize]; - - scram::generateSecrets(authDigest, - reinterpret_cast<const unsigned char*>(base64::decode(creds.scram.salt).c_str()), - 16, - creds.scram.iterationCount, - storedKey, - serverKey); - if (creds.scram.storedKey != base64::encode(reinterpret_cast<const char*>(storedKey), - scram::hashSize)){ - return StatusWith<bool>(ErrorCodes::AuthenticationFailed, - mongoutils::str::stream() << "Incorrect user name or password"); - } + } else { + // Handle schemaVersion28SCRAM (SCRAM only mode) + unsigned char storedKey[scram::hashSize]; + unsigned char serverKey[scram::hashSize]; + + scram::generateSecrets( + authDigest, + reinterpret_cast<const unsigned char*>(base64::decode(creds.scram.salt).c_str()), + 16, + creds.scram.iterationCount, + storedKey, + serverKey); + if (creds.scram.storedKey != + base64::encode(reinterpret_cast<const char*>(storedKey), scram::hashSize)) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + mongoutils::str::stream() << "Incorrect user name or password"); } + } - *outputData = ""; + *outputData = ""; - return StatusWith<bool>(true); - } + return StatusWith<bool>(true); +} } // namespace mongo diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.h b/src/mongo/db/auth/sasl_plain_server_conversation.h index d8e33e99905..5d3b57ffa89 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.h +++ b/src/mongo/db/auth/sasl_plain_server_conversation.h @@ -36,21 +36,22 @@ #include "mongo/db/auth/sasl_server_conversation.h" namespace mongo { +/** + * Server side authentication session for SASL PLAIN. + */ +class SaslPLAINServerConversation : public SaslServerConversation { + MONGO_DISALLOW_COPYING(SaslPLAINServerConversation); + +public: /** - * Server side authentication session for SASL PLAIN. - */ - class SaslPLAINServerConversation : public SaslServerConversation { - MONGO_DISALLOW_COPYING(SaslPLAINServerConversation); - public: - /** - * Implements the server side of a SASL PLAIN mechanism session. - * - **/ - explicit SaslPLAINServerConversation(SaslAuthenticationSession* saslAuthSession); - - virtual ~SaslPLAINServerConversation(); - - virtual StatusWith<bool> step(StringData inputData, std::string* outputData); - }; + * Implements the server side of a SASL PLAIN mechanism session. + * + **/ + explicit SaslPLAINServerConversation(SaslAuthenticationSession* saslAuthSession); + + virtual ~SaslPLAINServerConversation(); + + virtual StatusWith<bool> step(StringData inputData, std::string* outputData); +}; } // namespace mongo diff --git a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp index 83137a8bd99..9fd8496b7bc 100644 --- a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp @@ -48,292 +48,289 @@ namespace mongo { - using std::unique_ptr; - using std::string; - - SaslSCRAMSHA1ServerConversation::SaslSCRAMSHA1ServerConversation( - SaslAuthenticationSession* saslAuthSession) : - SaslServerConversation(saslAuthSession), - _step(0), - _authMessage(""), - _nonce("") { +using std::unique_ptr; +using std::string; + +SaslSCRAMSHA1ServerConversation::SaslSCRAMSHA1ServerConversation( + SaslAuthenticationSession* saslAuthSession) + : SaslServerConversation(saslAuthSession), _step(0), _authMessage(""), _nonce("") {} + +StatusWith<bool> SaslSCRAMSHA1ServerConversation::step(StringData inputData, + std::string* outputData) { + std::vector<std::string> input = StringSplitter::split(inputData.toString(), ","); + _step++; + + if (_step > 3 || _step <= 0) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + mongoutils::str::stream() + << "Invalid SCRAM-SHA-1 authentication step: " << _step); } - - StatusWith<bool> SaslSCRAMSHA1ServerConversation::step(StringData inputData, - std::string* outputData) { - - std::vector<std::string> input = StringSplitter::split(inputData.toString(), ","); - _step++; - - if (_step > 3 || _step <= 0) { - return StatusWith<bool>(ErrorCodes::AuthenticationFailed, - mongoutils::str::stream() << "Invalid SCRAM-SHA-1 authentication step: " << _step); - } - if (_step == 1) { - return _firstStep(input, outputData); - } - if (_step == 2) { - return _secondStep(input, outputData); - } - - *outputData = ""; - - return StatusWith<bool>(true); + if (_step == 1) { + return _firstStep(input, outputData); } - - /* - * RFC 5802 specifies that in SCRAM user names characters ',' and '=' are encoded as - * =2C and =3D respectively. - */ - static void decodeSCRAMUsername(std::string& user) { - boost::replace_all(user, "=2C", ","); - boost::replace_all(user, "=3D", "="); + if (_step == 2) { + return _secondStep(input, outputData); } - /* - * Parse client-first-message of the form: - * n,a=authzid,n=encoded-username,r=client-nonce - * - * Generate server-first-message on the form: - * r=client-nonce|server-nonce,s=user-salt,i=iteration-count - * - * NOTE: we are ignoring the authorization ID part of the message - */ - StatusWith<bool> SaslSCRAMSHA1ServerConversation::_firstStep(std::vector<string>& input, - std::string* outputData) { - std::string authzId = ""; - - if (input.size() == 4) { - /* The second entry a=authzid is optional. If provided it will be - * validated against the encoded username. - * - * The two allowed input forms are: - * n,,n=encoded-username,r=client-nonce - * n,a=authzid,n=encoded-username,r=client-nonce - */ - if (!str::startsWith(input[1], "a=") || input[1].size() < 3) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Incorrect SCRAM-SHA-1 authzid: " << input[1]); - } - authzId = input[1].substr(2); - input.erase(input.begin() + 1); - } - - if (input.size() != 3) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Incorrect number of arguments for first SCRAM-SHA-1 client message, got " << - input.size() << " expected 4"); - } - else if (input[0] != "n") { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Incorrect SCRAM-SHA-1 client message prefix: " << input[0]); - } - else if (!str::startsWith(input[1], "n=") || input[1].size() < 3) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Incorrect SCRAM-SHA-1 user name: " << input[1]); - } - else if(!str::startsWith(input[2], "r=") || input[2].size() < 6) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Incorrect SCRAM-SHA-1 client nonce: " << input[2]); - } + *outputData = ""; - _user = input[1].substr(2); - if (!authzId.empty() && _user != authzId) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "SCRAM-SHA-1 user name " << _user << " does not match authzid " << authzId); - } + return StatusWith<bool>(true); +} - decodeSCRAMUsername(_user); +/* + * RFC 5802 specifies that in SCRAM user names characters ',' and '=' are encoded as + * =2C and =3D respectively. + */ +static void decodeSCRAMUsername(std::string& user) { + boost::replace_all(user, "=2C", ","); + boost::replace_all(user, "=3D", "="); +} - // SERVER-16534, SCRAM-SHA-1 must be enabled for authenticating the internal user, so that - // cluster members may communicate with each other. Hence ignore disabled auth mechanism - // for the internal user. - UserName user(_user, _saslAuthSession->getAuthenticationDatabase()); - if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") && - user != internalSecurity.user->getName()) { +/* + * Parse client-first-message of the form: + * n,a=authzid,n=encoded-username,r=client-nonce + * + * Generate server-first-message on the form: + * r=client-nonce|server-nonce,s=user-salt,i=iteration-count + * + * NOTE: we are ignoring the authorization ID part of the message + */ +StatusWith<bool> SaslSCRAMSHA1ServerConversation::_firstStep(std::vector<string>& input, + std::string* outputData) { + std::string authzId = ""; + + if (input.size() == 4) { + /* The second entry a=authzid is optional. If provided it will be + * validated against the encoded username. + * + * The two allowed input forms are: + * n,,n=encoded-username,r=client-nonce + * n,a=authzid,n=encoded-username,r=client-nonce + */ + if (!str::startsWith(input[1], "a=") || input[1].size() < 3) { return StatusWith<bool>(ErrorCodes::BadValue, - "SCRAM-SHA-1 authentication is disabled"); + mongoutils::str::stream() + << "Incorrect SCRAM-SHA-1 authzid: " << input[1]); } + authzId = input[1].substr(2); + input.erase(input.begin() + 1); + } - // add client-first-message-bare to _authMessage - _authMessage += input[1] + "," + input[2] + ","; - - std::string clientNonce = input[2].substr(2); - - // The authentication database is also the source database for the user. - User* userObj; - Status status = _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). - acquireUser(_saslAuthSession->getOpCtxt(), - user, - &userObj); + if (input.size() != 3) { + return StatusWith<bool>( + ErrorCodes::BadValue, + mongoutils::str::stream() + << "Incorrect number of arguments for first SCRAM-SHA-1 client message, got " + << input.size() << " expected 4"); + } else if (input[0] != "n") { + return StatusWith<bool>(ErrorCodes::BadValue, + mongoutils::str::stream() + << "Incorrect SCRAM-SHA-1 client message prefix: " << input[0]); + } else if (!str::startsWith(input[1], "n=") || input[1].size() < 3) { + return StatusWith<bool>(ErrorCodes::BadValue, + mongoutils::str::stream() + << "Incorrect SCRAM-SHA-1 user name: " << input[1]); + } else if (!str::startsWith(input[2], "r=") || input[2].size() < 6) { + return StatusWith<bool>(ErrorCodes::BadValue, + mongoutils::str::stream() + << "Incorrect SCRAM-SHA-1 client nonce: " << input[2]); + } - if (!status.isOK()) { - return StatusWith<bool>(status); - } + _user = input[1].substr(2); + if (!authzId.empty() && _user != authzId) { + return StatusWith<bool>(ErrorCodes::BadValue, + mongoutils::str::stream() << "SCRAM-SHA-1 user name " << _user + << " does not match authzid " << authzId); + } - _creds = userObj->getCredentials(); - UserName userName = userObj->getName(); + decodeSCRAMUsername(_user); - _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). - releaseUser(userObj); + // SERVER-16534, SCRAM-SHA-1 must be enabled for authenticating the internal user, so that + // cluster members may communicate with each other. Hence ignore disabled auth mechanism + // for the internal user. + UserName user(_user, _saslAuthSession->getAuthenticationDatabase()); + if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") && + user != internalSecurity.user->getName()) { + return StatusWith<bool>(ErrorCodes::BadValue, "SCRAM-SHA-1 authentication is disabled"); + } - // Check for authentication attempts of the __system user on - // systems started without a keyfile. - if (userName == internalSecurity.user->getName() && - _creds.scram.salt.empty()) { - return StatusWith<bool>(ErrorCodes::AuthenticationFailed, - "It is not possible to authenticate as the __system user " - "on servers started without a --keyFile parameter"); - } + // add client-first-message-bare to _authMessage + _authMessage += input[1] + "," + input[2] + ","; - // Generate SCRAM credentials on the fly for mixed MONGODB-CR/SCRAM mode. - if (_creds.scram.salt.empty() && !_creds.password.empty()) { - // Use a default value of 5000 for the scramIterationCount when in mixed mode, - // overriding the default value (10000) used for SCRAM mode or the user-given value. - const int mixedModeScramIterationCount = 5000; - BSONObj scramCreds = scram::generateCredentials(_creds.password, - mixedModeScramIterationCount); - _creds.scram.iterationCount = scramCreds[scram::iterationCountFieldName].Int(); - _creds.scram.salt = scramCreds[scram::saltFieldName].String(); - _creds.scram.storedKey = scramCreds[scram::storedKeyFieldName].String(); - _creds.scram.serverKey = scramCreds[scram::serverKeyFieldName].String(); - } + std::string clientNonce = input[2].substr(2); - // Generate server-first-message - // Create text-based nonce as base64 encoding of a binary blob of length multiple of 3 - const int nonceLenQWords = 3; - uint64_t binaryNonce[nonceLenQWords]; + // The authentication database is also the source database for the user. + User* userObj; + Status status = + _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().acquireUser( + _saslAuthSession->getOpCtxt(), user, &userObj); - unique_ptr<SecureRandom> sr(SecureRandom::create()); + if (!status.isOK()) { + return StatusWith<bool>(status); + } - binaryNonce[0] = sr->nextInt64(); - binaryNonce[1] = sr->nextInt64(); - binaryNonce[2] = sr->nextInt64(); + _creds = userObj->getCredentials(); + UserName userName = userObj->getName(); - _nonce = clientNonce + - base64::encode(reinterpret_cast<char*>(binaryNonce), sizeof(binaryNonce)); - StringBuilder sb; - sb << "r=" << _nonce << - ",s=" << _creds.scram.salt << - ",i=" << _creds.scram.iterationCount; - *outputData = sb.str(); + _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().releaseUser(userObj); - // add server-first-message to authMessage - _authMessage += *outputData + ","; + // Check for authentication attempts of the __system user on + // systems started without a keyfile. + if (userName == internalSecurity.user->getName() && _creds.scram.salt.empty()) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + "It is not possible to authenticate as the __system user " + "on servers started without a --keyFile parameter"); + } - return StatusWith<bool>(false); + // Generate SCRAM credentials on the fly for mixed MONGODB-CR/SCRAM mode. + if (_creds.scram.salt.empty() && !_creds.password.empty()) { + // Use a default value of 5000 for the scramIterationCount when in mixed mode, + // overriding the default value (10000) used for SCRAM mode or the user-given value. + const int mixedModeScramIterationCount = 5000; + BSONObj scramCreds = + scram::generateCredentials(_creds.password, mixedModeScramIterationCount); + _creds.scram.iterationCount = scramCreds[scram::iterationCountFieldName].Int(); + _creds.scram.salt = scramCreds[scram::saltFieldName].String(); + _creds.scram.storedKey = scramCreds[scram::storedKeyFieldName].String(); + _creds.scram.serverKey = scramCreds[scram::serverKeyFieldName].String(); } - /** - * Parse client-final-message of the form: - * c=channel-binding(base64),r=client-nonce|server-nonce,p=ClientProof - * - * Generate successful authentication server-final-message on the form: - * v=ServerSignature - * - * or failed authentication server-final-message on the form: - * e=message - * - * NOTE: we are ignoring the channel binding part of the message - **/ - StatusWith<bool> SaslSCRAMSHA1ServerConversation::_secondStep(const std::vector<string>& input, - std::string* outputData) { - if (input.size() != 3) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Incorrect number of arguments for second SCRAM-SHA-1 client message, got " << - input.size() << " expected 3"); - } - else if (!str::startsWith(input[0], "c=") || input[0].size() < 3) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Incorrect SCRAM-SHA-1 channel binding: " << input[0]); - } - else if (!str::startsWith(input[1], "r=") || input[1].size() < 6) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Incorrect SCRAM-SHA-1 client|server nonce: " << input[1]); - } - else if(!str::startsWith(input[2], "p=") || input[2].size() < 3) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Incorrect SCRAM-SHA-1 ClientProof: " << input[2]); - } + // Generate server-first-message + // Create text-based nonce as base64 encoding of a binary blob of length multiple of 3 + const int nonceLenQWords = 3; + uint64_t binaryNonce[nonceLenQWords]; - // add client-final-message-without-proof to authMessage - _authMessage += input[0] + "," + input[1]; + unique_ptr<SecureRandom> sr(SecureRandom::create()); - // Concatenated nonce sent by client should equal the one in server-first-message - std::string nonce = input[1].substr(2); - if (nonce != _nonce) { - return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << - "Unmatched SCRAM-SHA-1 nonce received from client in second step, expected " << - _nonce << " but received " << nonce); - } + binaryNonce[0] = sr->nextInt64(); + binaryNonce[1] = sr->nextInt64(); + binaryNonce[2] = sr->nextInt64(); - std::string clientProof = input[2].substr(2); - - // Do server side computations, compare storedKeys and generate client-final-message - // AuthMessage := client-first-message-bare + "," + - // server-first-message + "," + - // client-final-message-without-proof - // ClientSignature := HMAC(StoredKey, AuthMessage) - // ClientKey := ClientSignature XOR ClientProof - // ServerSignature := HMAC(ServerKey, AuthMessage) - - unsigned int hashLen = 0; - unsigned char clientSignature[scram::hashSize]; - - std::string decodedStoredKey = base64::decode(_creds.scram.storedKey); - // ClientSignature := HMAC(StoredKey, AuthMessage) - fassert(18662, crypto::hmacSha1( - reinterpret_cast<const unsigned char*>(decodedStoredKey.c_str()), - scram::hashSize, - reinterpret_cast<const unsigned char*>(_authMessage.c_str()), - _authMessage.size(), - clientSignature, - &hashLen)); - - fassert(18658, hashLen == scram::hashSize); - - try { - clientProof = base64::decode(clientProof); - } - catch (const DBException& ex) { - return StatusWith<bool>(ex.toStatus()); - } - const unsigned char *decodedClientProof = - reinterpret_cast<const unsigned char*>(clientProof.c_str()); + _nonce = + clientNonce + base64::encode(reinterpret_cast<char*>(binaryNonce), sizeof(binaryNonce)); + StringBuilder sb; + sb << "r=" << _nonce << ",s=" << _creds.scram.salt << ",i=" << _creds.scram.iterationCount; + *outputData = sb.str(); - // ClientKey := ClientSignature XOR ClientProof - unsigned char clientKey[scram::hashSize]; - for(size_t i=0; i<scram::hashSize; i++) { - clientKey[i] = clientSignature[i]^decodedClientProof[i]; - } + // add server-first-message to authMessage + _authMessage += *outputData + ","; - // StoredKey := H(ClientKey) - unsigned char computedStoredKey[scram::hashSize]; - fassert(18659, crypto::sha1(clientKey, scram::hashSize, computedStoredKey)); + return StatusWith<bool>(false); +} - if (memcmp(decodedStoredKey.c_str(), computedStoredKey, scram::hashSize) != 0) { - return StatusWith<bool>(ErrorCodes::AuthenticationFailed, - mongoutils::str::stream() << - "SCRAM-SHA-1 authentication failed, storedKey mismatch"); - } +/** + * Parse client-final-message of the form: + * c=channel-binding(base64),r=client-nonce|server-nonce,p=ClientProof + * + * Generate successful authentication server-final-message on the form: + * v=ServerSignature + * + * or failed authentication server-final-message on the form: + * e=message + * + * NOTE: we are ignoring the channel binding part of the message +**/ +StatusWith<bool> SaslSCRAMSHA1ServerConversation::_secondStep(const std::vector<string>& input, + std::string* outputData) { + if (input.size() != 3) { + return StatusWith<bool>( + ErrorCodes::BadValue, + mongoutils::str::stream() + << "Incorrect number of arguments for second SCRAM-SHA-1 client message, got " + << input.size() << " expected 3"); + } else if (!str::startsWith(input[0], "c=") || input[0].size() < 3) { + return StatusWith<bool>(ErrorCodes::BadValue, + mongoutils::str::stream() + << "Incorrect SCRAM-SHA-1 channel binding: " << input[0]); + } else if (!str::startsWith(input[1], "r=") || input[1].size() < 6) { + return StatusWith<bool>(ErrorCodes::BadValue, + mongoutils::str::stream() + << "Incorrect SCRAM-SHA-1 client|server nonce: " << input[1]); + } else if (!str::startsWith(input[2], "p=") || input[2].size() < 3) { + return StatusWith<bool>(ErrorCodes::BadValue, + mongoutils::str::stream() + << "Incorrect SCRAM-SHA-1 ClientProof: " << input[2]); + } + + // add client-final-message-without-proof to authMessage + _authMessage += input[0] + "," + input[1]; + + // Concatenated nonce sent by client should equal the one in server-first-message + std::string nonce = input[1].substr(2); + if (nonce != _nonce) { + return StatusWith<bool>( + ErrorCodes::BadValue, + mongoutils::str::stream() + << "Unmatched SCRAM-SHA-1 nonce received from client in second step, expected " + << _nonce << " but received " << nonce); + } - // ServerSignature := HMAC(ServerKey, AuthMessage) - unsigned char serverSignature[scram::hashSize]; - std::string decodedServerKey = base64::decode(_creds.scram.serverKey); - fassert(18660, crypto::hmacSha1( - reinterpret_cast<const unsigned char*>(decodedServerKey.c_str()), - scram::hashSize, - reinterpret_cast<const unsigned char*>(_authMessage.c_str()), - _authMessage.size(), - serverSignature, - &hashLen)); + std::string clientProof = input[2].substr(2); + + // Do server side computations, compare storedKeys and generate client-final-message + // AuthMessage := client-first-message-bare + "," + + // server-first-message + "," + + // client-final-message-without-proof + // ClientSignature := HMAC(StoredKey, AuthMessage) + // ClientKey := ClientSignature XOR ClientProof + // ServerSignature := HMAC(ServerKey, AuthMessage) + + unsigned int hashLen = 0; + unsigned char clientSignature[scram::hashSize]; + + std::string decodedStoredKey = base64::decode(_creds.scram.storedKey); + // ClientSignature := HMAC(StoredKey, AuthMessage) + fassert(18662, + crypto::hmacSha1(reinterpret_cast<const unsigned char*>(decodedStoredKey.c_str()), + scram::hashSize, + reinterpret_cast<const unsigned char*>(_authMessage.c_str()), + _authMessage.size(), + clientSignature, + &hashLen)); + + fassert(18658, hashLen == scram::hashSize); + + try { + clientProof = base64::decode(clientProof); + } catch (const DBException& ex) { + return StatusWith<bool>(ex.toStatus()); + } + const unsigned char* decodedClientProof = + reinterpret_cast<const unsigned char*>(clientProof.c_str()); - fassert(18661, hashLen == scram::hashSize); + // ClientKey := ClientSignature XOR ClientProof + unsigned char clientKey[scram::hashSize]; + for (size_t i = 0; i < scram::hashSize; i++) { + clientKey[i] = clientSignature[i] ^ decodedClientProof[i]; + } - StringBuilder sb; - sb << "v=" << base64::encode(reinterpret_cast<char*>(serverSignature), scram::hashSize); - *outputData = sb.str(); + // StoredKey := H(ClientKey) + unsigned char computedStoredKey[scram::hashSize]; + fassert(18659, crypto::sha1(clientKey, scram::hashSize, computedStoredKey)); - return StatusWith<bool>(false); + if (memcmp(decodedStoredKey.c_str(), computedStoredKey, scram::hashSize) != 0) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + mongoutils::str::stream() + << "SCRAM-SHA-1 authentication failed, storedKey mismatch"); } + + // ServerSignature := HMAC(ServerKey, AuthMessage) + unsigned char serverSignature[scram::hashSize]; + std::string decodedServerKey = base64::decode(_creds.scram.serverKey); + fassert(18660, + crypto::hmacSha1(reinterpret_cast<const unsigned char*>(decodedServerKey.c_str()), + scram::hashSize, + reinterpret_cast<const unsigned char*>(_authMessage.c_str()), + _authMessage.size(), + serverSignature, + &hashLen)); + + fassert(18661, hashLen == scram::hashSize); + + StringBuilder sb; + sb << "v=" << base64::encode(reinterpret_cast<char*>(serverSignature), scram::hashSize); + *outputData = sb.str(); + + return StatusWith<bool>(false); +} } // namespace mongo diff --git a/src/mongo/db/auth/sasl_scramsha1_server_conversation.h b/src/mongo/db/auth/sasl_scramsha1_server_conversation.h index 1a2e1ad8fbd..68b3e226168 100644 --- a/src/mongo/db/auth/sasl_scramsha1_server_conversation.h +++ b/src/mongo/db/auth/sasl_scramsha1_server_conversation.h @@ -37,45 +37,46 @@ #include "mongo/db/auth/sasl_server_conversation.h" namespace mongo { +/** + * Server side authentication session for SASL SCRAM-SHA-1. + */ +class SaslSCRAMSHA1ServerConversation : public SaslServerConversation { + MONGO_DISALLOW_COPYING(SaslSCRAMSHA1ServerConversation); + +public: /** - * Server side authentication session for SASL SCRAM-SHA-1. - */ - class SaslSCRAMSHA1ServerConversation : public SaslServerConversation { - MONGO_DISALLOW_COPYING(SaslSCRAMSHA1ServerConversation); - public: - /** - * Implements the server side of a SASL SCRAM-SHA-1 mechanism session. - **/ - explicit SaslSCRAMSHA1ServerConversation(SaslAuthenticationSession* saslAuthSession); + * Implements the server side of a SASL SCRAM-SHA-1 mechanism session. + **/ + explicit SaslSCRAMSHA1ServerConversation(SaslAuthenticationSession* saslAuthSession); - virtual ~SaslSCRAMSHA1ServerConversation() {}; + virtual ~SaslSCRAMSHA1ServerConversation(){}; - /** - * Take one step in a SCRAM-SHA-1 conversation. - * - * @return !Status::OK() if auth failed. The boolean part indicates if the - * authentication conversation is finished or not. - * - **/ - virtual StatusWith<bool> step(StringData inputData, std::string* outputData); + /** + * Take one step in a SCRAM-SHA-1 conversation. + * + * @return !Status::OK() if auth failed. The boolean part indicates if the + * authentication conversation is finished or not. + * + **/ + virtual StatusWith<bool> step(StringData inputData, std::string* outputData); - private: - /** - * Parse client-first-message and generate server-first-message - **/ - StatusWith<bool> _firstStep(std::vector<std::string>& input, std::string* outputData); +private: + /** + * Parse client-first-message and generate server-first-message + **/ + StatusWith<bool> _firstStep(std::vector<std::string>& input, std::string* outputData); - /** - * Parse client-final-message and generate server-final-message - **/ - StatusWith<bool> _secondStep(const std::vector<std::string>& input, std::string* outputData); + /** + * Parse client-final-message and generate server-final-message + **/ + StatusWith<bool> _secondStep(const std::vector<std::string>& input, std::string* outputData); - int _step; - std::string _authMessage; - User::CredentialData _creds; + int _step; + std::string _authMessage; + User::CredentialData _creds; - // client and server nonce concatenated - std::string _nonce; - }; + // client and server nonce concatenated + std::string _nonce; +}; } // namespace mongo diff --git a/src/mongo/db/auth/sasl_server_conversation.cpp b/src/mongo/db/auth/sasl_server_conversation.cpp index aa7f662535d..75f680e9a0d 100644 --- a/src/mongo/db/auth/sasl_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_server_conversation.cpp @@ -31,11 +31,11 @@ #include <string> namespace mongo { - - SaslServerConversation::~SaslServerConversation() {}; - - std::string SaslServerConversation::getPrincipalId() { - return _user; - } + +SaslServerConversation::~SaslServerConversation(){}; + +std::string SaslServerConversation::getPrincipalId() { + return _user; +} } // namespace mongo diff --git a/src/mongo/db/auth/sasl_server_conversation.h b/src/mongo/db/auth/sasl_server_conversation.h index 005e5fc67f1..5d0ce497523 100644 --- a/src/mongo/db/auth/sasl_server_conversation.h +++ b/src/mongo/db/auth/sasl_server_conversation.h @@ -37,52 +37,53 @@ #include "mongo/db/auth/user.h" namespace mongo { - - class SaslAuthenticationSession; - template <typename T> class StatusWith; - + +class SaslAuthenticationSession; +template <typename T> +class StatusWith; + +/** + * Abstract class for implementing the server-side + * of a SASL mechanism conversation. + */ +class SaslServerConversation { + MONGO_DISALLOW_COPYING(SaslServerConversation); + +public: /** - * Abstract class for implementing the server-side - * of a SASL mechanism conversation. - */ - class SaslServerConversation { - MONGO_DISALLOW_COPYING(SaslServerConversation); - public: - /** - * Implements the server side of a SASL authentication mechanism. - * - * "saslAuthSession" is the corresponding SASLAuthenticationSession. - * "saslAuthSession" must stay in scope until the SaslServerConversation's - * destructor completes. - * - **/ - explicit SaslServerConversation(SaslAuthenticationSession* saslAuthSession) : - _saslAuthSession(saslAuthSession), - _user("") {} + * Implements the server side of a SASL authentication mechanism. + * + * "saslAuthSession" is the corresponding SASLAuthenticationSession. + * "saslAuthSession" must stay in scope until the SaslServerConversation's + * destructor completes. + * + **/ + explicit SaslServerConversation(SaslAuthenticationSession* saslAuthSession) + : _saslAuthSession(saslAuthSession), _user("") {} - virtual ~SaslServerConversation(); + virtual ~SaslServerConversation(); - /** - * Performs one step of the server side of the authentication session, - * consuming "inputData" and producing "*outputData". - * - * A return of Status::OK() indicates successful progress towards authentication. - * A return of !Status::OK() indicates failed authentication - * - * A return of true means that the authentication process has finished. - * A return of false means that the authentication process has more steps. - * - */ - virtual StatusWith<bool> step(StringData inputData, std::string* outputData) = 0; + /** + * Performs one step of the server side of the authentication session, + * consuming "inputData" and producing "*outputData". + * + * A return of Status::OK() indicates successful progress towards authentication. + * A return of !Status::OK() indicates failed authentication + * + * A return of true means that the authentication process has finished. + * A return of false means that the authentication process has more steps. + * + */ + virtual StatusWith<bool> step(StringData inputData, std::string* outputData) = 0; + + /** + * Gets the SASL principal id (user name) for the conversation + **/ + std::string getPrincipalId(); - /** - * Gets the SASL principal id (user name) for the conversation - **/ - std::string getPrincipalId(); - - protected: - SaslAuthenticationSession* _saslAuthSession; - std::string _user; - }; +protected: + SaslAuthenticationSession* _saslAuthSession; + std::string _user; +}; } // namespace mongo diff --git a/src/mongo/db/auth/sasl_test_crutch.cpp b/src/mongo/db/auth/sasl_test_crutch.cpp index da6eae4d332..d64877492aa 100644 --- a/src/mongo/db/auth/sasl_test_crutch.cpp +++ b/src/mongo/db/auth/sasl_test_crutch.cpp @@ -30,5 +30,5 @@ #include "mongo/db/commands.h" namespace mongo { - int Command::testCommandsEnabled = 0; +int Command::testCommandsEnabled = 0; } diff --git a/src/mongo/db/auth/security_key.cpp b/src/mongo/db/auth/security_key.cpp index 8edbc6ef140..d7d7c96410e 100644 --- a/src/mongo/db/auth/security_key.cpp +++ b/src/mongo/db/auth/security_key.cpp @@ -51,97 +51,97 @@ namespace mongo { - using std::endl; - using std::string; +using std::endl; +using std::string; - bool setUpSecurityKey(const string& filename) { - struct stat stats; +bool setUpSecurityKey(const string& filename) { + struct stat stats; - // check obvious file errors - if (stat(filename.c_str(), &stats) == -1) { - log() << "error getting file " << filename << ": " << strerror(errno) << endl; - return false; - } + // check obvious file errors + if (stat(filename.c_str(), &stats) == -1) { + log() << "error getting file " << filename << ": " << strerror(errno) << endl; + return false; + } #if !defined(_WIN32) - // check permissions: must be X00, where X is >= 4 - if ((stats.st_mode & (S_IRWXG|S_IRWXO)) != 0) { - log() << "permissions on " << filename << " are too open" << endl; - return false; - } + // check permissions: must be X00, where X is >= 4 + if ((stats.st_mode & (S_IRWXG | S_IRWXO)) != 0) { + log() << "permissions on " << filename << " are too open" << endl; + return false; + } #endif - FILE* file = fopen( filename.c_str(), "rb" ); - if (!file) { - log() << "error opening file: " << filename << ": " << strerror(errno) << endl; + FILE* file = fopen(filename.c_str(), "rb"); + if (!file) { + log() << "error opening file: " << filename << ": " << strerror(errno) << endl; + return false; + } + + string str = ""; + + // strip key file + const unsigned long long fileLength = stats.st_size; + unsigned long long read = 0; + while (read < fileLength) { + char buf; + int readLength = fread(&buf, 1, 1, file); + if (readLength < 1) { + log() << "error reading file " << filename << endl; + fclose(file); return false; } + read++; - string str = ""; - - // strip key file - const unsigned long long fileLength = stats.st_size; - unsigned long long read = 0; - while (read < fileLength) { - char buf; - int readLength = fread(&buf, 1, 1, file); - if (readLength < 1) { - log() << "error reading file " << filename << endl; - fclose( file ); - return false; - } - read++; - - // check for whitespace - if ((buf >= '\x09' && buf <= '\x0D') || buf == ' ') { - continue; - } - - // check valid base64 - if ((buf < 'A' || buf > 'Z') && (buf < 'a' || buf > 'z') && (buf < '0' || buf > '9') && buf != '+' && buf != '/') { - log() << "invalid char in key file " << filename << ": " << buf << endl; - fclose( file ); - return false; - } - - str += buf; + // check for whitespace + if ((buf >= '\x09' && buf <= '\x0D') || buf == ' ') { + continue; } - fclose( file ); - - const unsigned long long keyLength = str.size(); - if (keyLength < 6 || keyLength > 1024) { - log() << " security key in " << filename << " has length " << keyLength - << ", must be between 6 and 1024 chars" << endl; + // check valid base64 + if ((buf < 'A' || buf > 'Z') && (buf < 'a' || buf > 'z') && (buf < '0' || buf > '9') && + buf != '+' && buf != '/') { + log() << "invalid char in key file " << filename << ": " << buf << endl; + fclose(file); return false; } - // Generate MONGODB-CR and SCRAM credentials for the internal user based on the keyfile. - User::CredentialData credentials; - credentials.password = mongo::createPasswordDigest( - internalSecurity.user->getName().getUser().toString(), str); - - BSONObj creds = scram::generateCredentials(credentials.password, - saslGlobalParams.scramIterationCount); - credentials.scram.iterationCount = creds[scram::iterationCountFieldName].Int(); - credentials.scram.salt = creds[scram::saltFieldName].String(); - credentials.scram.storedKey = creds[scram::storedKeyFieldName].String(); - credentials.scram.serverKey = creds[scram::serverKeyFieldName].String(); - - internalSecurity.user->setCredentials(credentials); - - int clusterAuthMode = serverGlobalParams.clusterAuthMode.load(); - if (clusterAuthMode == ServerGlobalParams::ClusterAuthMode_keyFile || - clusterAuthMode == ServerGlobalParams::ClusterAuthMode_sendKeyFile) { - setInternalUserAuthParams( - BSON(saslCommandMechanismFieldName << "SCRAM-SHA-1" << - saslCommandUserDBFieldName << - internalSecurity.user->getName().getDB() << - saslCommandUserFieldName << internalSecurity.user->getName().getUser() << - saslCommandPasswordFieldName << credentials.password << - saslCommandDigestPasswordFieldName << false)); - } - return true; + str += buf; + } + + fclose(file); + + const unsigned long long keyLength = str.size(); + if (keyLength < 6 || keyLength > 1024) { + log() << " security key in " << filename << " has length " << keyLength + << ", must be between 6 and 1024 chars" << endl; + return false; + } + + // Generate MONGODB-CR and SCRAM credentials for the internal user based on the keyfile. + User::CredentialData credentials; + credentials.password = + mongo::createPasswordDigest(internalSecurity.user->getName().getUser().toString(), str); + + BSONObj creds = + scram::generateCredentials(credentials.password, saslGlobalParams.scramIterationCount); + credentials.scram.iterationCount = creds[scram::iterationCountFieldName].Int(); + credentials.scram.salt = creds[scram::saltFieldName].String(); + credentials.scram.storedKey = creds[scram::storedKeyFieldName].String(); + credentials.scram.serverKey = creds[scram::serverKeyFieldName].String(); + + internalSecurity.user->setCredentials(credentials); + + int clusterAuthMode = serverGlobalParams.clusterAuthMode.load(); + if (clusterAuthMode == ServerGlobalParams::ClusterAuthMode_keyFile || + clusterAuthMode == ServerGlobalParams::ClusterAuthMode_sendKeyFile) { + setInternalUserAuthParams( + BSON(saslCommandMechanismFieldName + << "SCRAM-SHA-1" << saslCommandUserDBFieldName + << internalSecurity.user->getName().getDB() << saslCommandUserFieldName + << internalSecurity.user->getName().getUser() << saslCommandPasswordFieldName + << credentials.password << saslCommandDigestPasswordFieldName << false)); } + return true; +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/security_key.h b/src/mongo/db/auth/security_key.h index a250b34a029..898efdf9e8a 100644 --- a/src/mongo/db/auth/security_key.h +++ b/src/mongo/db/auth/security_key.h @@ -31,13 +31,13 @@ #include <string> namespace mongo { - /** - * This method checks the validity of filename as a security key, hashes its - * contents, and stores it in the internalSecurity variable. Prints an - * error message to the logs if there's an error. - * @param filename the file containing the key - * @return if the key was successfully stored - */ - bool setUpSecurityKey(const std::string& filename); +/** + * This method checks the validity of filename as a security key, hashes its + * contents, and stores it in the internalSecurity variable. Prints an + * error message to the logs if there's an error. + * @param filename the file containing the key + * @return if the key was successfully stored + */ +bool setUpSecurityKey(const std::string& filename); -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/user.cpp b/src/mongo/db/auth/user.cpp index a2afa97a4e3..dd4dd3196a9 100644 --- a/src/mongo/db/auth/user.cpp +++ b/src/mongo/db/auth/user.cpp @@ -41,123 +41,119 @@ namespace mongo { - User::User(const UserName& name) : - _name(name), - _refCount(0), - _isValid(1) {} +User::User(const UserName& name) : _name(name), _refCount(0), _isValid(1) {} - User::~User() { - dassert(_refCount == 0); - } - - const UserName& User::getName() const { - return _name; - } - - RoleNameIterator User::getRoles() const { - return makeRoleNameIteratorForContainer(_roles); - } - - RoleNameIterator User::getIndirectRoles() const { - return makeRoleNameIteratorForContainer(_indirectRoles); - } - - bool User::hasRole(const RoleName& roleName) const { - return _roles.count(roleName); - } - - const User::CredentialData& User::getCredentials() const { - return _credentials; - } - - bool User::isValid() const { - return _isValid.loadRelaxed() == 1; - } - - uint32_t User::getRefCount() const { - return _refCount; - } - - const ActionSet User::getActionsForResource(const ResourcePattern& resource) const { - unordered_map<ResourcePattern, Privilege>::const_iterator it = _privileges.find(resource); - if (it == _privileges.end()) { - return ActionSet(); - } - return it->second.getActions(); - } - - User* User::clone() const { - std::unique_ptr<User> result(new User(_name)); - result->_privileges = _privileges; - result->_roles = _roles; - result->_credentials = _credentials; - return result.release(); - } +User::~User() { + dassert(_refCount == 0); +} - void User::setCredentials(const CredentialData& credentials) { - _credentials = credentials; - } - - void User::setRoles(RoleNameIterator roles) { - _roles.clear(); - while (roles.more()) { - _roles.insert(roles.next()); - } - } - - void User::setIndirectRoles(RoleNameIterator indirectRoles) { - _indirectRoles.clear(); - while (indirectRoles.more()) { - _indirectRoles.push_back(indirectRoles.next()); - } - } +const UserName& User::getName() const { + return _name; +} - void User::setPrivileges(const PrivilegeVector& privileges) { - _privileges.clear(); - for (size_t i = 0; i < privileges.size(); ++i) { - const Privilege& privilege = privileges[i]; - _privileges[privilege.getResourcePattern()] = privilege; - } - } - - void User::addRole(const RoleName& roleName) { - _roles.insert(roleName); - } - - void User::addRoles(const std::vector<RoleName>& roles) { - for (std::vector<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) { - addRole(*it); - } - } +RoleNameIterator User::getRoles() const { + return makeRoleNameIteratorForContainer(_roles); +} - void User::addPrivilege(const Privilege& privilegeToAdd) { - ResourcePrivilegeMap::iterator it = _privileges.find(privilegeToAdd.getResourcePattern()); - if (it == _privileges.end()) { - // No privilege exists yet for this resource - _privileges.insert(std::make_pair(privilegeToAdd.getResourcePattern(), privilegeToAdd)); - } else { - dassert(it->first == privilegeToAdd.getResourcePattern()); - it->second.addActions(privilegeToAdd.getActions()); - } - } - - void User::addPrivileges(const PrivilegeVector& privileges) { - for (PrivilegeVector::const_iterator it = privileges.begin(); - it != privileges.end(); ++it) { - addPrivilege(*it); - } - } - - void User::invalidate() { - _isValid.store(0); - } - - void User::incrementRefCount() { - ++_refCount; - } - - void User::decrementRefCount() { - dassert(_refCount > 0); - --_refCount; - } -} // namespace mongo +RoleNameIterator User::getIndirectRoles() const { + return makeRoleNameIteratorForContainer(_indirectRoles); +} + +bool User::hasRole(const RoleName& roleName) const { + return _roles.count(roleName); +} + +const User::CredentialData& User::getCredentials() const { + return _credentials; +} + +bool User::isValid() const { + return _isValid.loadRelaxed() == 1; +} + +uint32_t User::getRefCount() const { + return _refCount; +} + +const ActionSet User::getActionsForResource(const ResourcePattern& resource) const { + unordered_map<ResourcePattern, Privilege>::const_iterator it = _privileges.find(resource); + if (it == _privileges.end()) { + return ActionSet(); + } + return it->second.getActions(); +} + +User* User::clone() const { + std::unique_ptr<User> result(new User(_name)); + result->_privileges = _privileges; + result->_roles = _roles; + result->_credentials = _credentials; + return result.release(); +} + +void User::setCredentials(const CredentialData& credentials) { + _credentials = credentials; +} + +void User::setRoles(RoleNameIterator roles) { + _roles.clear(); + while (roles.more()) { + _roles.insert(roles.next()); + } +} + +void User::setIndirectRoles(RoleNameIterator indirectRoles) { + _indirectRoles.clear(); + while (indirectRoles.more()) { + _indirectRoles.push_back(indirectRoles.next()); + } +} + +void User::setPrivileges(const PrivilegeVector& privileges) { + _privileges.clear(); + for (size_t i = 0; i < privileges.size(); ++i) { + const Privilege& privilege = privileges[i]; + _privileges[privilege.getResourcePattern()] = privilege; + } +} + +void User::addRole(const RoleName& roleName) { + _roles.insert(roleName); +} + +void User::addRoles(const std::vector<RoleName>& roles) { + for (std::vector<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) { + addRole(*it); + } +} + +void User::addPrivilege(const Privilege& privilegeToAdd) { + ResourcePrivilegeMap::iterator it = _privileges.find(privilegeToAdd.getResourcePattern()); + if (it == _privileges.end()) { + // No privilege exists yet for this resource + _privileges.insert(std::make_pair(privilegeToAdd.getResourcePattern(), privilegeToAdd)); + } else { + dassert(it->first == privilegeToAdd.getResourcePattern()); + it->second.addActions(privilegeToAdd.getActions()); + } +} + +void User::addPrivileges(const PrivilegeVector& privileges) { + for (PrivilegeVector::const_iterator it = privileges.begin(); it != privileges.end(); ++it) { + addPrivilege(*it); + } +} + +void User::invalidate() { + _isValid.store(0); +} + +void User::incrementRefCount() { + ++_refCount; +} + +void User::decrementRefCount() { + dassert(_refCount > 0); + --_refCount; +} +} // namespace mongo diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index d920abdda9d..d4aea7e442b 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -41,193 +41,188 @@ namespace mongo { +/** + * Represents a MongoDB user. Stores information about the user necessary for access control + * checks and authentications, such as what privileges this user has, as well as what roles + * the user belongs to. + * + * Every User object is owned by an AuthorizationManager. The AuthorizationManager is the only + * one that should construct, modify, or delete a User object. All other consumers of User must + * use only the const methods. The AuthorizationManager is responsible for maintaining the + * reference count on all User objects it gives out and must not mutate any User objects with + * a non-zero reference count (except to call invalidate()). Any consumer of a User object + * should check isInvalidated() before using it, and if it has been invalidated, it should + * return the object to the AuthorizationManager and fetch a new User object instance for this + * user from the AuthorizationManager. + */ +class User { + MONGO_DISALLOW_COPYING(User); + +public: + struct SCRAMCredentials { + SCRAMCredentials() : iterationCount(0), salt(""), serverKey(""), storedKey("") {} + + int iterationCount; + std::string salt; + std::string serverKey; + std::string storedKey; + }; + struct CredentialData { + CredentialData() : password(""), scram(), isExternal(false) {} + + std::string password; + SCRAMCredentials scram; + bool isExternal; + }; + + typedef unordered_map<ResourcePattern, Privilege> ResourcePrivilegeMap; + + explicit User(const UserName& name); + ~User(); + + /** + * Returns the user name for this user. + */ + const UserName& getName() const; + + /** + * Returns an iterator over the names of the user's direct roles + */ + RoleNameIterator getRoles() const; + + /** + * Returns an iterator over the names of the user's indirect roles + */ + RoleNameIterator getIndirectRoles() const; + + /** + * Returns true if this user is a member of the given role. + */ + bool hasRole(const RoleName& roleName) const; + + /** + * Returns a reference to the information about the user's privileges. + */ + const ResourcePrivilegeMap& getPrivileges() const { + return _privileges; + } + + /** + * Returns the CredentialData for this user. + */ + const CredentialData& getCredentials() const; + + /** + * Gets the set of actions this user is allowed to perform on the given resource. + */ + const ActionSet getActionsForResource(const ResourcePattern& resource) const; + + /** + * Returns true if this copy of information about this user is still valid. If this returns + * false, this object should no longer be used and should be returned to the + * AuthorizationManager and a new User object for this user should be requested. + */ + bool isValid() const; + + /** + * This returns the reference count for this User. The AuthorizationManager should be the + * only caller of this. + */ + uint32_t getRefCount() const; + + /** + * Clones this user into a new, valid User object with refcount of 0. + */ + User* clone() const; + + // Mutators below. Mutation functions should *only* be called by the AuthorizationManager + /** - * Represents a MongoDB user. Stores information about the user necessary for access control - * checks and authentications, such as what privileges this user has, as well as what roles - * the user belongs to. + * Sets this user's authentication credentials. + */ + void setCredentials(const CredentialData& credentials); + + /** + * Replaces any existing user role membership information with the roles from "roles". + */ + void setRoles(RoleNameIterator roles); + + /** + * Replaces any existing indirect user role membership information with the roles from + * "indirectRoles". + */ + void setIndirectRoles(RoleNameIterator indirectRoles); + + /** + * Replaces any existing user privilege information with "privileges". + */ + void setPrivileges(const PrivilegeVector& privileges); + + /** + * Adds the given role name to the list of roles of which this user is a member. + */ + void addRole(const RoleName& role); + + /** + * Adds the given role names to the list of roles that this user belongs to. + */ + void addRoles(const std::vector<RoleName>& roles); + + /** + * Adds the given privilege to the list of privileges this user is authorized for. + */ + void addPrivilege(const Privilege& privilege); + + /** + * Adds the given privileges to the list of privileges this user is authorized for. + */ + void addPrivileges(const PrivilegeVector& privileges); + + /** + * Marks this instance of the User object as invalid, most likely because information about + * the user has been updated and needs to be reloaded from the AuthorizationManager. * - * Every User object is owned by an AuthorizationManager. The AuthorizationManager is the only - * one that should construct, modify, or delete a User object. All other consumers of User must - * use only the const methods. The AuthorizationManager is responsible for maintaining the - * reference count on all User objects it gives out and must not mutate any User objects with - * a non-zero reference count (except to call invalidate()). Any consumer of a User object - * should check isInvalidated() before using it, and if it has been invalidated, it should - * return the object to the AuthorizationManager and fetch a new User object instance for this - * user from the AuthorizationManager. - */ - class User { - MONGO_DISALLOW_COPYING(User); - public: - struct SCRAMCredentials { - SCRAMCredentials() : - iterationCount(0), - salt(""), - serverKey(""), - storedKey("") {} - - int iterationCount; - std::string salt; - std::string serverKey; - std::string storedKey; - }; - struct CredentialData { - CredentialData() : - password(""), - scram(), - isExternal(false) {} - - std::string password; - SCRAMCredentials scram; - bool isExternal; - }; - - typedef unordered_map<ResourcePattern, Privilege> ResourcePrivilegeMap; - - explicit User(const UserName& name); - ~User(); - - /** - * Returns the user name for this user. - */ - const UserName& getName() const; - - /** - * Returns an iterator over the names of the user's direct roles - */ - RoleNameIterator getRoles() const; - - /** - * Returns an iterator over the names of the user's indirect roles - */ - RoleNameIterator getIndirectRoles() const; - - /** - * Returns true if this user is a member of the given role. - */ - bool hasRole(const RoleName& roleName) const; - - /** - * Returns a reference to the information about the user's privileges. - */ - const ResourcePrivilegeMap& getPrivileges() const { return _privileges; } - - /** - * Returns the CredentialData for this user. - */ - const CredentialData& getCredentials() const; - - /** - * Gets the set of actions this user is allowed to perform on the given resource. - */ - const ActionSet getActionsForResource(const ResourcePattern& resource) const; - - /** - * Returns true if this copy of information about this user is still valid. If this returns - * false, this object should no longer be used and should be returned to the - * AuthorizationManager and a new User object for this user should be requested. - */ - bool isValid() const; - - /** - * This returns the reference count for this User. The AuthorizationManager should be the - * only caller of this. - */ - uint32_t getRefCount() const; - - /** - * Clones this user into a new, valid User object with refcount of 0. - */ - User* clone() const; - - // Mutators below. Mutation functions should *only* be called by the AuthorizationManager - - /** - * Sets this user's authentication credentials. - */ - void setCredentials(const CredentialData& credentials); - - /** - * Replaces any existing user role membership information with the roles from "roles". - */ - void setRoles(RoleNameIterator roles); - - /** - * Replaces any existing indirect user role membership information with the roles from - * "indirectRoles". - */ - void setIndirectRoles(RoleNameIterator indirectRoles); - - /** - * Replaces any existing user privilege information with "privileges". - */ - void setPrivileges(const PrivilegeVector& privileges); - - /** - * Adds the given role name to the list of roles of which this user is a member. - */ - void addRole(const RoleName& role); - - /** - * Adds the given role names to the list of roles that this user belongs to. - */ - void addRoles(const std::vector<RoleName>& roles); - - /** - * Adds the given privilege to the list of privileges this user is authorized for. - */ - void addPrivilege(const Privilege& privilege); - - /** - * Adds the given privileges to the list of privileges this user is authorized for. - */ - void addPrivileges(const PrivilegeVector& privileges); - - /** - * Marks this instance of the User object as invalid, most likely because information about - * the user has been updated and needs to be reloaded from the AuthorizationManager. - * - * This method should *only* be called by the AuthorizationManager. - */ - void invalidate(); - - /** - * Increments the reference count for this User object, which records how many threads have - * a reference to it. - * - * This method should *only* be called by the AuthorizationManager. - */ - void incrementRefCount(); - - /** - * Decrements the reference count for this User object, which records how many threads have - * a reference to it. Once the reference count goes to zero, the AuthorizationManager is - * allowed to destroy this instance. - * - * This method should *only* be called by the AuthorizationManager. - */ - void decrementRefCount(); - - private: - - UserName _name; - - // Maps resource name to privilege on that resource - ResourcePrivilegeMap _privileges; - - // Roles the user has privileges from - unordered_set<RoleName> _roles; - - // Roles that the user indirectly has privileges from, due to role inheritance. - std::vector<RoleName> _indirectRoles; - - // Credential information. - CredentialData _credentials; - - // _refCount and _isInvalidated are modified exclusively by the AuthorizationManager - // _isInvalidated can be read by any consumer of User, but _refCount can only be - // meaningfully read by the AuthorizationManager, as _refCount is guarded by the AM's _lock - uint32_t _refCount; - AtomicUInt32 _isValid; // Using as a boolean - }; + * This method should *only* be called by the AuthorizationManager. + */ + void invalidate(); + + /** + * Increments the reference count for this User object, which records how many threads have + * a reference to it. + * + * This method should *only* be called by the AuthorizationManager. + */ + void incrementRefCount(); + + /** + * Decrements the reference count for this User object, which records how many threads have + * a reference to it. Once the reference count goes to zero, the AuthorizationManager is + * allowed to destroy this instance. + * + * This method should *only* be called by the AuthorizationManager. + */ + void decrementRefCount(); + +private: + UserName _name; + + // Maps resource name to privilege on that resource + ResourcePrivilegeMap _privileges; + + // Roles the user has privileges from + unordered_set<RoleName> _roles; + + // Roles that the user indirectly has privileges from, due to role inheritance. + std::vector<RoleName> _indirectRoles; + + // Credential information. + CredentialData _credentials; + + // _refCount and _isInvalidated are modified exclusively by the AuthorizationManager + // _isInvalidated can be read by any consumer of User, but _refCount can only be + // meaningfully read by the AuthorizationManager, as _refCount is guarded by the AM's _lock + uint32_t _refCount; + AtomicUInt32 _isValid; // Using as a boolean +}; -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/user_cache_invalidator_job.cpp b/src/mongo/db/auth/user_cache_invalidator_job.cpp index 0faaaa84bc6..dd44b200f60 100644 --- a/src/mongo/db/auth/user_cache_invalidator_job.cpp +++ b/src/mongo/db/auth/user_cache_invalidator_job.cpp @@ -51,132 +51,127 @@ namespace mongo { namespace { - // How often to check with the config servers whether authorization information has changed. - int userCacheInvalidationIntervalSecs = 30; // 30 second default - stdx::mutex invalidationIntervalMutex; - stdx::condition_variable invalidationIntervalChangedCondition; - Date_t lastInvalidationTime; - - class ExportedInvalidationIntervalParameter : public ExportedServerParameter<int> { - public: - ExportedInvalidationIntervalParameter() : - ExportedServerParameter<int>(ServerParameterSet::getGlobal(), - "userCacheInvalidationIntervalSecs", - &userCacheInvalidationIntervalSecs, - true, - true) {} - - virtual Status validate( const int& potentialNewValue ) - { - if (potentialNewValue < 1 || potentialNewValue > 86400) { - return Status(ErrorCodes::BadValue, - "userCacheInvalidationIntervalSecs must be between 1 " - "and 86400 (24 hours)"); - } - return Status::OK(); +// How often to check with the config servers whether authorization information has changed. +int userCacheInvalidationIntervalSecs = 30; // 30 second default +stdx::mutex invalidationIntervalMutex; +stdx::condition_variable invalidationIntervalChangedCondition; +Date_t lastInvalidationTime; + +class ExportedInvalidationIntervalParameter : public ExportedServerParameter<int> { +public: + ExportedInvalidationIntervalParameter() + : ExportedServerParameter<int>(ServerParameterSet::getGlobal(), + "userCacheInvalidationIntervalSecs", + &userCacheInvalidationIntervalSecs, + true, + true) {} + + virtual Status validate(const int& potentialNewValue) { + if (potentialNewValue < 1 || potentialNewValue > 86400) { + return Status(ErrorCodes::BadValue, + "userCacheInvalidationIntervalSecs must be between 1 " + "and 86400 (24 hours)"); } + return Status::OK(); + } - // Without this the compiler complains that defining set(const int&) - // hides set(const BSONElement&) - using ExportedServerParameter<int>::set; + // Without this the compiler complains that defining set(const int&) + // hides set(const BSONElement&) + using ExportedServerParameter<int>::set; - virtual Status set( const int& newValue ) { - stdx::unique_lock<stdx::mutex> lock(invalidationIntervalMutex); - Status status = ExportedServerParameter<int>::set(newValue); - invalidationIntervalChangedCondition.notify_all(); - return status; - } + virtual Status set(const int& newValue) { + stdx::unique_lock<stdx::mutex> lock(invalidationIntervalMutex); + Status status = ExportedServerParameter<int>::set(newValue); + invalidationIntervalChangedCondition.notify_all(); + return status; + } - } exportedIntervalParam; - - StatusWith<OID> getCurrentCacheGeneration() { - try { - BSONObjBuilder result; - const bool ok = grid.catalogManager()->runUserManagementReadCommand( - "admin", - BSON("_getUserCacheGeneration" << 1), - &result); - if (!ok) { - return Command::getStatusFromCommandResult(result.obj()); - } - return result.obj()["cacheGeneration"].OID(); - } catch (const DBException& e) { - return StatusWith<OID>(e.toStatus()); - } catch (const std::exception& e) { - return StatusWith<OID>(ErrorCodes::UnknownError, e.what()); +} exportedIntervalParam; + +StatusWith<OID> getCurrentCacheGeneration() { + try { + BSONObjBuilder result; + const bool ok = grid.catalogManager()->runUserManagementReadCommand( + "admin", BSON("_getUserCacheGeneration" << 1), &result); + if (!ok) { + return Command::getStatusFromCommandResult(result.obj()); } + return result.obj()["cacheGeneration"].OID(); + } catch (const DBException& e) { + return StatusWith<OID>(e.toStatus()); + } catch (const std::exception& e) { + return StatusWith<OID>(ErrorCodes::UnknownError, e.what()); } +} -} // namespace +} // namespace - UserCacheInvalidator::UserCacheInvalidator(AuthorizationManager* authzManager) : - _authzManager(authzManager) { +UserCacheInvalidator::UserCacheInvalidator(AuthorizationManager* authzManager) + : _authzManager(authzManager) { + StatusWith<OID> currentGeneration = getCurrentCacheGeneration(); + if (currentGeneration.isOK()) { + _previousCacheGeneration = currentGeneration.getValue(); + return; + } - StatusWith<OID> currentGeneration = getCurrentCacheGeneration(); - if (currentGeneration.isOK()) { - _previousCacheGeneration = currentGeneration.getValue(); - return; + if (currentGeneration.getStatus().code() == ErrorCodes::CommandNotFound) { + warning() << "_getUserCacheGeneration command not found while fetching initial user " + "cache generation from the config server(s). This most likely means you are " + "running an outdated version of mongod on the config servers"; + } else { + warning() << "An error occurred while fetching initial user cache generation from " + "config servers: " << currentGeneration.getStatus(); + } + _previousCacheGeneration = OID(); +} + +void UserCacheInvalidator::run() { + Client::initThread("UserCacheInvalidator"); + lastInvalidationTime = Date_t::now(); + + while (true) { + stdx::unique_lock<stdx::mutex> lock(invalidationIntervalMutex); + Date_t sleepUntil = lastInvalidationTime + Seconds(userCacheInvalidationIntervalSecs); + Date_t now = Date_t::now(); + while (now < sleepUntil) { + invalidationIntervalChangedCondition.wait_for(lock, sleepUntil - now); + sleepUntil = lastInvalidationTime + Seconds(userCacheInvalidationIntervalSecs); + now = Date_t::now(); } + lastInvalidationTime = now; + lock.unlock(); - if (currentGeneration.getStatus().code() == ErrorCodes::CommandNotFound) { - warning() << "_getUserCacheGeneration command not found while fetching initial user " - "cache generation from the config server(s). This most likely means you are " - "running an outdated version of mongod on the config servers"; - } else { - warning() << "An error occurred while fetching initial user cache generation from " - "config servers: " << currentGeneration.getStatus(); + if (inShutdown()) { + break; } - _previousCacheGeneration = OID(); - } - - void UserCacheInvalidator::run() { - Client::initThread("UserCacheInvalidator"); - lastInvalidationTime = Date_t::now(); - - while (true) { - stdx::unique_lock<stdx::mutex> lock(invalidationIntervalMutex); - Date_t sleepUntil = lastInvalidationTime + Seconds(userCacheInvalidationIntervalSecs); - Date_t now = Date_t::now(); - while (now < sleepUntil) { - invalidationIntervalChangedCondition.wait_for(lock, sleepUntil - now); - sleepUntil = lastInvalidationTime + Seconds(userCacheInvalidationIntervalSecs); - now = Date_t::now(); - } - lastInvalidationTime = now; - lock.unlock(); - - if (inShutdown()) { - break; - } - StatusWith<OID> currentGeneration = getCurrentCacheGeneration(); - if (!currentGeneration.isOK()) { - if (currentGeneration.getStatus().code() == ErrorCodes::CommandNotFound) { - warning() << "_getUserCacheGeneration command not found on config server(s), " - "this most likely means you are running an outdated version of mongod " - "on the config servers" << std::endl; - } else { - warning() << "An error occurred while fetching current user cache generation " - "to check if user cache needs invalidation: " << - currentGeneration.getStatus() << std::endl; - } - // When in doubt, invalidate the cache - _authzManager->invalidateUserCache(); - continue; + StatusWith<OID> currentGeneration = getCurrentCacheGeneration(); + if (!currentGeneration.isOK()) { + if (currentGeneration.getStatus().code() == ErrorCodes::CommandNotFound) { + warning() << "_getUserCacheGeneration command not found on config server(s), " + "this most likely means you are running an outdated version of mongod " + "on the config servers" << std::endl; + } else { + warning() << "An error occurred while fetching current user cache generation " + "to check if user cache needs invalidation: " + << currentGeneration.getStatus() << std::endl; } + // When in doubt, invalidate the cache + _authzManager->invalidateUserCache(); + continue; + } - if (currentGeneration.getValue() != _previousCacheGeneration) { - log() << "User cache generation changed from " << _previousCacheGeneration << - " to " << currentGeneration.getValue() << "; invalidating user cache" << - std::endl; - _authzManager->invalidateUserCache(); - _previousCacheGeneration = currentGeneration.getValue(); - } + if (currentGeneration.getValue() != _previousCacheGeneration) { + log() << "User cache generation changed from " << _previousCacheGeneration << " to " + << currentGeneration.getValue() << "; invalidating user cache" << std::endl; + _authzManager->invalidateUserCache(); + _previousCacheGeneration = currentGeneration.getValue(); } } +} - std::string UserCacheInvalidator::name() const { - return "UserCacheInvalidatorThread"; - } +std::string UserCacheInvalidator::name() const { + return "UserCacheInvalidatorThread"; +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/user_cache_invalidator_job.h b/src/mongo/db/auth/user_cache_invalidator_job.h index 8742a28c79c..3eb173b0a56 100644 --- a/src/mongo/db/auth/user_cache_invalidator_job.h +++ b/src/mongo/db/auth/user_cache_invalidator_job.h @@ -32,25 +32,25 @@ namespace mongo { - class AuthorizationManager; - - /** - * Background job that runs only in mongos and periodically checks in with the config servers - * to determine whether any authorization information has changed, and if so causes the - * AuthorizationManager to throw out its in-memory cache of User objects (which contains the - * users' credentials, roles, privileges, etc). - */ - class UserCacheInvalidator : public BackgroundJob { - public: - explicit UserCacheInvalidator(AuthorizationManager* authzManager); - - protected: - virtual std::string name() const; - virtual void run(); - - private: - AuthorizationManager* _authzManager; - OID _previousCacheGeneration; - }; - -} // namespace mongo +class AuthorizationManager; + +/** + * Background job that runs only in mongos and periodically checks in with the config servers + * to determine whether any authorization information has changed, and if so causes the + * AuthorizationManager to throw out its in-memory cache of User objects (which contains the + * users' credentials, roles, privileges, etc). + */ +class UserCacheInvalidator : public BackgroundJob { +public: + explicit UserCacheInvalidator(AuthorizationManager* authzManager); + +protected: + virtual std::string name() const; + virtual void run(); + +private: + AuthorizationManager* _authzManager; + OID _previousCacheGeneration; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/user_document_parser.cpp b/src/mongo/db/auth/user_document_parser.cpp index d4a095c661d..058829622f5 100644 --- a/src/mongo/db/auth/user_document_parser.cpp +++ b/src/mongo/db/auth/user_document_parser.cpp @@ -44,142 +44,135 @@ namespace mongo { namespace { - const std::string ADMIN_DBNAME = "admin"; - - const std::string ROLES_FIELD_NAME = "roles"; - const std::string PRIVILEGES_FIELD_NAME = "inheritedPrivileges"; - const std::string INHERITED_ROLES_FIELD_NAME = "inheritedRoles"; - const std::string OTHER_DB_ROLES_FIELD_NAME = "otherDBRoles"; - const std::string READONLY_FIELD_NAME = "readOnly"; - const std::string CREDENTIALS_FIELD_NAME = "credentials"; - const std::string ROLE_NAME_FIELD_NAME = "role"; - const std::string ROLE_DB_FIELD_NAME = "db"; - const std::string MONGODB_CR_CREDENTIAL_FIELD_NAME = "MONGODB-CR"; - const std::string SCRAM_CREDENTIAL_FIELD_NAME = "SCRAM-SHA-1"; - const std::string MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME = "external"; - - inline Status _badValue(const char* reason, int location) { - return Status(ErrorCodes::BadValue, reason, location); +const std::string ADMIN_DBNAME = "admin"; + +const std::string ROLES_FIELD_NAME = "roles"; +const std::string PRIVILEGES_FIELD_NAME = "inheritedPrivileges"; +const std::string INHERITED_ROLES_FIELD_NAME = "inheritedRoles"; +const std::string OTHER_DB_ROLES_FIELD_NAME = "otherDBRoles"; +const std::string READONLY_FIELD_NAME = "readOnly"; +const std::string CREDENTIALS_FIELD_NAME = "credentials"; +const std::string ROLE_NAME_FIELD_NAME = "role"; +const std::string ROLE_DB_FIELD_NAME = "db"; +const std::string MONGODB_CR_CREDENTIAL_FIELD_NAME = "MONGODB-CR"; +const std::string SCRAM_CREDENTIAL_FIELD_NAME = "SCRAM-SHA-1"; +const std::string MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME = "external"; + +inline Status _badValue(const char* reason, int location) { + return Status(ErrorCodes::BadValue, reason, location); +} + +inline Status _badValue(const std::string& reason, int location) { + return Status(ErrorCodes::BadValue, reason, location); +} + +Status _checkV1RolesArray(const BSONElement& rolesElement) { + if (rolesElement.type() != Array) { + return _badValue("Role fields must be an array when present in system.users entries", 0); } - - inline Status _badValue(const std::string& reason, int location) { - return Status(ErrorCodes::BadValue, reason, location); - } - - Status _checkV1RolesArray(const BSONElement& rolesElement) { - if (rolesElement.type() != Array) { - return _badValue("Role fields must be an array when present in system.users entries", - 0); - } - for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) { - BSONElement element = *iter; - if (element.type() != String || element.valueStringData().empty()) { - return _badValue("Roles must be non-empty strings.", 0); - } + for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) { + BSONElement element = *iter; + if (element.type() != String || element.valueStringData().empty()) { + return _badValue("Roles must be non-empty strings.", 0); } - return Status::OK(); } + return Status::OK(); +} } // namespace - std::string V1UserDocumentParser::extractUserNameFromUserDocument( - const BSONObj& doc) const { - return doc[AuthorizationManager::V1_USER_NAME_FIELD_NAME].str(); - } - - Status V1UserDocumentParser::initializeUserCredentialsFromUserDocument( - User* user, const BSONObj& privDoc) const { - User::CredentialData credentials; - if (privDoc.hasField(AuthorizationManager::PASSWORD_FIELD_NAME)) { - credentials.password = privDoc[AuthorizationManager::PASSWORD_FIELD_NAME].String(); - credentials.isExternal = false; - } - else if (privDoc.hasField(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME)) { - std::string userSource = privDoc[AuthorizationManager::V1_USER_SOURCE_FIELD_NAME].String(); - if (userSource != "$external") { - return Status(ErrorCodes::UnsupportedFormat, - "Cannot extract credentials from user documents without a password " - "and with userSource != \"$external\""); - } else { - credentials.isExternal = true; - } - } - else { +std::string V1UserDocumentParser::extractUserNameFromUserDocument(const BSONObj& doc) const { + return doc[AuthorizationManager::V1_USER_NAME_FIELD_NAME].str(); +} + +Status V1UserDocumentParser::initializeUserCredentialsFromUserDocument( + User* user, const BSONObj& privDoc) const { + User::CredentialData credentials; + if (privDoc.hasField(AuthorizationManager::PASSWORD_FIELD_NAME)) { + credentials.password = privDoc[AuthorizationManager::PASSWORD_FIELD_NAME].String(); + credentials.isExternal = false; + } else if (privDoc.hasField(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME)) { + std::string userSource = privDoc[AuthorizationManager::V1_USER_SOURCE_FIELD_NAME].String(); + if (userSource != "$external") { return Status(ErrorCodes::UnsupportedFormat, - "Invalid user document: must have one of \"pwd\" and \"userSource\""); + "Cannot extract credentials from user documents without a password " + "and with userSource != \"$external\""); + } else { + credentials.isExternal = true; } - - user->setCredentials(credentials); - return Status::OK(); + } else { + return Status(ErrorCodes::UnsupportedFormat, + "Invalid user document: must have one of \"pwd\" and \"userSource\""); } - static void _initializeUserRolesFromV0UserDocument( - User* user, const BSONObj& privDoc, StringData dbname) { - bool readOnly = privDoc["readOnly"].trueValue(); - if (dbname == "admin") { - if (readOnly) { - user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ, "admin")); - } else { - user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ_WRITE, "admin")); - } + user->setCredentials(credentials); + return Status::OK(); +} + +static void _initializeUserRolesFromV0UserDocument(User* user, + const BSONObj& privDoc, + StringData dbname) { + bool readOnly = privDoc["readOnly"].trueValue(); + if (dbname == "admin") { + if (readOnly) { + user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ, "admin")); } else { - if (readOnly) { - user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_READ, dbname)); - } else { - user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_READ_WRITE, dbname)); - } + user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ_WRITE, "admin")); + } + } else { + if (readOnly) { + user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_READ, dbname)); + } else { + user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_READ_WRITE, dbname)); } } +} - Status _initializeUserRolesFromV1RolesArray(User* user, - const BSONElement& rolesElement, - StringData dbname) { - static const char privilegesTypeMismatchMessage[] = - "Roles in V1 user documents must be enumerated in an array of strings."; +Status _initializeUserRolesFromV1RolesArray(User* user, + const BSONElement& rolesElement, + StringData dbname) { + static const char privilegesTypeMismatchMessage[] = + "Roles in V1 user documents must be enumerated in an array of strings."; - if (rolesElement.type() != Array) - return Status(ErrorCodes::TypeMismatch, privilegesTypeMismatchMessage); + if (rolesElement.type() != Array) + return Status(ErrorCodes::TypeMismatch, privilegesTypeMismatchMessage); - for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) { - BSONElement roleElement = *iter; - if (roleElement.type() != String) - return Status(ErrorCodes::TypeMismatch, privilegesTypeMismatchMessage); + for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) { + BSONElement roleElement = *iter; + if (roleElement.type() != String) + return Status(ErrorCodes::TypeMismatch, privilegesTypeMismatchMessage); - user->addRole(RoleName(roleElement.String(), dbname)); - } - return Status::OK(); + user->addRole(RoleName(roleElement.String(), dbname)); + } + return Status::OK(); +} + +static Status _initializeUserRolesFromV1UserDocument(User* user, + const BSONObj& privDoc, + StringData dbname) { + if (!privDoc[READONLY_FIELD_NAME].eoo()) { + return Status(ErrorCodes::UnsupportedFormat, + "User documents may not contain both \"readonly\" and " + "\"roles\" fields"); } - static Status _initializeUserRolesFromV1UserDocument( - User* user, const BSONObj& privDoc, StringData dbname) { - - if (!privDoc[READONLY_FIELD_NAME].eoo()) { - return Status(ErrorCodes::UnsupportedFormat, - "User documents may not contain both \"readonly\" and " - "\"roles\" fields"); - } - - Status status = _initializeUserRolesFromV1RolesArray(user, - privDoc[ROLES_FIELD_NAME], - dbname); - if (!status.isOK()) { - return status; - } + Status status = _initializeUserRolesFromV1RolesArray(user, privDoc[ROLES_FIELD_NAME], dbname); + if (!status.isOK()) { + return status; + } - // If "dbname" is the admin database, handle the otherDBPrivileges field, which - // grants privileges on databases other than "dbname". - BSONElement otherDbPrivileges = privDoc[OTHER_DB_ROLES_FIELD_NAME]; - if (dbname == ADMIN_DBNAME) { - switch (otherDbPrivileges.type()) { + // If "dbname" is the admin database, handle the otherDBPrivileges field, which + // grants privileges on databases other than "dbname". + BSONElement otherDbPrivileges = privDoc[OTHER_DB_ROLES_FIELD_NAME]; + if (dbname == ADMIN_DBNAME) { + switch (otherDbPrivileges.type()) { case EOO: break; case Object: { - for (BSONObjIterator iter(otherDbPrivileges.embeddedObject()); - iter.more(); iter.next()) { - + for (BSONObjIterator iter(otherDbPrivileges.embeddedObject()); iter.more(); + iter.next()) { BSONElement rolesElement = *iter; - status = _initializeUserRolesFromV1RolesArray(user, - rolesElement, - rolesElement.fieldName()); + status = _initializeUserRolesFromV1RolesArray( + user, rolesElement, rolesElement.fieldName()); if (!status.isOK()) return status; } @@ -188,359 +181,337 @@ namespace { default: return Status(ErrorCodes::TypeMismatch, "Field \"otherDBRoles\" must be an object, if present."); - } - } - else if (!otherDbPrivileges.eoo()) { - return Status(ErrorCodes::UnsupportedFormat, - "Only the admin database may contain a field called \"otherDBRoles\""); } + } else if (!otherDbPrivileges.eoo()) { + return Status(ErrorCodes::UnsupportedFormat, + "Only the admin database may contain a field called \"otherDBRoles\""); + } - return Status::OK(); + return Status::OK(); +} + +Status V1UserDocumentParser::initializeUserRolesFromUserDocument(User* user, + const BSONObj& privDoc, + StringData dbname) const { + if (!privDoc.hasField("roles")) { + _initializeUserRolesFromV0UserDocument(user, privDoc, dbname); + } else { + return _initializeUserRolesFromV1UserDocument(user, privDoc, dbname); } + return Status::OK(); +} - Status V1UserDocumentParser::initializeUserRolesFromUserDocument( - User* user, const BSONObj& privDoc, StringData dbname) const { - if (!privDoc.hasField("roles")) { - _initializeUserRolesFromV0UserDocument(user, privDoc, dbname); - } else { - return _initializeUserRolesFromV1UserDocument(user, privDoc, dbname); + +Status _checkV2RolesArray(const BSONElement& rolesElement) { + if (rolesElement.eoo()) { + return _badValue("User document needs 'roles' field to be provided", 0); + } + if (rolesElement.type() != Array) { + return _badValue("'roles' field must be an array", 0); + } + for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) { + if ((*iter).type() != Object) { + return _badValue("Elements in 'roles' array must objects", 0); } - return Status::OK(); + Status status = V2UserDocumentParser::checkValidRoleObject((*iter).Obj()); + if (!status.isOK()) + return status; + } + return Status::OK(); +} + +Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const { + BSONElement userElement = doc[AuthorizationManager::USER_NAME_FIELD_NAME]; + BSONElement userDBElement = doc[AuthorizationManager::USER_DB_FIELD_NAME]; + BSONElement credentialsElement = doc[CREDENTIALS_FIELD_NAME]; + BSONElement rolesElement = doc[ROLES_FIELD_NAME]; + + // Validate the "user" element. + if (userElement.type() != String) + return _badValue("User document needs 'user' field to be a string", 0); + if (userElement.valueStringData().empty()) + return _badValue("User document needs 'user' field to be non-empty", 0); + + // Validate the "db" element + if (userDBElement.type() != String || userDBElement.valueStringData().empty()) { + return _badValue("User document needs 'db' field to be a non-empty string", 0); + } + StringData userDBStr = userDBElement.valueStringData(); + if (!NamespaceString::validDBName(userDBStr) && userDBStr != "$external") { + return _badValue(mongoutils::str::stream() << "'" << userDBStr + << "' is not a valid value for the db field.", + 0); } + // Validate the "credentials" element + if (credentialsElement.eoo()) { + return _badValue("User document needs 'credentials' object", 0); + } + if (credentialsElement.type() != Object) { + return _badValue("User document needs 'credentials' field to be an object", 0); + } - Status _checkV2RolesArray(const BSONElement& rolesElement) { - if (rolesElement.eoo()) { - return _badValue("User document needs 'roles' field to be provided", 0); - } - if (rolesElement.type() != Array) { - return _badValue("'roles' field must be an array", 0); + BSONObj credentialsObj = credentialsElement.Obj(); + if (credentialsObj.isEmpty()) { + return _badValue("User document needs 'credentials' field to be a non-empty object", 0); + } + if (userDBStr == "$external") { + BSONElement externalElement = credentialsObj[MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME]; + if (externalElement.eoo() || externalElement.type() != Bool || !externalElement.Bool()) { + return _badValue( + "User documents for users defined on '$external' must have " + "'credentials' field set to {external: true}", + 0); } - for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) { - if ((*iter).type() != Object) { - return _badValue("Elements in 'roles' array must objects", 0); + } else { + BSONElement scramElement = credentialsObj[SCRAM_CREDENTIAL_FIELD_NAME]; + BSONElement mongoCRElement = credentialsObj[MONGODB_CR_CREDENTIAL_FIELD_NAME]; + + if (!mongoCRElement.eoo()) { + if (mongoCRElement.type() != String || mongoCRElement.valueStringData().empty()) { + return _badValue( + "MONGODB-CR credential must to be a non-empty string" + ", if present", + 0); + } + } else if (!scramElement.eoo()) { + if (scramElement.type() != Object) { + return _badValue("SCRAM credential must be an object, if present", 0); } - Status status = V2UserDocumentParser::checkValidRoleObject((*iter).Obj()); - if (!status.isOK()) - return status; + } else { + return _badValue( + "User document must provide credentials for all " + "non-external users", + 0); } - return Status::OK(); } - Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const { - BSONElement userElement = doc[AuthorizationManager::USER_NAME_FIELD_NAME]; - BSONElement userDBElement = doc[AuthorizationManager::USER_DB_FIELD_NAME]; - BSONElement credentialsElement = doc[CREDENTIALS_FIELD_NAME]; - BSONElement rolesElement = doc[ROLES_FIELD_NAME]; - - // Validate the "user" element. - if (userElement.type() != String) - return _badValue("User document needs 'user' field to be a string", 0); - if (userElement.valueStringData().empty()) - return _badValue("User document needs 'user' field to be non-empty", 0); - - // Validate the "db" element - if (userDBElement.type() != String || userDBElement.valueStringData().empty()) { - return _badValue("User document needs 'db' field to be a non-empty string", 0); - } - StringData userDBStr = userDBElement.valueStringData(); - if (!NamespaceString::validDBName(userDBStr) && userDBStr != "$external") { - return _badValue(mongoutils::str::stream() << "'" << userDBStr << - "' is not a valid value for the db field.", - 0); - } + // Validate the "roles" element. + Status status = _checkV2RolesArray(rolesElement); + if (!status.isOK()) + return status; - // Validate the "credentials" element - if (credentialsElement.eoo()) { - return _badValue("User document needs 'credentials' object", - 0); - } - if (credentialsElement.type() != Object) { - return _badValue("User document needs 'credentials' field to be an object", 0); - } + return Status::OK(); +} + +std::string V2UserDocumentParser::extractUserNameFromUserDocument(const BSONObj& doc) const { + return doc[AuthorizationManager::USER_NAME_FIELD_NAME].str(); +} - BSONObj credentialsObj = credentialsElement.Obj(); - if (credentialsObj.isEmpty()) { - return _badValue("User document needs 'credentials' field to be a non-empty object", - 0); +Status V2UserDocumentParser::initializeUserCredentialsFromUserDocument( + User* user, const BSONObj& privDoc) const { + User::CredentialData credentials; + std::string userDB = privDoc[AuthorizationManager::USER_DB_FIELD_NAME].String(); + BSONElement credentialsElement = privDoc[CREDENTIALS_FIELD_NAME]; + if (!credentialsElement.eoo()) { + if (credentialsElement.type() != Object) { + return Status(ErrorCodes::UnsupportedFormat, + "'credentials' field in user documents must be an object"); } - if (userDBStr == "$external") { - BSONElement externalElement = credentialsObj[MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME]; - if (externalElement.eoo() || externalElement.type() != Bool || - !externalElement.Bool()) { - return _badValue("User documents for users defined on '$external' must have " - "'credentials' field set to {external: true}", 0); - } - } - else { - BSONElement scramElement = credentialsObj[SCRAM_CREDENTIAL_FIELD_NAME]; - BSONElement mongoCRElement = credentialsObj[MONGODB_CR_CREDENTIAL_FIELD_NAME]; - - if (!mongoCRElement.eoo()) { - if (mongoCRElement.type() != String || mongoCRElement.valueStringData().empty()) { - return _badValue("MONGODB-CR credential must to be a non-empty string" - ", if present", 0); - } - } - else if (!scramElement.eoo()) { - if (scramElement.type() != Object) { - return _badValue("SCRAM credential must be an object, if present", 0); + if (userDB == "$external") { + BSONElement externalCredentialElement = + credentialsElement.Obj()[MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME]; + if (!externalCredentialElement.eoo()) { + if (externalCredentialElement.type() != Bool || !externalCredentialElement.Bool()) { + return Status(ErrorCodes::UnsupportedFormat, + "'external' field in credentials object must be set to true"); + } else { + credentials.isExternal = true; } + } else { + return Status(ErrorCodes::UnsupportedFormat, + "User documents defined on '$external' must provide set " + "credentials to {external:true}"); } - else { - return _badValue("User document must provide credentials for all " - "non-external users", 0); + } else { + BSONElement scramElement = credentialsElement.Obj()[SCRAM_CREDENTIAL_FIELD_NAME]; + BSONElement mongoCRCredentialElement = + credentialsElement.Obj()[MONGODB_CR_CREDENTIAL_FIELD_NAME]; + + if (scramElement.eoo() && mongoCRCredentialElement.eoo()) { + return Status(ErrorCodes::UnsupportedFormat, + "User documents must provide credentials for SCRAM-SHA-1 " + "or MONGODB-CR authentication"); } - } - // Validate the "roles" element. - Status status = _checkV2RolesArray(rolesElement); - if (!status.isOK()) - return status; + if (!scramElement.eoo()) { + // We are asserting rather then returning errors since these + // fields should have been prepopulated by the calling code. + credentials.scram.iterationCount = scramElement.Obj()["iterationCount"].numberInt(); + uassert(17501, + "Invalid or missing SCRAM iteration count", + credentials.scram.iterationCount > 0); - return Status::OK(); - } + credentials.scram.salt = scramElement.Obj()["salt"].str(); + uassert(17502, "Missing SCRAM salt", !credentials.scram.salt.empty()); - std::string V2UserDocumentParser::extractUserNameFromUserDocument( - const BSONObj& doc) const { - return doc[AuthorizationManager::USER_NAME_FIELD_NAME].str(); - } + credentials.scram.serverKey = scramElement["serverKey"].str(); + uassert(17503, "Missing SCRAM serverKey", !credentials.scram.serverKey.empty()); - Status V2UserDocumentParser::initializeUserCredentialsFromUserDocument( - User* user, const BSONObj& privDoc) const { - User::CredentialData credentials; - std::string userDB = privDoc[AuthorizationManager::USER_DB_FIELD_NAME].String(); - BSONElement credentialsElement = privDoc[CREDENTIALS_FIELD_NAME]; - if (!credentialsElement.eoo()) { - if (credentialsElement.type() != Object) { - return Status(ErrorCodes::UnsupportedFormat, - "'credentials' field in user documents must be an object"); + credentials.scram.storedKey = scramElement["storedKey"].str(); + uassert(17504, "Missing SCRAM storedKey", !credentials.scram.storedKey.empty()); } - if (userDB == "$external") { - BSONElement externalCredentialElement = - credentialsElement.Obj()[MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME]; - if (!externalCredentialElement.eoo()) { - if (externalCredentialElement.type() != Bool || - !externalCredentialElement.Bool()) { - return Status(ErrorCodes::UnsupportedFormat, - "'external' field in credentials object must be set to true"); - } else { - credentials.isExternal = true; - } - } else { - return Status(ErrorCodes::UnsupportedFormat, - "User documents defined on '$external' must provide set " - "credentials to {external:true}"); - } - } else { - - BSONElement scramElement = - credentialsElement.Obj()[SCRAM_CREDENTIAL_FIELD_NAME]; - BSONElement mongoCRCredentialElement = - credentialsElement.Obj()[MONGODB_CR_CREDENTIAL_FIELD_NAME]; - - if (scramElement.eoo() && mongoCRCredentialElement.eoo()) { - return Status(ErrorCodes::UnsupportedFormat, - "User documents must provide credentials for SCRAM-SHA-1 " - "or MONGODB-CR authentication"); - } - if (!scramElement.eoo()) { - // We are asserting rather then returning errors since these - // fields should have been prepopulated by the calling code. - credentials.scram.iterationCount = - scramElement.Obj()["iterationCount"].numberInt(); - uassert(17501, "Invalid or missing SCRAM iteration count", - credentials.scram.iterationCount > 0); - - credentials.scram.salt = - scramElement.Obj()["salt"].str(); - uassert(17502, "Missing SCRAM salt", - !credentials.scram.salt.empty()); - - credentials.scram.serverKey = - scramElement["serverKey"].str(); - uassert(17503, "Missing SCRAM serverKey", - !credentials.scram.serverKey.empty()); - - credentials.scram.storedKey = - scramElement["storedKey"].str(); - uassert(17504, "Missing SCRAM storedKey", - !credentials.scram.storedKey.empty()); - } - - if (!mongoCRCredentialElement.eoo()) { - if (mongoCRCredentialElement.type() != String || - mongoCRCredentialElement.valueStringData().empty()) { + if (!mongoCRCredentialElement.eoo()) { + if (mongoCRCredentialElement.type() != String || + mongoCRCredentialElement.valueStringData().empty()) { + return Status(ErrorCodes::UnsupportedFormat, + "MONGODB-CR credentials must be non-empty strings"); + } else { + credentials.password = mongoCRCredentialElement.String(); + if (credentials.password.empty()) { return Status(ErrorCodes::UnsupportedFormat, - "MONGODB-CR credentials must be non-empty strings"); - } else { - credentials.password = mongoCRCredentialElement.String(); - if (credentials.password.empty()) { - return Status(ErrorCodes::UnsupportedFormat, - "User documents must provide authentication credentials"); - } + "User documents must provide authentication credentials"); } } - credentials.isExternal = false; } - } else { - return Status(ErrorCodes::UnsupportedFormat, - "Cannot extract credentials from user documents without a " - "'credentials' field"); + credentials.isExternal = false; } - - user->setCredentials(credentials); - return Status::OK(); + } else { + return Status(ErrorCodes::UnsupportedFormat, + "Cannot extract credentials from user documents without a " + "'credentials' field"); } - static Status _extractRoleDocumentElements( - const BSONObj& roleObject, - BSONElement* roleNameElement, - BSONElement* roleSourceElement) { + user->setCredentials(credentials); + return Status::OK(); +} - *roleNameElement = roleObject[ROLE_NAME_FIELD_NAME]; - *roleSourceElement = roleObject[ROLE_DB_FIELD_NAME]; - - if (roleNameElement->type() != String || roleNameElement->valueStringData().empty()) { - return Status(ErrorCodes::UnsupportedFormat, - "Role names must be non-empty strings"); - } - if (roleSourceElement->type() != String || roleSourceElement->valueStringData().empty()) { - return Status(ErrorCodes::UnsupportedFormat, "Role db must be non-empty strings"); - } +static Status _extractRoleDocumentElements(const BSONObj& roleObject, + BSONElement* roleNameElement, + BSONElement* roleSourceElement) { + *roleNameElement = roleObject[ROLE_NAME_FIELD_NAME]; + *roleSourceElement = roleObject[ROLE_DB_FIELD_NAME]; - return Status::OK(); + if (roleNameElement->type() != String || roleNameElement->valueStringData().empty()) { + return Status(ErrorCodes::UnsupportedFormat, "Role names must be non-empty strings"); } - - Status V2UserDocumentParser::checkValidRoleObject(const BSONObj& roleObject) { - BSONElement roleNameElement; - BSONElement roleSourceElement; - return _extractRoleDocumentElements( - roleObject, - &roleNameElement, - &roleSourceElement); + if (roleSourceElement->type() != String || roleSourceElement->valueStringData().empty()) { + return Status(ErrorCodes::UnsupportedFormat, "Role db must be non-empty strings"); } - Status V2UserDocumentParser::parseRoleName(const BSONObj& roleObject, RoleName* result) { - BSONElement roleNameElement; - BSONElement roleSourceElement; - Status status = _extractRoleDocumentElements( - roleObject, - &roleNameElement, - &roleSourceElement); - if (!status.isOK()) - return status; - *result = RoleName(roleNameElement.str(), roleSourceElement.str()); - return status; - } + return Status::OK(); +} - Status V2UserDocumentParser::parseRoleVector(const BSONArray& rolesArray, - std::vector<RoleName>* result) { - std::vector<RoleName> roles; - for (BSONObjIterator it(rolesArray); it.more(); it.next()) { - if ((*it).type() != Object) { - return Status(ErrorCodes::TypeMismatch, "Roles must be objects."); - } - RoleName role; - Status status = parseRoleName((*it).Obj(), &role); - if (!status.isOK()) - return status; - roles.push_back(role); +Status V2UserDocumentParser::checkValidRoleObject(const BSONObj& roleObject) { + BSONElement roleNameElement; + BSONElement roleSourceElement; + return _extractRoleDocumentElements(roleObject, &roleNameElement, &roleSourceElement); +} + +Status V2UserDocumentParser::parseRoleName(const BSONObj& roleObject, RoleName* result) { + BSONElement roleNameElement; + BSONElement roleSourceElement; + Status status = _extractRoleDocumentElements(roleObject, &roleNameElement, &roleSourceElement); + if (!status.isOK()) + return status; + *result = RoleName(roleNameElement.str(), roleSourceElement.str()); + return status; +} + +Status V2UserDocumentParser::parseRoleVector(const BSONArray& rolesArray, + std::vector<RoleName>* result) { + std::vector<RoleName> roles; + for (BSONObjIterator it(rolesArray); it.more(); it.next()) { + if ((*it).type() != Object) { + return Status(ErrorCodes::TypeMismatch, "Roles must be objects."); } - std::swap(*result, roles); - return Status::OK(); + RoleName role; + Status status = parseRoleName((*it).Obj(), &role); + if (!status.isOK()) + return status; + roles.push_back(role); } + std::swap(*result, roles); + return Status::OK(); +} - Status V2UserDocumentParser::initializeUserRolesFromUserDocument( - const BSONObj& privDoc, User* user) const { +Status V2UserDocumentParser::initializeUserRolesFromUserDocument(const BSONObj& privDoc, + User* user) const { + BSONElement rolesElement = privDoc[ROLES_FIELD_NAME]; - BSONElement rolesElement = privDoc[ROLES_FIELD_NAME]; + if (rolesElement.type() != Array) { + return Status(ErrorCodes::UnsupportedFormat, + "User document needs 'roles' field to be an array"); + } - if (rolesElement.type() != Array) { + std::vector<RoleName> roles; + for (BSONObjIterator it(rolesElement.Obj()); it.more(); it.next()) { + if ((*it).type() != Object) { return Status(ErrorCodes::UnsupportedFormat, - "User document needs 'roles' field to be an array"); + "User document needs values in 'roles' array to be a sub-documents"); } + BSONObj roleObject = (*it).Obj(); - std::vector<RoleName> roles; - for (BSONObjIterator it(rolesElement.Obj()); it.more(); it.next()) { - if ((*it).type() != Object) { - return Status(ErrorCodes::UnsupportedFormat, - "User document needs values in 'roles' array to be a sub-documents"); - } - BSONObj roleObject = (*it).Obj(); - - RoleName role; - Status status = parseRoleName(roleObject, &role); - if (!status.isOK()) { - return status; - } - roles.push_back(role); + RoleName role; + Status status = parseRoleName(roleObject, &role); + if (!status.isOK()) { + return status; } - user->setRoles(makeRoleNameIteratorForContainer(roles)); - return Status::OK(); + roles.push_back(role); } + user->setRoles(makeRoleNameIteratorForContainer(roles)); + return Status::OK(); +} - Status V2UserDocumentParser::initializeUserIndirectRolesFromUserDocument( - const BSONObj& privDoc, User* user) const { +Status V2UserDocumentParser::initializeUserIndirectRolesFromUserDocument(const BSONObj& privDoc, + User* user) const { + BSONElement indirectRolesElement = privDoc[INHERITED_ROLES_FIELD_NAME]; - BSONElement indirectRolesElement = privDoc[INHERITED_ROLES_FIELD_NAME]; + if (indirectRolesElement.type() != Array) { + return Status(ErrorCodes::UnsupportedFormat, + "User document needs 'inheritedRoles' field to be an array"); + } - if (indirectRolesElement.type() != Array) { + std::vector<RoleName> indirectRoles; + for (BSONObjIterator it(indirectRolesElement.Obj()); it.more(); it.next()) { + if ((*it).type() != Object) { return Status(ErrorCodes::UnsupportedFormat, - "User document needs 'inheritedRoles' field to be an array"); + "User document needs values in 'inheritedRoles'" + " array to be a sub-documents"); } + BSONObj indirectRoleObject = (*it).Obj(); - std::vector<RoleName> indirectRoles; - for (BSONObjIterator it(indirectRolesElement.Obj()); it.more(); it.next()) { - if ((*it).type() != Object) { - return Status(ErrorCodes::UnsupportedFormat, - "User document needs values in 'inheritedRoles'" - " array to be a sub-documents"); - } - BSONObj indirectRoleObject = (*it).Obj(); - - RoleName indirectRole; - Status status = parseRoleName(indirectRoleObject, &indirectRole); - if (!status.isOK()) { - return status; - } - indirectRoles.push_back(indirectRole); + RoleName indirectRole; + Status status = parseRoleName(indirectRoleObject, &indirectRole); + if (!status.isOK()) { + return status; } - user->setIndirectRoles(makeRoleNameIteratorForContainer(indirectRoles)); + indirectRoles.push_back(indirectRole); + } + user->setIndirectRoles(makeRoleNameIteratorForContainer(indirectRoles)); + return Status::OK(); +} + +Status V2UserDocumentParser::initializeUserPrivilegesFromUserDocument(const BSONObj& doc, + User* user) const { + BSONElement privilegesElement = doc[PRIVILEGES_FIELD_NAME]; + if (privilegesElement.eoo()) return Status::OK(); + if (privilegesElement.type() != Array) { + return Status(ErrorCodes::UnsupportedFormat, + "User document 'inheritedPrivileges' element must be Array if present."); } - - Status V2UserDocumentParser::initializeUserPrivilegesFromUserDocument(const BSONObj& doc, - User* user) const { - BSONElement privilegesElement = doc[PRIVILEGES_FIELD_NAME]; - if (privilegesElement.eoo()) - return Status::OK(); - if (privilegesElement.type() != Array) { - return Status(ErrorCodes::UnsupportedFormat, - "User document 'inheritedPrivileges' element must be Array if present."); + PrivilegeVector privileges; + std::string errmsg; + for (BSONObjIterator it(privilegesElement.Obj()); it.more(); it.next()) { + if ((*it).type() != Object) { + warning() << "Wrong type of element in inheritedPrivileges array for " + << user->getName() << ": " << *it; + continue; } - PrivilegeVector privileges; - std::string errmsg; - for (BSONObjIterator it(privilegesElement.Obj()); it.more(); it.next()) { - if ((*it).type() != Object) { - warning() << "Wrong type of element in inheritedPrivileges array for " << - user->getName() << ": " << *it; - continue; - } - Privilege privilege; - ParsedPrivilege pp; - if (!pp.parseBSON((*it).Obj(), &errmsg) || - !ParsedPrivilege::parsedPrivilegeToPrivilege(pp, &privilege, &errmsg)) { - - warning() << "Could not parse privilege element in user document for " << - user->getName() << ": " << errmsg; - continue; - } - privileges.push_back(privilege); + Privilege privilege; + ParsedPrivilege pp; + if (!pp.parseBSON((*it).Obj(), &errmsg) || + !ParsedPrivilege::parsedPrivilegeToPrivilege(pp, &privilege, &errmsg)) { + warning() << "Could not parse privilege element in user document for " + << user->getName() << ": " << errmsg; + continue; } - user->setPrivileges(privileges); - return Status::OK(); + privileges.push_back(privilege); } + user->setPrivileges(privileges); + return Status::OK(); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/db/auth/user_document_parser.h b/src/mongo/db/auth/user_document_parser.h index 28b93ee5673..502a3f349ea 100644 --- a/src/mongo/db/auth/user_document_parser.h +++ b/src/mongo/db/auth/user_document_parser.h @@ -36,41 +36,43 @@ namespace mongo { - class V1UserDocumentParser { - MONGO_DISALLOW_COPYING(V1UserDocumentParser); - public: - V1UserDocumentParser() {} - std::string extractUserNameFromUserDocument(const BSONObj& doc) const; +class V1UserDocumentParser { + MONGO_DISALLOW_COPYING(V1UserDocumentParser); - Status initializeUserCredentialsFromUserDocument(User* user, - const BSONObj& privDoc) const; +public: + V1UserDocumentParser() {} + std::string extractUserNameFromUserDocument(const BSONObj& doc) const; - Status initializeUserRolesFromUserDocument( - User* user, const BSONObj& privDoc, StringData dbname) const; - }; + Status initializeUserCredentialsFromUserDocument(User* user, const BSONObj& privDoc) const; - class V2UserDocumentParser { - MONGO_DISALLOW_COPYING(V2UserDocumentParser); - public: - V2UserDocumentParser() {} - Status checkValidUserDocument(const BSONObj& doc) const; + Status initializeUserRolesFromUserDocument(User* user, + const BSONObj& privDoc, + StringData dbname) const; +}; - /** - * Returns Status::OK() iff the given BSONObj describes a valid element from a roles array. - */ - static Status checkValidRoleObject(const BSONObj& roleObject); +class V2UserDocumentParser { + MONGO_DISALLOW_COPYING(V2UserDocumentParser); - static Status parseRoleName(const BSONObj& roleObject, RoleName* result); +public: + V2UserDocumentParser() {} + Status checkValidUserDocument(const BSONObj& doc) const; - static Status parseRoleVector(const BSONArray& rolesArray, std::vector<RoleName>* result); + /** + * Returns Status::OK() iff the given BSONObj describes a valid element from a roles array. + */ + static Status checkValidRoleObject(const BSONObj& roleObject); - std::string extractUserNameFromUserDocument(const BSONObj& doc) const; + static Status parseRoleName(const BSONObj& roleObject, RoleName* result); - Status initializeUserCredentialsFromUserDocument(User* user, const BSONObj& privDoc) const; + static Status parseRoleVector(const BSONArray& rolesArray, std::vector<RoleName>* result); - Status initializeUserRolesFromUserDocument(const BSONObj& doc, User* user) const; - Status initializeUserIndirectRolesFromUserDocument(const BSONObj& doc, User* user) const; - Status initializeUserPrivilegesFromUserDocument(const BSONObj& doc, User* user) const; - }; + std::string extractUserNameFromUserDocument(const BSONObj& doc) const; -} // namespace mongo + Status initializeUserCredentialsFromUserDocument(User* user, const BSONObj& privDoc) const; + + Status initializeUserRolesFromUserDocument(const BSONObj& doc, User* user) const; + Status initializeUserIndirectRolesFromUserDocument(const BSONObj& doc, User* user) const; + Status initializeUserPrivilegesFromUserDocument(const BSONObj& doc, User* user) const; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/user_document_parser_test.cpp b/src/mongo/db/auth/user_document_parser_test.cpp index d2dff197b12..ae6c566d109 100644 --- a/src/mongo/db/auth/user_document_parser_test.cpp +++ b/src/mongo/db/auth/user_document_parser_test.cpp @@ -44,360 +44,450 @@ namespace mongo { namespace { - using std::unique_ptr; - - class V1UserDocumentParsing : public ::mongo::unittest::Test { - public: - V1UserDocumentParsing() {} - - unique_ptr<User> user; - unique_ptr<User> adminUser; - V1UserDocumentParser v1parser; - - void setUp() { - resetUsers(); - } - - void resetUsers() { - user.reset(new User(UserName("spencer", "test"))); - adminUser.reset(new User(UserName("admin", "admin"))); - } - }; - - TEST_F(V1UserDocumentParsing, testParsingV0UserDocuments) { - BSONObj readWrite = BSON("user" << "spencer" << "pwd" << "passwordHash"); - BSONObj readOnly = BSON("user" << "spencer" << "pwd" << "passwordHash" << - "readOnly" << true); - BSONObj readWriteAdmin = BSON("user" << "admin" << "pwd" << "passwordHash"); - BSONObj readOnlyAdmin = BSON("user" << "admin" << "pwd" << "passwordHash" << - "readOnly" << true); - - ASSERT_OK(v1parser.initializeUserRolesFromUserDocument( - user.get(), readOnly, "test")); - RoleNameIterator roles = user->getRoles(); - ASSERT_EQUALS(RoleName("read", "test"), roles.next()); - ASSERT_FALSE(roles.more()); +using std::unique_ptr; - resetUsers(); - ASSERT_OK(v1parser.initializeUserRolesFromUserDocument( - user.get(), readWrite, "test")); - roles = user->getRoles(); - ASSERT_EQUALS(RoleName("dbOwner", "test"), roles.next()); - ASSERT_FALSE(roles.more()); +class V1UserDocumentParsing : public ::mongo::unittest::Test { +public: + V1UserDocumentParsing() {} - resetUsers(); - ASSERT_OK(v1parser.initializeUserRolesFromUserDocument( - adminUser.get(), readOnlyAdmin, "admin")); - roles = adminUser->getRoles(); - ASSERT_EQUALS(RoleName("readAnyDatabase", "admin"), roles.next()); - ASSERT_FALSE(roles.more()); + unique_ptr<User> user; + unique_ptr<User> adminUser; + V1UserDocumentParser v1parser; + void setUp() { resetUsers(); - ASSERT_OK(v1parser.initializeUserRolesFromUserDocument( - adminUser.get(), readWriteAdmin, "admin")); - roles = adminUser->getRoles(); - ASSERT_EQUALS(RoleName("root", "admin"), roles.next()); - ASSERT_FALSE(roles.more()); - } - - TEST_F(V1UserDocumentParsing, VerifyRolesFieldMustBeAnArray) { - ASSERT_NOT_OK(v1parser.initializeUserRolesFromUserDocument( - user.get(), - BSON("user" << "spencer" << "pwd" << "" << "roles" << "read"), - "test")); - ASSERT_FALSE(user->getRoles().more()); - } - - TEST_F(V1UserDocumentParsing, VerifySemanticallyInvalidRolesStillParse) { - ASSERT_OK(v1parser.initializeUserRolesFromUserDocument( - user.get(), - BSON("user" << "spencer" << - "pwd" << "" << - "roles" << BSON_ARRAY("read" << "frim")), - "test")); - RoleNameIterator roles = user->getRoles(); - RoleName role = roles.next(); - if (role == RoleName("read", "test")) { - ASSERT_EQUALS(RoleName("frim", "test"), roles.next()); - } else { - ASSERT_EQUALS(RoleName("frim", "test"), role); - ASSERT_EQUALS(RoleName("read", "test"), roles.next()); - } - ASSERT_FALSE(roles.more()); - } - - TEST_F(V1UserDocumentParsing, VerifyOtherDBRolesMustBeAnObjectOfArraysOfStrings) { - ASSERT_NOT_OK(v1parser.initializeUserRolesFromUserDocument( - adminUser.get(), - BSON("user" << "admin" << - "pwd" << "" << - "roles" << BSON_ARRAY("read") << - "otherDBRoles" << BSON_ARRAY("read")), - "admin")); - - ASSERT_NOT_OK(v1parser.initializeUserRolesFromUserDocument( - adminUser.get(), - BSON("user" << "admin" << - "pwd" << "" << - "roles" << BSON_ARRAY("read") << - "otherDBRoles" << BSON("test2" << "read")), - "admin")); } - TEST_F(V1UserDocumentParsing, VerifyCannotGrantPrivilegesOnOtherDatabasesNormally) { - // Cannot grant roles on other databases, except from admin database. - ASSERT_NOT_OK(v1parser.initializeUserRolesFromUserDocument( - user.get(), - BSON("user" << "spencer" << - "pwd" << "" << - "roles" << BSONArrayBuilder().arr() << - "otherDBRoles" << BSON("test2" << BSON_ARRAY("read"))), - "test")); - ASSERT_FALSE(user->getRoles().more()); + void resetUsers() { + user.reset(new User(UserName("spencer", "test"))); + adminUser.reset(new User(UserName("admin", "admin"))); } - - TEST_F(V1UserDocumentParsing, GrantUserAdminOnTestViaAdmin) { - // Grant userAdmin on test via admin. - ASSERT_OK(v1parser.initializeUserRolesFromUserDocument( - adminUser.get(), - BSON("user" << "admin" << - "pwd" << "" << - "roles" << BSONArrayBuilder().arr() << - "otherDBRoles" << BSON("test" << BSON_ARRAY("userAdmin"))), - "admin")); - RoleNameIterator roles = adminUser->getRoles(); - ASSERT_EQUALS(RoleName("userAdmin", "test"), roles.next()); - ASSERT_FALSE(roles.more()); - } - - TEST_F(V1UserDocumentParsing, MixedV0V1UserDocumentsAreInvalid) { - // Try to mix fields from V0 and V1 user documents and make sure it fails. - ASSERT_NOT_OK(v1parser.initializeUserRolesFromUserDocument( - user.get(), - BSON("user" << "spencer" << - "pwd" << "passwordHash" << - "readOnly" << false << - "roles" << BSON_ARRAY("read")), - "test")); - ASSERT_FALSE(user->getRoles().more()); - } - - class V2UserDocumentParsing : public ::mongo::unittest::Test { - public: - V2UserDocumentParsing() {} - - unique_ptr<User> user; - unique_ptr<User> adminUser; - V2UserDocumentParser v2parser; - - void setUp() { - user.reset(new User(UserName("spencer", "test"))); - adminUser.reset(new User(UserName("admin", "admin"))); - } - }; - - - TEST_F(V2UserDocumentParsing, V2DocumentValidation) { - BSONArray emptyArray = BSONArrayBuilder().arr(); - - // V1 documents don't work - ASSERT_NOT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << "pwd" << "a" << - "roles" << BSON_ARRAY("read")))); - - // Need name field - ASSERT_NOT_OK(v2parser.checkValidUserDocument( - BSON("db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << emptyArray))); - - // Need source field - ASSERT_NOT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << emptyArray))); - - // Need credentials field - ASSERT_NOT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "test" << - "roles" << emptyArray))); - - // Need roles field - ASSERT_NOT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a")))); - - // Empty roles arrays are OK - ASSERT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << emptyArray))); - - // Need credentials of {external: true} if user's db is $external - ASSERT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "$external" << - "credentials" << BSON("external" << true) << - "roles" << emptyArray))); - - // Roles must be objects - ASSERT_NOT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY("read")))); - - // Role needs name - ASSERT_NOT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("db" << "dbA"))))); - - // Role needs source - ASSERT_NOT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "roleA"))))); - - - // Basic valid user document - ASSERT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "roleA" << - "db" << "dbA"))))); - - // Multiple roles OK - ASSERT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "roles" << BSON_ARRAY(BSON("role" << "roleA" << - "db" << "dbA") << - BSON("role" << "roleB" << - "db" << "dbB"))))); - - // Optional extraData field OK - ASSERT_OK(v2parser.checkValidUserDocument( - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a") << - "extraData" << BSON("foo" << "bar") << - "roles" << BSON_ARRAY(BSON("role" << "roleA" << - "db" << "dbA"))))); +}; + +TEST_F(V1UserDocumentParsing, testParsingV0UserDocuments) { + BSONObj readWrite = BSON("user" + << "spencer" + << "pwd" + << "passwordHash"); + BSONObj readOnly = BSON("user" + << "spencer" + << "pwd" + << "passwordHash" + << "readOnly" << true); + BSONObj readWriteAdmin = BSON("user" + << "admin" + << "pwd" + << "passwordHash"); + BSONObj readOnlyAdmin = BSON("user" + << "admin" + << "pwd" + << "passwordHash" + << "readOnly" << true); + + ASSERT_OK(v1parser.initializeUserRolesFromUserDocument(user.get(), readOnly, "test")); + RoleNameIterator roles = user->getRoles(); + ASSERT_EQUALS(RoleName("read", "test"), roles.next()); + ASSERT_FALSE(roles.more()); + + resetUsers(); + ASSERT_OK(v1parser.initializeUserRolesFromUserDocument(user.get(), readWrite, "test")); + roles = user->getRoles(); + ASSERT_EQUALS(RoleName("dbOwner", "test"), roles.next()); + ASSERT_FALSE(roles.more()); + + resetUsers(); + ASSERT_OK( + v1parser.initializeUserRolesFromUserDocument(adminUser.get(), readOnlyAdmin, "admin")); + roles = adminUser->getRoles(); + ASSERT_EQUALS(RoleName("readAnyDatabase", "admin"), roles.next()); + ASSERT_FALSE(roles.more()); + + resetUsers(); + ASSERT_OK( + v1parser.initializeUserRolesFromUserDocument(adminUser.get(), readWriteAdmin, "admin")); + roles = adminUser->getRoles(); + ASSERT_EQUALS(RoleName("root", "admin"), roles.next()); + ASSERT_FALSE(roles.more()); +} + +TEST_F(V1UserDocumentParsing, VerifyRolesFieldMustBeAnArray) { + ASSERT_NOT_OK(v1parser.initializeUserRolesFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "pwd" + << "" + << "roles" + << "read"), + "test")); + ASSERT_FALSE(user->getRoles().more()); +} + +TEST_F(V1UserDocumentParsing, VerifySemanticallyInvalidRolesStillParse) { + ASSERT_OK( + v1parser.initializeUserRolesFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "pwd" + << "" + << "roles" << BSON_ARRAY("read" + << "frim")), + "test")); + RoleNameIterator roles = user->getRoles(); + RoleName role = roles.next(); + if (role == RoleName("read", "test")) { + ASSERT_EQUALS(RoleName("frim", "test"), roles.next()); + } else { + ASSERT_EQUALS(RoleName("frim", "test"), role); + ASSERT_EQUALS(RoleName("read", "test"), roles.next()); } - - TEST_F(V2UserDocumentParsing, V2CredentialExtraction) { - // Old "pwd" field not valid - ASSERT_NOT_OK(v2parser.initializeUserCredentialsFromUserDocument( - user.get(), - BSON("user" << "spencer" << - "db" << "test" << - "pwd" << ""))); - - // Credentials must be provided - ASSERT_NOT_OK(v2parser.initializeUserCredentialsFromUserDocument( - user.get(), - BSON("user" << "spencer" << - "db" << "test"))); - - // Credentials must be object - ASSERT_NOT_OK(v2parser.initializeUserCredentialsFromUserDocument( - user.get(), - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << "a"))); - - // Must specify credentials for MONGODB-CR - ASSERT_NOT_OK(v2parser.initializeUserCredentialsFromUserDocument( - user.get(), - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("foo" << "bar")))); - - // Make sure extracting valid credentials works - ASSERT_OK(v2parser.initializeUserCredentialsFromUserDocument( - user.get(), - BSON("user" << "spencer" << - "db" << "test" << - "credentials" << BSON("MONGODB-CR" << "a")))); - ASSERT(user->getCredentials().password == "a"); - ASSERT(!user->getCredentials().isExternal); - - // Credentials are {external:true if users's db is $external - ASSERT_OK(v2parser.initializeUserCredentialsFromUserDocument( - user.get(), - BSON("user" << "spencer" << - "db" << "$external" << - "credentials" << BSON("external" << true)))); - ASSERT(user->getCredentials().password.empty()); - ASSERT(user->getCredentials().isExternal); - + ASSERT_FALSE(roles.more()); +} + +TEST_F(V1UserDocumentParsing, VerifyOtherDBRolesMustBeAnObjectOfArraysOfStrings) { + ASSERT_NOT_OK( + v1parser.initializeUserRolesFromUserDocument(adminUser.get(), + BSON("user" + << "admin" + << "pwd" + << "" + << "roles" << BSON_ARRAY("read") + << "otherDBRoles" << BSON_ARRAY("read")), + "admin")); + + ASSERT_NOT_OK( + v1parser.initializeUserRolesFromUserDocument(adminUser.get(), + BSON("user" + << "admin" + << "pwd" + << "" + << "roles" << BSON_ARRAY("read") + << "otherDBRoles" << BSON("test2" + << "read")), + "admin")); +} + +TEST_F(V1UserDocumentParsing, VerifyCannotGrantPrivilegesOnOtherDatabasesNormally) { + // Cannot grant roles on other databases, except from admin database. + ASSERT_NOT_OK( + v1parser.initializeUserRolesFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "pwd" + << "" + << "roles" << BSONArrayBuilder().arr() + << "otherDBRoles" + << BSON("test2" << BSON_ARRAY("read"))), + "test")); + ASSERT_FALSE(user->getRoles().more()); +} + +TEST_F(V1UserDocumentParsing, GrantUserAdminOnTestViaAdmin) { + // Grant userAdmin on test via admin. + ASSERT_OK(v1parser.initializeUserRolesFromUserDocument( + adminUser.get(), + BSON("user" + << "admin" + << "pwd" + << "" + << "roles" << BSONArrayBuilder().arr() << "otherDBRoles" + << BSON("test" << BSON_ARRAY("userAdmin"))), + "admin")); + RoleNameIterator roles = adminUser->getRoles(); + ASSERT_EQUALS(RoleName("userAdmin", "test"), roles.next()); + ASSERT_FALSE(roles.more()); +} + +TEST_F(V1UserDocumentParsing, MixedV0V1UserDocumentsAreInvalid) { + // Try to mix fields from V0 and V1 user documents and make sure it fails. + ASSERT_NOT_OK( + v1parser.initializeUserRolesFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "pwd" + << "passwordHash" + << "readOnly" << false << "roles" + << BSON_ARRAY("read")), + "test")); + ASSERT_FALSE(user->getRoles().more()); +} + +class V2UserDocumentParsing : public ::mongo::unittest::Test { +public: + V2UserDocumentParsing() {} + + unique_ptr<User> user; + unique_ptr<User> adminUser; + V2UserDocumentParser v2parser; + + void setUp() { + user.reset(new User(UserName("spencer", "test"))); + adminUser.reset(new User(UserName("admin", "admin"))); } - - TEST_F(V2UserDocumentParsing, V2RoleExtraction) { - // "roles" field must be provided - ASSERT_NOT_OK(v2parser.initializeUserRolesFromUserDocument( - BSON("user" << "spencer"), - user.get())); - - // V1-style roles arrays no longer work - ASSERT_NOT_OK(v2parser.initializeUserRolesFromUserDocument( - BSON("user" << "spencer" << - "roles" << BSON_ARRAY("read")), - user.get())); - - // Roles must have "db" field - ASSERT_NOT_OK(v2parser.initializeUserRolesFromUserDocument( - BSON("user" << "spencer" << - "roles" << BSON_ARRAY(BSONObj())), - user.get())); - - ASSERT_NOT_OK(v2parser.initializeUserRolesFromUserDocument( - BSON("user" << "spencer" << - "roles" << BSON_ARRAY(BSON("role" << "roleA"))), - user.get())); - - ASSERT_NOT_OK(v2parser.initializeUserRolesFromUserDocument( - BSON("user" << "spencer" << - "roles" << BSON_ARRAY(BSON("user" << "roleA" << - "db" << "dbA"))), - user.get())); - - // Valid role names are extracted successfully - ASSERT_OK(v2parser.initializeUserRolesFromUserDocument( - BSON("user" << "spencer" << - "roles" << BSON_ARRAY(BSON("role" << "roleA" << - "db" << "dbA"))), - user.get())); - RoleNameIterator roles = user->getRoles(); +}; + + +TEST_F(V2UserDocumentParsing, V2DocumentValidation) { + BSONArray emptyArray = BSONArrayBuilder().arr(); + + // V1 documents don't work + ASSERT_NOT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "pwd" + << "a" + << "roles" << BSON_ARRAY("read")))); + + // Need name field + ASSERT_NOT_OK(v2parser.checkValidUserDocument(BSON("db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << emptyArray))); + + // Need source field + ASSERT_NOT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << emptyArray))); + + // Need credentials field + ASSERT_NOT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "roles" << emptyArray))); + + // Need roles field + ASSERT_NOT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a")))); + + // Empty roles arrays are OK + ASSERT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << emptyArray))); + + // Need credentials of {external: true} if user's db is $external + ASSERT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "$external" + << "credentials" << BSON("external" << true) + << "roles" << emptyArray))); + + // Roles must be objects + ASSERT_NOT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY("read")))); + + // Role needs name + ASSERT_NOT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("db" + << "dbA"))))); + + // Role needs source + ASSERT_NOT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "roleA"))))); + + + // Basic valid user document + ASSERT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "roleA" + << "db" + << "dbA"))))); + + // Multiple roles OK + ASSERT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "roles" + << BSON_ARRAY(BSON("role" + << "roleA" + << "db" + << "dbA") + << BSON("role" + << "roleB" + << "db" + << "dbB"))))); + + // Optional extraData field OK + ASSERT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" << BSON("MONGODB-CR" + << "a") << "extraData" + << BSON("foo" + << "bar") << "roles" + << BSON_ARRAY(BSON("role" + << "roleA" + << "db" + << "dbA"))))); +} + +TEST_F(V2UserDocumentParsing, V2CredentialExtraction) { + // Old "pwd" field not valid + ASSERT_NOT_OK(v2parser.initializeUserCredentialsFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "db" + << "test" + << "pwd" + << ""))); + + // Credentials must be provided + ASSERT_NOT_OK(v2parser.initializeUserCredentialsFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "db" + << "test"))); + + // Credentials must be object + ASSERT_NOT_OK(v2parser.initializeUserCredentialsFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" + << "a"))); + + // Must specify credentials for MONGODB-CR + ASSERT_NOT_OK(v2parser.initializeUserCredentialsFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" + << BSON("foo" + << "bar")))); + + // Make sure extracting valid credentials works + ASSERT_OK(v2parser.initializeUserCredentialsFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" + << BSON("MONGODB-CR" + << "a")))); + ASSERT(user->getCredentials().password == "a"); + ASSERT(!user->getCredentials().isExternal); + + // Credentials are {external:true if users's db is $external + ASSERT_OK( + v2parser.initializeUserCredentialsFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "db" + << "$external" + << "credentials" + << BSON("external" << true)))); + ASSERT(user->getCredentials().password.empty()); + ASSERT(user->getCredentials().isExternal); +} + +TEST_F(V2UserDocumentParsing, V2RoleExtraction) { + // "roles" field must be provided + ASSERT_NOT_OK(v2parser.initializeUserRolesFromUserDocument(BSON("user" + << "spencer"), + user.get())); + + // V1-style roles arrays no longer work + ASSERT_NOT_OK( + v2parser.initializeUserRolesFromUserDocument(BSON("user" + << "spencer" + << "roles" << BSON_ARRAY("read")), + user.get())); + + // Roles must have "db" field + ASSERT_NOT_OK( + v2parser.initializeUserRolesFromUserDocument(BSON("user" + << "spencer" + << "roles" << BSON_ARRAY(BSONObj())), + user.get())); + + ASSERT_NOT_OK(v2parser.initializeUserRolesFromUserDocument(BSON("user" + << "spencer" + << "roles" << BSON_ARRAY(BSON( + "role" + << "roleA"))), + user.get())); + + ASSERT_NOT_OK( + v2parser.initializeUserRolesFromUserDocument(BSON("user" + << "spencer" + << "roles" << BSON_ARRAY(BSON("user" + << "roleA" + << "db" + << "dbA"))), + user.get())); + + // Valid role names are extracted successfully + ASSERT_OK( + v2parser.initializeUserRolesFromUserDocument(BSON("user" + << "spencer" + << "roles" << BSON_ARRAY(BSON("role" + << "roleA" + << "db" + << "dbA"))), + user.get())); + RoleNameIterator roles = user->getRoles(); + ASSERT_EQUALS(RoleName("roleA", "dbA"), roles.next()); + ASSERT_FALSE(roles.more()); + + // Multiple roles OK + ASSERT_OK(v2parser.initializeUserRolesFromUserDocument(BSON("user" + << "spencer" + << "roles" + << BSON_ARRAY(BSON("role" + << "roleA" + << "db" + << "dbA") + << BSON("role" + << "roleB" + << "db" + << "dbB"))), + user.get())); + roles = user->getRoles(); + RoleName role = roles.next(); + if (role == RoleName("roleA", "dbA")) { + ASSERT_EQUALS(RoleName("roleB", "dbB"), roles.next()); + } else { + ASSERT_EQUALS(RoleName("roleB", "dbB"), role); ASSERT_EQUALS(RoleName("roleA", "dbA"), roles.next()); - ASSERT_FALSE(roles.more()); - - // Multiple roles OK - ASSERT_OK(v2parser.initializeUserRolesFromUserDocument( - BSON("user" << "spencer" << - "roles" << BSON_ARRAY(BSON("role" << "roleA" << - "db" << "dbA") << - BSON("role" << "roleB" << - "db" << "dbB"))), - user.get())); - roles = user->getRoles(); - RoleName role = roles.next(); - if (role == RoleName("roleA", "dbA")) { - ASSERT_EQUALS(RoleName("roleB", "dbB"), roles.next()); - } else { - ASSERT_EQUALS(RoleName("roleB", "dbB"), role); - ASSERT_EQUALS(RoleName("roleA", "dbA"), roles.next()); - } - ASSERT_FALSE(roles.more()); } + ASSERT_FALSE(roles.more()); +} } // namespace } // namespace mongo diff --git a/src/mongo/db/auth/user_management_commands_parser.cpp b/src/mongo/db/auth/user_management_commands_parser.cpp index eecef9d9301..82130e2a530 100644 --- a/src/mongo/db/auth/user_management_commands_parser.cpp +++ b/src/mongo/db/auth/user_management_commands_parser.cpp @@ -47,652 +47,620 @@ namespace mongo { namespace auth { - using std::vector; - - /** - * Writes into *writeConcern a BSONObj describing the parameters to getLastError to use for - * the write confirmation. - */ - Status _extractWriteConcern(const BSONObj& cmdObj, BSONObj* writeConcern) { - BSONElement writeConcernElement; - Status status = bsonExtractTypedField(cmdObj, "writeConcern", Object, &writeConcernElement); - if (!status.isOK()) { - if (status.code() == ErrorCodes::NoSuchKey) { - *writeConcern = BSONObj(); - return Status::OK(); - } - return status; - } - *writeConcern = writeConcernElement.Obj().getOwned();; - return Status::OK(); - } - - Status _checkNoExtraFields(const BSONObj& cmdObj, - StringData cmdName, - const unordered_set<std::string>& validFieldNames) { - // Iterate through all fields in command object and make sure there are no unexpected - // ones. - for (BSONObjIterator iter(cmdObj); iter.more(); iter.next()) { - StringData fieldName = (*iter).fieldNameStringData(); - if (!validFieldNames.count(fieldName.toString())) { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << "\"" << fieldName << "\" is not " - "a valid argument to " << cmdName); - } - } - return Status::OK(); - } - - // Extracts a UserName or RoleName object from a BSONElement. - template <typename Name> - Status _parseNameFromBSONElement(const BSONElement& element, - StringData dbname, - StringData nameFieldName, - StringData sourceFieldName, - Name* parsedName) { - if (element.type() == String) { - *parsedName = Name(element.String(), dbname); - } - else if (element.type() == Object) { - BSONObj obj = element.Obj(); - - std::string name; - std::string source; - Status status = bsonExtractStringField(obj, nameFieldName, &name); - if (!status.isOK()) { - return status; - } - status = bsonExtractStringField(obj, sourceFieldName, &source); - if (!status.isOK()) { - return status; - } - - *parsedName = Name(name, source); - } - else { +using std::vector; + +/** + * Writes into *writeConcern a BSONObj describing the parameters to getLastError to use for + * the write confirmation. + */ +Status _extractWriteConcern(const BSONObj& cmdObj, BSONObj* writeConcern) { + BSONElement writeConcernElement; + Status status = bsonExtractTypedField(cmdObj, "writeConcern", Object, &writeConcernElement); + if (!status.isOK()) { + if (status.code() == ErrorCodes::NoSuchKey) { + *writeConcern = BSONObj(); + return Status::OK(); + } + return status; + } + *writeConcern = writeConcernElement.Obj().getOwned(); + ; + return Status::OK(); +} + +Status _checkNoExtraFields(const BSONObj& cmdObj, + StringData cmdName, + const unordered_set<std::string>& validFieldNames) { + // Iterate through all fields in command object and make sure there are no unexpected + // ones. + for (BSONObjIterator iter(cmdObj); iter.more(); iter.next()) { + StringData fieldName = (*iter).fieldNameStringData(); + if (!validFieldNames.count(fieldName.toString())) { return Status(ErrorCodes::BadValue, - "User and role names must be either strings or objects"); - } - return Status::OK(); - } - - // Extracts UserName or RoleName objects from a BSONArray of role/user names. - template <typename Name> - Status _parseNamesFromBSONArray(const BSONArray& array, - StringData dbname, - StringData nameFieldName, - StringData sourceFieldName, - std::vector<Name>* parsedNames) { - for (BSONObjIterator it(array); it.more(); it.next()) { - BSONElement element = *it; - Name name; - Status status = _parseNameFromBSONElement(element, - dbname, - nameFieldName, - sourceFieldName, - &name); - if (!status.isOK()) { - return status; - } - parsedNames->push_back(name); - } - return Status::OK(); - } - - Status parseUserNamesFromBSONArray(const BSONArray& usersArray, - StringData dbname, - std::vector<UserName>* parsedUserNames) { - return _parseNamesFromBSONArray(usersArray, - dbname, - AuthorizationManager::USER_NAME_FIELD_NAME, - AuthorizationManager::USER_DB_FIELD_NAME, - parsedUserNames); - } - - Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray, - StringData dbname, - std::vector<RoleName>* parsedRoleNames) { - return _parseNamesFromBSONArray(rolesArray, - dbname, - AuthorizationManager::ROLE_NAME_FIELD_NAME, - AuthorizationManager::ROLE_DB_FIELD_NAME, - parsedRoleNames); - } - - Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - std::string* parsedName, - vector<RoleName>* parsedRoleNames, - BSONObj* parsedWriteConcern) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert(cmdName.toString()); - validFieldNames.insert("roles"); - validFieldNames.insert("writeConcern"); - - Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); - if (!status.isOK()) { - return status; + mongoutils::str::stream() << "\"" << fieldName << "\" is not " + "a valid argument to " + << cmdName); } + } + return Status::OK(); +} - status = _extractWriteConcern(cmdObj, parsedWriteConcern); - if (!status.isOK()) { - return status; - } +// Extracts a UserName or RoleName object from a BSONElement. +template <typename Name> +Status _parseNameFromBSONElement(const BSONElement& element, + StringData dbname, + StringData nameFieldName, + StringData sourceFieldName, + Name* parsedName) { + if (element.type() == String) { + *parsedName = Name(element.String(), dbname); + } else if (element.type() == Object) { + BSONObj obj = element.Obj(); - status = bsonExtractStringField(cmdObj, cmdName, parsedName); + std::string name; + std::string source; + Status status = bsonExtractStringField(obj, nameFieldName, &name); if (!status.isOK()) { return status; } - - BSONElement rolesElement; - status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); + status = bsonExtractStringField(obj, sourceFieldName, &source); if (!status.isOK()) { return status; } - status = parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), - dbname, - parsedRoleNames); - if (!status.isOK()) { - return status; - } + *parsedName = Name(name, source); + } else { + return Status(ErrorCodes::BadValue, + "User and role names must be either strings or objects"); + } + return Status::OK(); +} - if (!parsedRoleNames->size()) { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << cmdName << " command requires a non-empty " - "\"roles\" array"); - } - return Status::OK(); - } - - Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - CreateOrUpdateUserArgs* parsedArgs) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert(cmdName.toString()); - validFieldNames.insert("customData"); - validFieldNames.insert("digestPassword"); - validFieldNames.insert("pwd"); - validFieldNames.insert("roles"); - validFieldNames.insert("writeConcern"); - - Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); +// Extracts UserName or RoleName objects from a BSONArray of role/user names. +template <typename Name> +Status _parseNamesFromBSONArray(const BSONArray& array, + StringData dbname, + StringData nameFieldName, + StringData sourceFieldName, + std::vector<Name>* parsedNames) { + for (BSONObjIterator it(array); it.more(); it.next()) { + BSONElement element = *it; + Name name; + Status status = + _parseNameFromBSONElement(element, dbname, nameFieldName, sourceFieldName, &name); if (!status.isOK()) { return status; } + parsedNames->push_back(name); + } + return Status::OK(); +} + +Status parseUserNamesFromBSONArray(const BSONArray& usersArray, + StringData dbname, + std::vector<UserName>* parsedUserNames) { + return _parseNamesFromBSONArray(usersArray, + dbname, + AuthorizationManager::USER_NAME_FIELD_NAME, + AuthorizationManager::USER_DB_FIELD_NAME, + parsedUserNames); +} + +Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray, + StringData dbname, + std::vector<RoleName>* parsedRoleNames) { + return _parseNamesFromBSONArray(rolesArray, + dbname, + AuthorizationManager::ROLE_NAME_FIELD_NAME, + AuthorizationManager::ROLE_DB_FIELD_NAME, + parsedRoleNames); +} + +Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj, + StringData cmdName, + const std::string& dbname, + std::string* parsedName, + vector<RoleName>* parsedRoleNames, + BSONObj* parsedWriteConcern) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert(cmdName.toString()); + validFieldNames.insert("roles"); + validFieldNames.insert("writeConcern"); + + Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); + if (!status.isOK()) { + return status; + } - status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); - if (!status.isOK()) { - return status; - } + status = _extractWriteConcern(cmdObj, parsedWriteConcern); + if (!status.isOK()) { + return status; + } - BSONObjBuilder userObjBuilder; + status = bsonExtractStringField(cmdObj, cmdName, parsedName); + if (!status.isOK()) { + return status; + } - // Parse user name - std::string userName; - status = bsonExtractStringField(cmdObj, cmdName, &userName); - if (!status.isOK()) { - return status; - } + BSONElement rolesElement; + status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); + if (!status.isOK()) { + return status; + } - parsedArgs->userName = UserName(userName, dbname); - - // Parse password - if (cmdObj.hasField("pwd")) { - std::string password; - status = bsonExtractStringField(cmdObj, "pwd", &password); - if (!status.isOK()) { - return status; - } - if (password.empty()) { - return Status(ErrorCodes::BadValue, "User passwords must not be empty"); - } - - bool digestPassword; // True if the server should digest the password - status = bsonExtractBooleanFieldWithDefault(cmdObj, - "digestPassword", - true, - &digestPassword); - if (!status.isOK()) { - return status; - } - - if (digestPassword) { - parsedArgs->hashedPassword = mongo::createPasswordDigest( - userName, password); - } else { - parsedArgs->hashedPassword = password; - } - parsedArgs->hasHashedPassword = true; - } + status = parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, parsedRoleNames); + if (!status.isOK()) { + return status; + } - // Parse custom data - if (cmdObj.hasField("customData")) { - BSONElement element; - status = bsonExtractTypedField(cmdObj, "customData", Object, &element); - if (!status.isOK()) { - return status; - } - parsedArgs->customData = element.Obj(); - parsedArgs->hasCustomData = true; - } + if (!parsedRoleNames->size()) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << cmdName << " command requires a non-empty " + "\"roles\" array"); + } + return Status::OK(); +} + +Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj, + StringData cmdName, + const std::string& dbname, + CreateOrUpdateUserArgs* parsedArgs) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert(cmdName.toString()); + validFieldNames.insert("customData"); + validFieldNames.insert("digestPassword"); + validFieldNames.insert("pwd"); + validFieldNames.insert("roles"); + validFieldNames.insert("writeConcern"); + + Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); + if (!status.isOK()) { + return status; + } - // Parse roles - if (cmdObj.hasField("roles")) { - BSONElement rolesElement; - status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); - if (!status.isOK()) { - return status; - } - status = parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), - dbname, - &parsedArgs->roles); - if (!status.isOK()) { - return status; - } - parsedArgs->hasRoles = true; - } + status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); + if (!status.isOK()) { + return status; + } + + BSONObjBuilder userObjBuilder; - return Status::OK(); + // Parse user name + std::string userName; + status = bsonExtractStringField(cmdObj, cmdName, &userName); + if (!status.isOK()) { + return status; } - Status parseAndValidateDropUserCommand(const BSONObj& cmdObj, - const std::string& dbname, - UserName* parsedUserName, - BSONObj* parsedWriteConcern) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert("dropUser"); - validFieldNames.insert("writeConcern"); + parsedArgs->userName = UserName(userName, dbname); - Status status = _checkNoExtraFields(cmdObj, "dropUser", validFieldNames); + // Parse password + if (cmdObj.hasField("pwd")) { + std::string password; + status = bsonExtractStringField(cmdObj, "pwd", &password); if (!status.isOK()) { return status; } + if (password.empty()) { + return Status(ErrorCodes::BadValue, "User passwords must not be empty"); + } - std::string user; - status = bsonExtractStringField(cmdObj, "dropUser", &user); + bool digestPassword; // True if the server should digest the password + status = + bsonExtractBooleanFieldWithDefault(cmdObj, "digestPassword", true, &digestPassword); if (!status.isOK()) { return status; } - status = _extractWriteConcern(cmdObj, parsedWriteConcern); + if (digestPassword) { + parsedArgs->hashedPassword = mongo::createPasswordDigest(userName, password); + } else { + parsedArgs->hashedPassword = password; + } + parsedArgs->hasHashedPassword = true; + } + + // Parse custom data + if (cmdObj.hasField("customData")) { + BSONElement element; + status = bsonExtractTypedField(cmdObj, "customData", Object, &element); if (!status.isOK()) { return status; } - - *parsedUserName = UserName(user, dbname); - return Status::OK(); + parsedArgs->customData = element.Obj(); + parsedArgs->hasCustomData = true; } - Status parseFromDatabaseCommand(const BSONObj& cmdObj, - const std::string& dbname, - BSONObj* parsedWriteConcern, - std::string command) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert(command); - validFieldNames.insert("writeConcern"); - - Status status = _checkNoExtraFields(cmdObj, command, validFieldNames); + // Parse roles + if (cmdObj.hasField("roles")) { + BSONElement rolesElement; + status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); if (!status.isOK()) { return status; } - - status = _extractWriteConcern(cmdObj, parsedWriteConcern); + status = + parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, &parsedArgs->roles); if (!status.isOK()) { return status; } + parsedArgs->hasRoles = true; + } + + return Status::OK(); +} + +Status parseAndValidateDropUserCommand(const BSONObj& cmdObj, + const std::string& dbname, + UserName* parsedUserName, + BSONObj* parsedWriteConcern) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert("dropUser"); + validFieldNames.insert("writeConcern"); - return Status::OK(); + Status status = _checkNoExtraFields(cmdObj, "dropUser", validFieldNames); + if (!status.isOK()) { + return status; } - Status parseAndValidateDropAllUsersFromDatabaseCommand(const BSONObj& cmdObj, - const std::string& dbname, - BSONObj* parsedWriteConcern) { - return parseFromDatabaseCommand(cmdObj, dbname, parsedWriteConcern, "dropAllUsersFromDatabase"); + + std::string user; + status = bsonExtractStringField(cmdObj, "dropUser", &user); + if (!status.isOK()) { + return status; } - Status parseUsersInfoCommand(const BSONObj& cmdObj, - StringData dbname, - UsersInfoArgs* parsedArgs) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert("usersInfo"); - validFieldNames.insert("showPrivileges"); - validFieldNames.insert("showCredentials"); + status = _extractWriteConcern(cmdObj, parsedWriteConcern); + if (!status.isOK()) { + return status; + } - Status status = _checkNoExtraFields(cmdObj, "usersInfo", validFieldNames); - if (!status.isOK()) { - return status; - } + *parsedUserName = UserName(user, dbname); + return Status::OK(); +} - if (cmdObj["usersInfo"].numberInt() == 1) { - parsedArgs->allForDB = true; - } else if (cmdObj["usersInfo"].type() == Array) { - status = parseUserNamesFromBSONArray(BSONArray(cmdObj["usersInfo"].Obj()), - dbname, - &parsedArgs->userNames); - if (!status.isOK()) { - return status; - } - } else { - UserName name; - status = _parseNameFromBSONElement(cmdObj["usersInfo"], - dbname, - AuthorizationManager::USER_NAME_FIELD_NAME, - AuthorizationManager::USER_DB_FIELD_NAME, - &name); - if (!status.isOK()) { - return status; - } - parsedArgs->userNames.push_back(name); - } +Status parseFromDatabaseCommand(const BSONObj& cmdObj, + const std::string& dbname, + BSONObj* parsedWriteConcern, + std::string command) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert(command); + validFieldNames.insert("writeConcern"); + + Status status = _checkNoExtraFields(cmdObj, command, validFieldNames); + if (!status.isOK()) { + return status; + } - status = bsonExtractBooleanFieldWithDefault(cmdObj, - "showPrivileges", - false, - &parsedArgs->showPrivileges); + status = _extractWriteConcern(cmdObj, parsedWriteConcern); + if (!status.isOK()) { + return status; + } + + return Status::OK(); +} +Status parseAndValidateDropAllUsersFromDatabaseCommand(const BSONObj& cmdObj, + const std::string& dbname, + BSONObj* parsedWriteConcern) { + return parseFromDatabaseCommand(cmdObj, dbname, parsedWriteConcern, "dropAllUsersFromDatabase"); +} + +Status parseUsersInfoCommand(const BSONObj& cmdObj, StringData dbname, UsersInfoArgs* parsedArgs) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert("usersInfo"); + validFieldNames.insert("showPrivileges"); + validFieldNames.insert("showCredentials"); + + Status status = _checkNoExtraFields(cmdObj, "usersInfo", validFieldNames); + if (!status.isOK()) { + return status; + } + + if (cmdObj["usersInfo"].numberInt() == 1) { + parsedArgs->allForDB = true; + } else if (cmdObj["usersInfo"].type() == Array) { + status = parseUserNamesFromBSONArray( + BSONArray(cmdObj["usersInfo"].Obj()), dbname, &parsedArgs->userNames); if (!status.isOK()) { return status; } - status = bsonExtractBooleanFieldWithDefault(cmdObj, - "showCredentials", - false, - &parsedArgs->showCredentials); + } else { + UserName name; + status = _parseNameFromBSONElement(cmdObj["usersInfo"], + dbname, + AuthorizationManager::USER_NAME_FIELD_NAME, + AuthorizationManager::USER_DB_FIELD_NAME, + &name); if (!status.isOK()) { return status; } + parsedArgs->userNames.push_back(name); + } - return Status::OK(); + status = bsonExtractBooleanFieldWithDefault( + cmdObj, "showPrivileges", false, &parsedArgs->showPrivileges); + if (!status.isOK()) { + return status; + } + status = bsonExtractBooleanFieldWithDefault( + cmdObj, "showCredentials", false, &parsedArgs->showCredentials); + if (!status.isOK()) { + return status; } - Status parseRolesInfoCommand(const BSONObj& cmdObj, - StringData dbname, - RolesInfoArgs* parsedArgs) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert("rolesInfo"); - validFieldNames.insert("showPrivileges"); - validFieldNames.insert("showBuiltinRoles"); + return Status::OK(); +} - Status status = _checkNoExtraFields(cmdObj, "rolesInfo", validFieldNames); - if (!status.isOK()) { - return status; - } +Status parseRolesInfoCommand(const BSONObj& cmdObj, StringData dbname, RolesInfoArgs* parsedArgs) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert("rolesInfo"); + validFieldNames.insert("showPrivileges"); + validFieldNames.insert("showBuiltinRoles"); - if (cmdObj["rolesInfo"].numberInt() == 1) { - parsedArgs->allForDB = true; - } else if (cmdObj["rolesInfo"].type() == Array) { - status = parseRoleNamesFromBSONArray(BSONArray(cmdObj["rolesInfo"].Obj()), - dbname, - &parsedArgs->roleNames); - if (!status.isOK()) { - return status; - } - } else { - RoleName name; - status = _parseNameFromBSONElement(cmdObj["rolesInfo"], - dbname, - AuthorizationManager::ROLE_NAME_FIELD_NAME, - AuthorizationManager::ROLE_DB_FIELD_NAME, - &name); - if (!status.isOK()) { - return status; - } - parsedArgs->roleNames.push_back(name); - } + Status status = _checkNoExtraFields(cmdObj, "rolesInfo", validFieldNames); + if (!status.isOK()) { + return status; + } - status = bsonExtractBooleanFieldWithDefault(cmdObj, - "showPrivileges", - false, - &parsedArgs->showPrivileges); + if (cmdObj["rolesInfo"].numberInt() == 1) { + parsedArgs->allForDB = true; + } else if (cmdObj["rolesInfo"].type() == Array) { + status = parseRoleNamesFromBSONArray( + BSONArray(cmdObj["rolesInfo"].Obj()), dbname, &parsedArgs->roleNames); if (!status.isOK()) { return status; } - - status = bsonExtractBooleanFieldWithDefault(cmdObj, - "showBuiltinRoles", - false, - &parsedArgs->showBuiltinRoles); + } else { + RoleName name; + status = _parseNameFromBSONElement(cmdObj["rolesInfo"], + dbname, + AuthorizationManager::ROLE_NAME_FIELD_NAME, + AuthorizationManager::ROLE_DB_FIELD_NAME, + &name); if (!status.isOK()) { return status; } + parsedArgs->roleNames.push_back(name); + } - return Status::OK(); - } - - /* - * Validates that the given privilege BSONArray is valid. - * If parsedPrivileges is not NULL, adds to it the privileges parsed out of the input BSONArray. - */ - Status parseAndValidatePrivilegeArray(const BSONArray& privileges, - PrivilegeVector* parsedPrivileges) { - for (BSONObjIterator it(privileges); it.more(); it.next()) { - BSONElement element = *it; - if (element.type() != Object) { - return Status(ErrorCodes::FailedToParse, - "Elements in privilege arrays must be objects"); - } - - ParsedPrivilege parsedPrivilege; - std::string errmsg; - if (!parsedPrivilege.parseBSON(element.Obj(), &errmsg)) { - return Status(ErrorCodes::FailedToParse, errmsg); - } - if (!parsedPrivilege.isValid(&errmsg)) { - return Status(ErrorCodes::FailedToParse, errmsg); - } - - Privilege privilege; - if (!ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)) { - return Status(ErrorCodes::FailedToParse, errmsg); - } - - parsedPrivileges->push_back(privilege); - } - return Status::OK(); + status = bsonExtractBooleanFieldWithDefault( + cmdObj, "showPrivileges", false, &parsedArgs->showPrivileges); + if (!status.isOK()) { + return status; } - Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - CreateOrUpdateRoleArgs* parsedArgs) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert(cmdName.toString()); - validFieldNames.insert("privileges"); - validFieldNames.insert("roles"); - validFieldNames.insert("writeConcern"); + status = bsonExtractBooleanFieldWithDefault( + cmdObj, "showBuiltinRoles", false, &parsedArgs->showBuiltinRoles); + if (!status.isOK()) { + return status; + } - Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); - if (!status.isOK()) { - return status; - } + return Status::OK(); +} - status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); - if (!status.isOK()) { - return status; +/* + * Validates that the given privilege BSONArray is valid. + * If parsedPrivileges is not NULL, adds to it the privileges parsed out of the input BSONArray. + */ +Status parseAndValidatePrivilegeArray(const BSONArray& privileges, + PrivilegeVector* parsedPrivileges) { + for (BSONObjIterator it(privileges); it.more(); it.next()) { + BSONElement element = *it; + if (element.type() != Object) { + return Status(ErrorCodes::FailedToParse, + "Elements in privilege arrays must be objects"); } - std::string roleName; - status = bsonExtractStringField(cmdObj, cmdName, &roleName); - if (!status.isOK()) { - return status; + ParsedPrivilege parsedPrivilege; + std::string errmsg; + if (!parsedPrivilege.parseBSON(element.Obj(), &errmsg)) { + return Status(ErrorCodes::FailedToParse, errmsg); } - parsedArgs->roleName = RoleName(roleName, dbname); - - // Parse privileges - if (cmdObj.hasField("privileges")) { - BSONElement privilegesElement; - status = bsonExtractTypedField(cmdObj, "privileges", Array, &privilegesElement); - if (!status.isOK()) { - return status; - } - status = parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), - &parsedArgs->privileges); - if (!status.isOK()) { - return status; - } - parsedArgs->hasPrivileges = true; + if (!parsedPrivilege.isValid(&errmsg)) { + return Status(ErrorCodes::FailedToParse, errmsg); } - // Parse roles - if (cmdObj.hasField("roles")) { - BSONElement rolesElement; - status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); - if (!status.isOK()) { - return status; - } - status = parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), - dbname, - &parsedArgs->roles); - if (!status.isOK()) { - return status; - } - parsedArgs->hasRoles = true; + Privilege privilege; + if (!ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)) { + return Status(ErrorCodes::FailedToParse, errmsg); } - return Status::OK(); - } - - Status parseAndValidateRolePrivilegeManipulationCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - RoleName* parsedRoleName, - PrivilegeVector* parsedPrivileges, - BSONObj* parsedWriteConcern) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert(cmdName.toString()); - validFieldNames.insert("privileges"); - validFieldNames.insert("writeConcern"); - Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); - if (!status.isOK()) { - return status; - } - - status = _extractWriteConcern(cmdObj, parsedWriteConcern); - if (!status.isOK()) { - return status; - } + parsedPrivileges->push_back(privilege); + } + return Status::OK(); +} + +Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj, + StringData cmdName, + const std::string& dbname, + CreateOrUpdateRoleArgs* parsedArgs) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert(cmdName.toString()); + validFieldNames.insert("privileges"); + validFieldNames.insert("roles"); + validFieldNames.insert("writeConcern"); + + Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); + if (!status.isOK()) { + return status; + } - BSONObjBuilder roleObjBuilder; + status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); + if (!status.isOK()) { + return status; + } - // Parse role name - std::string roleName; - status = bsonExtractStringField(cmdObj, cmdName, &roleName); - if (!status.isOK()) { - return status; - } - *parsedRoleName = RoleName(roleName, dbname); + std::string roleName; + status = bsonExtractStringField(cmdObj, cmdName, &roleName); + if (!status.isOK()) { + return status; + } + parsedArgs->roleName = RoleName(roleName, dbname); - // Parse privileges + // Parse privileges + if (cmdObj.hasField("privileges")) { BSONElement privilegesElement; status = bsonExtractTypedField(cmdObj, "privileges", Array, &privilegesElement); if (!status.isOK()) { return status; } status = parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), - parsedPrivileges); + &parsedArgs->privileges); if (!status.isOK()) { return status; } - if (!parsedPrivileges->size()) { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << cmdName << " command requires a non-empty " - "\"privileges\" array"); - } - - return Status::OK(); + parsedArgs->hasPrivileges = true; } - Status parseDropRoleCommand(const BSONObj& cmdObj, - const std::string& dbname, - RoleName* parsedRoleName, - BSONObj* parsedWriteConcern) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert("dropRole"); - validFieldNames.insert("writeConcern"); - - Status status = _checkNoExtraFields(cmdObj, "dropRole", validFieldNames); + // Parse roles + if (cmdObj.hasField("roles")) { + BSONElement rolesElement; + status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); if (!status.isOK()) { return status; } - - std::string user; - status = bsonExtractStringField(cmdObj, "dropRole", &user); + status = + parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, &parsedArgs->roles); if (!status.isOK()) { return status; } + parsedArgs->hasRoles = true; + } + return Status::OK(); +} + +Status parseAndValidateRolePrivilegeManipulationCommands(const BSONObj& cmdObj, + StringData cmdName, + const std::string& dbname, + RoleName* parsedRoleName, + PrivilegeVector* parsedPrivileges, + BSONObj* parsedWriteConcern) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert(cmdName.toString()); + validFieldNames.insert("privileges"); + validFieldNames.insert("writeConcern"); + + Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); + if (!status.isOK()) { + return status; + } - status = _extractWriteConcern(cmdObj, parsedWriteConcern); - if (!status.isOK()) { - return status; - } + status = _extractWriteConcern(cmdObj, parsedWriteConcern); + if (!status.isOK()) { + return status; + } + + BSONObjBuilder roleObjBuilder; - *parsedRoleName = RoleName(user, dbname); - return Status::OK(); + // Parse role name + std::string roleName; + status = bsonExtractStringField(cmdObj, cmdName, &roleName); + if (!status.isOK()) { + return status; } + *parsedRoleName = RoleName(roleName, dbname); - Status parseDropAllRolesFromDatabaseCommand(const BSONObj& cmdObj, - const std::string& dbname, - BSONObj* parsedWriteConcern) { - return parseFromDatabaseCommand(cmdObj, dbname, parsedWriteConcern, "dropAllRolesFromDatabase"); + // Parse privileges + BSONElement privilegesElement; + status = bsonExtractTypedField(cmdObj, "privileges", Array, &privilegesElement); + if (!status.isOK()) { + return status; + } + status = parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), parsedPrivileges); + if (!status.isOK()) { + return status; + } + if (!parsedPrivileges->size()) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << cmdName << " command requires a non-empty " + "\"privileges\" array"); } - Status parseMergeAuthzCollectionsCommand(const BSONObj& cmdObj, - MergeAuthzCollectionsArgs* parsedArgs) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert("_mergeAuthzCollections"); - validFieldNames.insert("tempUsersCollection"); - validFieldNames.insert("tempRolesCollection"); - validFieldNames.insert("db"); - validFieldNames.insert("drop"); - validFieldNames.insert("writeConcern"); + return Status::OK(); +} - Status status = _checkNoExtraFields(cmdObj, "_mergeAuthzCollections", validFieldNames); - if (!status.isOK()) { - return status; - } +Status parseDropRoleCommand(const BSONObj& cmdObj, + const std::string& dbname, + RoleName* parsedRoleName, + BSONObj* parsedWriteConcern) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert("dropRole"); + validFieldNames.insert("writeConcern"); - status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); - if (!status.isOK()) { - return status; - } + Status status = _checkNoExtraFields(cmdObj, "dropRole", validFieldNames); + if (!status.isOK()) { + return status; + } - status = bsonExtractStringFieldWithDefault(cmdObj, - "tempUsersCollection", - "", - &parsedArgs->usersCollName); - if (!status.isOK()) { - return status; - } + std::string user; + status = bsonExtractStringField(cmdObj, "dropRole", &user); + if (!status.isOK()) { + return status; + } - status = bsonExtractStringFieldWithDefault(cmdObj, - "tempRolesCollection", - "", - &parsedArgs->rolesCollName); - if (!status.isOK()) { - return status; - } + status = _extractWriteConcern(cmdObj, parsedWriteConcern); + if (!status.isOK()) { + return status; + } - status = bsonExtractStringField(cmdObj, "db", &parsedArgs->db); - if (!status.isOK()) { - if (status == ErrorCodes::NoSuchKey) { - return Status(ErrorCodes::OutdatedClient, - "Missing \"db\" field for _mergeAuthzCollections command. This is " - "most likely due to running an outdated (pre-2.6.4) version of " - "mongorestore."); - } - return status; - } + *parsedRoleName = RoleName(user, dbname); + return Status::OK(); +} + +Status parseDropAllRolesFromDatabaseCommand(const BSONObj& cmdObj, + const std::string& dbname, + BSONObj* parsedWriteConcern) { + return parseFromDatabaseCommand(cmdObj, dbname, parsedWriteConcern, "dropAllRolesFromDatabase"); +} + +Status parseMergeAuthzCollectionsCommand(const BSONObj& cmdObj, + MergeAuthzCollectionsArgs* parsedArgs) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert("_mergeAuthzCollections"); + validFieldNames.insert("tempUsersCollection"); + validFieldNames.insert("tempRolesCollection"); + validFieldNames.insert("db"); + validFieldNames.insert("drop"); + validFieldNames.insert("writeConcern"); + + Status status = _checkNoExtraFields(cmdObj, "_mergeAuthzCollections", validFieldNames); + if (!status.isOK()) { + return status; + } - status = bsonExtractBooleanFieldWithDefault(cmdObj, - "drop", - false, - &parsedArgs->drop); - if (!status.isOK()) { - return status; + status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); + if (!status.isOK()) { + return status; + } + + status = bsonExtractStringFieldWithDefault( + cmdObj, "tempUsersCollection", "", &parsedArgs->usersCollName); + if (!status.isOK()) { + return status; + } + + status = bsonExtractStringFieldWithDefault( + cmdObj, "tempRolesCollection", "", &parsedArgs->rolesCollName); + if (!status.isOK()) { + return status; + } + + status = bsonExtractStringField(cmdObj, "db", &parsedArgs->db); + if (!status.isOK()) { + if (status == ErrorCodes::NoSuchKey) { + return Status(ErrorCodes::OutdatedClient, + "Missing \"db\" field for _mergeAuthzCollections command. This is " + "most likely due to running an outdated (pre-2.6.4) version of " + "mongorestore."); } + return status; + } - return Status::OK(); + status = bsonExtractBooleanFieldWithDefault(cmdObj, "drop", false, &parsedArgs->drop); + if (!status.isOK()) { + return status; } -} // namespace auth -} // namespace mongo + return Status::OK(); +} + +} // namespace auth +} // namespace mongo diff --git a/src/mongo/db/auth/user_management_commands_parser.h b/src/mongo/db/auth/user_management_commands_parser.h index 68ef26ce894..c55210e8978 100644 --- a/src/mongo/db/auth/user_management_commands_parser.h +++ b/src/mongo/db/auth/user_management_commands_parser.h @@ -43,190 +43,185 @@ namespace mongo { namespace auth { - struct CreateOrUpdateUserArgs { - UserName userName; - bool hasHashedPassword; - std::string hashedPassword; - bool hasCustomData; - BSONObj customData; - bool hasRoles; - std::vector<RoleName> roles; - BSONObj writeConcern; - - CreateOrUpdateUserArgs() : - hasHashedPassword(false), hasCustomData(false), hasRoles(false) {} - }; - - /** - * Takes a command object describing an invocation of the "createUser" or "updateUser" commands - * (which command it is is specified in "cmdName") on the database "dbname", and parses out all - * the arguments into the "parsedArgs" output param. - */ - Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - CreateOrUpdateUserArgs* parsedArgs); - - /** - * Takes a command object describing an invocation of one of "grantRolesToUser", - * "revokeRolesFromUser", "grantDelegateRolesToUser", "revokeDelegateRolesFromUser", - * "grantRolesToRole", and "revokeRolesFromRoles" (which command it is is specified in the - * "cmdName" argument), and parses out (into the parsedName out param) the user/role name of - * the user/roles being modified, the roles being granted or revoked, and the write concern to - * use. - */ - Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - std::string* parsedName, - std::vector<RoleName>* parsedRoleNames, - BSONObj* parsedWriteConcern); - - /** - * Takes a command object describing an invocation of the "dropUser" command and parses out - * the UserName of the user to be removed and the writeConcern. - * Also validates the input and returns a non-ok Status if there is anything wrong. - */ - Status parseAndValidateDropUserCommand(const BSONObj& cmdObj, - const std::string& dbname, - UserName* parsedUserName, - BSONObj* parsedWriteConcern); - - /** - * Takes a command object describing an invocation of the "dropAllUsersFromDatabase" command and - * parses out the write concern. - * Also validates the input and returns a non-ok Status if there is anything wrong. - */ - Status parseAndValidateDropAllUsersFromDatabaseCommand(const BSONObj& cmdObj, - const std::string& dbname, - BSONObj* parsedWriteConcern); - - struct UsersInfoArgs { - std::vector<UserName> userNames; - bool allForDB; - bool showPrivileges; - bool showCredentials; - UsersInfoArgs() : allForDB(false), showPrivileges(false), showCredentials(false) {} - }; - - /** - * Takes a command object describing an invocation of the "usersInfo" command and parses out - * all the arguments into the "parsedArgs" output param. - */ - Status parseUsersInfoCommand(const BSONObj& cmdObj, - StringData dbname, - UsersInfoArgs* parsedArgs); - - struct RolesInfoArgs { - std::vector<RoleName> roleNames; - bool allForDB; - bool showPrivileges; - bool showBuiltinRoles; - RolesInfoArgs() : allForDB(false), showPrivileges(false), showBuiltinRoles(false) {} - }; - - /** - * Takes a command object describing an invocation of the "rolesInfo" command and parses out - * the arguments into the "parsedArgs" output param. - */ - Status parseRolesInfoCommand(const BSONObj& cmdObj, - StringData dbname, - RolesInfoArgs* parsedArgs); - - struct CreateOrUpdateRoleArgs { - RoleName roleName; - bool hasRoles; - std::vector<RoleName> roles; - bool hasPrivileges; - PrivilegeVector privileges; - BSONObj writeConcern; - CreateOrUpdateRoleArgs() : hasRoles(false), hasPrivileges(false) {} - }; - - /** - * Takes a command object describing an invocation of the "createRole" or "updateRole" commands - * (which command it is is specified in "cmdName") on the database "dbname", and parses out all - * the arguments into the "parsedArgs" output param. - */ - Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - CreateOrUpdateRoleArgs* parsedArgs); - - /** - * Takes a command object describing an invocation of the "grantPrivilegesToRole" or - * "revokePrivilegesFromRole" commands, and parses out the role name of the - * role being modified, the privileges being granted or revoked, and the write concern to use. - */ - Status parseAndValidateRolePrivilegeManipulationCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - RoleName* parsedRoleName, - PrivilegeVector* parsedPrivileges, - BSONObj* parsedWriteConcern); - - /** - * Takes a command object describing an invocation of the "dropRole" command and parses out - * the RoleName of the role to be removed and the writeConcern. - */ - Status parseDropRoleCommand(const BSONObj& cmdObj, - const std::string& dbname, - RoleName* parsedRoleName, - BSONObj* parsedWriteConcern); - - /** - * Takes a command object describing an invocation of the "dropAllRolesFromDatabase" command and - * parses out the write concern. - */ - Status parseDropAllRolesFromDatabaseCommand(const BSONObj& cmdObj, - const std::string& dbname, - BSONObj* parsedWriteConcern); - - /** - * Parses the privileges described in "privileges" into a vector of Privilege objects. - * Returns Status::OK() upon successfully parsing all the elements of "privileges". - */ - Status parseAndValidatePrivilegeArray(const BSONArray& privileges, - PrivilegeVector* parsedPrivileges); - - /** - * Takes a BSONArray of name,db pair documents, parses that array and returns (via the - * output param parsedRoleNames) a list of the role names in the input array. - * Performs syntactic validation of "rolesArray", only. - */ - Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray, - StringData dbname, - std::vector<RoleName>* parsedRoleNames); - - /** - * Takes a BSONArray of name,db pair documents, parses that array and returns (via the - * output param parsedUserNames) a list of the usernames in the input array. - * Performs syntactic validation of "usersArray", only. - */ - Status parseUserNamesFromBSONArray(const BSONArray& usersArray, - StringData dbname, - std::vector<UserName>* parsedUserNames); - - struct MergeAuthzCollectionsArgs { - std::string usersCollName; - std::string rolesCollName; - std::string db; - bool drop; - BSONObj writeConcern; - MergeAuthzCollectionsArgs() : drop(false) {} - }; - - /** - * Takes a command object describing an invocation of the "_mergeAuthzCollections" command and - * parses out the name of the temporary collections to use for user and role data, whether or - * not to drop the existing users/roles, the database if this is a for a db-specific restore, - * and the writeConcern. - * Returns ErrorCodes::OutdatedClient if the "db" field is missing, as that likely indicates - * the command was sent by an outdated (pre 2.6.4) version of mongorestore. - * Returns other codes indicating missing or incorrectly typed fields. - */ - Status parseMergeAuthzCollectionsCommand(const BSONObj& cmdObj, - MergeAuthzCollectionsArgs* parsedArgs); - -} // namespace auth -} // namespace mongo +struct CreateOrUpdateUserArgs { + UserName userName; + bool hasHashedPassword; + std::string hashedPassword; + bool hasCustomData; + BSONObj customData; + bool hasRoles; + std::vector<RoleName> roles; + BSONObj writeConcern; + + CreateOrUpdateUserArgs() : hasHashedPassword(false), hasCustomData(false), hasRoles(false) {} +}; + +/** + * Takes a command object describing an invocation of the "createUser" or "updateUser" commands + * (which command it is is specified in "cmdName") on the database "dbname", and parses out all + * the arguments into the "parsedArgs" output param. + */ +Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj, + StringData cmdName, + const std::string& dbname, + CreateOrUpdateUserArgs* parsedArgs); + +/** + * Takes a command object describing an invocation of one of "grantRolesToUser", + * "revokeRolesFromUser", "grantDelegateRolesToUser", "revokeDelegateRolesFromUser", + * "grantRolesToRole", and "revokeRolesFromRoles" (which command it is is specified in the + * "cmdName" argument), and parses out (into the parsedName out param) the user/role name of + * the user/roles being modified, the roles being granted or revoked, and the write concern to + * use. + */ +Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj, + StringData cmdName, + const std::string& dbname, + std::string* parsedName, + std::vector<RoleName>* parsedRoleNames, + BSONObj* parsedWriteConcern); + +/** + * Takes a command object describing an invocation of the "dropUser" command and parses out + * the UserName of the user to be removed and the writeConcern. + * Also validates the input and returns a non-ok Status if there is anything wrong. + */ +Status parseAndValidateDropUserCommand(const BSONObj& cmdObj, + const std::string& dbname, + UserName* parsedUserName, + BSONObj* parsedWriteConcern); + +/** + * Takes a command object describing an invocation of the "dropAllUsersFromDatabase" command and + * parses out the write concern. + * Also validates the input and returns a non-ok Status if there is anything wrong. + */ +Status parseAndValidateDropAllUsersFromDatabaseCommand(const BSONObj& cmdObj, + const std::string& dbname, + BSONObj* parsedWriteConcern); + +struct UsersInfoArgs { + std::vector<UserName> userNames; + bool allForDB; + bool showPrivileges; + bool showCredentials; + UsersInfoArgs() : allForDB(false), showPrivileges(false), showCredentials(false) {} +}; + +/** + * Takes a command object describing an invocation of the "usersInfo" command and parses out + * all the arguments into the "parsedArgs" output param. + */ +Status parseUsersInfoCommand(const BSONObj& cmdObj, StringData dbname, UsersInfoArgs* parsedArgs); + +struct RolesInfoArgs { + std::vector<RoleName> roleNames; + bool allForDB; + bool showPrivileges; + bool showBuiltinRoles; + RolesInfoArgs() : allForDB(false), showPrivileges(false), showBuiltinRoles(false) {} +}; + +/** + * Takes a command object describing an invocation of the "rolesInfo" command and parses out + * the arguments into the "parsedArgs" output param. + */ +Status parseRolesInfoCommand(const BSONObj& cmdObj, StringData dbname, RolesInfoArgs* parsedArgs); + +struct CreateOrUpdateRoleArgs { + RoleName roleName; + bool hasRoles; + std::vector<RoleName> roles; + bool hasPrivileges; + PrivilegeVector privileges; + BSONObj writeConcern; + CreateOrUpdateRoleArgs() : hasRoles(false), hasPrivileges(false) {} +}; + +/** + * Takes a command object describing an invocation of the "createRole" or "updateRole" commands + * (which command it is is specified in "cmdName") on the database "dbname", and parses out all + * the arguments into the "parsedArgs" output param. + */ +Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj, + StringData cmdName, + const std::string& dbname, + CreateOrUpdateRoleArgs* parsedArgs); + +/** + * Takes a command object describing an invocation of the "grantPrivilegesToRole" or + * "revokePrivilegesFromRole" commands, and parses out the role name of the + * role being modified, the privileges being granted or revoked, and the write concern to use. + */ +Status parseAndValidateRolePrivilegeManipulationCommands(const BSONObj& cmdObj, + StringData cmdName, + const std::string& dbname, + RoleName* parsedRoleName, + PrivilegeVector* parsedPrivileges, + BSONObj* parsedWriteConcern); + +/** + * Takes a command object describing an invocation of the "dropRole" command and parses out + * the RoleName of the role to be removed and the writeConcern. + */ +Status parseDropRoleCommand(const BSONObj& cmdObj, + const std::string& dbname, + RoleName* parsedRoleName, + BSONObj* parsedWriteConcern); + +/** + * Takes a command object describing an invocation of the "dropAllRolesFromDatabase" command and + * parses out the write concern. + */ +Status parseDropAllRolesFromDatabaseCommand(const BSONObj& cmdObj, + const std::string& dbname, + BSONObj* parsedWriteConcern); + +/** + * Parses the privileges described in "privileges" into a vector of Privilege objects. + * Returns Status::OK() upon successfully parsing all the elements of "privileges". + */ +Status parseAndValidatePrivilegeArray(const BSONArray& privileges, + PrivilegeVector* parsedPrivileges); + +/** + * Takes a BSONArray of name,db pair documents, parses that array and returns (via the + * output param parsedRoleNames) a list of the role names in the input array. + * Performs syntactic validation of "rolesArray", only. + */ +Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray, + StringData dbname, + std::vector<RoleName>* parsedRoleNames); + +/** + * Takes a BSONArray of name,db pair documents, parses that array and returns (via the + * output param parsedUserNames) a list of the usernames in the input array. + * Performs syntactic validation of "usersArray", only. + */ +Status parseUserNamesFromBSONArray(const BSONArray& usersArray, + StringData dbname, + std::vector<UserName>* parsedUserNames); + +struct MergeAuthzCollectionsArgs { + std::string usersCollName; + std::string rolesCollName; + std::string db; + bool drop; + BSONObj writeConcern; + MergeAuthzCollectionsArgs() : drop(false) {} +}; + +/** + * Takes a command object describing an invocation of the "_mergeAuthzCollections" command and + * parses out the name of the temporary collections to use for user and role data, whether or + * not to drop the existing users/roles, the database if this is a for a db-specific restore, + * and the writeConcern. + * Returns ErrorCodes::OutdatedClient if the "db" field is missing, as that likely indicates + * the command was sent by an outdated (pre 2.6.4) version of mongorestore. + * Returns other codes indicating missing or incorrectly typed fields. + */ +Status parseMergeAuthzCollectionsCommand(const BSONObj& cmdObj, + MergeAuthzCollectionsArgs* parsedArgs); + +} // namespace auth +} // namespace mongo diff --git a/src/mongo/db/auth/user_name.cpp b/src/mongo/db/auth/user_name.cpp index 422f415d478..c2e768edf95 100644 --- a/src/mongo/db/auth/user_name.cpp +++ b/src/mongo/db/auth/user_name.cpp @@ -35,20 +35,19 @@ namespace mongo { - UserName::UserName(StringData user, StringData dbname) { - _fullName.resize(user.size() + dbname.size() + 1); - std::string::iterator iter = std::copy(user.rawData(), - user.rawData() + user.size(), - _fullName.begin()); - *iter = '@'; - ++iter; - iter = std::copy(dbname.rawData(), dbname.rawData() + dbname.size(), iter); - dassert(iter == _fullName.end()); - _splitPoint = user.size(); - } +UserName::UserName(StringData user, StringData dbname) { + _fullName.resize(user.size() + dbname.size() + 1); + std::string::iterator iter = + std::copy(user.rawData(), user.rawData() + user.size(), _fullName.begin()); + *iter = '@'; + ++iter; + iter = std::copy(dbname.rawData(), dbname.rawData() + dbname.size(), iter); + dassert(iter == _fullName.end()); + _splitPoint = user.size(); +} - std::ostream& operator<<(std::ostream& os, const UserName& name) { - return os << name.getFullName(); - } +std::ostream& operator<<(std::ostream& os, const UserName& name) { + return os << name.getFullName(); +} } // namespace mongo diff --git a/src/mongo/db/auth/user_name.h b/src/mongo/db/auth/user_name.h index 55220f33e10..03f39fa42d6 100644 --- a/src/mongo/db/auth/user_name.h +++ b/src/mongo/db/auth/user_name.h @@ -37,126 +37,153 @@ namespace mongo { +/** + * Representation of a name of a principal (authenticatable user) in a MongoDB system. + * + * Consists of a "user name" part, and a "database name" part. + */ +class UserName { +public: + UserName() : _splitPoint(0) {} + UserName(StringData user, StringData dbname); + /** - * Representation of a name of a principal (authenticatable user) in a MongoDB system. - * - * Consists of a "user name" part, and a "database name" part. + * Gets the user part of a UserName. */ - class UserName { - public: - UserName() : _splitPoint(0) {} - UserName(StringData user, StringData dbname); - - /** - * Gets the user part of a UserName. - */ - StringData getUser() const { return StringData(_fullName).substr(0, _splitPoint); } - - /** - * Gets the database name part of a UserName. - */ - StringData getDB() const { return StringData(_fullName).substr(_splitPoint + 1); } - - /** - * Gets the full unique name of a user as a string, formatted as "user@db". - */ - const std::string& getFullName() const { return _fullName; } - - /** - * Stringifies the object, for logging/debugging. - */ - std::string toString() const { return getFullName(); } - - private: - std::string _fullName; // The full name, stored as a string. "user@db". - size_t _splitPoint; // The index of the "@" separating the user and db name parts. - }; - - static inline bool operator==(const UserName& lhs, const UserName& rhs) { - return lhs.getFullName() == rhs.getFullName(); + StringData getUser() const { + return StringData(_fullName).substr(0, _splitPoint); } - static inline bool operator!=(const UserName& lhs, const UserName& rhs) { - return lhs.getFullName() != rhs.getFullName(); + /** + * Gets the database name part of a UserName. + */ + StringData getDB() const { + return StringData(_fullName).substr(_splitPoint + 1); } - static inline bool operator<(const UserName& lhs, const UserName& rhs) { - return lhs.getFullName() < rhs.getFullName(); + /** + * Gets the full unique name of a user as a string, formatted as "user@db". + */ + const std::string& getFullName() const { + return _fullName; } - std::ostream& operator<<(std::ostream& os, const UserName& name); - /** - * Iterator over an unspecified container of UserName objects. + * Stringifies the object, for logging/debugging. */ - class UserNameIterator { - public: - class Impl { - MONGO_DISALLOW_COPYING(Impl); - public: - Impl() {}; - virtual ~Impl() {}; - static Impl* clone(Impl* orig) { return orig ? orig->doClone(): NULL; } - virtual bool more() const = 0; - virtual const UserName& get() const = 0; - - virtual const UserName& next() = 0; - - private: - virtual Impl* doClone() const = 0; - }; - - UserNameIterator() : _impl(nullptr) {} - UserNameIterator(const UserNameIterator& other) : _impl(Impl::clone(other._impl.get())) {} - explicit UserNameIterator(Impl* impl) : _impl(impl) {} - - UserNameIterator& operator=(const UserNameIterator& other) { - _impl.reset(Impl::clone(other._impl.get())); - return *this; - } + std::string toString() const { + return getFullName(); + } - bool more() const { return _impl.get() && _impl->more(); } - const UserName& get() const { return _impl->get(); } +private: + std::string _fullName; // The full name, stored as a string. "user@db". + size_t _splitPoint; // The index of the "@" separating the user and db name parts. +}; - const UserName& next() { return _impl->next(); } +static inline bool operator==(const UserName& lhs, const UserName& rhs) { + return lhs.getFullName() == rhs.getFullName(); +} - const UserName& operator*() const { return get(); } - const UserName* operator->() const { return &get(); } +static inline bool operator!=(const UserName& lhs, const UserName& rhs) { + return lhs.getFullName() != rhs.getFullName(); +} - private: - std::unique_ptr<Impl> _impl; - }; +static inline bool operator<(const UserName& lhs, const UserName& rhs) { + return lhs.getFullName() < rhs.getFullName(); +} + +std::ostream& operator<<(std::ostream& os, const UserName& name); +/** + * Iterator over an unspecified container of UserName objects. + */ +class UserNameIterator { +public: + class Impl { + MONGO_DISALLOW_COPYING(Impl); - template <typename ContainerIterator> - class UserNameContainerIteratorImpl : public UserNameIterator::Impl { - MONGO_DISALLOW_COPYING(UserNameContainerIteratorImpl); public: - UserNameContainerIteratorImpl(const ContainerIterator& begin, - const ContainerIterator& end) : - _curr(begin), _end(end) {} - virtual ~UserNameContainerIteratorImpl() {} - virtual bool more() const { return _curr != _end; } - virtual const UserName& next() { return *(_curr++); } - virtual const UserName& get() const { return *_curr; } - virtual UserNameIterator::Impl* doClone() const { - return new UserNameContainerIteratorImpl(_curr, _end); + Impl(){}; + virtual ~Impl(){}; + static Impl* clone(Impl* orig) { + return orig ? orig->doClone() : NULL; } + virtual bool more() const = 0; + virtual const UserName& get() const = 0; + + virtual const UserName& next() = 0; private: - ContainerIterator _curr; - ContainerIterator _end; + virtual Impl* doClone() const = 0; }; - template <typename ContainerIterator> - UserNameIterator makeUserNameIterator(const ContainerIterator& begin, - const ContainerIterator& end) { - return UserNameIterator( new UserNameContainerIteratorImpl<ContainerIterator>(begin, end)); + UserNameIterator() : _impl(nullptr) {} + UserNameIterator(const UserNameIterator& other) : _impl(Impl::clone(other._impl.get())) {} + explicit UserNameIterator(Impl* impl) : _impl(impl) {} + + UserNameIterator& operator=(const UserNameIterator& other) { + _impl.reset(Impl::clone(other._impl.get())); + return *this; + } + + bool more() const { + return _impl.get() && _impl->more(); + } + const UserName& get() const { + return _impl->get(); + } + + const UserName& next() { + return _impl->next(); } - template <typename Container> - UserNameIterator makeUserNameIteratorForContainer(const Container& container) { - return makeUserNameIterator(container.begin(), container.end()); + const UserName& operator*() const { + return get(); } + const UserName* operator->() const { + return &get(); + } + +private: + std::unique_ptr<Impl> _impl; +}; + + +template <typename ContainerIterator> +class UserNameContainerIteratorImpl : public UserNameIterator::Impl { + MONGO_DISALLOW_COPYING(UserNameContainerIteratorImpl); + +public: + UserNameContainerIteratorImpl(const ContainerIterator& begin, const ContainerIterator& end) + : _curr(begin), _end(end) {} + virtual ~UserNameContainerIteratorImpl() {} + virtual bool more() const { + return _curr != _end; + } + virtual const UserName& next() { + return *(_curr++); + } + virtual const UserName& get() const { + return *_curr; + } + virtual UserNameIterator::Impl* doClone() const { + return new UserNameContainerIteratorImpl(_curr, _end); + } + +private: + ContainerIterator _curr; + ContainerIterator _end; +}; + +template <typename ContainerIterator> +UserNameIterator makeUserNameIterator(const ContainerIterator& begin, + const ContainerIterator& end) { + return UserNameIterator(new UserNameContainerIteratorImpl<ContainerIterator>(begin, end)); +} + +template <typename Container> +UserNameIterator makeUserNameIteratorForContainer(const Container& container) { + return makeUserNameIterator(container.begin(), container.end()); +} } // namespace mongo diff --git a/src/mongo/db/auth/user_name_hash.h b/src/mongo/db/auth/user_name_hash.h index f60a897f590..6f38d9ccfda 100644 --- a/src/mongo/db/auth/user_name_hash.h +++ b/src/mongo/db/auth/user_name_hash.h @@ -35,9 +35,10 @@ // Define hash function for UserNames so they can be keys in std::unordered_map MONGO_HASH_NAMESPACE_START - template <> struct hash<mongo::UserName> { - size_t operator()(const mongo::UserName& pname) const { - return hash<std::string>()(pname.getFullName()); - } - }; +template <> +struct hash<mongo::UserName> { + size_t operator()(const mongo::UserName& pname) const { + return hash<std::string>()(pname.getFullName()); + } +}; MONGO_HASH_NAMESPACE_END diff --git a/src/mongo/db/auth/user_set.cpp b/src/mongo/db/auth/user_set.cpp index c81869a8ad5..2616b5cc697 100644 --- a/src/mongo/db/auth/user_set.cpp +++ b/src/mongo/db/auth/user_set.cpp @@ -36,94 +36,99 @@ namespace mongo { namespace { - class UserSetNameIteratorImpl : public UserNameIterator::Impl { - MONGO_DISALLOW_COPYING(UserSetNameIteratorImpl); - public: - UserSetNameIteratorImpl(const UserSet::iterator& begin, - const UserSet::iterator& end) : - _curr(begin), _end(end) {} - virtual ~UserSetNameIteratorImpl() {} - virtual bool more() const { return _curr != _end; } - virtual const UserName& next() { return (*(_curr++))->getName(); } - virtual const UserName& get() const { return (*_curr)->getName(); } - virtual UserNameIterator::Impl* doClone() const { - return new UserSetNameIteratorImpl(_curr, _end); - } +class UserSetNameIteratorImpl : public UserNameIterator::Impl { + MONGO_DISALLOW_COPYING(UserSetNameIteratorImpl); - private: - UserSet::iterator _curr; - UserSet::iterator _end; - }; -} // namespace +public: + UserSetNameIteratorImpl(const UserSet::iterator& begin, const UserSet::iterator& end) + : _curr(begin), _end(end) {} + virtual ~UserSetNameIteratorImpl() {} + virtual bool more() const { + return _curr != _end; + } + virtual const UserName& next() { + return (*(_curr++))->getName(); + } + virtual const UserName& get() const { + return (*_curr)->getName(); + } + virtual UserNameIterator::Impl* doClone() const { + return new UserSetNameIteratorImpl(_curr, _end); + } - UserSet::UserSet() : _users(), _usersEnd(_users.end()) {} - UserSet::~UserSet() {} +private: + UserSet::iterator _curr; + UserSet::iterator _end; +}; +} // namespace - User* UserSet::add(User* user) { - for (mutable_iterator it = mbegin(); it != mend(); ++it) { - User* current = *it; - if (current->getName().getDB() == user->getName().getDB()) { - // There can be only one user per database. - *it = user; - return current; - } - } - if (_usersEnd == _users.end()) { - _users.push_back(user); - _usersEnd = _users.end(); - } - else { - *_usersEnd = user; - ++_usersEnd; +UserSet::UserSet() : _users(), _usersEnd(_users.end()) {} +UserSet::~UserSet() {} + +User* UserSet::add(User* user) { + for (mutable_iterator it = mbegin(); it != mend(); ++it) { + User* current = *it; + if (current->getName().getDB() == user->getName().getDB()) { + // There can be only one user per database. + *it = user; + return current; } - return NULL; } + if (_usersEnd == _users.end()) { + _users.push_back(user); + _usersEnd = _users.end(); + } else { + *_usersEnd = user; + ++_usersEnd; + } + return NULL; +} - User* UserSet::removeByDBName(StringData dbname) { - for (iterator it = begin(); it != end(); ++it) { - User* current = *it; - if (current->getName().getDB() == dbname) { - return removeAt(it); - } +User* UserSet::removeByDBName(StringData dbname) { + for (iterator it = begin(); it != end(); ++it) { + User* current = *it; + if (current->getName().getDB() == dbname) { + return removeAt(it); } - return NULL; } + return NULL; +} - User* UserSet::replaceAt(iterator it, User* replacement) { - size_t offset = it - begin(); - User* old = _users[offset]; - _users[offset] = replacement; - return old; - } +User* UserSet::replaceAt(iterator it, User* replacement) { + size_t offset = it - begin(); + User* old = _users[offset]; + _users[offset] = replacement; + return old; +} - User* UserSet::removeAt(iterator it) { - size_t offset = it - begin(); - User* old = _users[offset]; - --_usersEnd; - _users[offset] = *_usersEnd; - *_usersEnd = NULL; - return old; - } +User* UserSet::removeAt(iterator it) { + size_t offset = it - begin(); + User* old = _users[offset]; + --_usersEnd; + _users[offset] = *_usersEnd; + *_usersEnd = NULL; + return old; +} - User* UserSet::lookup(const UserName& name) const { - User* user = lookupByDBName(name.getDB()); - if (user && user->getName() == name) { - return user; - } - return NULL; +User* UserSet::lookup(const UserName& name) const { + User* user = lookupByDBName(name.getDB()); + if (user && user->getName() == name) { + return user; } + return NULL; +} - User* UserSet::lookupByDBName(StringData dbname) const { - for (iterator it = begin(); it != end(); ++it) { - User* current = *it; - if (current->getName().getDB() == dbname) { - return current; - } +User* UserSet::lookupByDBName(StringData dbname) const { + for (iterator it = begin(); it != end(); ++it) { + User* current = *it; + if (current->getName().getDB() == dbname) { + return current; } - return NULL; } + return NULL; +} - UserNameIterator UserSet::getNames() const { - return UserNameIterator(new UserSetNameIteratorImpl(begin(), end())); - } -} // namespace mongo +UserNameIterator UserSet::getNames() const { + return UserNameIterator(new UserSetNameIteratorImpl(begin(), end())); +} +} // namespace mongo diff --git a/src/mongo/db/auth/user_set.h b/src/mongo/db/auth/user_set.h index 8d2bc630d2e..9ad0ab03202 100644 --- a/src/mongo/db/auth/user_set.h +++ b/src/mongo/db/auth/user_set.h @@ -39,81 +39,90 @@ namespace mongo { +/** + * A collection of authenticated users. + * This class does not do any locking/synchronization, the consumer will be responsible for + * synchronizing access. + */ +class UserSet { + MONGO_DISALLOW_COPYING(UserSet); + +public: + typedef std::vector<User*>::const_iterator iterator; + + UserSet(); + ~UserSet(); + + /** + * Adds a User to the UserSet. + * + * The UserSet does not take ownership of the User. + * + * As there can only be one user per database in the UserSet, if a User already exists for + * the new User's database, the old user will be removed from the set and returned. It is + * the caller's responsibility to then release that user. If no user already exists for the + * new user's database, returns NULL. + * + * Invalidates any outstanding iterators or NameIterators. + */ + User* add(User* user); + + /** + * Replaces the user at "it" with "replacement." Does not take ownership of the User. + * Returns a pointer to the old user referenced by "it". Does _not_ invalidate "iterator" + * instances. + */ + User* replaceAt(iterator it, User* replacement); + + /** + * Removes the user at "it", and returns a pointer to it. After this call, "it" remains + * valid. It will either equal "end()", or refer to some user between the values of "it" + * and "end()" before this call was made. + */ + User* removeAt(iterator it); + /** - * A collection of authenticated users. - * This class does not do any locking/synchronization, the consumer will be responsible for - * synchronizing access. + * Removes the User whose authentication credentials came from dbname, and returns that + * user. It is the caller's responsibility to then release that user back to the + * authorizationManger. If no user exists for the given database, returns NULL; */ - class UserSet { - MONGO_DISALLOW_COPYING(UserSet); - public: - typedef std::vector<User*>::const_iterator iterator; - - UserSet(); - ~UserSet(); - - /** - * Adds a User to the UserSet. - * - * The UserSet does not take ownership of the User. - * - * As there can only be one user per database in the UserSet, if a User already exists for - * the new User's database, the old user will be removed from the set and returned. It is - * the caller's responsibility to then release that user. If no user already exists for the - * new user's database, returns NULL. - * - * Invalidates any outstanding iterators or NameIterators. - */ - User* add(User* user); - - /** - * Replaces the user at "it" with "replacement." Does not take ownership of the User. - * Returns a pointer to the old user referenced by "it". Does _not_ invalidate "iterator" - * instances. - */ - User* replaceAt(iterator it, User* replacement); - - /** - * Removes the user at "it", and returns a pointer to it. After this call, "it" remains - * valid. It will either equal "end()", or refer to some user between the values of "it" - * and "end()" before this call was made. - */ - User* removeAt(iterator it); - - /** - * Removes the User whose authentication credentials came from dbname, and returns that - * user. It is the caller's responsibility to then release that user back to the - * authorizationManger. If no user exists for the given database, returns NULL; - */ - User* removeByDBName(StringData dbname); - - // Returns the User with the given name, or NULL if not found. - // Ownership of the returned User remains with the UserSet. The pointer - // returned is only guaranteed to remain valid until the next non-const method is called - // on the UserSet. - User* lookup(const UserName& name) const; - - // Gets the user whose authentication credentials came from dbname, or NULL if none - // exist. There should be at most one such user. - User* lookupByDBName(StringData dbname) const; - - // Gets an iterator over the names of the users stored in the set. The iterator is - // valid until the next non-const method is called on the UserSet. - UserNameIterator getNames() const; - - iterator begin() const { return _users.begin(); } - iterator end() const { return _usersEnd; } - - private: - typedef std::vector<User*>::iterator mutable_iterator; - - mutable_iterator mbegin() { return _users.begin(); } - mutable_iterator mend() { return _usersEnd; } - - // The UserSet maintains ownership of the Users in it, and is responsible for - // returning them to the AuthorizationManager when done with them. - std::vector<User*> _users; - std::vector<User*>::iterator _usersEnd; - }; - -} // namespace mongo + User* removeByDBName(StringData dbname); + + // Returns the User with the given name, or NULL if not found. + // Ownership of the returned User remains with the UserSet. The pointer + // returned is only guaranteed to remain valid until the next non-const method is called + // on the UserSet. + User* lookup(const UserName& name) const; + + // Gets the user whose authentication credentials came from dbname, or NULL if none + // exist. There should be at most one such user. + User* lookupByDBName(StringData dbname) const; + + // Gets an iterator over the names of the users stored in the set. The iterator is + // valid until the next non-const method is called on the UserSet. + UserNameIterator getNames() const; + + iterator begin() const { + return _users.begin(); + } + iterator end() const { + return _usersEnd; + } + +private: + typedef std::vector<User*>::iterator mutable_iterator; + + mutable_iterator mbegin() { + return _users.begin(); + } + mutable_iterator mend() { + return _usersEnd; + } + + // The UserSet maintains ownership of the Users in it, and is responsible for + // returning them to the AuthorizationManager when done with them. + std::vector<User*> _users; + std::vector<User*>::iterator _usersEnd; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/user_set_test.cpp b/src/mongo/db/auth/user_set_test.cpp index c1f036ae289..d8c4290d7c8 100644 --- a/src/mongo/db/auth/user_set_test.cpp +++ b/src/mongo/db/auth/user_set_test.cpp @@ -39,78 +39,78 @@ namespace mongo { namespace { - TEST(UserSetTest, BasicTest) { - UserSet set; - - User* p1 = new User(UserName("Bob", "test")); - User* p2 = new User(UserName("George", "test")); - User* p3 = new User(UserName("Bob", "test2")); - - const std::unique_ptr<User> delp1(p1); - const std::unique_ptr<User> delp2(p2); - const std::unique_ptr<User> delp3(p3); - - ASSERT_NULL(set.lookup(UserName("Bob", "test"))); - ASSERT_NULL(set.lookup(UserName("George", "test"))); - ASSERT_NULL(set.lookup(UserName("Bob", "test2"))); - ASSERT_NULL(set.lookupByDBName("test")); - ASSERT_NULL(set.lookupByDBName("test2")); - - ASSERT_NULL(set.add(p1)); - - ASSERT_EQUALS(p1, set.lookup(UserName("Bob", "test"))); - ASSERT_EQUALS(p1, set.lookupByDBName("test")); - ASSERT_NULL(set.lookup(UserName("George", "test"))); - ASSERT_NULL(set.lookup(UserName("Bob", "test2"))); - ASSERT_NULL(set.lookupByDBName("test2")); - - // This should not replace the existing user "Bob" because they are different databases - ASSERT_NULL(set.add(p3)); - - ASSERT_EQUALS(p1, set.lookup(UserName("Bob", "test"))); - ASSERT_EQUALS(p1, set.lookupByDBName("test")); - ASSERT_NULL(set.lookup(UserName("George", "test"))); - ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); - ASSERT_EQUALS(p3, set.lookupByDBName("test2")); - - User* replaced = set.add(p2); // This should replace Bob since they're on the same database - - ASSERT_EQUALS(replaced, p1); - ASSERT_NULL(set.lookup(UserName("Bob", "test"))); - ASSERT_EQUALS(p2, set.lookup(UserName("George", "test"))); - ASSERT_EQUALS(p2, set.lookupByDBName("test")); - ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); - ASSERT_EQUALS(p3, set.lookupByDBName("test2")); - - User* removed = set.removeByDBName("test"); - - ASSERT_EQUALS(removed, p2); - ASSERT_NULL(set.lookup(UserName("Bob", "test"))); - ASSERT_NULL(set.lookup(UserName("George", "test"))); - ASSERT_NULL(set.lookupByDBName("test")); - ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); - ASSERT_EQUALS(p3, set.lookupByDBName("test2")); - - UserNameIterator iter = set.getNames(); - ASSERT_TRUE(iter.more()); - ASSERT_EQUALS(iter.next(), UserName("Bob", "test2")); - ASSERT_FALSE(iter.more()); - } - - TEST(UserSetTest, IterateNames) { - UserSet pset; - UserNameIterator iter = pset.getNames(); - ASSERT(!iter.more()); - - std::unique_ptr<User> user(new User(UserName("bob", "test"))); - ASSERT_NULL(pset.add(user.get())); - - iter = pset.getNames(); - ASSERT(iter.more()); - ASSERT_EQUALS(*iter, UserName("bob", "test")); - ASSERT_EQUALS(iter.next(), UserName("bob", "test")); - ASSERT(!iter.more()); - } +TEST(UserSetTest, BasicTest) { + UserSet set; + + User* p1 = new User(UserName("Bob", "test")); + User* p2 = new User(UserName("George", "test")); + User* p3 = new User(UserName("Bob", "test2")); + + const std::unique_ptr<User> delp1(p1); + const std::unique_ptr<User> delp2(p2); + const std::unique_ptr<User> delp3(p3); + + ASSERT_NULL(set.lookup(UserName("Bob", "test"))); + ASSERT_NULL(set.lookup(UserName("George", "test"))); + ASSERT_NULL(set.lookup(UserName("Bob", "test2"))); + ASSERT_NULL(set.lookupByDBName("test")); + ASSERT_NULL(set.lookupByDBName("test2")); + + ASSERT_NULL(set.add(p1)); + + ASSERT_EQUALS(p1, set.lookup(UserName("Bob", "test"))); + ASSERT_EQUALS(p1, set.lookupByDBName("test")); + ASSERT_NULL(set.lookup(UserName("George", "test"))); + ASSERT_NULL(set.lookup(UserName("Bob", "test2"))); + ASSERT_NULL(set.lookupByDBName("test2")); + + // This should not replace the existing user "Bob" because they are different databases + ASSERT_NULL(set.add(p3)); + + ASSERT_EQUALS(p1, set.lookup(UserName("Bob", "test"))); + ASSERT_EQUALS(p1, set.lookupByDBName("test")); + ASSERT_NULL(set.lookup(UserName("George", "test"))); + ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); + ASSERT_EQUALS(p3, set.lookupByDBName("test2")); + + User* replaced = set.add(p2); // This should replace Bob since they're on the same database + + ASSERT_EQUALS(replaced, p1); + ASSERT_NULL(set.lookup(UserName("Bob", "test"))); + ASSERT_EQUALS(p2, set.lookup(UserName("George", "test"))); + ASSERT_EQUALS(p2, set.lookupByDBName("test")); + ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); + ASSERT_EQUALS(p3, set.lookupByDBName("test2")); + + User* removed = set.removeByDBName("test"); + + ASSERT_EQUALS(removed, p2); + ASSERT_NULL(set.lookup(UserName("Bob", "test"))); + ASSERT_NULL(set.lookup(UserName("George", "test"))); + ASSERT_NULL(set.lookupByDBName("test")); + ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); + ASSERT_EQUALS(p3, set.lookupByDBName("test2")); + + UserNameIterator iter = set.getNames(); + ASSERT_TRUE(iter.more()); + ASSERT_EQUALS(iter.next(), UserName("Bob", "test2")); + ASSERT_FALSE(iter.more()); +} + +TEST(UserSetTest, IterateNames) { + UserSet pset; + UserNameIterator iter = pset.getNames(); + ASSERT(!iter.more()); + + std::unique_ptr<User> user(new User(UserName("bob", "test"))); + ASSERT_NULL(pset.add(user.get())); + + iter = pset.getNames(); + ASSERT(iter.more()); + ASSERT_EQUALS(*iter, UserName("bob", "test")); + ASSERT_EQUALS(iter.next(), UserName("bob", "test")); + ASSERT(!iter.more()); +} } // namespace } // namespace mongo |