utils.py 9.6 KB
Newer Older
W
wangyong 已提交
1 2 3
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
4

baltery's avatar
baltery 已提交
5
from collections import OrderedDict
6
from six import string_types
baltery's avatar
baltery 已提交
7
import base64
baltery's avatar
baltery 已提交
8
import os
baltery's avatar
baltery 已提交
9 10
from itertools import chain
import string
baltery's avatar
baltery 已提交
11
import logging
baltery's avatar
baltery 已提交
12
import datetime
baltery's avatar
baltery 已提交
13 14 15 16 17
import time
import hashlib
from email.utils import formatdate
import calendar
import threading
18
from io import StringIO
W
wangyong 已提交
19

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

baltery's avatar
baltery 已提交
28

baltery's avatar
baltery 已提交
29 30
from .compat import to_bytes, to_string

baltery's avatar
baltery 已提交
31
SECRET_KEY = settings.SECRET_KEY
W
wangyong 已提交
32

baltery's avatar
baltery 已提交
33

34 35 36 37
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 已提交
38 39 40 41 42 43 44 45 46

    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 已提交
47
    except model.DoesNotExist:
48
        return None
W
wangyong 已提交
49
    return obj
baltery's avatar
baltery 已提交
50 51


baltery's avatar
baltery 已提交
52
class Signer(object):
baltery's avatar
baltery 已提交
53
    """用来加密,解密,和基于时间戳的方式验证token"""
baltery's avatar
baltery 已提交
54 55
    def __init__(self, secret_key=SECRET_KEY):
        self.secret_key = secret_key
baltery's avatar
baltery 已提交
56

baltery's avatar
baltery 已提交
57 58 59
    def sign(self, value):
        s = JSONWebSignatureSerializer(self.secret_key)
        return s.dumps(value)
baltery's avatar
baltery 已提交
60

baltery's avatar
baltery 已提交
61 62
    def unsign(self, value):
        s = JSONWebSignatureSerializer(self.secret_key)
baltery's avatar
baltery 已提交
63 64 65 66
        try:
            return s.loads(value)
        except BadSignature:
            return None
baltery's avatar
baltery 已提交
67

baltery's avatar
baltery 已提交
68 69 70
    def sign_t(self, value, expires_in=3600):
        s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
        return s.dumps(value)
baltery's avatar
baltery 已提交
71

baltery's avatar
baltery 已提交
72 73
    def unsign_t(self, value):
        s = TimedJSONWebSignatureSerializer(self.secret_key)
baltery's avatar
baltery 已提交
74 75 76 77
        try:
            return s.loads(value)
        except (BadSignature, SignatureExpired):
            return None
baltery's avatar
baltery 已提交
78 79


baltery's avatar
baltery 已提交
80 81 82 83 84
def date_expired_default():
    try:
        years = int(settings.CONFIG.DEFAULT_EXPIRED_YEARS)
    except TypeError:
        years = 70
baltery's avatar
baltery 已提交
85 86
    return timezone.now() + timezone.timedelta(days=365*years)

baltery's avatar
baltery 已提交
87

baltery's avatar
baltery 已提交
88 89 90 91 92 93 94 95 96 97 98 99
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


def search_object_attr(obj, value='', attr_list=None, ignore_case=False):
100
    """It's provide a method to search a object attribute equal some value
baltery's avatar
baltery 已提交
101

102 103 104 105 106
    If object some attribute equal :param: value, return True else return False

    class A():
        name = 'admin'
        age = 7
baltery's avatar
baltery 已提交
107

108 109 110 111 112 113
    :param obj: A object
    :param value: A string match object attribute
    :param attr_list: Only match attribute in attr_list
    :param ignore_case: Ignore case
    :return: Boolean
    """
baltery's avatar
baltery 已提交
114 115 116
    if value == '':
        return True

117 118 119 120 121
    try:
        object_attr = obj.__dict__
    except AttributeError:
        return False

baltery's avatar
baltery 已提交
122
    if attr_list is not None:
123
        new_object_attr = {}
baltery's avatar
baltery 已提交
124
        for attr in attr_list:
125 126
            new_object_attr[attr] = object_attr.pop(attr)
        object_attr = new_object_attr
baltery's avatar
baltery 已提交
127 128

    if ignore_case:
129 130 131 132
        if not isinstance(value, string_types):
            return False

        if value.lower() in map(string.lower, map(str, object_attr.values())):
baltery's avatar
baltery 已提交
133 134 135 136 137 138 139
            return True
    else:
        if value in object_attr.values():
            return True
    return False


baltery's avatar
baltery 已提交
140 141
def get_logger(name=None):
    return logging.getLogger('jumpserver.%s' % name)
baltery's avatar
baltery 已提交
142 143 144 145 146 147 148


def int_seq(seq):
    try:
        return map(int, seq)
    except ValueError:
        return seq
baltery's avatar
baltery 已提交
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 178 179


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 已提交
180

baltery's avatar
baltery 已提交
181
def ssh_key_string_to_obj(text):
baltery's avatar
baltery 已提交
182
    key_f = StringIO(text)
baltery's avatar
baltery 已提交
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
    key = None
    try:
        key = paramiko.RSAKey.from_private_key(key_f)
    except paramiko.SSHException:
        pass

    try:
        key = paramiko.DSSKey.from_private_key(key_f)
    except paramiko.SSHException:
        pass
    return key


def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost'):
    if isinstance(private_key, string_types):
        private_key = ssh_key_string_to_obj(private_key)

    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 已提交
222
    f = StringIO()
baltery's avatar
baltery 已提交
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246

    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.')


def validate_ssh_private_key(text):
    key = ssh_key_string_to_obj(text)
    if key is None:
        return False
    else:
        return True


baltery's avatar
baltery 已提交
247 248 249 250
def validate_ssh_public_key(text):
    ssh = sshpubkeys.SSHKey(text)
    try:
        ssh.parse()
baltery's avatar
baltery 已提交
251
    except (sshpubkeys.InvalidKeyException, UnicodeDecodeError):
baltery's avatar
baltery 已提交
252 253 254 255 256 257
        return False
    except NotImplementedError as e:
        return False
    return True


baltery's avatar
baltery 已提交
258 259 260 261 262 263 264
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 已提交
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
def content_md5(data):
    """计算data的MD5值,经过Base64编码并返回str类型。

    返回值可以直接作为HTTP Content-Type头部的值
    """
    m = hashlib.md5(to_bytes(data))
    return to_string(base64.b64encode(m.digest()))

_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):
280
    time_string = time_string.decode("ascii")
baltery's avatar
baltery 已提交
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
    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):
306 307
    if isinstance(date, bytes):
        date = bytes.decode(date)
baltery's avatar
baltery 已提交
308 309 310 311 312 313 314 315 316 317 318
    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)


baltery's avatar
baltery 已提交
319 320 321 322 323 324 325
def encrypt_password(password):
    from passlib.hash import sha512_crypt
    if password:
        return sha512_crypt.using(rounds=5000).hash(password)
    return None


baltery's avatar
baltery 已提交
326 327 328 329


def capacity_convert(size, expect='auto', rate=1000):
    """
baltery's avatar
baltery 已提交
330
    :param size: '100MB', '1G'
baltery's avatar
baltery 已提交
331
    :param expect: 'K, M, G, T
baltery's avatar
baltery 已提交
332
    :param rate: Default 1000, may be 1024
baltery's avatar
baltery 已提交
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
    :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
    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 已提交
374
signer = Signer()