parsing, but need to finish extended value validations for confparser and then get to work actually making the stuff go

This commit is contained in:
brent s. 2018-05-30 13:43:30 -04:00
parent 7c0c7bf5c0
commit 0682137b21
5 changed files with 88 additions and 21 deletions

View File

@ -645,6 +645,13 @@
<xs:attribute name="keyid" type="t_gpg_keyid" use="required"/> <xs:attribute name="keyid" type="t_gpg_keyid" use="required"/>
<xs:attribute name="publish" type="xs:boolean" use="optional"/> <xs:attribute name="publish" type="xs:boolean" use="optional"/>
<xs:attribute name="prompt_passphrase" type="xs:boolean" use="required"/> <xs:attribute name="prompt_passphrase" type="xs:boolean" use="required"/>
<xs:attribute name="passphrase" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[!&quot;#$%&amp;\\'\(\)\*\+,\-\./0123456789:;&lt;=&gt;\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}~ ]+"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="gnupghome" use="optional"> <xs:attribute name="gnupghome" use="optional">
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">

View File

@ -56,7 +56,7 @@ class Conf(object):
self.cfg = {} self.cfg = {}
if validate_cfg: if validate_cfg:
# Validation post-substitution # Validation post-substitution
self.validate() self.validate(parsed = False)


def get_pki_obj(self, pki, pki_type): def get_pki_obj(self, pki, pki_type):
elem = {} elem = {}
@ -82,8 +82,7 @@ class Conf(object):
return(elem) return(elem)


def get_source(self, source, item, _source): def get_source(self, source, item, _source):
_source_item = {'flags': [], _source_item = {'flags': [], 'fname': None}
'fname': None}
elem = source.xpath('./{0}'.format(item))[0] elem = source.xpath('./{0}'.format(item))[0]
if item == 'checksum': if item == 'checksum':
if elem.get('explicit', False): if elem.get('explicit', False):
@ -132,6 +131,8 @@ class Conf(object):
return(_source_item) return(_source_item)


def get_xsd(self): def get_xsd(self):
if isinstance(self.xsd, lxml.etree.XMLSchema):
return(self.xsd)
if not self.xsd: if not self.xsd:
path = os.path.join(os.path.dirname(__file__), 'bdisk.xsd') path = os.path.join(os.path.dirname(__file__), 'bdisk.xsd')
else: else:
@ -231,10 +232,12 @@ class Conf(object):
for e in self.profile.xpath(_xpath): for e in self.profile.xpath(_xpath):
for se in e: for se in e:
if not isinstance(se, lxml.etree._Comment): if not isinstance(se, lxml.etree._Comment):
self.cfg[t][se.tag] = se.text self.cfg[t][se.tag] = transform.xml2py(se.text,
attrib = False)
for e in ('desc', 'uri', 'ver', 'max_recurse'): for e in ('desc', 'uri', 'ver', 'max_recurse'):
_xpath = './meta/{0}/text()'.format(e) _xpath = './meta/{0}/text()'.format(e)
self.cfg[e] = self.profile.xpath(_xpath)[0] self.cfg[e] = transform.xml2py(self.profile.xpath(_xpath)[0],
attrib = False)
# HERE is where we would handle regex patterns. # HERE is where we would handle regex patterns.
# But we don't, because they're in self.xml_suppl.btags['regex']. # But we don't, because they're in self.xml_suppl.btags['regex'].
#self.cfg['regexes'] = {} #self.cfg['regexes'] = {}
@ -249,7 +252,7 @@ class Conf(object):
self.cfg['pki'] = {'clients': []} self.cfg['pki'] = {'clients': []}
elem = self.profile.xpath('./pki')[0] elem = self.profile.xpath('./pki')[0]
self.cfg['pki']['overwrite'] = transform.xml2py( self.cfg['pki']['overwrite'] = transform.xml2py(
elem.get('overwrite', 'no')) elem.get('overwrite', 'false'))
ca = elem.xpath('./ca')[0] ca = elem.xpath('./ca')[0]
clients = elem.xpath('./client') clients = elem.xpath('./client')
self.cfg['pki']['ca'] = self.get_pki_obj(ca, 'ca') self.cfg['pki']['ca'] = self.get_pki_obj(ca, 'ca')
@ -266,7 +269,9 @@ class Conf(object):
'uuid': None} 'uuid': None}
for a in self.cfg['profile']: for a in self.cfg['profile']:
if a in self.profile.attrib: if a in self.profile.attrib:
self.cfg['profile'][a] = self.profile.attrib[a] self.cfg['profile'][a] = transform.xml2py(
self.profile.attrib[a],
attrib = True)
return() return()


def parse_sources(self): def parse_sources(self):
@ -306,13 +311,49 @@ class Conf(object):
attrib = False) attrib = False)
return() return()


def validate(self): def validate(self, parsed = False):
# TODO: perform further validations that we can't do in XSD.
xsd = self.get_xsd() xsd = self.get_xsd()
self.xsd = etree.XMLSchema(xsd) if not isinstance(xsd, lxml.etree.XMLSchema):
self.xsd = etree.XMLSchema(xsd)
else:
pass
# This would return a bool if it validates or not. # This would return a bool if it validates or not.
#self.xsd.validate(self.xml) #self.xsd.validate(self.xml)
# We want to get a more detailed exception. # We want to get a more detailed exception.
xml = etree.fromstring(self.xml_suppl.return_full()) xml = etree.fromstring(self.xml_suppl.return_full())
self.xsd.assertValid(xml) self.xsd.assertValid(xml)
if parsed:
# TODO: perform further validations that we can't do in XSD.
# We wait until after it's parsed to evaluate because otherwise we
# can't use utils.valid().
# We only bother with stuff that would hinder building, though -
# e.g. we don't check that profile's UUID is a valid UUID4.
# URLs
for url in (self.cfg['uri'], self.cfg['dev']['website']):
if not valid.url(url):
raise ValueError('{0} is not a valid URL.'.format(url))
# Emails
for k in self.cfg['gpg']['keys']:
if not valid.email(k['email']):
raise ValueError(
'GPG key {0}: {1} is not a valid email '
'address'.format(k['name'], k['email']))
if not valid.email(self.cfg['dev']['email']):
raise ValueError('{0} is not a valid email address'.format(
self.cfg['dev']['email']))
if self.cfg['pki']:
if 'subject' in self.cfg['pki']['ca']:
if not valid.email(
self.cfg['pki']['ca']['subject']['emailAddress']):
raise ValueError('{0} is not a valid email '
'address'.format(
self.cfg['pki']['ca']['subject']['emailAddress']))

if not self.cfg['pki'][x]['subject']:
continue
if not valid.email(
self.cfg['pki'][x]['subject']['emailAddress']):
raise ValueError('{0} is not a valid email '
'address'.format(
self.cfg['pki'][x]['subject']['email']))
return() return()

View File

@ -77,6 +77,8 @@ class detect(object):


def password_hash_salt(self, salted_hash): def password_hash_salt(self, salted_hash):
_hash_list = salted_hash.split('$') _hash_list = salted_hash.split('$')
if len(_hash_list) < 3:
return(None)
salt = _hash_list[2] salt = _hash_list[2]
return(salt) return(salt)


@ -630,10 +632,28 @@ class transform(object):
if _hash: if _hash:
acct['hash_algo'] = _hash acct['hash_algo'] = _hash
else: else:
raise ValueError( acct['hash_algo'] = None
'Invalid salted password hash: {0}'.format( # We no longer raise ValueError. Per shadow(5):
elem.text) #######################################################
) # If the password field contains some string that is
# not a valid result of crypt(3), for instance ! or *,
# the user will not be able to use a unix password to
# log in (but the user may log in the system by other
# means).
# This field may be empty, in which case no passwords
# are required to authenticate as the specified login
# name. However, some applications which read the
# /etc/shadow file may decide not to permit any access
# at all if the password field is empty.
# A password field which starts with an exclamation
# mark means that the password is locked. The remaining
# characters on the line represent the password field
# before the password was locked.
#######################################################
# raise ValueError(
# 'Invalid salted password hash: {0}'.format(
# elem.text)
# )
acct['salt_hash'] = elem.text acct['salt_hash'] = elem.text
acct['passphrase'] = None acct['passphrase'] = None
else: else:
@ -873,13 +893,13 @@ class xml_supplicant(object):
return(cfg) return(cfg)


def _parse_regexes(self): def _parse_regexes(self):
for regex in self.profile.xpath('//meta/regexes/pattern'): for regex in self.profile.xpath('./meta/regexes/pattern'):
_key = 'regex%{0}'.format(regex.attrib['id']) _key = 'regex%{0}'.format(regex.attrib['id'])
self.btags['regex'][_key] = regex.text self.btags['regex'][_key] = regex.text
return() return()


def _parse_variables(self): def _parse_variables(self):
for variable in self.profile.xpath('//meta/variables/variable'): for variable in self.profile.xpath('./meta/variables/variable'):
self.btags['variable'][ self.btags['variable'][
'variable%{0}'.format(variable.attrib['id']) 'variable%{0}'.format(variable.attrib['id'])
] = variable.text ] = variable.text
@ -976,7 +996,6 @@ class xml_supplicant(object):
return(path) return(path)


def return_full(self): def return_full(self):
#nsmap = self.return_naked_ns()
# https://stackoverflow.com/a/22553145/733214 # https://stackoverflow.com/a/22553145/733214
local_xml = lxml.etree.Element('bdisk', local_xml = lxml.etree.Element('bdisk',
nsmap = self.orig_xml.nsmap, nsmap = self.orig_xml.nsmap,

View File

@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash


xmllint -schema ../bdisk/bdisk.xsd ../docs/examples/multi_profile.xml --noout xmllint -schema /opt/dev/bdisk/bdisk/bdisk.xsd /opt/dev/bdisk/docs/examples/multi_profile.xml --noout

View File

@ -43,10 +43,10 @@
<!-- Salted/hashed password is "test" --> <!-- Salted/hashed password is "test" -->
<rootpass hashed="true">$6$7KfIdtHTcXwVrZAC$LZGNeMNz7v5o/cYuA48FAxtZynpIwO5B1CPGXnOW5kCTVpXVt4SypRqfM.AoKkFt/O7MZZ8ySXJmxpELKmdlF1</rootpass> <rootpass hashed="true">$6$7KfIdtHTcXwVrZAC$LZGNeMNz7v5o/cYuA48FAxtZynpIwO5B1CPGXnOW5kCTVpXVt4SypRqfM.AoKkFt/O7MZZ8ySXJmxpELKmdlF1</rootpass>
<user sudo="true"> <user sudo="true">
<username>{xpath%//meta/names/uxname/text()}</username> <username>{xpath%../../../meta/names/uxname/text()}</username>
<!-- You can also use substitution from different profiles in this same configuration: --> <!-- You can also use substitution from different profiles in this same configuration: -->
<!-- <username>{xpath%//profile[@name='another_profile']/meta/names/uxname"}</username> --> <!-- <username>{xpath%//profile[@name='another_profile']/meta/names/uxname"}</username> -->
<comment>{xpath%//meta/dev/author/text()}</comment> <comment>{xpath%../../../meta/dev/author/text()}</comment>
<password hashed="false" <password hashed="false"
hash_algo="sha512" hash_algo="sha512"
salt="auto">testpassword</password> salt="auto">testpassword</password>
@ -153,7 +153,7 @@
</subject> </subject>
</client> </client>
</pki> </pki>
<!-- If prompt_passphrase is "no" and passphrase attribute is not given for a gpg element, we will try to use a <!-- If prompt_passphrase is "false" and passphrase attribute is not given for a gpg element, we will try to use a
blank passphrase for all operations. --> blank passphrase for all operations. -->
<gpg keyid="none" <gpg keyid="none"
gnupghome="none" gnupghome="none"