models.py 12.1 KB
Newer Older
1 2
import logging

3
from django.conf import settings
4 5
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as BaseUserManager
6
from django.contrib.auth.hashers import make_password
7
from django.core import validators
8
from django.db import models
9
from django.utils import timezone
10
from django.utils.crypto import salted_hmac
11
from django.utils.deconstruct import deconstructible
12 13
from django.utils.translation import ugettext_lazy as _
from ldapregister.models import LdapPerson
14 15
from limitmonitor import models as limitmonitor_models
from limitmonitor.task_resources import common as limitmonitor_common
16 17
from limitmonitor.tunnel import TunnelManager
from cryptography.fernet import Fernet
18

19 20
from woocommerce import API as WOO_API

21 22 23
log = logging.getLogger(__name__)


24 25 26 27 28 29 30 31 32 33
def get_woo_connection():
    return WOO_API(
        url=settings.WOO_URL,
        consumer_key=settings.WOO_CONSUMER_KEY,
        consumer_secret=settings.WOO_CONSUMER_SECRET,
        wp_api=settings.WOO_WP_API,
        version=settings.WOO_VERSION,
        query_string_auth=settings.WOO_QUERY_STRING_AUTH,
    )

34 35 36 37
@deconstructible
class UsernameValidator(validators.RegexValidator):
    regex = r'^[A-Za-z][A-Za-z0-9]*$'
    message = _(
38
        'Enter a valid address prefix. Must start with a letter, followed by letters or numbers.'
39 40 41 42
        ' No punctuation or special characters.'
    )


43 44 45 46 47
class UserManager(BaseUserManager):

    def create_user(self, username, email=None, password=None, **extra_fields):
        """Create regular users in LDAP, with no Django password."""

48
        return super(UserManager, self).create_user(username, email, password, **extra_fields)
49 50 51 52

    def create_superuser(self, username, email=None, password=None, **extra_fields):
        """Create superusers with a Django password."""

53
        return super(UserManager, self).create_superuser(username, email, password, **extra_fields)
54 55 56 57 58


class User(AbstractUser):
    objects = UserManager()
    REQUIRED_FIELDS = []
59
    username_validator = UsernameValidator()
60
    woo_connection = None
61 62 63 64 65

    username = models.CharField(
        _('username'),
        max_length=150,
        unique=True,
66
        help_text=_('Required. Start with a letter, followed by letters or numbers.'),
67 68
        validators=[username_validator],
        error_messages={
69
            'unique': _("An account with that address already exists."),
70 71
        },
    )
72

73 74 75
    tunnel_user = models.CharField(max_length=250, default=None, null=True)
    tunnel_password = models.CharField(max_length=250, default=None, null=True)

76 77 78
    @classmethod
    def normalize_username(cls, username):
        username = super(User, cls).normalize_username(username)
79 80

        username = username.lower()
81 82
        suffix = "@" + settings.SITE_DOMAIN.lower()
        offset = 0 - len(suffix)
83 84

        if username.endswith(suffix):
85
            username = username[:offset]
86 87

        return username
88 89 90 91 92 93 94 95 96 97

    def get_ldap(self):
        return LdapPerson.objects.get(uid=self.get_username())

    def has_ldap(self):
        result = LdapPerson.objects.filter(uid=self.get_username())
        return len(result) == 1

    def create_ldap(self):
        username = self.get_username()
98 99
        mail = self.get_identity()
        LdapPerson.objects.create(uid=username, cn=username, sn=username, mail=mail)
100 101 102 103 104

    def set_ldap_password(self, raw_password):
        ldap_person = self.get_ldap()
        ldap_person.change_password(raw_password)

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
    def woo_get_json(self, query):

        try:
            if self.woo_connection is None:
                woo = get_woo_connection()
            result = woo.get(query).json()
        except Exception as e:
            logging.exception("Could not retrieve WooCommerce ID with query " + query)
            result = None

        return result

    def woo_post_json(self, query, data):

        try:
            if self.woo_connection is None:
                woo = get_woo_connection()
            result = woo.post(query, data).json()
        except Exception as e:
            logging.exception("Could not post " + query + " with data " + str(data))
            result = None

        return result

    def woo_put_json(self, query, data):

        try:
            if self.woo_connection is None:
                woo = get_woo_connection()
            result = woo.put(query, data).json()
        except Exception as e:
            logging.exception("Could not put " + query + " with data " + str(data))
            result = None

        return result

    def get_woocommerce_id(self):

        query = "customers?email=" + self.get_identity()
        json = self.woo_get_json(query)

        if len(json) == 1:
            return json[0]["id"]

        return None

    def create_woocommerce_account(self):

        query = "customers"

        data = {
            "email": self.get_identity(),
            "first_name": self.username,
            "last_name": settings.SITE_TITLE,
            "username": self.get_identity(),
            "password": make_password(None),  # unusable password
        }

        return self.woo_post_json(query, data)

    def set_woocommerce_password(self, raw_password, woocommerce_id=None):

        if woocommerce_id is None:
            woocommerce_id = self.get_woocommerce_id()

        query = "customers/" + str(woocommerce_id)

        data = {
            "password": raw_password,
        }

        return self.woo_put_json(query, data)

178
    def get_identity(self):
179
        return self.get_username() + "@" + settings.SITE_DOMAIN.lower()
180 181 182 183 184 185 186 187 188 189

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):

        # save django user
        super(User, self).save(force_insert, force_update, using, update_fields)

        # create LDAP user (if required)
        if not self.has_ldap():
            self.create_ldap()

190
        # force null Django password (will use LDAP password instead)
191 192
        self.set_unusable_password()

193 194 195 196
        # create WooCommerce account (if required)
        if self.get_woocommerce_id() is None:
            self.create_woocommerce_account()

197 198 199
        # create any missing limits
        limitmonitor_models.create_missing_user_limits(self)

200 201 202
        # delete invalid credits (they will be re-parsed)
        limitmonitor_models.delete_unconverted_user_credits(self)

203 204 205 206 207 208
        if settings.DEBUG_ALL_ACCESS:

            ssh = limitmonitor_common.get_openvpn_ssh_connection()
            renewal_date = timezone.now() + timezone.timedelta(weeks=5200)

            for limit in limitmonitor_models.Limit.objects.filter(user=self, is_active=False):
209 210 211
                limitmonitor_common.activate_single_limit(ssh, limit, renewal_date=renewal_date)

            ssh.close()
212

213 214 215 216 217 218 219 220 221 222 223 224 225 226
    def set_password(self, raw_password):

        # force null Django password (will use LDAP password)
        self.set_unusable_password()

        # create LDAP user (if required)
        if self.get_username():

            if not self.has_ldap():
                self.create_ldap()

            # set LDAP password
            self.set_ldap_password(raw_password)

227 228 229 230 231 232
            # FIXME: we actually need to update WooCommerce LDAP authentication
            if self.get_woocommerce_id() is None:
                self.create_woocommerce_account()

            self.set_woocommerce_password(raw_password)

233 234 235 236 237 238
    def get_session_auth_hash(self):
        """
        Return an HMAC of the password field.
        """
        key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
        return salted_hmac(key_salt, self.get_username()).hexdigest()  # FIXME: should use LDAP password value!
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256

    def has_tunnel_account(self):
        """Returns True if the user has a tunnel account. Returns False if the
        user does NOT have a tunnel account.
        """
        return self.tunnel_user is not None

    def generate_tunnel_account(self):
        """Creates a new tunnel account if the user does NOT have a tunnel
        account. Returns True when the account is created and False
        when it couldn't be created.
        """
        if not self.has_tunnel_account():
            tun = TunnelManager(settings.TUNNEL_IDENTITY,
                                settings.TUNNEL_SECRET,
                                settings.TUNNEL_HOST)
            new_account = tun.create_account()
            if new_account:
257 258
                tun_user = encrypt(new_account['username'])
                tun_passwd = encrypt(new_account['password'])
259 260 261 262 263
                self.tunnel_user = tun_user
                self.tunnel_password = tun_passwd
                self.save()
                return True
            return False
264 265
        else:
            return True
266 267 268 269 270 271 272 273 274 275

    def pause_tunnel_account(self):
        """Pauses the tunnel account for the user, setting it is_active to
        False. Returns True when it was possible to pause the account
        and False when it couldn't.
        """
        if self.has_tunnel_account():
            tun = TunnelManager(settings.TUNNEL_IDENTITY,
                                settings.TUNNEL_SECRET,
                                settings.TUNNEL_HOST)
276
            tun_user = decrypt(self.tunnel_user)
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
            result = tun.pause_account(tun_user)
            if result:
                tunnel_limit = self.limit_set.filter(
                    service=settings.LM_SERVICES.TUNNEL).first()
                tunnel_limit.is_active = False
                tunnel_limit.save()
                return True
            return False

    def resume_tunnel_account(self):
        """Resumes the tunnel account for the user, setting it is_active to
        True. Returns True when it was possible to resume the account
        and False when it was not.
        """
        if self.has_tunnel_account():
            tun = TunnelManager(settings.TUNNEL_IDENTITY,
                                settings.TUNNEL_SECRET,
                                settings.TUNNEL_HOST)
295
            tun_user = decrypt(self.tunnel_user)
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
            result = tun.resume_account(tun_user)
            if result:
                tunnel_limit = self.limit_set.filter(
                    service=settings.LM_SERVICES.TUNNEL).first()
                tunnel_limit.is_active = True
                tunnel_limit.save()
                return True
            return False

    def terminate_tunnel_account(self):
        """Terminates the tunnel account for the user. Returns True when it
        was possible to terminate the account and False when it was
        not.
        """
        if self.has_tunnel_account():
            tun = TunnelManager(settings.TUNNEL_IDENTITY,
                                settings.TUNNEL_SECRET,
                                settings.TUNNEL_HOST)
314
            tun_user = decrypt(self.tunnel_user)
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
            result = tun.terminate_account(tun_user)
            if result:
                self.tunnel_user = None
                self.tunnel_password = None
                self.save()
                tunnel_limit = self.limit_set.filter(
                    service=settings.LM_SERVICES.TUNNEL).first()
                tunnel_limit.is_active = False
                tunnel_limit.save()
                return True
            return False

    def get_tunnel_credentials(self):
        """Returns tunnel account credentials for the user decrypting
        them. Returns the tuple (tun_user, tun_passwd) if the user has
        a tunnel account and is activated. Returns None in any other
        case.
        """
        if self.has_tunnel_account():
            tunnel_limit = self.limit_set.filter(
                    service=settings.LM_SERVICES.TUNNEL).first()
            if tunnel_limit.is_active:
337 338
                tun_user = decrypt(self.tunnel_user)
                tun_passwd = decrypt(self.tunnel_password)
339 340
                return (tun_user, tun_passwd)
        return None
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361


def encrypt(data):
    # Encrypt data with Fernet
    if data is None:
        ret = None
    else:
        fer = Fernet(settings.TUNNEL_KEY)
        ret = fer.encrypt(data.encode())
    return ret


def decrypt(data):
    # Decrypt data with Fernet
    if data is None:
        ret = None
    else:
        fer = Fernet(settings.TUNNEL_KEY)
        data = data.encode()
        ret = fer.decrypt(data).decode()
    return ret