LCOV - code coverage report
Current view: top level - DCPS/security/AccessControl - Permissions.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 106 133 79.7 %
Date: 2023-04-30 01:32:43 Functions: 5 5 100.0 %

          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

Generated by: LCOV version 1.16