names.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. import six
  2. from gssapi.raw import names as rname
  3. from gssapi.raw import NameType
  4. from gssapi.raw import named_tuples as tuples
  5. from gssapi import _utils
  6. if six.PY2:
  7. from collections import MutableMapping, Iterable
  8. else:
  9. from collections.abc import MutableMapping, Iterable
  10. rname_rfc6680 = _utils.import_gssapi_extension('rfc6680')
  11. rname_rfc6680_comp_oid = _utils.import_gssapi_extension('rfc6680_comp_oid')
  12. class Name(rname.Name):
  13. """A GSSAPI Name
  14. This class represents a GSSAPI name which may be used with and/or returned
  15. by other GSSAPI methods.
  16. It inherits from the low-level GSSAPI :class:`~gssapi.raw.names.Name`
  17. class, and thus may used with both low-level and high-level API methods.
  18. This class may be pickled and unpickled, as well as copied.
  19. The :func:`str` and :func:`bytes` methods may be used to retrieve the
  20. text of the name.
  21. Note:
  22. Name strings will be automatically converted to and from unicode
  23. strings as appropriate. If a method is listed as returning a
  24. :class:`str` object, it will return a unicode string.
  25. The encoding used will be python-gssapi's current encoding, which
  26. defaults to UTF-8.
  27. """
  28. __slots__ = ('_attr_obj')
  29. def __new__(cls, base=None, name_type=None, token=None,
  30. composite=False):
  31. if token is not None:
  32. if composite:
  33. if rname_rfc6680 is None:
  34. raise NotImplementedError(
  35. "Your GSSAPI implementation does not support RFC 6680 "
  36. "(the GSSAPI naming extensions)")
  37. if rname_rfc6680_comp_oid is not None:
  38. base_name = rname.import_name(token,
  39. NameType.composite_export)
  40. displ_name = rname.display_name(base_name, name_type=True)
  41. if displ_name.name_type == NameType.composite_export:
  42. # NB(directxman12): there's a bug in MIT krb5 <= 1.13
  43. # where GSS_C_NT_COMPOSITE_EXPORT doesn't trigger
  44. # immediate import logic. However, we can just use
  45. # the normal GSS_C_NT_EXPORT_NAME in this case.
  46. base_name = rname.import_name(token, NameType.export)
  47. else:
  48. # NB(directxman12): some older versions of MIT krb5 don't
  49. # have support for the GSS_C_NT_COMPOSITE_EXPORT, but do
  50. # support composite tokens via GSS_C_NT_EXPORT_NAME.
  51. base_name = rname.import_name(token, NameType.export)
  52. else:
  53. base_name = rname.import_name(token, NameType.export)
  54. elif isinstance(base, rname.Name):
  55. base_name = base
  56. else:
  57. if isinstance(base, six.text_type):
  58. base = base.encode(_utils._get_encoding())
  59. base_name = rname.import_name(base, name_type)
  60. return super(Name, cls).__new__(cls, base_name)
  61. def __init__(self, base=None, name_type=None, token=None, composite=False):
  62. """
  63. The constructor can be used to "import" a name from a human readable
  64. representation, or from a token, and can also be used to convert a
  65. low-level :class:`gssapi.raw.names.Name` object into a high-level
  66. object.
  67. If a :class:`~gssapi.raw.names.Name` object from the low-level API
  68. is passed as the `base` argument, it will be converted into a
  69. high-level object.
  70. If the `token` argument is used, the name will be imported using
  71. the token. If the token was exported as a composite token,
  72. pass `composite=True`.
  73. Otherwise, a new name will be created, using the `base` argument as
  74. the human-readable string and the `name_type` argument to denote the
  75. name type.
  76. Raises:
  77. BadNameTypeError
  78. BadNameError
  79. BadMechanismError
  80. """
  81. if rname_rfc6680 is not None:
  82. self._attr_obj = _NameAttributeMapping(self)
  83. else:
  84. self._attr_obj = None
  85. def __str__(self):
  86. if issubclass(str, six.text_type):
  87. # Python 3 -- we should return unicode
  88. return bytes(self).decode(_utils._get_encoding())
  89. else:
  90. # Python 2 -- we should return a string
  91. return self.__bytes__()
  92. def __unicode__(self):
  93. # Python 2 -- someone asked for unicode
  94. return self.__bytes__().decode(_utils._get_encoding())
  95. def __bytes__(self):
  96. # Python 3 -- someone asked for bytes
  97. return rname.display_name(self, name_type=False).name
  98. def display_as(self, name_type):
  99. """
  100. Display this name as the given name type.
  101. This method attempts to display the current :class:`Name`
  102. using the syntax of the given :class:`NameType`, if possible.
  103. Warning:
  104. In MIT krb5 versions below 1.13.3, this method can segfault if
  105. the name was not *originally* created with a `name_type` that was
  106. not ``None`` (even in cases when a ``name_type``
  107. is later "added", such as via :meth:`canonicalize`).
  108. **Do not use this method unless you are sure the above
  109. conditions can never happen in your code.**
  110. Warning:
  111. In addition to the above warning, current versions of MIT krb5 do
  112. not actually fully implement this method, and it may return
  113. incorrect results in the case of canonicalized names.
  114. :requires-ext:`rfc6680`
  115. Args:
  116. name_type (OID): the :class:`NameType` to use to display the given
  117. name
  118. Returns:
  119. str: the displayed name
  120. Raises:
  121. OperationUnavailableError
  122. """
  123. if rname_rfc6680 is None:
  124. raise NotImplementedError("Your GSSAPI implementation does not "
  125. "support RFC 6680 (the GSSAPI naming "
  126. "extensions)")
  127. return rname_rfc6680.display_name_ext(self, name_type).decode(
  128. _utils._get_encoding())
  129. @property
  130. def name_type(self):
  131. """The :class:`NameType` of this name"""
  132. return rname.display_name(self, name_type=True).name_type
  133. def __eq__(self, other):
  134. if not isinstance(other, rname.Name):
  135. # maybe something else can compare this
  136. # to other classes, but we certainly can't
  137. return NotImplemented
  138. else:
  139. return rname.compare_name(self, other)
  140. def __ne__(self, other):
  141. return not self.__eq__(other)
  142. def __repr__(self):
  143. disp_res = rname.display_name(self, name_type=True)
  144. return "Name({name}, {name_type})".format(name=disp_res.name,
  145. name_type=disp_res.name_type)
  146. def export(self, composite=False):
  147. """Export this name as a token.
  148. This method exports the name into a byte string which can then be
  149. imported by using the `token` argument of the constructor.
  150. Args:
  151. composite (bool): whether or not use to a composite token --
  152. :requires-ext:`rfc6680`
  153. Returns:
  154. bytes: the exported name in token form
  155. Raises:
  156. MechanismNameRequiredError
  157. BadNameTypeError
  158. BadNameError
  159. """
  160. if composite:
  161. if rname_rfc6680 is None:
  162. raise NotImplementedError("Your GSSAPI implementation does "
  163. "not support RFC 6680 (the GSSAPI "
  164. "naming extensions)")
  165. return rname_rfc6680.export_name_composite(self)
  166. else:
  167. return rname.export_name(self)
  168. def canonicalize(self, mech):
  169. """Canonicalize a name with respect to a mechanism.
  170. This method returns a new :class:`Name` that is canonicalized according
  171. to the given mechanism.
  172. Args:
  173. mech (OID): the :class:`MechType` to use
  174. Returns:
  175. Name: the canonicalized name
  176. Raises:
  177. BadMechanismError
  178. BadNameTypeError
  179. BadNameError
  180. """
  181. return type(self)(rname.canonicalize_name(self, mech))
  182. def __copy__(self):
  183. return type(self)(rname.duplicate_name(self))
  184. def __deepcopy__(self, memo):
  185. return type(self)(rname.duplicate_name(self))
  186. def _inquire(self, **kwargs):
  187. """Inspect this name for information.
  188. This method inspects the name for information.
  189. If no keyword arguments are passed, all available information
  190. is returned. Otherwise, only the keyword arguments that
  191. are passed and set to `True` are returned.
  192. Args:
  193. mech_name (bool): get whether this is a mechanism name,
  194. and, if so, the associated mechanism
  195. attrs (bool): get the attributes names for this name
  196. Returns:
  197. InquireNameResult: the results of the inquiry, with unused
  198. fields set to None
  199. Raises:
  200. GSSError
  201. """
  202. if rname_rfc6680 is None:
  203. raise NotImplementedError("Your GSSAPI implementation does not "
  204. "support RFC 6680 (the GSSAPI naming "
  205. "extensions)")
  206. if not kwargs:
  207. default_val = True
  208. else:
  209. default_val = False
  210. attrs = kwargs.get('attrs', default_val)
  211. mech_name = kwargs.get('mech_name', default_val)
  212. return rname_rfc6680.inquire_name(self, mech_name=mech_name,
  213. attrs=attrs)
  214. @property
  215. def is_mech_name(self):
  216. """Whether or not this name is a mechanism name
  217. (:requires-ext:`rfc6680`)
  218. """
  219. return self._inquire(mech_name=True).is_mech_name
  220. @property
  221. def mech(self):
  222. """The mechanism associated with this name (:requires-ext:`rfc6680`)
  223. """
  224. return self._inquire(mech_name=True).mech
  225. @property
  226. def attributes(self):
  227. """The attributes of this name (:requires-ext:`rfc6680`)
  228. The attributes are presenting in the form of a
  229. :class:`~collections.MutableMapping` (a dict-like object).
  230. Retrieved values will always be in the form of :class:`frozensets`.
  231. When assigning values, if iterables are used, they be considered to be
  232. the set of values for the given attribute. If a non-iterable is used,
  233. it will be considered a single value, and automatically wrapped in an
  234. iterable.
  235. Note:
  236. String types (includes :class:`bytes`) are not considered to
  237. be iterables in this case.
  238. """
  239. if self._attr_obj is None:
  240. raise NotImplementedError("Your GSSAPI implementation does not "
  241. "support RFC 6680 (the GSSAPI naming "
  242. "extensions)")
  243. return self._attr_obj
  244. class _NameAttributeMapping(MutableMapping):
  245. """Provides dict-like access to RFC 6680 Name attributes."""
  246. def __init__(self, name):
  247. self._name = name
  248. def __getitem__(self, key):
  249. if isinstance(key, six.text_type):
  250. key = key.encode(_utils._get_encoding())
  251. res = rname_rfc6680.get_name_attribute(self._name, key)
  252. return tuples.GetNameAttributeResult(frozenset(res.values),
  253. frozenset(res.display_values),
  254. res.authenticated,
  255. res.complete)
  256. def __setitem__(self, key, value):
  257. if isinstance(key, six.text_type):
  258. key = key.encode(_utils._get_encoding())
  259. rname_rfc6680.delete_name_attribute(self._name, key)
  260. if isinstance(value, tuples.GetNameAttributeResult):
  261. complete = value.complete
  262. value = value.values
  263. elif isinstance(value, tuple) and len(value) == 2:
  264. complete = value[1]
  265. value = value[0]
  266. else:
  267. complete = False
  268. if (isinstance(value, (six.string_types, bytes)) or
  269. not isinstance(value, Iterable)):
  270. # NB(directxman12): this allows us to easily assign a single
  271. # value, since that's a common case
  272. value = [value]
  273. rname_rfc6680.set_name_attribute(self._name, key, value,
  274. complete=complete)
  275. def __delitem__(self, key):
  276. if isinstance(key, six.text_type):
  277. key = key.encode(_utils._get_encoding())
  278. rname_rfc6680.delete_name_attribute(self._name, key)
  279. def __iter__(self):
  280. return iter(self._name._inquire(attrs=True).attrs)
  281. def __len__(self):
  282. return len(self._name._inquire(attrs=True).attrs)