project-callisto/callisto-core

View on GitHub
callisto_core/reporting/validators.py

Summary

Maintainability
A
1 hr
Test Coverage
import logging
import re

from six.moves.urllib.parse import parse_qs, urlsplit

from django.core.exceptions import ValidationError
from django.core.validators import EmailValidator
from django.forms import URLField

logger = logging.getLogger(__name__)


def _clean_url(url):
    url_field = URLField()
    return url_field.clean(url.strip())


def _get_url_parts(url):
    url = _clean_url(url)
    return urlsplit(url)


def _get_initial_path(url_parts):
    return url_parts[2].strip("/").split("/")[0].lower()


def email_validation_function(value):
    validator = EmailValidator()
    validator(value)
    return value


def phone_validation_function(value):
    if sum(map(str.isdigit, value)) != 10:
        raise ValidationError("Invalid phone number, must be 10 numbers long")
    phone = ""
    for number in re.findall(r"\d+", value):
        phone = phone + number
    return phone


def instagram_validation_function(value):
    instagram_re = "https?:\/\/(www\.)?instagram\.com\/\
([A-Za-z0-9_](?:(?:[A-Za-z0-9_]|(?:\.(?!\.))){0,28}(?:[A-Za-z0-9_]))?)"
    instagram_url = re.match(instagram_re, value)
    if instagram_url:
        instagram_url = instagram_url.group(0)
        return instagram_url
    raise ValidationError("Invalid instagram account URL.")


generic_twitter_urls = [
    "i",
    "following",
    "followers",
    "who_to_follow",
    "settings",
    "search",
    "tos",
    "privacy",
    "about",
]


def twitter_validation_function(value):
    path = None
    try:
        url_parts = _get_url_parts(value)
        # check if acceptable domain
        domain = url_parts[1]
        if not (
            domain == "twitter.com"
            or domain == "www.twitter.com"
            or domain == "mobile.twitter.com"
        ):
            return None
        path = _get_initial_path(url_parts)
    except ValidationError:
        if value.startswith("@"):
            path = value[1:]
    if not path or path == "" or len(path) > 15 or path in generic_twitter_urls:
        return None
    else:
        return path


generic_fb_urls = [
    "messages",
    "hashtag",
    "events",
    "pages",
    "groups",
    "bookmarks",
    "lists",
    "developers",
    "topic",
    "help",
    "privacy",
    "campaign",
    "policies",
    "support",
    "settings",
    "games",
    "people",
]


def facebook_validation_function(url):
    try:
        url_parts = _get_url_parts(url)
        # check if acceptable domain
        domain = url_parts[1]
        if not (domain == "facebook.com" or domain.endswith(".facebook.com")):
            return None
        path = _get_initial_path(url_parts)

        # old style numeric profiles
        if (
            path == "profile.php"
        ):  # ex. https://www.facebook.com/profile.php?id=100010279981469
            path = parse_qs(url_parts[3]).get("id")[0]
        if (
            path == "people"
        ):  # ex. https://www.facebook.com/people/John-Doe/100013326345115
            path = url_parts[2].strip("/").split("/")[2].lower()

        # TODO: validate against allowed username characteristics
        # https://github.com/project-callisto/callisto-core/issues/181
        if not path or path == "" or path.endswith(".php") or path in generic_fb_urls:
            return None
        else:
            return path
    except ValidationError:
        return None


"""
 NOTE: because identifiers are irreversibly encrypted and Facebook was the original matching identifier, Facebook
 identifiers are stored plain, with the prefix "www.facebook.com/" stripped. All other identifiers should be prefixed
 to allow for global uniqueness from Facebook profile identifiers.
"""


def perp_identifiers():
    return {
        "email": {
            "label": "WHAT IS THEIR TWITTER HANDLE?",
            "id": "email",
            "validation_function": email_validation_function,
            "example": "",
            "unique_prefix": "email",
        },
        "twitter": {
            "label": "WHAT IS THEIR TWITTER HANDLE?",
            "id": "twitter",
            "validation_function": twitter_validation_function,
            "example": "http://www.twitter.com/perpetratorname or @perpetratorname",
            "unique_prefix": "twitter",
        },
        "facebook": {
            "label": "WHAT IS THEIR FACEBOOK URL?",
            "id": "facebook",
            "validation_function": facebook_validation_function,
            "example": "http://www.facebook.com/perpetratorname",
            "unique_prefix": "",  # Left blank by DESIGN (backwards compat to original matching system)
        },
        "phone": {
            "label": "WHAT IS THEIR MOBILE NUMBER?",
            "id": "phone",
            "validation_function": phone_validation_function,
            "example": "(xxx) xxx xxxx",
            "unique_prefix": "phone",
        },
        "instagram": {
            "label": "WHAT IS THEIR INSTAGRAM?",
            "id": "instagram",
            "validation_function": instagram_validation_function,
            "example": "http://www.instagram.com/perpetratorname",
            "unique_prefix": "instagram",
        },
    }


def join_list_with_or(lst):
    if len(lst) < 2:
        return lst[0]
    all_but_last = ", ".join(lst[:-1])
    last = lst[-1]
    return " or ".join([all_but_last, last])


class Validators(object):
    def __init__(self, validator):
        self.validator = validator

    def invalid(self):
        return "Please enter a valid " + self.validator["id"]

    def titled(self):
        return "Perpetrator's " + self.validator["id"]

    def examples(self):
        return "ex. " + self.validator["example"]