forms.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. from Crypto.Cipher import PKCS1_OAEP
  2. from Crypto.PublicKey import RSA
  3. from django import forms
  4. from taggit.forms import TagField
  5. from dcim.models import Device
  6. from extras.forms import (
  7. AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelForm, CustomFieldModelCSVForm,
  8. )
  9. from utilities.forms import (
  10. APISelect, APISelectMultiple, BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
  11. FlexibleModelChoiceField, SlugField, StaticSelect2Multiple, TagFilterField,
  12. )
  13. from .constants import *
  14. from .models import Secret, SecretRole, UserKey
  15. def validate_rsa_key(key, is_secret=True):
  16. """
  17. Validate the format and type of an RSA key.
  18. """
  19. if key.startswith('ssh-rsa '):
  20. raise forms.ValidationError("OpenSSH line format is not supported. Please ensure that your public is in PEM (base64) format.")
  21. try:
  22. key = RSA.importKey(key)
  23. except ValueError:
  24. raise forms.ValidationError("Invalid RSA key. Please ensure that your key is in PEM (base64) format.")
  25. except Exception as e:
  26. raise forms.ValidationError("Invalid key detected: {}".format(e))
  27. if is_secret and not key.has_private():
  28. raise forms.ValidationError("This looks like a public key. Please provide your private RSA key.")
  29. elif not is_secret and key.has_private():
  30. raise forms.ValidationError("This looks like a private key. Please provide your public RSA key.")
  31. try:
  32. PKCS1_OAEP.new(key)
  33. except Exception:
  34. raise forms.ValidationError("Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP.")
  35. #
  36. # Secret roles
  37. #
  38. class SecretRoleForm(BootstrapMixin, forms.ModelForm):
  39. slug = SlugField()
  40. class Meta:
  41. model = SecretRole
  42. fields = [
  43. 'name', 'slug', 'description', 'users', 'groups',
  44. ]
  45. widgets = {
  46. 'users': StaticSelect2Multiple(),
  47. 'groups': StaticSelect2Multiple(),
  48. }
  49. class SecretRoleCSVForm(forms.ModelForm):
  50. slug = SlugField()
  51. class Meta:
  52. model = SecretRole
  53. fields = SecretRole.csv_headers
  54. help_texts = {
  55. 'name': 'Name of secret role',
  56. }
  57. #
  58. # Secrets
  59. #
  60. class SecretForm(BootstrapMixin, CustomFieldModelForm):
  61. plaintext = forms.CharField(
  62. max_length=SECRET_PLAINTEXT_MAX_LENGTH,
  63. required=False,
  64. label='Plaintext',
  65. widget=forms.PasswordInput(
  66. attrs={
  67. 'class': 'requires-session-key',
  68. }
  69. )
  70. )
  71. plaintext2 = forms.CharField(
  72. max_length=SECRET_PLAINTEXT_MAX_LENGTH,
  73. required=False,
  74. label='Plaintext (verify)',
  75. widget=forms.PasswordInput()
  76. )
  77. role = DynamicModelChoiceField(
  78. queryset=SecretRole.objects.all(),
  79. widget=APISelect(
  80. api_url="/api/secrets/secret-roles/"
  81. )
  82. )
  83. tags = TagField(
  84. required=False
  85. )
  86. class Meta:
  87. model = Secret
  88. fields = [
  89. 'role', 'name', 'plaintext', 'plaintext2', 'tags',
  90. ]
  91. def __init__(self, *args, **kwargs):
  92. super().__init__(*args, **kwargs)
  93. # A plaintext value is required when creating a new Secret
  94. if not self.instance.pk:
  95. self.fields['plaintext'].required = True
  96. def clean(self):
  97. # Verify that the provided plaintext values match
  98. if self.cleaned_data['plaintext'] != self.cleaned_data['plaintext2']:
  99. raise forms.ValidationError({
  100. 'plaintext2': "The two given plaintext values do not match. Please check your input."
  101. })
  102. class SecretCSVForm(CustomFieldModelCSVForm):
  103. device = FlexibleModelChoiceField(
  104. queryset=Device.objects.all(),
  105. to_field_name='name',
  106. help_text='Device name or ID',
  107. error_messages={
  108. 'invalid_choice': 'Device not found.',
  109. }
  110. )
  111. role = forms.ModelChoiceField(
  112. queryset=SecretRole.objects.all(),
  113. to_field_name='name',
  114. help_text='Name of assigned role',
  115. error_messages={
  116. 'invalid_choice': 'Invalid secret role.',
  117. }
  118. )
  119. plaintext = forms.CharField(
  120. help_text='Plaintext secret data'
  121. )
  122. class Meta:
  123. model = Secret
  124. fields = Secret.csv_headers
  125. help_texts = {
  126. 'name': 'Name or username',
  127. }
  128. def save(self, *args, **kwargs):
  129. s = super().save(*args, **kwargs)
  130. s.plaintext = str(self.cleaned_data['plaintext'])
  131. return s
  132. class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
  133. pk = forms.ModelMultipleChoiceField(
  134. queryset=Secret.objects.all(),
  135. widget=forms.MultipleHiddenInput()
  136. )
  137. role = DynamicModelChoiceField(
  138. queryset=SecretRole.objects.all(),
  139. required=False,
  140. widget=APISelect(
  141. api_url="/api/secrets/secret-roles/"
  142. )
  143. )
  144. name = forms.CharField(
  145. max_length=100,
  146. required=False
  147. )
  148. class Meta:
  149. nullable_fields = [
  150. 'name',
  151. ]
  152. class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
  153. model = Secret
  154. q = forms.CharField(
  155. required=False,
  156. label='Search'
  157. )
  158. role = DynamicModelMultipleChoiceField(
  159. queryset=SecretRole.objects.all(),
  160. to_field_name='slug',
  161. required=True,
  162. widget=APISelectMultiple(
  163. api_url="/api/secrets/secret-roles/",
  164. value_field="slug",
  165. )
  166. )
  167. tag = TagFilterField(model)
  168. #
  169. # UserKeys
  170. #
  171. class UserKeyForm(BootstrapMixin, forms.ModelForm):
  172. class Meta:
  173. model = UserKey
  174. fields = ['public_key']
  175. help_texts = {
  176. 'public_key': "Enter your public RSA key. Keep the private one with you; you'll need it for decryption. "
  177. "Please note that passphrase-protected keys are not supported.",
  178. }
  179. labels = {
  180. 'public_key': ''
  181. }
  182. def clean_public_key(self):
  183. key = self.cleaned_data['public_key']
  184. # Validate the RSA key format.
  185. validate_rsa_key(key, is_secret=False)
  186. return key
  187. class ActivateUserKeyForm(forms.Form):
  188. _selected_action = forms.ModelMultipleChoiceField(
  189. queryset=UserKey.objects.all(),
  190. label='User Keys'
  191. )
  192. secret_key = forms.CharField(
  193. widget=forms.Textarea(
  194. attrs={
  195. 'class': 'vLargeTextField',
  196. }
  197. ),
  198. label='Your private key'
  199. )