utils.py 9.5 KB
Newer Older
W
wangyong 已提交
1 2
# -*- coding: utf-8 -*-
#
baltery's avatar
baltery 已提交
3
import re
baltery's avatar
baltery 已提交
4
from collections import OrderedDict
5
from six import string_types
baltery's avatar
baltery 已提交
6
import base64
baltery's avatar
baltery 已提交
7
import os
baltery's avatar
baltery 已提交
8
from itertools import chain
baltery's avatar
baltery 已提交
9
import logging
baltery's avatar
baltery 已提交
10
import datetime
baltery's avatar
baltery 已提交
11 12 13 14 15
import time
import hashlib
from email.utils import formatdate
import calendar
import threading
baltery's avatar
baltery 已提交
16
from io import StringIO
baltery's avatar
baltery 已提交
17
import uuid
W
wangyong 已提交
18

baltery's avatar
baltery 已提交
19
import paramiko
baltery's avatar
baltery 已提交
20
import sshpubkeys
baltery's avatar
baltery 已提交
21
from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \
baltery's avatar
baltery 已提交
22
    BadSignature, SignatureExpired
W
wangyong 已提交
23 24
from django.shortcuts import reverse as dj_reverse
from django.conf import settings
baltery's avatar
baltery 已提交
25
from django.utils import timezone
W
wangyong 已提交
26

baltery's avatar
baltery 已提交
27

baltery's avatar
baltery 已提交
28
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
W
wangyong 已提交
29

baltery's avatar
baltery 已提交
30

31 32 33 34
def reverse(view_name, urlconf=None, args=None, kwargs=None,
            current_app=None, external=False):
    url = dj_reverse(view_name, urlconf=urlconf, args=args,
                     kwargs=kwargs, current_app=current_app)
W
wangyong 已提交
35 36 37 38 39 40 41 42 43

    if external:
        url = settings.SITE_URL.strip('/') + url
    return url


def get_object_or_none(model, **kwargs):
    try:
        obj = model.objects.get(**kwargs)
baltery's avatar
baltery 已提交
44
    except model.DoesNotExist:
45
        return None
W
wangyong 已提交
46
    return obj
baltery's avatar
baltery 已提交
47 48


baltery's avatar
baltery 已提交
49 50 51 52 53 54 55 56 57 58 59 60 61 62
class Singleton(type):
    def __init__(cls, *args, **kwargs):
        cls.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super().__call__(*args, **kwargs)
            return cls.__instance
        else:
            return cls.__instance


class Signer(metaclass=Singleton):
baltery's avatar
baltery 已提交
63
    """用来加密,解密,和基于时间戳的方式验证token"""
baltery's avatar
baltery 已提交
64
    def __init__(self, secret_key=None):
baltery's avatar
baltery 已提交
65
        self.secret_key = secret_key
baltery's avatar
baltery 已提交
66

baltery's avatar
baltery 已提交
67
    def sign(self, value):
baltery's avatar
baltery 已提交
68 69
        if isinstance(value, bytes):
            value = value.decode("utf-8")
baltery's avatar
baltery 已提交
70 71
        s = JSONWebSignatureSerializer(self.secret_key)
        return s.dumps(value)
baltery's avatar
baltery 已提交
72

baltery's avatar
baltery 已提交
73 74
    def unsign(self, value):
        s = JSONWebSignatureSerializer(self.secret_key)
baltery's avatar
baltery 已提交
75 76 77
        try:
            return s.loads(value)
        except BadSignature:
baltery's avatar
baltery 已提交
78
            return {}
baltery's avatar
baltery 已提交
79

baltery's avatar
baltery 已提交
80 81
    def sign_t(self, value, expires_in=3600):
        s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
82
        return str(s.dumps(value), encoding="utf8")
baltery's avatar
baltery 已提交
83

baltery's avatar
baltery 已提交
84 85
    def unsign_t(self, value):
        s = TimedJSONWebSignatureSerializer(self.secret_key)
baltery's avatar
baltery 已提交
86 87 88
        try:
            return s.loads(value)
        except (BadSignature, SignatureExpired):
baltery's avatar
baltery 已提交
89
            return {}
baltery's avatar
baltery 已提交
90 91


baltery's avatar
baltery 已提交
92 93
def date_expired_default():
    try:
baltery's avatar
baltery 已提交
94
        years = int(settings.DEFAULT_EXPIRED_YEARS)
baltery's avatar
baltery 已提交
95 96
    except TypeError:
        years = 70
baltery's avatar
baltery 已提交
97 98
    return timezone.now() + timezone.timedelta(days=365*years)

baltery's avatar
baltery 已提交
99

baltery's avatar
baltery 已提交
100 101 102 103 104 105 106 107 108 109 110
def combine_seq(s1, s2, callback=None):
    for s in (s1, s2):
        if not hasattr(s, '__iter__'):
            return []

    seq = chain(s1, s2)
    if callback:
        seq = map(callback, seq)
    return seq


baltery's avatar
baltery 已提交
111 112
def get_logger(name=None):
    return logging.getLogger('jumpserver.%s' % name)
baltery's avatar
baltery 已提交
113 114


baltery's avatar
baltery 已提交
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
def timesince(dt, since='', default="just now"):
    """
    Returns string representing "time since" e.g.
    3 days, 5 hours.
    """

    if since is '':
        since = datetime.datetime.utcnow()

    if since is None:
        return default

    diff = since - dt

    periods = (
        (diff.days / 365, "year", "years"),
        (diff.days / 30, "month", "months"),
        (diff.days / 7, "week", "weeks"),
        (diff.days, "day", "days"),
        (diff.seconds / 3600, "hour", "hours"),
        (diff.seconds / 60, "minute", "minutes"),
        (diff.seconds, "second", "seconds"),
    )

    for period, singular, plural in periods:
        if period:
            return "%d %s" % (period, singular if period == 1 else plural)
    return default

baltery's avatar
baltery 已提交
144

baltery's avatar
baltery 已提交
145
def ssh_key_string_to_obj(text, password=None):
baltery's avatar
baltery 已提交
146 147
    key = None
    try:
baltery's avatar
baltery 已提交
148
        key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
baltery's avatar
baltery 已提交
149 150 151 152
    except paramiko.SSHException:
        pass

    try:
baltery's avatar
baltery 已提交
153
        key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
baltery's avatar
baltery 已提交
154 155 156 157 158
    except paramiko.SSHException:
        pass
    return key


baltery's avatar
baltery 已提交
159
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
baltery's avatar
baltery 已提交
160 161
    if isinstance(private_key, bytes):
        private_key = private_key.decode("utf-8")
baltery's avatar
baltery 已提交
162
    if isinstance(private_key, string_types):
baltery's avatar
baltery 已提交
163
        private_key = ssh_key_string_to_obj(private_key, password=password)
baltery's avatar
baltery 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
    if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
        raise IOError('Invalid private key')

    public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
        'key_type': private_key.get_name(),
        'key_content': private_key.get_base64(),
        'username': username,
        'hostname': hostname,
    }
    return public_key


def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None):
    """Generate user ssh private and public key

    Use paramiko RSAKey generate it.
    :return private key str and public key str
    """

    if hostname is None:
        hostname = os.uname()[1]

baltery's avatar
baltery 已提交
186
    f = StringIO()
baltery's avatar
baltery 已提交
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    try:
        if type == 'rsa':
            private_key_obj = paramiko.RSAKey.generate(length)
        elif type == 'dsa':
            private_key_obj = paramiko.DSSKey.generate(length)
        else:
            raise IOError('SSH private key must be `rsa` or `dsa`')
        private_key_obj.write_private_key(f, password=password)
        private_key = f.getvalue()
        public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname)
        return private_key, public_key
    except IOError:
        raise IOError('These is error when generate ssh key.')


baltery's avatar
baltery 已提交
202
def validate_ssh_private_key(text, password=None):
baltery's avatar
baltery 已提交
203
    if isinstance(text, bytes):
baltery's avatar
baltery 已提交
204 205 206 207 208
        try:
            text = text.decode("utf-8")
        except UnicodeDecodeError:
            return False

baltery's avatar
baltery 已提交
209
    key = ssh_key_string_to_obj(text, password=password)
baltery's avatar
baltery 已提交
210 211 212 213 214 215
    if key is None:
        return False
    else:
        return True


baltery's avatar
baltery 已提交
216 217 218 219
def validate_ssh_public_key(text):
    ssh = sshpubkeys.SSHKey(text)
    try:
        ssh.parse()
baltery's avatar
baltery 已提交
220
    except (sshpubkeys.InvalidKeyException, UnicodeDecodeError):
baltery's avatar
baltery 已提交
221 222 223 224 225 226
        return False
    except NotImplementedError as e:
        return False
    return True


baltery's avatar
baltery 已提交
227 228 229 230 231 232 233
def setattr_bulk(seq, key, value):
    def set_attr(obj):
        setattr(obj, key, value)
        return obj
    return map(set_attr, seq)


baltery's avatar
baltery 已提交
234 235 236 237 238
def content_md5(data):
    """计算data的MD5值,经过Base64编码并返回str类型。

    返回值可以直接作为HTTP Content-Type头部的值
    """
baltery's avatar
baltery 已提交
239 240 241 242
    if isinstance(data, str):
        data = hashlib.md5(data.encode('utf-8'))
    value = base64.b64encode(data.digest())
    return value.decode('utf-8')
baltery's avatar
baltery 已提交
243 244 245 246 247 248 249 250

_STRPTIME_LOCK = threading.Lock()

_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"


def to_unixtime(time_string, format_string):
251
    time_string = time_string.decode("ascii")
baltery's avatar
baltery 已提交
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
    with _STRPTIME_LOCK:
        return int(calendar.timegm(time.strptime(time_string, format_string)))


def http_date(timeval=None):
    """返回符合HTTP标准的GMT时间字符串,用strftime的格式表示就是"%a, %d %b %Y %H:%M:%S GMT"。
    但不能使用strftime,因为strftime的结果是和locale相关的。
    """
    return formatdate(timeval, usegmt=True)


def http_to_unixtime(time_string):
    """把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。

    HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
    """
    return to_unixtime(time_string, _GMT_FORMAT)


def iso8601_to_unixtime(time_string):
    """把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
    return to_unixtime(time_string, _ISO8601_FORMAT)


def make_signature(access_key_secret, date=None):
277 278
    if isinstance(date, bytes):
        date = bytes.decode(date)
baltery's avatar
baltery 已提交
279 280 281 282 283 284 285 286 287 288 289
    if isinstance(date, int):
        date_gmt = http_date(date)
    elif date is None:
        date_gmt = http_date(int(time.time()))
    else:
        date_gmt = date

    data = str(access_key_secret) + "\n" + date_gmt
    return content_md5(data)


290
def encrypt_password(password, salt=None):
baltery's avatar
baltery 已提交
291 292
    from passlib.hash import sha512_crypt
    if password:
293
        return sha512_crypt.using(rounds=5000).hash(password, salt=salt)
baltery's avatar
baltery 已提交
294 295 296
    return None


baltery's avatar
baltery 已提交
297 298
def capacity_convert(size, expect='auto', rate=1000):
    """
baltery's avatar
baltery 已提交
299
    :param size: '100MB', '1G'
baltery's avatar
baltery 已提交
300
    :param expect: 'K, M, G, T
baltery's avatar
baltery 已提交
301
    :param rate: Default 1000, may be 1024
baltery's avatar
baltery 已提交
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
    :return:
    """
    rate_mapping = (
        ('K', rate),
        ('KB', rate),
        ('M', rate**2),
        ('MB', rate**2),
        ('G', rate**3),
        ('GB', rate**3),
        ('T', rate**4),
        ('TB', rate**4),
    )

    rate_mapping = OrderedDict(rate_mapping)

    std_size = 0  # To KB
    for unit in rate_mapping:
        if size.endswith(unit):
            try:
                std_size = float(size.strip(unit).strip()) * rate_mapping[unit]
            except ValueError:
                pass

    if expect == 'auto':
        for unit, rate_ in rate_mapping.items():
            if rate > std_size/rate_ > 1:
                expect = unit
                break
baltery's avatar
baltery 已提交
330 331 332 333

    if expect not in rate_mapping:
        expect = 'K'

baltery's avatar
baltery 已提交
334 335 336 337 338 339 340 341 342 343 344 345 346
    expect_size = std_size / rate_mapping[expect]
    return expect_size, expect


def sum_capacity(cap_list):
    total = 0
    for cap in cap_list:
        size, _ = capacity_convert(cap, expect='K')
        total += size
    total = '{} K'.format(total)
    return capacity_convert(total, expect='auto')


baltery's avatar
baltery 已提交
347 348 349 350
def get_short_uuid_str():
    return str(uuid.uuid4()).split('-')[-1]


baltery's avatar
baltery 已提交
351 352 353 354 355 356 357
def is_uuid(s):
    if UUID_PATTERN.match(s):
        return True
    else:
        return False


baltery's avatar
baltery 已提交
358 359 360
def get_signer():
    signer = Signer(settings.SECRET_KEY)
    return signer