Line data Source code
1 : /* 2 : * Distributed under the OpenDDS License. 3 : * See: http://www.OpenDDS.org/license.html 4 : */ 5 : 6 : #include "Permissions.h" 7 : 8 : #include "XmlUtils.h" 9 : 10 : #include <dds/DCPS/security/AccessControlBuiltInImpl.h> 11 : 12 : OPENDDS_BEGIN_VERSIONED_NAMESPACE_DECL 13 : 14 : namespace OpenDDS { 15 : namespace Security { 16 : 17 : using OpenDDS::DCPS::security_debug; 18 : 19 28 : int Permissions::load(const SSL::SignedDocument& doc) 20 : { 21 : using XML::XStr; 22 : using namespace XmlUtils; 23 : 24 28 : const std::string& xml = doc.content(); 25 28 : ParserPtr parser; 26 28 : if (!get_parser(parser, doc.filename(), xml)) { 27 0 : if (security_debug.access_error) { 28 0 : ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} Permissions::load: " 29 : "get_parser failed\n")); 30 : } 31 0 : return -1; 32 : } 33 : 34 : // Find the validity rules 35 : const xercesc::DOMNodeList* const grantRules = 36 28 : parser->getDocument()->getElementsByTagName(XStr(ACE_TEXT("grant"))); 37 : 38 56 : for (XMLSize_t r = 0, r_len = grantRules->getLength(); r < r_len; ++r) { 39 28 : Grant_rch grant = DCPS::make_rch<Grant>(); 40 28 : const xercesc::DOMNode* const grantRule = grantRules->item(r); 41 : 42 : // Pull out the grant name for this grant 43 28 : xercesc::DOMNamedNodeMap* rattrs = grantRule->getAttributes(); 44 28 : grant->name = to_string(rattrs->item(0)); 45 : 46 : // Pull out subject name, validity, and default 47 28 : const xercesc::DOMNodeList* grantNodes = grantRule->getChildNodes(); 48 : 49 28 : bool valid_subject = false, valid_default = false; 50 280 : for (XMLSize_t gn = 0, gn_len = grantNodes->getLength(); gn < gn_len; ++gn) { 51 : 52 252 : const xercesc::DOMNode* grantNode = grantNodes->item(gn); 53 : 54 252 : const XStr g_tag = grantNode->getNodeName(); 55 : 56 252 : if (g_tag == ACE_TEXT("subject_name")) { 57 28 : valid_subject = grant->subject.parse(to_string(grantNode)) == 0; 58 : 59 224 : } else if (g_tag == ACE_TEXT("validity")) { 60 28 : const xercesc::DOMNodeList* validityNodes = grantNode->getChildNodes(); 61 168 : for (XMLSize_t vn = 0, vn_len = validityNodes->getLength(); vn < vn_len; ++vn) { 62 140 : const xercesc::DOMNode* validityNode = validityNodes->item(vn); 63 140 : const XStr v_tag = validityNode->getNodeName(); 64 140 : if (v_tag == ACE_TEXT("not_before")) { 65 28 : if (!parse_time(validityNode->getTextContent(), grant->validity.not_before)) { 66 0 : if (security_debug.access_error) { 67 0 : ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} Permissions::load: " 68 : "invalid datetime in not_before\n")); 69 : } 70 0 : return -1; 71 : } 72 112 : } else if (v_tag == ACE_TEXT("not_after")) { 73 28 : if (!parse_time(validityNode->getTextContent(), grant->validity.not_after)) { 74 0 : if (security_debug.access_error) { 75 0 : ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} Permissions::load: " 76 : "invalid datetime in not_after\n")); 77 : } 78 0 : return -1; 79 : } 80 : } 81 140 : } 82 : 83 196 : } else if (g_tag == ACE_TEXT("default")) { 84 28 : const std::string def = to_string(grantNode); 85 28 : valid_default = true; 86 28 : if (def == "ALLOW") { 87 1 : grant->default_permission = ALLOW; 88 27 : } else if (def == "DENY") { 89 27 : grant->default_permission = DENY; 90 : } else { 91 0 : if (security_debug.access_error) { 92 0 : ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} Permissions::load: " 93 : "<default> must be ALLOW or DENY\n")); 94 : } 95 0 : return -1; 96 : } 97 28 : } 98 252 : } 99 : 100 28 : if (!valid_default) { 101 0 : if (security_debug.access_error) { 102 0 : ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} Permissions::load: " 103 : "<default> is required\n")); 104 : } 105 0 : return -1; 106 : } 107 : 108 : // Pull out allow/deny rules 109 28 : const xercesc::DOMNodeList* adGrantNodes = grantRule->getChildNodes(); 110 : 111 280 : for (XMLSize_t gn = 0, gn_len = adGrantNodes->getLength(); gn < gn_len; ++gn) { 112 : 113 252 : const xercesc::DOMNode* adGrantNode = adGrantNodes->item(gn); 114 : 115 252 : const XStr g_tag = adGrantNode->getNodeName(); 116 : 117 252 : if (g_tag == ACE_TEXT("allow_rule") || g_tag == ACE_TEXT("deny_rule")) { 118 28 : Rule rule; 119 : 120 28 : rule.ad_type = (g_tag == ACE_TEXT("allow_rule")) ? ALLOW : DENY; 121 : 122 28 : const xercesc::DOMNodeList* adNodeChildren = adGrantNode->getChildNodes(); 123 : 124 224 : for (XMLSize_t anc = 0, anc_len = adNodeChildren->getLength(); anc < anc_len; ++anc) { 125 196 : const xercesc::DOMNode* const adNodeChild = adNodeChildren->item(anc); 126 196 : const XStr anc_tag = adNodeChild->getNodeName(); 127 196 : if (anc_tag == ACE_TEXT("domains")) { 128 28 : if (!parse_domain_id_set(adNodeChild, rule.domains)) { 129 0 : if (security_debug.access_error) { 130 0 : ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} Permissions::load: " 131 : "failed to parse domain set\n")); 132 : } 133 0 : return -1; 134 : } 135 : 136 168 : } else if (anc_tag == ACE_TEXT("publish") || anc_tag == ACE_TEXT("subscribe")) { 137 56 : Action action; 138 : 139 56 : action.ps_type = (anc_tag == ACE_TEXT("publish")) ? PUBLISH : SUBSCRIBE; 140 56 : const xercesc::DOMNodeList* topicListNodes = adNodeChild->getChildNodes(); 141 : 142 228 : for (XMLSize_t tln = 0, tln_len = topicListNodes->getLength(); tln < tln_len; ++tln) { 143 : 144 172 : const xercesc::DOMNode* topicListNode = topicListNodes->item(tln); 145 : 146 172 : if (ACE_TEXT("topics") == XStr(topicListNode->getNodeName())) { 147 56 : const xercesc::DOMNodeList* topicNodes = topicListNode->getChildNodes(); 148 : 149 226 : for (XMLSize_t tn = 0, tn_len = topicNodes->getLength(); tn < tn_len; ++tn) { 150 : 151 170 : const xercesc::DOMNode* topicNode = topicNodes->item(tn); 152 : 153 170 : if (ACE_TEXT("topic") == XStr(topicNode->getNodeName())) { 154 57 : action.topics.push_back(to_string(topicNode)); 155 : } 156 : } 157 : 158 116 : } else if (ACE_TEXT("partitions") == XStr(topicListNode->getNodeName())) { 159 2 : const xercesc::DOMNodeList* partitionNodes = topicListNode->getChildNodes(); 160 : 161 12 : for (XMLSize_t pn = 0, pn_len = partitionNodes->getLength(); pn < pn_len; ++pn) { 162 : 163 10 : const xercesc::DOMNode* partitionNode = partitionNodes->item(pn); 164 : 165 10 : if (ACE_TEXT("partition") == XStr(partitionNode->getNodeName())) { 166 4 : action.partitions.push_back(to_string(partitionNode)); 167 : } 168 : } 169 : } 170 : } 171 : 172 56 : rule.actions.push_back(action); 173 56 : } 174 196 : } 175 : 176 28 : grant->rules.push_back(rule); 177 28 : } 178 252 : } 179 : 180 28 : if (!valid_subject) { 181 0 : if (security_debug.access_warn) { 182 0 : ACE_DEBUG((LM_WARNING, ACE_TEXT("(%P|%t) WARNING: {access_warn} Permissions::load: ") 183 : ACE_TEXT("Unable to parse subject name, ignoring grant.\n"))); 184 : } 185 28 : } else if (find_grant(grant->subject)) { 186 0 : if (security_debug.access_warn) { 187 0 : ACE_DEBUG((LM_WARNING, ACE_TEXT("(%P|%t) WARNING: {access_warn} Permissions::load: ") 188 : ACE_TEXT("Ignoring grant with duplicate subject name.\n"))); 189 : } 190 : } else { 191 28 : grants_.push_back(grant); 192 : } 193 28 : } // grant_rules 194 : 195 28 : return 0; 196 28 : } 197 : 198 28 : bool Permissions::has_grant(const SSL::SubjectName& name) const 199 : { 200 28 : for (Grants::const_iterator it = grants_.begin(); it != grants_.end(); ++it) { 201 28 : if (name == (*it)->subject) { 202 28 : return true; 203 : } 204 : } 205 0 : return false; 206 : } 207 : 208 35 : Permissions::Grant_rch Permissions::find_grant(const SSL::SubjectName& name) const 209 : { 210 35 : for (Grants::const_iterator it = grants_.begin(); it != grants_.end(); ++it) { 211 7 : if (name == (*it)->subject) { 212 7 : return *it; 213 : } 214 : } 215 28 : return Grant_rch(); 216 : } 217 : 218 : namespace { 219 : typedef std::vector<std::string>::const_iterator vsiter_t; 220 : } 221 : 222 5 : bool Permissions::Action::topic_matches(const char* topic) const 223 : { 224 5 : for (vsiter_t it = topics.begin(); it != topics.end(); ++it) { 225 5 : if (AccessControlBuiltInImpl::pattern_match(topic, it->c_str())) { 226 5 : return true; 227 : } 228 : } 229 0 : return false; 230 : } 231 : 232 5 : bool Permissions::Action::partitions_match(const DDS::StringSeq& entity_partitions, AllowDeny_t allow_or_deny) const 233 : { 234 5 : const unsigned int n_entity_names = entity_partitions.length(); 235 5 : if (partitions.empty()) { 236 4 : if (allow_or_deny == DENY) { 237 : // DDS-Security v1.1 9.4.1.3.2.3.2.4 238 : // If there is no <partitions> section ... the deny action would 239 : // apply independent of the partition associated with the DDS Endpoint 240 0 : return true; 241 : } 242 : // DDS-Security v1.1 9.4.1.3.2.3.1.4 243 : // If there is no <partitions> Section within an allow rule, then the default "empty string" partition is 244 : // assumed. ... This means that the allow rule would only allow a DataWriter to publish on 245 : // the "empty string" partition. 246 : // DDS v1.4 2.2.3 "PARTITION" 247 : // The zero-length sequence is treated as a special value equivalent to a sequence containing a single 248 : // element consisting of the empty string. 249 4 : return n_entity_names == 0 || (n_entity_names == 1 && entity_partitions[0].in()[0] == 0); 250 : } 251 : 252 2 : for (unsigned int i = 0; i < n_entity_names; ++i) { 253 1 : bool found = false; 254 2 : for (vsiter_t perm_it = partitions.begin(); !found && perm_it != partitions.end(); ++perm_it) { 255 1 : if (AccessControlBuiltInImpl::pattern_match(entity_partitions[i], perm_it->c_str())) { 256 1 : found = true; 257 : } 258 : } 259 1 : if (allow_or_deny == ALLOW && !found) { 260 : // DDS-Security v1.1 9.4.1.3.2.3.1.4 261 : // In order for an action to meet the allowed partitions condition that appears 262 : // within an allow rule, the set of the Partitions associated with the DDS entity 263 : // ... must be contained in the set of partitions defined by the allowed partitions 264 : // condition section. 265 0 : return false; // i'th QoS partition name is not matched by any <partition> in Permissions 266 : } 267 1 : if (allow_or_deny == DENY && found) { 268 : // DDS-Security v1.1 9.4.1.3.2.3.2.4 269 : // In order for an action to be denied it must meet the denied partitions condition. 270 : // For this to happen one [or] more of the partition names associated with the DDS Entity 271 : // ... must match one [of] the partitions ... listed in the partitions condition section. 272 0 : return true; // i'th QoS partition name matches some <partition> in Permissions 273 : } 274 : } 275 : 276 1 : return allow_or_deny == ALLOW; 277 : } 278 : 279 : } 280 : } 281 : 282 : OPENDDS_END_VERSIONED_NAMESPACE_DECL