#3951 API fields validator for avatar_email
Merged 5 years ago by pingou. Opened 5 years ago by lenkaseg.
Unknown source wtforms_api_validators  into  master

file modified
+6 -2
@@ -29,10 +29,10 @@

  import wtforms

  

  import pagure.lib.query

+ import pagure.validators

  from pagure.config import config as pagure_config

  from pagure.utils import urlpattern, is_admin

  

- 

  STRICT_REGEX = "^[a-zA-Z0-9-_]+$"

  TAGS_REGEX = "^[a-zA-Z0-9-_, .:]+$"

  FALSE_VALUES = ("false", "", False, "False", 0, "0")
@@ -141,7 +141,11 @@

          ],

      )

      avatar_email = wtforms.StringField(

-         "Avatar email", [wtforms.validators.optional()]

+         "Avatar email",

+         [

+             pagure.validators.EmailValidator("avatar_email must be an email"),

+             wtforms.validators.optional(),

+         ],

      )

      tags = wtforms.StringField(

          "Project tags",

file added
+21
@@ -0,0 +1,21 @@

+ import re

+ 

+ import six

+ from wtforms import validators

+ 

+ 

+ class EmailValidator(object):

+     """Validates wtform email field"""

+ 

+     def __init__(self, message="The field data was not an email"):

+         self._message = message

+ 

+     def __call__(self, form, email):

+         if not isinstance(email.data, six.text_type):

+             raise validators.ValidationError(

+                 "Email fields should be of text type. Found {0}".format(

+                     type(email.data)

+                 )

+             )

+         elif not re.match(r"[^@]+@[^@]+\.[^@]+", email.data):

+             raise validators.ValidationError(self._message)

@@ -2102,6 +2102,78 @@

          )

  

          data = {

+              'name': 'api1',

+              'description': 'Mighty mighty description',

+              'avatar_email': 123

+         }

+ 

+         # invalid avatar_email - number

+         output = self.app.post(

+                 '/api/0/new/', data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data,

+                 {"error": "Invalid or incomplete input submitted",

+                     "error_code": "EINVALIDREQ",

+                     "errors":

+                     {"avatar_email": ['avatar_email must be an email']}

+                 }

+         )

+ 

+         data = {

+              'name': 'api1',

+              'description': 'Mighty mighty description',

+              'avatar_email': [1,2,3]

+         }

+ 

+         # invalid avatar_email - list

+         output = self.app.post(

+                 '/api/0/new/', data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data,

+                 {"error": "Invalid or incomplete input submitted",

+                     "error_code": "EINVALIDREQ",

+                     "errors":

+                     {"avatar_email": ['avatar_email must be an email']}

+                 }

+         )

+ 

+         data = {

+              'name': 'api1',

+              'description': 'Mighty mighty description',

+              'avatar_email': True

+         }

+ 

+         # invalid avatar_email - boolean

+         output = self.app.post(

+                 '/api/0/new/', data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data,

+                 {"error": "Invalid or incomplete input submitted",

+                     "error_code": "EINVALIDREQ",

+                     "errors":

+                     {"avatar_email": ['avatar_email must be an email']}

+                 }

+         )

+ 

+         data = {

+              'name': 'api1',

+              'description': 'Mighty mighty description',

+              'avatar_email': 'mighty@email.com'

+         }

+ 

+         # valid avatar_email

+         output = self.app.post(

+                 '/api/0/new/', data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+                 data,

+                 {'message': 'Project "api1" created'})

+ 

+         data = {

              'name': 'test_42',

              'description': 'Just another small test project',

          }

Custom wtform validator made for avatar_email.

Partly fixes #2485

We use full-path imports (see below), let's follow this style here as well, and since it's a pagure module, it should be in the next block of imports with the other imports from pagure

This is a select field, so I'm not sure these checks are necessary, did you really have a problem with them? If so that sounds like a bug in our code

We use full-path imports (see below), let's follow this style here as well, and since it's a pagure module, it should be in the next block of imports with the other imports from pagure

done

Did you see: http://wtforms.simplecodes.com/docs/0.6.1/validators.html#wtforms.validators.Email ?

The problem with their email validator is, that they assume it's a string (as coming from a form, not any value type as coming from a json API call).
Also, the error messages were unclear - too general. Are you ok with this custom one, or should I return to the built-in one?

This is a select field, so I'm not sure these checks are necessary, did you really have a problem with them? If so that sounds like a bug in our code

As a namespace, in the api call, I was able to pass whatever: True, 123, [1,2,3].

As a namespace, in the api call, I was able to pass whatever: True, 123, [1,2,3].

Hm, odd, that sounds like a bug in the API code, thanks for finding this

Hm, looks like the API endpoint at https://pagure.io/pagure/blob/master/f/pagure/api/project.py#_758 restricts the namespace available, odd :(

I tried to pass different types of values as namespace in api call and it throws an error as it should. It seems I made a mistake and tested something else...who knows..anyways, it works :)

But I found a bug on wtforms. When I try to pass True as namespace, I think I should get this error: raise ValueError(self.gettext('Invalid Choice: could not coerce')). Instead I get an AttributeError: 'bool' object has no attribute 'decode'

Hm, seems it's expected a string and you're passing a boolean. Is this while calling wtforms via the API or by calling it directly? I'd expect that via the API (or the UI), everything gets passed as a string (since wtforms is what converts these variable to boolean when they should be)

Yes, it is while calling wtforms via the API, using python requests. Do you want me to show you the script I'm using?

I think the problem could be in def convert_value. The method it trying to call decode on boolean when it should instead just do six.string_type(val).

Yes, it is while calling wtforms via the API, using python requests. Do you want me to show you the script I'm using?

If you still have it, it would be most interesting yes, we could/should even turn it into a test for pagure :)

Pingou, does the email validator look good to you?

@lenkaseg Alright, if you have some time this week, let's try to end this PR (by either merging it or not :)).

I ran the script you linked to and it seems to have worked fine, was this not the intent?
Could we add some tests to this PR showing the issues with the current validators?

I think the issue with the email was that in an API call I could pass [1,2,3] or True as an email.

I tried to pass following parameters with values.

avatar_email = 123 (pass)
avatar_email = [1,2,3] (pass as 1)
avatar_email = True (pass as true)
avatar_email = 'fdagre' (pass)

namespace = 123 (doesn't pass, attribute error, forms.py 65)
namespace = [1,2,3] (doesn't pass, attribute error, forms.py, 65)
namespace = True (doesn't pass, attribute error, forms.py, 65)
namespace = 'fdgfs' (doesn't pass, bad request)

I'm going to write the tests.

rebased onto 572fd37a746f56516315d300cf36886af6a9e7be

5 years ago

results with validator:

avatar_email = 123 (does not pass: Email fields should be of text type.) avatar_email = [1,2,3] (does not pass: Email fields should be of text type.)
avatar_email = True (does not pass; Email fields should be of text type.)
avatar_email = 'fdagre' (does not pass: avatar_email must be an email) avatar_email = 'mighty@email.com' (pass)

pretty please pagure-ci rebuild

5 years ago

pretty please pagure-ci rebuild

5 years ago

rebased onto 066455a1b06c6e8309f12552a3fc405bdb4e1969

5 years ago

rebased onto c9d87d568b3378d160ef50ca8c0ef6f3c2e99c85

5 years ago

pretty please pagure-ci rebuild

5 years ago

pretty please pagure-ci rebuild

5 years ago

rebased onto 9f6b369d4031add04ef947c2c91143e7e20011d8

5 years ago

rebased onto 1b5751b25034d3169acc88b5c2edc2cb5634807c

5 years ago

let's do: if not isinstance(email.data, six.tex_type):, it's the more pythonic way to do this :)

Shouldn't we check the data returned as well?

rebased onto 90a7a3a074a84e8f847bf6e90ee7687e8b102b21

5 years ago

rebased onto d6070b5764e256ea8cb9fa3fdc5125ce61adfc80

5 years ago

rebased onto b7b6b4683b6b1bce64c21134b6d98b73c26de246

5 years ago

rebased onto a46c81f7191621348d4660f0fdeb1f590a3f1b8c

5 years ago

I incorporated the changes and rebased.

pretty please pagure-ci rebuild

5 years ago

In pagure we keep the import ... above the from ... import... so we should move two lines up :)

One last detail and it's looking good :)

I'll fix it! Now only why Jenkins fails..

it's failing for all PRs atm :(

rebased onto 64cf5d2

5 years ago

Pull-Request has been merged by pingou

5 years ago