/* * * Copyright (c) 2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include "qpid/acl/AclData.h" #include "qpid/log/Statement.h" #include "qpid/sys/IntegerTypes.h" #include namespace qpid { namespace acl { // // Instantiate the keyword strings // const std::string AclData::ACL_KEYWORD_USER_SUBST = "${user}"; const std::string AclData::ACL_KEYWORD_DOMAIN_SUBST = "${domain}"; const std::string AclData::ACL_KEYWORD_USERDOMAIN_SUBST = "${userdomain}"; const std::string AclData::ACL_KEYWORD_ALL = "all"; const std::string AclData::ACL_KEYWORD_ACL = "acl"; const std::string AclData::ACL_KEYWORD_GROUP = "group"; const std::string AclData::ACL_KEYWORD_QUOTA = "quota"; const std::string AclData::ACL_KEYWORD_QUOTA_CONNECTIONS = "connections"; const char AclData::ACL_SYMBOL_WILDCARD = '*'; const std::string AclData::ACL_KEYWORD_WILDCARD = "*"; const char AclData::ACL_SYMBOL_LINE_CONTINUATION = '\\'; // // constructor // AclData::AclData(): decisionMode(qpid::acl::DENY), transferAcl(false), aclSource("UNKNOWN"), connQuotaRulesExist(false), connQuotaRuleSettings(new quotaRuleSet) { for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++) { actionList[cnt]=0; } } // // clear // void AclData::clear () { for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++) { if (actionList[cnt]) { for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++) delete actionList[cnt][cnt1]; } delete[] actionList[cnt]; } transferAcl = false; connQuotaRulesExist = false; connQuotaRuleSettings->clear(); } // // matchProp // // Compare a rule's property name with a lookup name, // The rule's name may contain a trailing '*' to specify a wildcard match. // bool AclData::matchProp(const std::string& ruleStr, const std::string& lookupStr) { // allow wildcard on the end of rule strings... if (ruleStr.data()[ruleStr.size()-1]==ACL_SYMBOL_WILDCARD) { return ruleStr.compare(0, ruleStr.size()-1, lookupStr, 0, ruleStr.size()-1 ) == 0; } else { return ruleStr.compare(lookupStr) == 0; } } // // lookup // // The ACL main business logic function of matching rules and declaring // an allow or deny result. // AclResult AclData::lookup( const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map* params) { QPID_LOG(debug, "ACL: Lookup for id:" << id << " action:" << AclHelper::getActionStr((Action) action) << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType) << " name:" << name << " with params " << AclHelper::propertyMapToString(params)); // A typical log looks like: // ACL: Lookup for id:bob@QPID action:create objectType:queue name:q2 // with params { durable=false passive=false autodelete=false // exclusive=false alternate= policytype= maxqueuesize=0 // maxqueuecount=0 } // Default result is blanket decision mode for the entire ACL list. AclResult aclresult = decisionMode; // Test for lists of rules at the intersection of the Action & Object if (actionList[action] && actionList[action][objType]) { // Find the list of rules for this actorId AclData::actObjItr itrRule = actionList[action][objType]->find(id); // If individual actorId not found then find a rule set for '*'. if (itrRule == actionList[action][objType]->end()) itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD); if (itrRule != actionList[action][objType]->end()) { // A list of rules exists for this actor/action/object tuple. // Iterate the rule set to search for a matching rule. ruleSetItr rsItr = itrRule->second.end(); for (int cnt = itrRule->second.size(); cnt != 0; cnt--) { rsItr--; QPID_LOG(debug, "ACL: checking rule " << rsItr->toString()); bool match = true; bool limitChecked = true; // Iterate this rule's properties. A 'match' is true when // all of the rule's properties are found to be satisfied // in the lookup param list. The lookup may specify things // (they usually do) that are not in the rule properties but // these things don't interfere with the rule match. for (specPropertyMapItr rulePropMapItr = rsItr->props.begin(); (rulePropMapItr != rsItr->props.end()) && match; rulePropMapItr++) { // The rule property map's NAME property is given in // the calling args and not in the param map. if (rulePropMapItr->first == acl::SPECPROP_NAME) { // substitute user name into object name bool result; if (rsItr->ruleHasUserSub[PROP_NAME]) { std::string sName(rulePropMapItr->second); substituteUserId(sName, id); result = matchProp(sName, name); } else { result = matchProp(rulePropMapItr->second, name); } if (result) { QPID_LOG(debug, "ACL: lookup name '" << name << "' matched with rule name '" << rulePropMapItr->second << "'"); } else { match = false; QPID_LOG(debug, "ACL: lookup name '" << name << "' didn't match with rule name '" << rulePropMapItr->second << "'"); } } else { if (params) { // The rule's property map non-NAME properties // found in the lookup's params list. // In some cases the param's index is not the same // as rule's index. propertyMapItr lookupParamItr; switch (rulePropMapItr->first) { case acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT: case acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT: lookupParamItr = params->find(PROP_MAXQUEUECOUNT); break; case acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT: case acl::SPECPROP_MAXQUEUESIZELOWERLIMIT: lookupParamItr = params->find(PROP_MAXQUEUESIZE); break; case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT: case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT: lookupParamItr = params->find(PROP_MAXFILECOUNT); break; case acl::SPECPROP_MAXFILESIZEUPPERLIMIT: case acl::SPECPROP_MAXFILESIZELOWERLIMIT: lookupParamItr = params->find(PROP_MAXFILESIZE); break; default: lookupParamItr = params->find((Property)rulePropMapItr->first); break; }; if (lookupParamItr == params->end()) { // Now the rule has a specified property // that does not exist in the caller's // lookup params list. // This rule does not match. match = false; QPID_LOG(debug, "ACL: lookup parameter map doesn't contain the rule property '" << AclHelper::getPropertyStr(rulePropMapItr->first) << "'"); } else { // Now account for the business of rules // whose property indexes are mismatched. switch (rulePropMapItr->first) { case acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT: case acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT: case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT: case acl::SPECPROP_MAXFILESIZEUPPERLIMIT: limitChecked &= compareIntMax( rulePropMapItr->first, boost::lexical_cast(rulePropMapItr->second), boost::lexical_cast(lookupParamItr->second)); break; case acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT: case acl::SPECPROP_MAXQUEUESIZELOWERLIMIT: case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT: case acl::SPECPROP_MAXFILESIZELOWERLIMIT: limitChecked &= compareIntMin( rulePropMapItr->first, boost::lexical_cast(rulePropMapItr->second), boost::lexical_cast(lookupParamItr->second)); break; default: bool result; if ((SPECPROP_ALTERNATE == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_ALTERNATE]) || (SPECPROP_QUEUENAME == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_QUEUENAME])) { // These properties are allowed to have username substitution std::string sName(rulePropMapItr->second); substituteUserId(sName, id); result = matchProp(sName, lookupParamItr->second); } else if (SPECPROP_ROUTINGKEY == rulePropMapItr->first) { // Routing key is allowed to have username substitution // and it gets topic exchange matching if (rsItr->ruleHasUserSub[PROP_ROUTINGKEY]) { std::string sKey(lookupParamItr->second); substituteKeywords(sKey, id); result = rsItr->matchRoutingKey(sKey); } else { result = rsItr->matchRoutingKey(lookupParamItr->second); } } else { // Rules without substitution result = matchProp(rulePropMapItr->second, lookupParamItr->second); } if (result) { QPID_LOG(debug, "ACL: the pair(" << AclHelper::getPropertyStr(lookupParamItr->first) << "," << lookupParamItr->second << ") given in lookup matched the pair(" << AclHelper::getPropertyStr(rulePropMapItr->first) << "," << rulePropMapItr->second << ") given in the rule"); } else { match = false; QPID_LOG(debug, "ACL: the pair(" << AclHelper::getPropertyStr(lookupParamItr->first) << "," << lookupParamItr->second << ") given in lookup doesn't match the pair(" << AclHelper::getPropertyStr(rulePropMapItr->first) << "," << rulePropMapItr->second << ") given in the rule"); } break; }; } } else { // params don't exist. } } } if (match) { aclresult = rsItr->ruleMode; if (!limitChecked) { // Now a lookup matched all rule properties but one // of the numeric limit checks has failed. // Demote allow rules to corresponding deny rules. switch (aclresult) { case acl::ALLOW: aclresult = acl::DENY; break; case acl::ALLOWLOG: aclresult = acl::DENYLOG; break; default: break; }; } QPID_LOG(debug,"ACL: Successful match, the decision is:" << AclHelper::getAclResultStr(aclresult)); return aclresult; } else { // This rule did not match the requested lookup and // does not contribute to an ACL decision. } } } else { // The Action-Object list has entries but not for this actorId // nor for *. } } else { // The Action-Object list has no entries. } QPID_LOG(debug,"ACL: No successful match, defaulting to the decision mode " << AclHelper::getAclResultStr(aclresult)); return aclresult; } // // lookup // // The ACL main business logic function of matching rules and declaring // an allow or deny result. This lookup is the fastpath per-message // lookup to verify if a user is allowed to publish to an exchange with // a given key. // AclResult AclData::lookup( const std::string& id, const Action& action, const ObjectType& objType, const std::string& /*Exchange*/ name, const std::string& routingKey) { QPID_LOG(debug, "ACL: Lookup for id:" << id << " action:" << AclHelper::getActionStr((Action) action) << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType) << " exchange name:" << name << " with routing key " << routingKey); AclResult aclresult = decisionMode; if (actionList[action] && actionList[action][objType]){ AclData::actObjItr itrRule = actionList[action][objType]->find(id); if (itrRule == actionList[action][objType]->end()) itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD); if (itrRule != actionList[action][objType]->end() ) { // Found a rule list for this user-action-object set. // Search the rule list for a matching rule. ruleSetItr rsItr = itrRule->second.end(); for (int cnt = itrRule->second.size(); cnt != 0; cnt--) { rsItr--; QPID_LOG(debug, "ACL: checking rule " << rsItr->toString()); // Search on exchange name and routing key only if specfied in rule. bool match =true; if (rsItr->pubExchNameInRule) { // substitute user name into object name bool result; if (rsItr->ruleHasUserSub[PROP_NAME]) { std::string sName(rsItr->pubExchName); substituteUserId(sName, id); result = matchProp(sName, name); } else { result = matchProp(rsItr->pubExchName, name); } if (result) { QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '" << name << "' matched with rule name '" << rsItr->pubExchName << "'"); } else { match= false; QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '" << name << "' did not match with rule name '" << rsItr->pubExchName << "'"); } } if (match && rsItr->pubRoutingKeyInRule) { if ((routingKey.find(ACL_KEYWORD_USER_SUBST, 0) != std::string::npos) || (routingKey.find(ACL_KEYWORD_DOMAIN_SUBST, 0) != std::string::npos) || (routingKey.find(ACL_KEYWORD_USERDOMAIN_SUBST, 0) != std::string::npos)) { // The user is not allowed to present a routing key with the substitution key in it QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " User-specified routing key has substitution wildcard:" << routingKey << ". Rule match prohibited."); match = false; } else { bool result; if (rsItr->ruleHasUserSub[PROP_ROUTINGKEY]) { std::string sKey(routingKey); substituteKeywords(sKey, id); result = rsItr->matchRoutingKey(sKey); } else { result = rsItr->matchRoutingKey(routingKey); } if (result) { QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '" << routingKey << "' matched with rule routing key '" << rsItr->pubRoutingKey << "'"); } else { QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '" << routingKey << "' did not match with rule routing key '" << rsItr->pubRoutingKey << "'"); match = false; } } } if (match){ aclresult = rsItr->ruleMode; QPID_LOG(debug,"ACL: Rule: " << rsItr->rawRuleNum << " Successful match, the decision is:" << AclHelper::getAclResultStr(aclresult)); return aclresult; } } } } QPID_LOG(debug,"ACL: No successful match, defaulting to the decision mode " << AclHelper::getAclResultStr(aclresult)); return aclresult; } // // // void AclData::setConnQuotaRuleSettings ( bool rulesExist, boost::shared_ptr quotaPtr) { connQuotaRulesExist = rulesExist; connQuotaRuleSettings = quotaPtr; } // // getConnQuotaForUser // // Return the true or false value of connQuotaRulesExist, // indicating whether any kind of lookup was done or not. // // When lookups are performed return the result value of // 1. The user's setting else // 2. The 'all' user setting else // 3. Zero // When lookups are not performed then return a result value of Zero. // bool AclData::getConnQuotaForUser(const std::string& theUserName, uint16_t* theResult) const { if (connQuotaRulesExist) { // look for this user explicitly quotaRuleSetItr nameItr = (*connQuotaRuleSettings).find(theUserName); if (nameItr != (*connQuotaRuleSettings).end()) { QPID_LOG(trace, "ACL: Connection quota for user " << theUserName << " explicitly set to : " << (*nameItr).second); *theResult = (*nameItr).second; } else { // Look for the 'all' user nameItr = (*connQuotaRuleSettings).find(ACL_KEYWORD_ALL); if (nameItr != (*connQuotaRuleSettings).end()) { QPID_LOG(trace, "ACL: Connection quota for user " << theUserName << " chosen through value for 'all' : " << (*nameItr).second); *theResult = (*nameItr).second; } else { // Neither userName nor "all" found. QPID_LOG(trace, "ACL: Connection quota for user " << theUserName << " absent in quota settings. Return value : 0"); *theResult = 0; } } } else { // Rules do not exist QPID_LOG(trace, "ACL: Connection quota for user " << theUserName << " unavailable; quota settings are not specified. Return value : 0"); *theResult = 0; } return connQuotaRulesExist; } // // // AclData::~AclData() { clear(); } // // Limit check a MAX int limit // bool AclData::compareIntMax(const qpid::acl::SpecProperty theProperty, const std::string theAclValue, const std::string theLookupValue) { uint64_t aclMax (0); uint64_t paramMax (0); try { aclMax = boost::lexical_cast(theAclValue); } catch(const boost::bad_lexical_cast&) { assert (false); return false; } try { paramMax = boost::lexical_cast(theLookupValue); } catch(const boost::bad_lexical_cast&) { QPID_LOG(error,"ACL: Error evaluating rule. " << "Illegal value given in lookup for property '" << AclHelper::getPropertyStr(theProperty) << "' : " << theLookupValue); return false; } QPID_LOG(debug, "ACL: Numeric greater-than comparison for property " << AclHelper::getPropertyStr(theProperty) << " (value given in lookup = " << theLookupValue << ", value give in rule = " << theAclValue << " )"); if (( aclMax ) && ( paramMax == 0 || paramMax > aclMax)) { QPID_LOG(debug, "ACL: Max limit exceeded for property '" << AclHelper::getPropertyStr(theProperty) << "'"); return false; } return true; } // // limit check a MIN int limit // bool AclData::compareIntMin(const qpid::acl::SpecProperty theProperty, const std::string theAclValue, const std::string theLookupValue) { uint64_t aclMin (0); uint64_t paramMin (0); try { aclMin = boost::lexical_cast(theAclValue); } catch(const boost::bad_lexical_cast&) { assert (false); return false; } try { paramMin = boost::lexical_cast(theLookupValue); } catch(const boost::bad_lexical_cast&) { QPID_LOG(error,"ACL: Error evaluating rule. " << "Illegal value given in lookup for property '" << AclHelper::getPropertyStr(theProperty) << "' : " << theLookupValue); return false; } QPID_LOG(debug, "ACL: Numeric less-than comparison for property " << AclHelper::getPropertyStr(theProperty) << " (value given in lookup = " << theLookupValue << ", value give in rule = " << theAclValue << " )"); if (( aclMin ) && ( paramMin == 0 || paramMin < aclMin)) { QPID_LOG(debug, "ACL: Min limit exceeded for property '" << AclHelper::getPropertyStr(theProperty) << "'"); return false; } return true; } const std::string DOMAIN_SEPARATOR("@"); const std::string PERIOD("."); const std::string UNDERSCORE("_"); // // substituteString // Given a name string from an Acl rule, substitute the replacement into it // wherever the placeholder directs. // void AclData::substituteString(std::string& targetString, const std::string& placeholder, const std::string& replacement) { assert (!placeholder.empty()); if (placeholder.empty()) return; size_t start_pos(0); while((start_pos = targetString.find(placeholder, start_pos)) != std::string::npos) { targetString.replace(start_pos, placeholder.length(), replacement); start_pos += replacement.length(); } } // // normalizeUserId // Given a name string return it in a form usable as topic keys: // change "@" and "." to "_". // std::string AclData::normalizeUserId(const std::string& userId) { std::string normalId(userId); substituteString(normalId, DOMAIN_SEPARATOR, UNDERSCORE); substituteString(normalId, PERIOD, UNDERSCORE); return normalId; } // // substituteUserId // Given an Acl rule and an authenticated userId // do the keyword substitutions on the rule. // void AclData::substituteUserId(std::string& ruleString, const std::string& userId) { size_t locDomSeparator(0); std::string user(""); std::string domain(""); std::string userdomain = normalizeUserId(userId); locDomSeparator = userId.find(DOMAIN_SEPARATOR); if (std::string::npos == locDomSeparator) { // "@" not found. There's just a user name user = normalizeUserId(userId); } else { // "@" found, split the names. Domain may be blank. user = normalizeUserId(userId.substr(0,locDomSeparator)); domain = normalizeUserId(userId.substr(locDomSeparator+1)); } substituteString(ruleString, ACL_KEYWORD_USER_SUBST, user); substituteString(ruleString, ACL_KEYWORD_DOMAIN_SUBST, domain); substituteString(ruleString, ACL_KEYWORD_USERDOMAIN_SUBST, userdomain); } // // substituteKeywords // Given an Acl rule and an authenticated userId // do reverse keyword substitutions on the rule. // That is, replace the normalized name in the rule string with // the keyword that represents it. This stragegy is used for // topic key lookups where the keyword string proper is in the // topic key search tree. // void AclData::substituteKeywords(std::string& ruleString, const std::string& userId) { size_t locDomSeparator(0); std::string user(""); std::string domain(""); std::string userdomain = normalizeUserId(userId); locDomSeparator = userId.find(DOMAIN_SEPARATOR); if (std::string::npos == locDomSeparator) { // "@" not found. There's just a user name user = normalizeUserId(userId); } else { // "@" found, split the names user = normalizeUserId(userId.substr(0,locDomSeparator)); domain = normalizeUserId(userId.substr(locDomSeparator+1)); } std::string oRule(ruleString); substituteString(ruleString, userdomain, ACL_KEYWORD_USERDOMAIN_SUBST); substituteString(ruleString, user, ACL_KEYWORD_USER_SUBST); substituteString(ruleString, domain, ACL_KEYWORD_DOMAIN_SUBST); } }}