diff options
author | Carl C. Trieloff <cctrieloff@apache.org> | 2008-09-02 21:49:55 +0000 |
---|---|---|
committer | Carl C. Trieloff <cctrieloff@apache.org> | 2008-09-02 21:49:55 +0000 |
commit | 804cfbdaf19ee803f362b6aa4c35696ca4e850c3 (patch) | |
tree | 090db6b2fc272c6df0a88b7f10bd816942a883b9 /cpp/src | |
parent | 1d07b5b9c71fb74ab87fc15d4559832bbc2d254c (diff) | |
download | qpid-python-804cfbdaf19ee803f362b6aa4c35696ca4e850c3.tar.gz |
QPID-107 Implementation for ACL for C++ broker
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@691396 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp/src')
-rw-r--r-- | cpp/src/acl.mk | 8 | ||||
-rw-r--r-- | cpp/src/qpid/acl/Acl.cpp | 64 | ||||
-rw-r--r-- | cpp/src/qpid/acl/Acl.h | 10 | ||||
-rw-r--r-- | cpp/src/qpid/acl/AclData.cpp | 158 | ||||
-rw-r--r-- | cpp/src/qpid/acl/AclData.h | 73 | ||||
-rw-r--r-- | cpp/src/qpid/acl/AclReader.cpp | 461 | ||||
-rw-r--r-- | cpp/src/qpid/acl/AclReader.h | 90 | ||||
-rw-r--r-- | cpp/src/qpid/broker/AclModule.h | 197 | ||||
-rw-r--r-- | cpp/src/qpid/broker/SessionAdapter.cpp | 34 |
9 files changed, 985 insertions, 110 deletions
diff --git a/cpp/src/acl.mk b/cpp/src/acl.mk index 28b5f4f89f..cad66adf1a 100644 --- a/cpp/src/acl.mk +++ b/cpp/src/acl.mk @@ -6,10 +6,12 @@ lib_LTLIBRARIES += libqpidacl.la libqpidacl_la_SOURCES = \ qpid/acl/Acl.cpp \ qpid/acl/Acl.h \ + qpid/acl/AclData.cpp \ + qpid/acl/AclData.h \ + qpid/acl/AclPlugin.cpp \ qpid/acl/AclReader.cpp \ - qpid/acl/AclReader.h \ - qpid/acl/AclPlugin.cpp + qpid/acl/AclReader.h -libqpidacl_la_LIBADD= -lacl libqpidbroker.la +libqpidacl_la_LIBADD = libqpidbroker.la diff --git a/cpp/src/qpid/acl/Acl.cpp b/cpp/src/qpid/acl/Acl.cpp index 79e4af57ee..f4ed9834d9 100644 --- a/cpp/src/qpid/acl/Acl.cpp +++ b/cpp/src/qpid/acl/Acl.cpp @@ -16,8 +16,8 @@ * */ -#include "Acl.h" - +#include "qpid/acl/Acl.h" +#include "qpid/acl/AclData.h" #include "qpid/broker/Broker.h" #include "qpid/Plugin.h" @@ -41,79 +41,43 @@ using namespace std; } - std::string Acl::printAction(acl::Action action) - { - switch (action) - { - case CONSUME: return "Consume"; - case PUBLISH: return "Publish"; - case CREATE: return "Create"; - case ACCESS: return "Access"; - case BIND: return "Bind"; - case UNBIND: return "Unbind"; - case DELETE: return "Delete"; - case PURGE: return "Purge"; - case UPDATE: return "Update"; - default: return "Unknown"; - } - } - - std::string Acl::printObjType(acl::ObjectType objType) - { - switch (objType) - { - case QUEUE: return "Queue"; - case EXCHANGE: return "Exchnage"; - case BROKER: return "Broker"; - case LINK: return "Link"; - case ROUTE: return "Route"; - default: return "Unknown"; - } - } - - bool Acl::authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string name, std::map<std::string, std::string>* - /*params*/) + bool Acl::authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params) { if (aclValues.noEnforce) return true; boost::shared_ptr<AclData> dataLocal = data; //rcu copy - // only use dataLocal here... - // add real ACL check here... - AclResult aclreslt = ALLOWLOG; // hack to test, set based on real decision. + AclResult aclreslt = dataLocal->lookup(id,action,objType,name,params); return result(aclreslt, id, action, objType, name); } - bool Acl::authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string ExchangeName, std::string /*RoutingKey*/) + bool Acl::authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& ExchangeName, const std::string& RoutingKey) { if (aclValues.noEnforce) return true; boost::shared_ptr<AclData> dataLocal = data; //rcu copy // only use dataLocal here... - - // add real ACL check here... - AclResult aclreslt = ALLOWLOG; // hack to test, set based on real decision. - + AclResult aclreslt = dataLocal->lookup(id,action,objType,ExchangeName,RoutingKey); return result(aclreslt, id, action, objType, ExchangeName); } - bool Acl::result(AclResult aclreslt, std::string id, acl::Action action, acl::ObjectType objType, std::string name) + bool Acl::result(const AclResult& aclreslt, const std::string& id, const Action& action, const ObjectType& objType, const std::string& name) { switch (aclreslt) { case ALLOWLOG: - QPID_LOG(info, "ACL Allow id:" << id <<" action:" << printAction(action) << " ObjectType:" << printObjType(objType) << " Name:" << name ); + QPID_LOG(info, "ACL Allow id:" << id <<" action:" << AclHelper::getActionStr(action) << " ObjectType:" << AclHelper::getObjectTypeStr(objType) << " Name:" << name ); case ALLOW: return true; - case DENYNOLOG: - return false; case DENY: + return false; + case DENYLOG: default: - QPID_LOG(info, "ACL Deny id:" << id << " action:" << printAction(action) << " ObjectType:" << printObjType(objType) << " Name:" << name); + QPID_LOG(info, "ACL Deny id:" << id << " action:" << AclHelper::getActionStr(action) << " ObjectType:" << AclHelper::getObjectTypeStr(objType) << " Name:" << name); return false; } return false; @@ -125,12 +89,14 @@ using namespace std; return readAclFile(aclValues.aclFile); } - bool Acl::readAclFile(std::string aclFile) { + bool Acl::readAclFile(std::string& aclFile) { boost::shared_ptr<AclData> d(new AclData); - if (AclReader::read(aclFile, d)) + AclReader ar; + if (ar.read(aclFile, d)) return false; data = d; + transferAcl = data->transferAcl; // any transfer ACL return true; } diff --git a/cpp/src/qpid/acl/Acl.h b/cpp/src/qpid/acl/Acl.h index a82add556c..2015c3555f 100644 --- a/cpp/src/qpid/acl/Acl.h +++ b/cpp/src/qpid/acl/Acl.h @@ -63,16 +63,14 @@ public: inline virtual bool doTransferAcl() {return transferAcl;}; // create specilied authorise methods for cases that need faster matching as needed. - virtual bool authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string name, std::map<std::string, std::string>* params); - virtual bool authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string ExchangeName, std::string RoutingKey); + virtual bool authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params); + virtual bool authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& ExchangeName,const std::string& RoutingKey); virtual ~Acl(); private: - std::string printAction(acl::Action action); - std::string printObjType(acl::ObjectType objType); - bool result(AclResult aclreslt, std::string id, acl::Action action, acl::ObjectType objType, std::string name); + bool result(const AclResult& aclreslt, const std::string& id, const Action& action, const ObjectType& objType, const std::string& name); bool readAclFile(); - bool readAclFile(std::string aclFile); + bool readAclFile(std::string& aclFile); }; diff --git a/cpp/src/qpid/acl/AclData.cpp b/cpp/src/qpid/acl/AclData.cpp new file mode 100644 index 0000000000..ee8b3b1b7d --- /dev/null +++ b/cpp/src/qpid/acl/AclData.cpp @@ -0,0 +1,158 @@ +/* + * + * 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" + + +namespace qpid { +namespace acl { + +AclData::AclData():decisionMode(qpid::acl::DENY),transferAcl(false) +{ + for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){ + actionList[cnt]=0; + } + +} + +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]; + } + +} + +bool AclData::matchProp(const std::string & src, const std::string& src1) +{ + // allow wildcard on the end of strings... + if (src.data()[src.size()-1]=='*') { + return (src.compare(0, src.size()-1, src1, 0,src.size()-1 ) == 0); + } else { + return (src.compare(src1)==0) ; + } +} + +AclResult AclData::lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params) +{ + AclResult aclresult = decisionMode; + + if (actionList[action] && actionList[action][objType]){ + AclData::actObjItr itrRule = actionList[action][objType]->find(id); + if (itrRule != actionList[action][objType]->end() ) { + + //loop the vector + for (ruleSetItr i=itrRule->second.begin(); i<itrRule->second.end(); i++) { + + // loop the names looking for match + bool match =true; + for (propertyMapItr pMItr = i->props.begin(); (pMItr != i->props.end()) && match; pMItr++) + { + //match name is exists first + if (pMItr->first == acl::NAME){ + if (!matchProp(pMItr->second, name)){ + match= false; + } + }else if (params){ //match pMItr against params + propertyMapItr paramItr = params->find (pMItr->first); + if (paramItr == params->end()){ + match = false; + }else if (!matchProp(paramItr->second, pMItr->second)){ + match = false; + } + } + } + if (match) return getACLResult(i->logOnly, i->log); + } + } + } + return aclresult; +} + +AclResult AclData::lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& /*Exchange*/ name, const std::string& RoutingKey) +{ + AclResult aclresult = decisionMode; + + if (actionList[action] && actionList[action][objType]){ + AclData::actObjItr itrRule = actionList[action][objType]->find(id); + if (itrRule != actionList[action][objType]->end() ) { + + //loop the vector + for (ruleSetItr i=itrRule->second.begin(); i<itrRule->second.end(); i++) { + + // loop the names looking for match + bool match =true; + for (propertyMapItr pMItr = i->props.begin(); (pMItr != i->props.end()) && match; pMItr++) + { + //match name is exists first + if (pMItr->first == acl::NAME){ + if (!matchProp(pMItr->second, name)){ + match= false; + } + }else if (pMItr->first == acl::ROUTINGKEY){ + if (!matchProp(pMItr->second, RoutingKey)){ + match= false; + } + } + } + if (match) return getACLResult(i->logOnly, i->log); + } + } + } + return aclresult; + +} + + +AclResult AclData::getACLResult(bool logOnly, bool log) +{ + switch (decisionMode) + { + case qpid::acl::ALLOWLOG: + case qpid::acl::ALLOW: + if (logOnly) return qpid::acl::ALLOWLOG; + if (log) + return qpid::acl::DENYLOG; + else + return qpid::acl::DENY; + + + case qpid::acl::DENYLOG: + case qpid::acl::DENY: + if (logOnly) return qpid::acl::DENYLOG; + if (log) + return qpid::acl::ALLOWLOG; + else + return qpid::acl::ALLOW; + } + + QPID_LOG(error, "ACL Decision Failed, setting DENY"); + return qpid::acl::DENY; +} + +AclData::~AclData() +{ + clear(); +} + +}} diff --git a/cpp/src/qpid/acl/AclData.h b/cpp/src/qpid/acl/AclData.h new file mode 100644 index 0000000000..a2809616fe --- /dev/null +++ b/cpp/src/qpid/acl/AclData.h @@ -0,0 +1,73 @@ +#ifndef QPID_ACL_ACLDATA_H +#define QPID_ACL_ACLDATA_H + + +/* + * + * 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/broker/AclModule.h" +#include <vector> + + +namespace qpid { +namespace acl { + +class AclData { + + +public: + + typedef std::map<qpid::acl::Property, std::string> propertyMap; + typedef propertyMap::const_iterator propertyMapItr; + struct rule { + + bool log; + bool logOnly; // this is a rule is to log only + + // key value map + //?? + propertyMap props; + + + rule (propertyMap& p):log(false),logOnly(false),props(p) {}; + }; + typedef std::vector<rule> ruleSet; + typedef ruleSet::const_iterator ruleSetItr; + typedef std::map<std::string, ruleSet > actionObject; // user + typedef actionObject::iterator actObjItr; + typedef actionObject* aclAction; + + // Action*[] -> Object*[] -> map<user -> set<Rule> > + aclAction* actionList[qpid::acl::ACTIONSIZE]; + qpid::acl::AclResult decisionMode; // determines if the rule set is an deny or accept basis. + bool transferAcl; + + AclResult lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params); + AclResult lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& ExchangeName, const std::string& RoutingKey); + AclResult getACLResult(bool logOnly, bool log); + + bool matchProp(const std::string & src, const std::string& src1); + void clear (); + + AclData(); + virtual ~AclData(); +}; + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACLDATA_H diff --git a/cpp/src/qpid/acl/AclReader.cpp b/cpp/src/qpid/acl/AclReader.cpp index 0a9517bc76..e8828a55bb 100644 --- a/cpp/src/qpid/acl/AclReader.cpp +++ b/cpp/src/qpid/acl/AclReader.cpp @@ -18,47 +18,274 @@ #include "qpid/acl/AclReader.h" +#include <cctype> #include <cstring> -//#include <iostream> // debug #include <fstream> +#include <sstream> +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" + +#include <iomanip> // degug +#include <iostream> // debug + +#define ACL_FORMAT_ERR_LOG_PREFIX "ACL format error: " << fileName << ":" << lineNumber << ": " namespace qpid { namespace acl { +AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups) : res(r), actionAll(true), objStatus(NONE) { + processName(n, groups); +} +AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a) : res(r), actionAll(false), action(a), objStatus(NONE) { + processName(n, groups); +} + +void AclReader::aclRule::setObjectType(const ObjectType o) { + objStatus = VALUE; + object = o; +} + +void AclReader::aclRule::setObjectTypeAll() { + objStatus = ALL; +} + +bool AclReader::aclRule::addProperty(const Property p, const std::string v) { + return props.insert(propNvPair(p, v)).second; +} + +bool AclReader::aclRule::validate(const AclHelper::objectMapPtr& /*validationMap*/) { + // TODO - invalid rules won't ever be called in real life... + return true; +} + +// Debug aid +std::string AclReader::aclRule::toString() { + std::ostringstream oss; + oss << AclHelper::getAclResultStr(res) << " ["; + for (nsCitr itr = names.begin(); itr != names.end(); itr++) { + if (itr != names.begin()) oss << ", "; + oss << *itr; + } + oss << "]"; + if (actionAll) { + oss << " *"; + } else { + oss << " " << AclHelper::getActionStr(action); + } + if (objStatus == ALL) { + oss << " *"; + } else if (objStatus == VALUE) { + oss << " " << AclHelper::getObjectTypeStr(object); + } + for (pmCitr i=props.begin(); i!=props.end(); i++) { + oss << " " << AclHelper::getPropertyStr(i->first) << "=" << i->second; + } + return oss.str(); +} + +void AclReader::loadDecisionData( boost::shared_ptr<AclData> d ) +{ + d->clear(); + QPID_LOG(debug, "ACL Load Rules"); + int cnt = rules.size(); + bool foundmode = false; + for (rlCitr i=rules.end()-1; cnt; i--,cnt--) { + QPID_LOG(debug, "ACL Processing " << std::setfill(' ') << std::setw(2) << cnt << " " << (*i)->toString()); + + if (!foundmode && (*i)->actionAll && (*i)->names.size()==1 && (*((*i)->names.begin())).compare("*")==0 ){ + d->decisionMode = (*i)->res; + QPID_LOG(debug, "ACL FoundMode " << AclHelper::getAclResultStr(d->decisionMode)); + foundmode=true; + }else{ + AclData::rule rule((*i)->props); + bool addrule= true; + + switch ((*i)->res) + { + case qpid::acl::ALLOWLOG: + rule.log = true; + if (d->decisionMode == qpid::acl::ALLOW || d->decisionMode == qpid::acl::ALLOWLOG) + rule.logOnly = true; + break; + case qpid::acl::ALLOW: + if (d->decisionMode == qpid::acl::ALLOW || d->decisionMode == qpid::acl::ALLOWLOG) + addrule = false; + break; + case qpid::acl::DENYLOG: + rule.log = true; + if (d->decisionMode == qpid::acl::DENY || d->decisionMode == qpid::acl::DENYLOG) + rule.logOnly = true; + break; + case qpid::acl::DENY: + if (d->decisionMode == qpid::acl::DENY || d->decisionMode == qpid::acl::DENYLOG) + addrule = false; + break; + default: + throw Exception("Invalid ACL Result loading rules."); + } + + + // Action -> Object -> map<user -> set<Rule> > + if (addrule){ + for (int acnt= ((*i)->actionAll?0:(*i)->action); + acnt< acl::ACTIONSIZE; (*i)->actionAll?acnt++:acnt=acl::ACTIONSIZE ) { + + if (acnt == acl::PUBLISH) d->transferAcl = true; // we have transfer ACL + + QPID_LOG(debug, "ACL Adding action:" << AclHelper::getActionStr((Action)acnt) ); + + //find the Action, create if not exist + if (d->actionList[acnt]==NULL) { + d->actionList[acnt] = new AclData::aclAction[qpid::acl::OBJECTSIZE]; + for (int j=0;j<qpid::acl::OBJECTSIZE; j++) + d->actionList[acnt][j] = NULL; + } + + // optimize this loop to limit to valid options only!! + for (int ocnt= ((*i)->objStatus!=aclRule::VALUE ?0:(*i)->object); + ocnt< acl::OBJECTSIZE; + (*i)->objStatus!=aclRule::VALUE?ocnt++:ocnt=acl::OBJECTSIZE ) { + + QPID_LOG(debug, "ACL Adding object:" << AclHelper::getObjectTypeStr((ObjectType)ocnt) ); + + //find the Object, create if not exist + if (d->actionList[acnt][ocnt] == NULL) + d->actionList[acnt][ocnt] = new AclData::actionObject; + + // add users and Rule to object set + bool allNames=false; + // check to see if names.begin is '*' + if ( (*(*i)->names.begin()).compare("*")==0 ) allNames = true; + + for (nsCitr itr = (allNames?names.begin():(*i)->names.begin()); + itr != (allNames?names.end():(*i)->names.end()); itr++) { + AclData::actObjItr itrRule = d->actionList[acnt][ocnt]->find(*itr); + if (itrRule == d->actionList[acnt][ocnt]->end()) { + QPID_LOG(debug, "ACL Adding rule & user:" << *itr); + AclData::ruleSet rSet; + rSet.push_back(rule); + d->actionList[acnt][ocnt]->insert(make_pair( std::string(*itr) , rSet) ); + }else{ + + // TODO add code to check for dead rules + // allow peter create queue name=tmp <-- dead rule!! + // allow peter create queue + + itrRule->second.push_back(rule); + QPID_LOG(debug, "ACL Adding rule to user:" << *itr); + } + } + + } + + } + }else{ + QPID_LOG(debug, "ACL Skipping based on Mode:" << AclHelper::getAclResultStr(d->decisionMode) ); + } + } + + } + + +} + + + + +void AclReader::aclRule::processName(const std::string& name, const groupMap& groups) { + if (name.compare("all") == 0) { + names.insert("*"); + } else { + gmCitr itr = groups.find(name); + if (itr == groups.end()) { + names.insert(name); + } else { + names.insert(itr->second->begin(), itr->second->end()); + } + } +} + +AclReader::AclReader() : lineNumber(0), contFlag(false), validationMap(new AclHelper::objectMap) { + AclHelper::loadValidationMap(validationMap); +} + +AclReader::~AclReader() {} + int AclReader::read(const std::string& fn, boost::shared_ptr<AclData> d) { -//std::cout << "AclReader::read(" << fn << ")" << std::endl << std::flush; + fileName = fn; + lineNumber = 0; char buff[1024]; std::ifstream ifs(fn.c_str(), std::ios_base::in); if (!ifs.good()) { - // error/exception - file open error + QPID_LOG(error, "Unable to open ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F")); return -1; } try { + bool err = false; while (ifs.good()) { ifs.getline(buff, 1024); - processLine(buff, d); + lineNumber++; + if (std::strlen(buff) > 0 && buff[0] != '#') // Ignore blank lines and comments + err |= !processLine(buff); + } + if (!ifs.eof()) + { + QPID_LOG(error, "Unable to read ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F")); + ifs.close(); + return -2; } ifs.close(); + if (err) return -3; + QPID_LOG(notice, "Read ACL file \"" << fn << "\""); + } catch (const std::exception& e) { + QPID_LOG(error, "Unable to read ACL file \"" << fn << "\": " << e.what()); + ifs.close(); + return -4; } catch (...) { - // error/exception - file read/processing error + QPID_LOG(error, "Unable to read ACL file \"" << fn << "\": Unknown exception"); ifs.close(); - return -2; + return -5; } + printNames(); + printRules(); + loadDecisionData(d); + return 0; } +bool AclReader::processLine(char* line) { + bool ret = false; + std::vector<std::string> toks; + + // Check for continuation + char* contCharPtr = std::strrchr(line, '\\'); + bool cont = contCharPtr != 0; + if (cont) *contCharPtr = 0; -void AclReader::processLine(char* line, boost::shared_ptr<AclData> /*d*/) { - std::vector<std::string> toks; - int numToks = tokenizeLine(line, toks); - for (int i=0; i<numToks; i++) { -// DO MAGIC STUFF HERE -//std::cout << "tok " << i << ": " << toks[i] << std::endl << std::flush; - } + int numToks = tokenize(line, toks); + if (numToks && (toks[0].compare("group") == 0 || contFlag)) { + ret = processGroupLine(toks, cont); + } else if (numToks && toks[0].compare("acl") == 0) { + ret = processAclLine(toks); + } else { + // Check for whitespace only line, ignore these + bool ws = true; + for (unsigned i=0; i<std::strlen(line) && ws; i++) { + if (!std::isspace(line[i])) ws = false; + } + if (ws) { + ret = true; + } else { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Non-continuation line must start with \"group\" or \"acl\"."); + ret = false; + } + } + contFlag = cont; + return ret; } -int AclReader::tokenizeLine(char* line, std::vector<std::string>& toks) { - const char* tokChars = " \t\n"; +int AclReader::tokenize(char* line, std::vector<std::string>& toks) { + const char* tokChars = " \t\n\f\v\r"; int cnt = 0; char* cp = std::strtok(line, tokChars); while (cp != 0) { @@ -69,5 +296,209 @@ int AclReader::tokenizeLine(char* line, std::vector<std::string>& toks) { return cnt; } +// Return true if the line is successfully processed without errors +// If cont is true, then groupName must be set to the continuation group name +bool AclReader::processGroupLine(tokList& toks, const bool cont) { + const unsigned toksSize = toks.size(); + if (contFlag) { + gmCitr citr = groups.find(groupName); + for (unsigned i = 0; i < toksSize; i++) { + if (!checkName(toks[i])) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Name \"" << toks[i] << "\" contains illegal characters."); + return false; + } + addName(toks[i], citr->second); + } + } else { + if (toksSize < (cont ? 2 : 3)) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Insufficient tokens for group definition."); + return false; + } + if (!checkName(toks[1])) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Group name \"" << toks[1] << "\" contains illegal characters."); + return false; + } + gmCitr citr = addGroup(toks[1]); + if (citr == groups.end()) return false; + for (unsigned i = 2; i < toksSize; i++) { + if (!checkName(toks[i])) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Name \"" << toks[i] << "\" contains illegal characters."); + return false; + } + addName(toks[i], citr->second); + } + } + return true; +} + +// Return true if sucessfully added group +AclReader::gmCitr AclReader::addGroup(const std::string& newGroupName) { + gmCitr citr = groups.find(newGroupName); + if (citr != groups.end()) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Duplicate group name \"" << newGroupName << "\"."); + return groups.end(); + } + groupPair p(newGroupName, nameSetPtr(new nameSet)); + gmRes res = groups.insert(p); + assert(res.second); + groupName = newGroupName; + return res.first; +} + +void AclReader::addName(const std::string& name, nameSetPtr groupNameSet) { + gmCitr citr = groups.find(name); + if (citr != groups.end()) { + // This is a previously defined group: add all the names in that group to this group + groupNameSet->insert(citr->second->begin(), citr->second->end()); + } else { + // Not a known group name + groupNameSet->insert(name); + addName(name); + } +} + +void AclReader::addName(const std::string& name) { + names.insert(name); +} + +// Debug aid +void AclReader::printNames() const { + QPID_LOG(debug, "Group list: " << groups.size() << " groups found:" ); + std::string tmp; + for (gmCitr i=groups.begin(); i!= groups.end(); i++) { + tmp += " \""; + tmp += i->first; + tmp += "\":"; + for (nsCitr j=i->second->begin(); j!=i->second->end(); j++) { + tmp += " "; + tmp += *j; + } + QPID_LOG(debug, tmp); + tmp.clear(); + } + QPID_LOG(debug, "Name list: " << names.size() << " names found:" ); + tmp.clear(); + for (nsCitr k=names.begin(); k!=names.end(); k++) { + tmp += " "; + tmp += *k; + } + QPID_LOG(debug, tmp); +} + +bool AclReader::processAclLine(tokList& toks) { + const unsigned toksSize = toks.size(); + if (toksSize < 4) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Insufficient tokens for acl definition."); + return false; + } + + AclResult res; + try { + res = AclHelper::getAclResult(toks[1]); + } catch (...) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Unknown ACL permission \"" << toks[1] << "\"."); + return false; + } + + bool actionAllFlag = toks[3].compare("all") == 0; + Action action; + if (actionAllFlag) { + if (toksSize > 4) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Tokens found after action \"all\"."); + return false; + } + action = CONSUME; // dummy; compiler must initialize action for this code path + } else { + try { + action = AclHelper::getAction(toks[3]); + } catch (...) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Unknown action \"" << toks[3] << "\"."); + return false; + } + } + + // Create rule obj; then add object (if any) and properties (if any) + aclRulePtr rule; + if (actionAllFlag) { + rule.reset(new aclRule(res, toks[2], groups)); + } else { + rule.reset(new aclRule(res, toks[2], groups, action)); + } + + if (toksSize >= 5) { // object name-value pair + if (toks[4].compare("all") == 0) { + rule->setObjectTypeAll(); + } else { + try { + rule->setObjectType(AclHelper::getObjectType(toks[4])); + } catch (...) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Unknown object \"" << toks[4] << "\"."); + return false; + } + } + } + + if (toksSize >= 6) { // property name-value pair(s) + for (unsigned i=5; i<toksSize; i++) { + nvPair propNvp = splitNameValuePair(toks[i]); + if (propNvp.second.size() == 0) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Badly formed property name-value pair \"" << propNvp.first << "\". (Must be name=value)"); + return false; + } + Property prop; + try { + prop = AclHelper::getProperty(propNvp.first); + } catch (...) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Unknown property \"" << propNvp.first << "\"."); + return false; + } + rule->addProperty(prop, propNvp.second); + } + } + // Check if name (toks[2]) is group; if not, add as name of individual + if (toks[2].compare("all") != 0) { + if (groups.find(toks[2]) == groups.end()) { + addName(toks[2]); + } + } + + // If rule validates, add to rule list + if (!rule->validate(validationMap)) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Invalid object/action/property combination."); + return false; + } + rules.push_back(rule); + + return true; +} + +// Debug aid +void AclReader::printRules() const { + QPID_LOG(debug, "Rule list: " << rules.size() << " ACL rules found:"); + int cnt = 0; + for (rlCitr i=rules.begin(); i<rules.end(); i++,cnt++) { + QPID_LOG(debug, " " << std::setfill(' ') << std::setw(2) << cnt << " " << (*i)->toString()); + } +} + +// Static function +// Return true if the name is well-formed (ie contains legal characters) +bool AclReader::checkName(const std::string& name) { + for (unsigned i=0; i<name.size(); i++) { + const char ch = name.at(i); + if (!std::isalnum(ch) && ch != '-' && ch != '_') return false; + } + return true; +} + +// Static function +// Split name-value pair around '=' char of the form "name=value" +AclReader::nvPair AclReader::splitNameValuePair(const std::string& nvpString) { + std::size_t pos = nvpString.find("="); + if (pos == std::string::npos || pos == nvpString.size() - 1) { + return nvPair(nvpString, ""); + } + return nvPair(nvpString.substr(0, pos), nvpString.substr(pos+1)); +} }} // namespace qpid::acl diff --git a/cpp/src/qpid/acl/AclReader.h b/cpp/src/qpid/acl/AclReader.h index 783b70d98a..c6e7770311 100644 --- a/cpp/src/qpid/acl/AclReader.h +++ b/cpp/src/qpid/acl/AclReader.h @@ -21,23 +21,93 @@ */ #include <boost/shared_ptr.hpp> +#include <map> +#include <set> #include <string> #include <vector> +#include "qpid/acl/AclData.h" +#include "qpid/broker/AclModule.h" + namespace qpid { namespace acl { -struct AclData { - bool lc; // Line continue flag - AclData() : lc(false) {} -}; - class AclReader { -public: - static int read(const std::string& fn, boost::shared_ptr<AclData> d); -private: - static void processLine(char* line, boost::shared_ptr<AclData> d); - static int tokenizeLine(char* line, std::vector<std::string>& toks); + typedef std::set<std::string> nameSet; + typedef nameSet::const_iterator nsCitr; + typedef boost::shared_ptr<nameSet> nameSetPtr; + + typedef std::pair<std::string, nameSetPtr> groupPair; + typedef std::map<std::string, nameSetPtr> groupMap; + typedef groupMap::const_iterator gmCitr; + typedef std::pair<gmCitr, bool> gmRes; + + typedef std::pair<Property, std::string> propNvPair; + typedef std::map<Property, std::string> propMap; + typedef propMap::const_iterator pmCitr; + + class aclRule { + public: + enum objectStatus {NONE, VALUE, ALL}; + AclResult res; + nameSet names; + bool actionAll; // True if action is set to keyword "all" + Action action; // Ignored if action is set to keyword "all" + objectStatus objStatus; + ObjectType object; // Ignored for all status values except VALUE + propMap props; + public: + aclRule(const AclResult r, const std::string n, const groupMap& groups); // action = "all" + aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a); + void setObjectType(const ObjectType o); + void setObjectTypeAll(); + bool addProperty(const Property p, const std::string v); + bool validate(const AclHelper::objectMapPtr& validationMap); + std::string toString(); // debug aid + private: + void processName(const std::string& name, const groupMap& groups); + }; + typedef boost::shared_ptr<aclRule> aclRulePtr; + typedef std::vector<aclRulePtr> ruleList; + typedef ruleList::const_iterator rlCitr; + + typedef std::vector<std::string> tokList; + typedef tokList::const_iterator tlCitr; + + typedef std::set<std::string> keywordSet; + typedef keywordSet::const_iterator ksCitr; + typedef std::pair<std::string, std::string> nvPair; // Name-Value pair + + std::string fileName; + int lineNumber; + bool contFlag; + std::string groupName; + nameSet names; + groupMap groups; + ruleList rules; + AclHelper::objectMapPtr validationMap; + + public: + AclReader(); + virtual ~AclReader(); + int read(const std::string& fn, boost::shared_ptr<AclData> d); + + private: + bool processLine(char* line); + void loadDecisionData( boost::shared_ptr<AclData> d); + int tokenize(char* line, tokList& toks); + + bool processGroupLine(tokList& toks, const bool cont); + gmCitr addGroup(const std::string& groupName); + void addName(const std::string& name, nameSetPtr groupNameSet); + void addName(const std::string& name); + void printNames() const; // debug aid + + bool processAclLine(tokList& toks); + void printRules() const; // debug aid + + static bool checkName(const std::string& name); + static nvPair splitNameValuePair(const std::string& nvpString); }; }} // namespace qpid::acl diff --git a/cpp/src/qpid/broker/AclModule.h b/cpp/src/qpid/broker/AclModule.h index f766978d18..ec832daf22 100644 --- a/cpp/src/qpid/broker/AclModule.h +++ b/cpp/src/qpid/broker/AclModule.h @@ -25,16 +25,20 @@ #include "qpid/shared_ptr.h" #include "qpid/RefCounted.h" #include <map> +#include <set> #include <string> namespace qpid { -namespace acl{ -enum ObjectType {QUEUE,EXCHANGE,BROKER,LINK,ROUTE}; -enum Action {CONSUME,PUBLISH,CREATE,ACCESS,BIND,UNBIND,DELETE,PURGE,UPDATE}; -enum AclResult {ALLOW,ALLOWLOG,DENY,DENYNOLOG}; -} +namespace acl { + +enum ObjectType {QUEUE, EXCHANGE, BROKER, LINK, ROUTE,OBJECTSIZE}; +enum Action {CONSUME, PUBLISH, CREATE, ACCESS, BIND, UNBIND, DELETE, PURGE, UPDATE, ACTIONSIZE}; +enum Property {NAME, DURABLE, OWNER, ROUTINGKEY, PASSIVE, AUTODELETE, EXCLUSIVE, TYPE, ALTERNATE, QUEUENAME}; +enum AclResult {ALLOW, ALLOWLOG, DENY, DENYLOG}; + +} // namespace acl namespace broker { @@ -47,17 +51,190 @@ public: // effienty turn off ACL on message transfer. virtual bool doTransferAcl()=0; - virtual bool authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string name, - std::map<std::string, std::string>* params)=0; - virtual bool authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string ExchangeName, - std::string RoutingKey)=0; + virtual bool authorise(const std::string& id, const acl::Action& action, const acl::ObjectType& objType, const std::string& name, + std::map<acl::Property, std::string>* params)=0; + virtual bool authorise(const std::string& id, const acl::Action& action, const acl::ObjectType& objType, const std::string& ExchangeName, + const std::string& RoutingKey)=0; // create specilied authorise methods for cases that need faster matching as needed. virtual ~AclModule() {}; }; +} // namespace broker + +namespace acl { + +class AclHelper { + private: + AclHelper(){} + public: + static inline ObjectType getObjectType(const std::string& str) { + if (str.compare("queue") == 0) return QUEUE; + if (str.compare("exchange") == 0) return EXCHANGE; + if (str.compare("broker") == 0) return BROKER; + if (str.compare("link") == 0) return LINK; + if (str.compare("route") == 0) return ROUTE; + throw str; + } + static inline std::string getObjectTypeStr(const ObjectType o) { + switch (o) { + case QUEUE: return "queue"; + case EXCHANGE: return "exchange"; + case BROKER: return "broker"; + case LINK: return "link"; + case ROUTE: return "route"; + default: assert(false); // should never get here + } + } + static inline Action getAction(const std::string& str) { + if (str.compare("consume") == 0) return CONSUME; + if (str.compare("publish") == 0) return PUBLISH; + if (str.compare("create") == 0) return CREATE; + if (str.compare("access") == 0) return ACCESS; + if (str.compare("bind") == 0) return BIND; + if (str.compare("unbind") == 0) return UNBIND; + if (str.compare("delete") == 0) return DELETE; + if (str.compare("purge") == 0) return PURGE; + if (str.compare("update") == 0) return UPDATE; + throw str; + } + static inline std::string getActionStr(const Action a) { + switch (a) { + case CONSUME: return "consume"; + case PUBLISH: return "publish"; + case CREATE: return "create"; + case ACCESS: return "access"; + case BIND: return "bind"; + case UNBIND: return "unbind"; + case DELETE: return "delete"; + case PURGE: return "purge"; + case UPDATE: return "update"; + default: assert(false); // should never get here + } + } + static inline Property getProperty(const std::string& str) { + if (str.compare("name") == 0) return NAME; + if (str.compare("durable") == 0) return DURABLE; + if (str.compare("owner") == 0) return OWNER; + if (str.compare("routingkey") == 0) return ROUTINGKEY; + if (str.compare("passive") == 0) return PASSIVE; + if (str.compare("autodelete") == 0) return AUTODELETE; + if (str.compare("exclusive") == 0) return EXCLUSIVE; + if (str.compare("type") == 0) return TYPE; + if (str.compare("alternate") == 0) return ALTERNATE; + if (str.compare("queuename") == 0) return QUEUENAME; + throw str; + } + static inline std::string getPropertyStr(const Property p) { + switch (p) { + case NAME: return "name"; + case DURABLE: return "durable"; + case OWNER: return "owner"; + case ROUTINGKEY: return "routingkey"; + case PASSIVE: return "passive"; + case AUTODELETE: return "autodelete"; + case EXCLUSIVE: return "exclusive"; + case TYPE: return "type"; + case ALTERNATE: return "alternate"; + case QUEUENAME: return "queuename"; + default: assert(false); // should never get here + } + } + static inline AclResult getAclResult(const std::string& str) { + if (str.compare("allow") == 0) return ALLOW; + if (str.compare("allow-log") == 0) return ALLOWLOG; + if (str.compare("deny") == 0) return DENY; + if (str.compare("deny-log") == 0) return DENYLOG; + throw str; + } + static inline std::string getAclResultStr(const AclResult r) { + switch (r) { + case ALLOW: return "allow"; + case ALLOWLOG: return "allow-log"; + case DENY: return "deny"; + case DENYLOG: return "deny-log"; + default: assert(false); // should never get here + } + } + + typedef std::set<Property> propSet; + typedef boost::shared_ptr<propSet> propSetPtr; + typedef std::pair<Action, propSetPtr> actionPair; + typedef std::map<Action, propSetPtr> actionMap; + typedef boost::shared_ptr<actionMap> actionMapPtr; + typedef std::pair<ObjectType, actionMapPtr> objectPair; + typedef std::map<ObjectType, actionMapPtr> objectMap; + typedef objectMap::const_iterator omCitr; + typedef boost::shared_ptr<objectMap> objectMapPtr; + + // This map contains the legal combinations of object/action/properties found in an ACL file + static void loadValidationMap(objectMapPtr& map) { + if (!map.get()) return; + map->clear(); + propSetPtr p0; // empty ptr, used for no properties + + // == Exchanges == + + propSetPtr p1(new propSet); + p1->insert(TYPE); + p1->insert(ALTERNATE); + p1->insert(PASSIVE); + p1->insert(DURABLE); + + propSetPtr p2(new propSet); + p2->insert(ROUTINGKEY); + + propSetPtr p3(new propSet); + p3->insert(QUEUENAME); + p3->insert(ROUTINGKEY); + + actionMapPtr a0(new actionMap); + a0->insert(actionPair(CREATE, p1)); + a0->insert(actionPair(DELETE, p0)); + a0->insert(actionPair(ACCESS, p0)); + a0->insert(actionPair(BIND, p2)); + a0->insert(actionPair(UNBIND, p2)); + a0->insert(actionPair(ACCESS, p3)); + a0->insert(actionPair(PUBLISH, p0)); + + map->insert(objectPair(EXCHANGE, a0)); + + // == Queues == + + propSetPtr p4(new propSet); + p3->insert(ALTERNATE); + p3->insert(PASSIVE); + p3->insert(DURABLE); + p3->insert(EXCLUSIVE); + p3->insert(AUTODELETE); + + actionMapPtr a1(new actionMap); + a1->insert(actionPair(ACCESS, p0)); + a1->insert(actionPair(CREATE, p4)); + a1->insert(actionPair(PURGE, p0)); + a1->insert(actionPair(DELETE, p0)); + a1->insert(actionPair(CONSUME, p0)); + + map->insert(objectPair(QUEUE, a1)); + + // == Links == + + actionMapPtr a2(new actionMap); + a2->insert(actionPair(CREATE, p0)); + + map->insert(objectPair(LINK, a2)); + + // == Route == + + actionMapPtr a3(new actionMap); + a3->insert(actionPair(CREATE, p0)); + a3->insert(actionPair(DELETE, p0)); + + map->insert(objectPair(ROUTE, a3)); + } +}; -}} // namespace qpid::broker +}} // namespace qpid::acl #endif // QPID_ACLMODULE_ACL_H diff --git a/cpp/src/qpid/broker/SessionAdapter.cpp b/cpp/src/qpid/broker/SessionAdapter.cpp index 03022b00bb..9ab2c3a75e 100644 --- a/cpp/src/qpid/broker/SessionAdapter.cpp +++ b/cpp/src/qpid/broker/SessionAdapter.cpp @@ -56,11 +56,11 @@ void SessionAdapter::ExchangeHandlerImpl::declare(const string& exchange, const AclModule* acl = getBroker().getAcl(); if (acl) { - std::map<std::string, std::string> params; - params.insert(make_pair("TYPE", type)); - params.insert(make_pair("ALT", alternateExchange)); - params.insert(make_pair("PAS", std::string(passive ? "Y" : "N") )); - params.insert(make_pair("DURA", std::string(durable ? "Y" : "N"))); + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::TYPE, type)); + params.insert(make_pair(acl::ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PASSIVE, std::string(passive ? "true" : "false") )); + params.insert(make_pair(acl::DURABLE, std::string(durable ? "true" : "false"))); if (!acl->authorise(getConnection().getUserId(),acl::CREATE,acl::EXCHANGE,exchange,¶ms) ) throw NotAllowedException("ACL denied exhange declare request"); } @@ -182,9 +182,9 @@ SessionAdapter::ExchangeHandlerImpl::unbind(const string& queueName, AclModule* acl = getBroker().getAcl(); if (acl) { - std::map<std::string, std::string> params; - params.insert(make_pair("QN", queueName)); - params.insert(make_pair("RKEY", routingKey)); + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::QUEUENAME, queueName)); + params.insert(make_pair(acl::ROUTINGKEY, routingKey)); if (!acl->authorise(getConnection().getUserId(),acl::UNBIND,acl::EXCHANGE,exchangeName,¶ms) ) throw NotAllowedException("ACL denied exchange unbind request"); } @@ -211,9 +211,9 @@ ExchangeBoundResult SessionAdapter::ExchangeHandlerImpl::bound(const std::string AclModule* acl = getBroker().getAcl(); if (acl) { - std::map<std::string, std::string> params; - params.insert(make_pair("QUEUE", queueName)); - params.insert(make_pair("RKEY", queueName)); + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::QUEUENAME, queueName)); + params.insert(make_pair(acl::ROUTINGKEY, key)); if (!acl->authorise(getConnection().getUserId(),acl::CREATE,acl::EXCHANGE,exchangeName,¶ms) ) throw NotAllowedException("ACL denied exhange bound request"); } @@ -310,12 +310,12 @@ void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& AclModule* acl = getBroker().getAcl(); if (acl) { - std::map<std::string, std::string> params; - params.insert(make_pair("ALT", alternateExchange)); - params.insert(make_pair("PAS", std::string(passive ? "Y" : "N") )); - params.insert(make_pair("DURA", std::string(durable ? "Y" : "N"))); - params.insert(make_pair("EXCLUS", std::string(exclusive ? "Y" : "N"))); - params.insert(make_pair("AUTOD", std::string(autoDelete ? "Y" : "N"))); + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PASSIVE, std::string(passive ? "true" : "false") )); + params.insert(make_pair(acl::DURABLE, std::string(durable ? "true" : "false"))); + params.insert(make_pair(acl::EXCLUSIVE, std::string(exclusive ? "true" : "false"))); + params.insert(make_pair(acl::AUTODELETE, std::string(autoDelete ? "true" : "false"))); if (!acl->authorise(getConnection().getUserId(),acl::CREATE,acl::QUEUE,name,¶ms) ) throw NotAllowedException("ACL denied queue create request"); } |