コード例 #1
0
ファイル: user.py プロジェクト: zhoudaqing/Icarus
class User(PostModel, BaseUser):
    email = TextField(index=True, unique=True, null=True, default=None)
    phone = TextField(index=True, unique=True, null=True, default=None)  # 大陆地区
    nickname = CITextField(index=True,
                           unique=True,
                           null=True,
                           help_text='用户昵称')  # CITextField
    password = BlobField()
    salt = BlobField()  # auto
    biology = TextField(null=True)  # 简介
    avatar = TextField(null=True)
    type = IntegerField(default=0)  # 账户类型,0默认,1组织
    url = TextField(null=True)  # 个人主页
    location = TextField(null=True)  # 所在地

    # level = IntegerField(index=True)  # 用户级别
    group = IntegerField(index=True, default=USER_GROUP.NORMAL)  # 用户权限组

    is_wiki_editor = BooleanField(default=False, index=True)  # 是否为wiki编辑
    is_board_moderator = BooleanField(default=False, index=True)  # 是否为版主
    is_forum_master = BooleanField(default=False, index=True)  # 超版

    access_time = MyTimestampField(null=True, default=None)  # 最后访问时间,以misc为准吧
    last_check_in_time = MyTimestampField(null=True, default=None)  # 上次签到时间
    check_in_his = IntegerField(default=0)  # 连续签到天数

    number = SerialField(
    )  # 序号,第N个用户,注意这有个问题是不能写sequence='user_count_seq',应该是peewee的bug
    credit = IntegerField(default=0)  # 积分,会消费
    exp = IntegerField(default=0)  # 经验值,不会消失
    repute = IntegerField(default=0)  # 声望
    ip_registered = INETField(default=None, null=True)  # 注册IP

    # ref_github = TextField(null=True)
    # ref_zhihu = TextField(null=True)
    # ref_weibo = TextField(null=True)

    is_new_user = BooleanField(default=True)  # 是否全新用户(刚注册,未经过修改昵称)
    phone_verified = BooleanField(default=False)  # 手机号已确认
    change_nickname_chance = IntegerField(default=0,
                                          help_text='改名机会数量')  # 改名机会数量
    reset_key = BlobField(index=True,
                          null=True,
                          default=None,
                          help_text='重置密码所用key')  # 重置密码所用key

    class Meta:
        db_table = 'user'

    #object_type = OBJECT_TYPES.USER

    @classmethod
    def new(cls,
            nickname,
            password,
            extra_values=None,
            *,
            auto_nickname=False,
            is_for_tests=True) -> Optional['User']:
        values = {
            'nickname': nickname,
            'is_new_user': True
            # 'is_for_tests': is_for_tests
        }

        values.update(extra_values)
        cls.append_post_id(values)

        info = cls.gen_password_and_salt(password)
        values.update(info)

        try:
            uid = cls.insert(values).execute()
            u: User = cls.get_by_pk(uid)

            uchanged = False
            # 如果是第一个用户,那么自动为管理员
            if u.number == 1:
                u.group = USER_GROUP.ADMIN
                uchanged = True

            # 注册成功后,如果要求自动设置用户名,那么修改用户名
            if auto_nickname:
                nprefix = config.USER_NICKNAME_AUTO_PREFIX + '_'
                u.change_nickname_chance = 1
                u.nickname = nprefix + uid.to_hex()
                uchanged = True

            if uchanged:
                u.save()

            return u
        except peewee.IntegrityError:
            traceback.print_exc()
            db.rollback()
            # if e.args[0].startswith('duplicate key | 错误:  重复键违反唯一约束'):
            #     return
            # 此处似乎无从得知,数据库会返回什么样的文本,应该是和语言相关
            # 那么姑且假定 IntegrityError 都是唯一性约束
        except peewee.DatabaseError:
            db.rollback()

    @classmethod
    def find_by_nicknames(cls, names):
        it = iter(names)
        try:
            condition = cls.nickname == next(it)
        except StopIteration:
            return []
        for i in it:
            condition |= cls.nickname == i
        return cls.select().where(condition)

    @property
    def main_role(self):
        """ 主要用户角色 """
        roles = set(self.roles)
        for i in MAIN_ROLE_ORDER:
            if i in roles: return i

    @property
    def roles(self):
        """
        这里角色权限由低到高
        :return:
        """
        ret = [None]
        if self.state == POST_STATE.DEL:
            return ret
        if self.group >= USER_GROUP.BAN:
            ret.append('banned_user')
        if self.group >= USER_GROUP.INACTIVE:
            ret.append('inactive_user')
        if self.group >= USER_GROUP.NORMAL:
            ret.append('user')
        if self.is_wiki_editor:
            ret.append('wiki_editor')
        if self.is_board_moderator:
            ret.append('board_moderator')
        if self.is_forum_master:
            ret.append('forum_master')
        if self.group >= USER_GROUP.SUPERUSER:
            ret.append('superuser')
        if self.group >= USER_GROUP.ADMIN:
            ret.append('admin')
        return ret

    @classmethod
    def gen_password_and_salt(cls, password_text):
        salt = os.urandom(32)
        dk = hashlib.pbkdf2_hmac(
            config.PASSWORD_SECURE_HASH_FUNC_NAME,
            password_text.encode('utf-8'),
            salt,
            config.PASSWORD_SECURE_HASH_ITERATIONS,
        )
        return {'password': dk, 'salt': salt}

    @classmethod
    def get_by_key(cls, key):
        try:
            return cls.get(cls.key == key)
        except DoesNotExist:
            return None

    @classmethod
    async def gen_reg_code_by_email(cls, email: str, password: str):
        t = int(time.time())
        code = os.urandom(8)
        email = email.encode('utf-8')
        pipe = redis.pipeline()
        pipe.set(RK_USER_REG_CODE_AVAILABLE_TIMES_BY_EMAIL % email,
                 config.USER_REG_CODE_AVAILABLE_TIMES_BY_EMAIL,
                 expire=config.USER_REG_CODE_EXPIRE)
        pipe.set(RK_USER_REG_CODE_BY_EMAIL % email,
                 code,
                 expire=config.USER_REG_CODE_EXPIRE)
        pipe.set(RK_USER_REG_PASSWORD % email,
                 password,
                 expire=config.USER_REG_CODE_EXPIRE)
        await pipe.execute()
        return code

    @classmethod
    async def reg_code_cleanup(cls, email):
        email = email.encode('utf-8')
        pipe = redis.pipeline()
        pipe.delete(RK_USER_REG_CODE_BY_EMAIL % email)
        pipe.delete(RK_USER_REG_CODE_AVAILABLE_TIMES_BY_EMAIL % email)
        pipe.delete(RK_USER_REG_PASSWORD % email)
        await pipe.execute()

    @classmethod
    async def check_reg_code_by_email(cls, email, code: Union[str, bytes]):
        """
        检查账户激活码是否可用
        :param uid:
        :param code:
        :return:
        """
        if not code: return
        if isinstance(code, str): code = to_bin(code)

        if len(code) == 8:
            email_bytes = email.encode('utf-8')
            rk_code = RK_USER_REG_CODE_BY_EMAIL % email_bytes
            rk_times = RK_USER_REG_CODE_AVAILABLE_TIMES_BY_EMAIL % email_bytes
            rk_pw = RK_USER_REG_PASSWORD % email_bytes

            if await redis.get(rk_code) == code:
                # 检查可用次数,decr的返回值是执行后的
                if int(await redis.decr(rk_times)) <= 0:
                    return await cls.reg_code_cleanup(email)
                # 无问题,取出储存值
                return (await redis.get(rk_pw)).decode('utf-8')

    async def can_request_reset_password(self):
        """
        是否能申请重置密码
        :return:
        """
        val = await redis.get(RK_USER_LAST_REQUEST_RESET_KEY_BY_USER_ID %
                              self.id)
        if val is None:
            return True
        if time.time() - int(val) > config.USER_RESET_PASSWORD_CODE_EXPIRE:
            return True

    def gen_reset_key(self) -> bytes:
        """
        生成一个重置密码key
        :return:
        """
        # len == 16 + 8 == 24
        t = int(time.time())
        code = os.urandom(16) + t.to_bytes(8, 'little')
        redis.set(RK_USER_LAST_REQUEST_RESET_KEY_BY_USER_ID % self.id,
                  t,
                  expire=config.USER_RESET_PASSWORD_REQUST_INTERVAL)
        redis.set(RK_USER_RESET_KEY_BY_USER_ID % self.id,
                  code,
                  expire=config.USER_RESET_PASSWORD_CODE_EXPIRE)
        return code

    @classmethod
    async def check_reset_key(cls, uid, code) -> Union['User', None]:
        """
        检查uid与code这一组密码重置密钥是否有效
        :param uid:
        :param code:
        :return:
        """
        if not code: return
        if isinstance(uid, str): uid = to_bin(uid)
        if isinstance(code, str): code = to_bin(code)

        if len(code) == 24:
            rkey = RK_USER_RESET_KEY_BY_USER_ID % uid
            if await redis.get(rkey) == code:
                try:
                    u = cls.get(cls.id == uid)
                    await redis.delete(rkey)
                    return u
                except cls.DoesNotExist:
                    pass

    def set_password(self, new_password):
        info = self.gen_password_and_salt(new_password)
        self.salt = info['salt']
        self.password = info['password']
        self.save()

    def update_access_time(self):
        self.access_time = int(time.time())
        self.save()
        return self.access_time

    def check_in(self):
        self.last_check_in_time = self.last_check_in_time or 0
        old_time = self.last_check_in_time
        last_midnight = get_today_start_timestamp()

        # 今日未签到
        if self.last_check_in_time < last_midnight:
            self.last_check_in_time = int(time.time())
            # 三天内有签到,连击
            if old_time > last_midnight - config.USER_CHECKIN_COUNTER_INTERVAL:
                self.check_in_his += 1
            else:
                self.check_in_his = 1
            self.save()

            # 签到加分
            credit = self.credit
            exp = self.exp
            self.credit += 5
            self.exp += 5
            self.save()

            ManageLog.add_by_credit_changed_sys(get_bytes_from_blob(self.id),
                                                credit,
                                                self.credit,
                                                note='每日签到')
            ManageLog.add_by_exp_changed_sys(get_bytes_from_blob(self.id),
                                             exp,
                                             self.exp,
                                             note='每日签到')

            return {
                'credit': 5,
                'exp': 5,
                'time': self.last_check_in_time,
                'check_in_his': self.check_in_his
            }

    def daily_access_reward(self):
        self.access_time = self.access_time or 0
        old_time = self.access_time
        self.update_access_time()

        if old_time < get_today_start_timestamp():
            exp = self.exp
            self.exp += 5
            self.save()
            ManageLog.add_by_exp_changed_sys(get_bytes_from_blob(self.id),
                                             exp,
                                             self.exp,
                                             note='每日登录')
            return {'exp': 5}

    def _auth_base(self, password_text):
        """
        已获取了用户对象,进行密码校验
        :param password_text:
        :return:
        """
        dk = hashlib.pbkdf2_hmac(
            config.PASSWORD_SECURE_HASH_FUNC_NAME,
            password_text.encode('utf-8'),
            get_bytes_from_blob(self.salt),
            config.PASSWORD_SECURE_HASH_ITERATIONS,
        )

        if get_bytes_from_blob(self.password) == dk:
            return self

    @classmethod
    def auth_by_mail(cls, email, password_text) -> ["User", bool]:
        try:
            u = cls.get(cls.email == email)
        except DoesNotExist:
            return None, False
        return u, u._auth_base(password_text)

    @classmethod
    def auth_by_username(cls, username, password_text) -> ["User", bool]:
        try:
            u = cls.get(cls.username == username)
        except DoesNotExist:
            return None, False
        return u, u._auth_base(password_text)

    def __repr__(self):
        return '<User id:%x nickname:%r>' % (int.from_bytes(
            get_bytes_from_blob(self.id), 'big'), self.nickname)

    @classmethod
    def get_post_type(cls):
        return POST_TYPES.USER

    def get_title(self):
        return self.nickname
コード例 #2
0
class User(PostModel, BaseUser):
    email = TextField(index=True, unique=True, null=True, default=None)
    phone = TextField(index=True, unique=True, null=True, default=None)  # 大陆地区
    nickname = CITextField(index=True, unique=True, null=True)  # CITextField
    password = BlobField()
    salt = BlobField()  # auto
    biology = TextField(null=True)  # 简介
    avatar = TextField(null=True)
    type = IntegerField(default=0)  # 账户类型,0默认,1组织
    url = TextField(null=True)  # 个人主页
    location = TextField(null=True)  # 所在地

    # level = IntegerField(index=True)  # 用户级别
    group = IntegerField(index=True, default=USER_GROUP.NORMAL)  # 用户权限组

    is_wiki_editor = BooleanField(default=False, index=True)  # 是否为wiki编辑
    is_board_moderator = BooleanField(default=False, index=True)  # 是否为版主
    is_forum_master = BooleanField(default=False, index=True)  # 超版

    key = BlobField(index=True, null=True)
    key_time = MyTimestampField()  # 最后登录时间
    access_time = MyTimestampField(null=True, default=None)  # 最后访问时间,以misc为准吧
    last_check_in_time = MyTimestampField(null=True, default=None)  # 上次签到时间
    check_in_his = IntegerField(default=0)  # 连续签到天数

    number = SerialField(
    )  # 序号,第N个用户,注意这有个问题是不能写sequence='user_count_seq',应该是peewee的bug
    credit = IntegerField(default=0)  # 积分,会消费
    exp = IntegerField(default=0)  # 经验值,不会消失
    repute = IntegerField(default=0)  # 声望
    ip_registered = INETField(default=None, null=True)  # 注册IP

    # ref_github = TextField(null=True)
    # ref_zhihu = TextField(null=True)
    # ref_weibo = TextField(null=True)

    is_new_user = BooleanField(default=True)  # 是否全新用户(刚注册,未经过修改昵称)
    phone_verified = BooleanField(default=False)  # 手机号已确认
    change_nickname_chance = IntegerField(default=0)  # 改名机会数量
    reset_key = BlobField(index=True, null=True, default=None)  # 重置密码所用key

    class Meta:
        db_table = 'user'

    #object_type = OBJECT_TYPES.USER

    @classmethod
    def find_by_nicknames(cls, names):
        it = iter(names)
        try:
            condition = cls.nickname == next(it)
        except StopIteration:
            return []
        for i in it:
            condition |= cls.nickname == i
        return cls.select().where(condition)

    @property
    def roles(self):
        """
        这里角色权限由低到高
        :return:
        """
        ret = [None]
        if self.state == POST_STATE.DEL:
            return ret
        if self.group >= USER_GROUP.BAN:
            ret.append('banned_user')
        if self.group >= USER_GROUP.INACTIVE:
            ret.append('inactive_user')
        if self.group >= USER_GROUP.NORMAL:
            ret.append('user')
        if self.is_wiki_editor:
            ret.append('wiki_editor')
        if self.is_board_moderator:
            ret.append('board_moderator')
        if self.is_forum_master:
            ret.append('forum_master')
        if self.group >= USER_GROUP.SUPERUSER:
            ret.append('superuser')
        if self.group >= USER_GROUP.ADMIN:
            ret.append('admin')
        return ret

    @classmethod
    def gen_id(cls):
        return config.POST_ID_GENERATOR()

    @classmethod
    def gen_password_and_salt(cls, password_text):
        salt = os.urandom(32)
        dk = hashlib.pbkdf2_hmac(
            config.PASSWORD_SECURE_HASH_FUNC_NAME,
            password_text.encode('utf-8'),
            salt,
            config.PASSWORD_SECURE_HASH_ITERATIONS,
        )
        return {'password': dk, 'salt': salt}

    @classmethod
    def gen_key(cls):
        key = os.urandom(16)
        key_time = int(time.time())
        return {'key': key, 'key_time': key_time}

    def refresh_key(self):
        count = 0
        while count < 10:
            with db.atomic():
                try:
                    k = self.gen_key()
                    self.key = k['key']
                    self.key_time = k['key_time']
                    self.save()
                    return k
                except DatabaseError:
                    count += 1
                    db.rollback()
        raise ValueError("generate key failed")

    @classmethod
    def get_by_key(cls, key):
        try:
            return cls.get(cls.key == key)
        except DoesNotExist:
            return None

    @classmethod
    async def gen_reg_code_by_email(cls, email: str, password: str):
        t = int(time.time())
        code = os.urandom(8)
        email = email.encode('utf-8')
        pipe = redis.pipeline()
        pipe.set(RK_USER_REG_CODE_AVAILABLE_TIMES_BY_EMAIL % email,
                 config.USER_REG_CODE_AVAILABLE_TIMES_BY_EMAIL,
                 expire=config.USER_REG_CODE_EXPIRE)
        pipe.set(RK_USER_REG_CODE_BY_EMAIL % email,
                 code,
                 expire=config.USER_REG_CODE_EXPIRE)
        pipe.set(RK_USER_REG_PASSWORD % email,
                 password,
                 expire=config.USER_REG_CODE_EXPIRE)
        await pipe.execute()
        return code

    @classmethod
    async def reg_code_cleanup(cls, email):
        email = email.encode('utf-8')
        pipe = redis.pipeline()
        pipe.delete(RK_USER_REG_CODE_BY_EMAIL % email)
        pipe.delete(RK_USER_REG_CODE_AVAILABLE_TIMES_BY_EMAIL % email)
        pipe.delete(RK_USER_REG_PASSWORD % email)
        await pipe.execute()

    @classmethod
    async def check_reg_code_by_email(cls, email, code):
        """
        检查账户激活码是否可用
        :param uid:
        :param code:
        :return:
        """
        if not code: return
        if isinstance(code, str): code = to_bin(code)

        if len(code) == 8:
            email_bytes = email.encode('utf-8')
            rk_code = RK_USER_REG_CODE_BY_EMAIL % email_bytes
            rk_times = RK_USER_REG_CODE_AVAILABLE_TIMES_BY_EMAIL % email_bytes
            rk_pw = RK_USER_REG_PASSWORD % email_bytes

            if await redis.get(rk_code) == code:
                # 检查可用次数,decr的返回值是执行后的
                if int(await redis.decr(rk_times)) <= 0:
                    return await cls.reg_code_cleanup(email)
                # 无问题,取出储存值
                return (await redis.get(rk_pw)).decode('utf-8')

    async def can_request_reset_password(self):
        """
        是否能申请重置密码
        :return:
        """
        val = await redis.get(RK_USER_LAST_REQUEST_RESET_KEY_BY_USER_ID %
                              self.id)
        if val is None:
            return True
        if time.time() - int(val) > config.USER_RESET_PASSWORD_CODE_EXPIRE:
            return True

    def gen_reset_key(self) -> bytes:
        """
        生成一个重置密码key
        :return:
        """
        # len == 16 + 8 == 24
        t = int(time.time())
        code = os.urandom(16) + t.to_bytes(8, 'little')
        redis.set(RK_USER_LAST_REQUEST_RESET_KEY_BY_USER_ID % self.id,
                  t,
                  expire=config.USER_RESET_PASSWORD_REQUST_INTERVAL)
        redis.set(RK_USER_RESET_KEY_BY_USER_ID % self.id,
                  code,
                  expire=config.USER_RESET_PASSWORD_CODE_EXPIRE)
        return code

    @classmethod
    async def check_reset_key(cls, uid, code) -> Union['User', None]:
        """
        检查uid与code这一组密码重置密钥是否有效
        :param uid:
        :param code:
        :return:
        """
        if not code: return
        if isinstance(uid, str): uid = to_bin(uid)
        if isinstance(code, str): code = to_bin(code)

        if len(code) == 24:
            rkey = RK_USER_RESET_KEY_BY_USER_ID % uid
            if await redis.get(rkey) == code:
                try:
                    u = cls.get(cls.id == uid)
                    await redis.delete(rkey)
                    return u
                except cls.DoesNotExist:
                    pass

    def set_password(self, new_password):
        info = self.gen_password_and_salt(new_password)
        self.salt = info['salt']
        self.password = info['password']
        self.save()

    def update_access_time(self):
        self.access_time = int(time.time())
        self.save()
        return self.access_time

    def check_in(self):
        self.last_check_in_time = self.last_check_in_time or 0
        old_time = self.last_check_in_time
        last_midnight = get_today_start_timestamp()

        # 今日未签到
        if self.last_check_in_time < last_midnight:
            self.last_check_in_time = int(time.time())
            # 三天内有签到,连击
            if old_time > last_midnight - config.USER_CHECKIN_COUNTER_INTERVAL:
                self.check_in_his += 1
            else:
                self.check_in_his = 1
            self.save()

            # 签到加分
            credit = self.credit
            exp = self.exp
            self.credit += 5
            self.exp += 5
            self.save()

            ManageLog.add_by_credit_changed_sys(self.id.tobytes(),
                                                credit,
                                                self.credit,
                                                note='每日签到')
            ManageLog.add_by_exp_changed_sys(self.id.tobytes(),
                                             exp,
                                             self.exp,
                                             note='每日签到')

            return {
                'credit': 5,
                'exp': 5,
                'time': self.last_check_in_time,
                'check_in_his': self.check_in_his
            }

    def daily_access_reward(self):
        self.access_time = self.access_time or 0
        old_time = self.access_time
        self.update_access_time()

        if old_time < get_today_start_timestamp():
            exp = self.exp
            self.exp += 5
            self.save()
            ManageLog.add_by_exp_changed_sys(self.id.tobytes(),
                                             exp,
                                             self.exp,
                                             note='每日登录')
            return 5

    def _auth_base(self, password_text):
        """
        已获取了用户对象,进行密码校验
        :param password_text:
        :return:
        """
        dk = hashlib.pbkdf2_hmac(
            config.PASSWORD_SECURE_HASH_FUNC_NAME,
            password_text.encode('utf-8'),
            self.salt.tobytes(),
            config.PASSWORD_SECURE_HASH_ITERATIONS,
        )

        if self.password.tobytes() == dk:
            return self

    @classmethod
    def auth_by_mail(cls, email, password_text):
        try:
            u = cls.get(cls.email == email)
        except DoesNotExist:
            return False
        return u._auth_base(password_text)

    @classmethod
    def auth_by_nickname(cls, nickname, password_text):
        try:
            u = cls.get(cls.nickname == nickname)
        except DoesNotExist:
            return False
        return u._auth_base(password_text)

    def __repr__(self):
        return '<User id:%x nickname:%r>' % (int.from_bytes(
            self.id.tobytes(), 'big'), self.nickname)

    @classmethod
    def get_post_type(cls):
        return POST_TYPES.USER

    def get_title(self):
        return self.nickname
コード例 #3
0
ファイル: user.py プロジェクト: EleComb/slim
class User(BaseModel, BaseUser):
    email = TextField(index=True, unique=True, null=True, default=None)
    username = CITextField(index=True, unique=True, null=True)

    nickname = TextField(index=True, null=True)
    password = BlobField()
    salt = BlobField()

    time = BigIntegerField()  # 创建时间
    state = IntegerField(default=POST_STATE.NORMAL, index=True)  # 当前状态

    class Meta:
        db_table = 'user'

    @property
    def roles(self) -> Set:
        """
        BaseUser.roles 的实现,返回用户可用角色
        :return:
        """
        ret = {None}
        if self.state == POST_STATE.DEL:
            return ret
        ret.add(ACCESS_ROLE.NORMAL_USER)
        return ret

    @classmethod
    def new(cls,
            username,
            password,
            *,
            email=None,
            nickname=None) -> Optional['User']:
        values = {
            'id': CustomID().to_bin(),
            'email': email,
            'username': username,
            'time': int(time.time())
        }

        info = cls.gen_password_and_salt(password)
        values.update(info)

        try:
            uid = User.insert(values).execute()
            u = User.get_by_pk(uid)
            return u
        except peewee.IntegrityError as e:
            # traceback.print_exc()
            db.rollback()
            if e.args[0].startswith('duplicate key'):
                return
        except peewee.DatabaseError:
            traceback.print_exc()
            db.rollback()

    @classmethod
    def gen_password_and_salt(cls, password_text):
        """ 生成加密后的密码和盐 """
        salt = os.urandom(32)
        dk = hashlib.pbkdf2_hmac(
            config.PASSWORD_HASH_FUNC_NAME,
            password_text.encode('utf-8'),
            salt,
            config.PASSWORD_HASH_ITERATIONS,
        )
        return {'password': dk, 'salt': salt}

    def set_password(self, new_password):
        """ 设置密码 """
        info = self.gen_password_and_salt(new_password)
        self.password = info['password']
        self.salt = info['salt']
        self.save()

    def _auth_base(self, password_text):
        """
        已获取了用户对象,进行密码校验
        :param password_text:
        :return:
        """
        dk = hashlib.pbkdf2_hmac(config.PASSWORD_HASH_FUNC_NAME,
                                 password_text.encode('utf-8'),
                                 get_bytes_from_blob(self.salt),
                                 config.PASSWORD_HASH_ITERATIONS)

        if get_bytes_from_blob(self.password) == get_bytes_from_blob(dk):
            return self

    @classmethod
    def auth_by_mail(cls, email, password_text):
        try:
            u = cls.get(cls.email == email)
        except DoesNotExist:
            return False
        return u._auth_base(password_text)

    @classmethod
    def auth_by_username(cls, username, password_text):
        try:
            u = cls.get(cls.username == username)
        except DoesNotExist:
            return False
        return u._auth_base(password_text)

    def __repr__(self):
        if isinstance(self.id, (bytes, memoryview)):
            return '<User id:%x username:%r>' % (int.from_bytes(
                get_bytes_from_blob(self.id), 'big'), self.username)
        elif isinstance(self.id, int):
            return '<User id:%d username:%r>' % (self.id, self.username)
        else:
            return '<User id:%s username:%r>' % (self.id, self.username)
コード例 #4
0
ファイル: user.py プロジェクト: chinarefers/Icarus
class User(PostModel, BaseUser):
    email = TextField(index=True, unique=True)
    nickname = CITextField(index=True, unique=True)  # CITextField
    password = BlobField()
    salt = BlobField()  # auto
    biology = TextField(null=True)  # 简介
    avatar = TextField(null=True)
    type = IntegerField(default=0)  # 账户类型,0默认,1组织
    url = TextField(null=True)  # 个人主页
    location = TextField(null=True)  # 所在地

    # level = IntegerField(index=True)  # 用户级别
    group = IntegerField(index=True, default=USER_GROUP.NORMAL)  # 用户权限组

    key = BlobField(index=True, null=True)
    key_time = MyTimestampField()  # 最后登录时间
    access_time = MyTimestampField(null=True, default=None)  # 最后访问时间,以misc为准吧
    last_check_in_time = MyTimestampField(null=True, default=None)  # 上次签到时间
    check_in_his = IntegerField(default=0)  # 连续签到天数

    phone = TextField(null=True, default=None)  # 大陆地区
    number = SerialField(
    )  # 序号,第N个用户,注意这有个问题是不能写sequence='user_count_seq',应该是peewee的bug
    credit = IntegerField(default=0)  # 积分,会消费
    exp = IntegerField(default=0)  # 经验值,不会消失
    reputation = IntegerField(default=0)  # 声望
    ip_registered = INETField(default=None, null=True)  # 注册IP

    reset_key = BlobField(index=True, null=True, default=None)  # 重置密码所用key

    class Meta:
        db_table = 'user'

    #object_type = OBJECT_TYPES.USER

    @classmethod
    def find_by_nicknames(cls, names):
        it = iter(names)
        try:
            condition = cls.nickname == next(it)
        except StopIteration:
            return []
        for i in it:
            condition |= cls.nickname == i
        return cls.select().where(condition)

    @property
    def roles(self):
        """
        这里角色权限由低到高
        :return:
        """
        ret = [None]
        if self.state == POST_STATE.DEL:
            return ret
        if self.group >= USER_GROUP.BAN:
            ret.append('banned_user')
        if self.group >= USER_GROUP.INACTIVE:
            ret.append('inactive_user')
        if self.group >= USER_GROUP.NORMAL:
            ret.append('user')
        if self.group >= USER_GROUP.SUPERUSER:
            ret.append('superuser')
        if self.group >= USER_GROUP.ADMIN:
            ret.append('admin')
        return ret

    @classmethod
    def gen_id(cls):
        return config.POST_ID_GENERATOR()

    @classmethod
    def gen_password_and_salt(cls, password_text):
        if config.USER_SECURE_AUTH_ENABLE:
            salt = os.urandom(32)
            dk = hashlib.pbkdf2_hmac(
                config.PASSWORD_SECURE_HASH_FUNC_NAME,
                password_text.encode('utf-8'),
                salt,
                config.PASSWORD_SECURE_HASH_ITERATIONS,
            )
            return {'password': dk, 'salt': salt}
        else:
            salt = os.urandom(16)
            m = hmac.new(salt, digestmod=config.PASSWORD_HASH_FUNC)
            m.update(password_text.encode('utf-8'))
            return {'password': m.digest(), 'salt': salt}

    @classmethod
    def gen_key(cls):
        key = os.urandom(16)
        key_time = int(time.time())
        return {'key': key, 'key_time': key_time}

    def refresh_key(self):
        count = 0
        while count < 10:
            with db.atomic():
                try:
                    k = self.gen_key()
                    self.key = k['key']
                    self.key_time = k['key_time']
                    self.save()
                    return k
                except DatabaseError:
                    count += 1
                    db.rollback()
        raise ValueError("generate key failed")

    @classmethod
    def get_by_key(cls, key):
        try:
            return cls.get(cls.key == key)
        except DoesNotExist:
            return None

    async def can_request_actcode(self):
        """
        是否能申请帐户激活码(用于发送激活邮件)
        :return:
        """
        if self.group != USER_GROUP.INACTIVE:
            return
        val = await redis.get(RK_USER_LAST_REQUEST_ACTCODE_BY_USER_ID % self.id
                              )
        if val is None:
            return True
        if time.time() - int(val) > config.USER_ACTIVATION_REQUEST_INTERVAL:
            return True

    async def gen_activation_code(self) -> bytes:
        """
        生成一个账户激活码
        :return:
        """
        t = int(time.time())
        code = os.urandom(8)
        pipe = redis.pipeline()
        pipe.set(RK_USER_LAST_REQUEST_ACTCODE_BY_USER_ID % self.id, t)
        pipe.set(RK_USER_ACTCODE_BY_USER_ID % self.id,
                 code,
                 expire=config.USER_ACTCODE_EXPIRE)
        await pipe.execute()
        return code

    @classmethod
    async def check_actcode(cls, uid, code):
        """
        检查账户激活码是否可用,若可用,激活账户
        :param uid:
        :param code:
        :return:
        """
        if not code: return
        if isinstance(uid, str): uid = to_bin(uid)
        if isinstance(code, str): code = to_bin(code)

        if len(code) == 8:
            rkey = RK_USER_ACTCODE_BY_USER_ID % uid
            if await redis.get(rkey) == code:
                try:
                    u = cls.get(cls.id == uid,
                                cls.group == USER_GROUP.INACTIVE)
                    u.group = USER_GROUP.NORMAL
                    u.save()
                    await redis.delete(rkey)
                    return u
                except cls.DoesNotExist:
                    pass

    async def can_request_reset_password(self):
        """
        是否能申请重置密码
        :return:
        """
        val = await redis.get(RK_USER_LAST_REQUEST_RESET_KEY_BY_USER_ID %
                              self.id)
        if val is None:
            return True
        if time.time() - int(val) > config.USER_RESET_PASSWORD_CODE_EXPIRE:
            return True

    def gen_reset_key(self) -> bytes:
        """
        生成一个重置密码key
        :return:
        """
        # len == 16 + 8 == 24
        t = int(time.time())
        code = os.urandom(16) + t.to_bytes(8, 'little')
        redis.set(RK_USER_LAST_REQUEST_RESET_KEY_BY_USER_ID % self.id,
                  t,
                  expire=config.USER_RESET_PASSWORD_REQUST_INTERVAL)
        redis.set(RK_USER_RESET_KEY_BY_USER_ID % self.id,
                  code,
                  expire=config.USER_RESET_PASSWORD_CODE_EXPIRE)
        return code

    @classmethod
    async def check_reset_key(cls, uid, code) -> Union['User', None]:
        """
        检查uid与code这一组密码重置密钥是否有效
        :param uid:
        :param code:
        :return:
        """
        if not code: return
        if isinstance(uid, str): uid = to_bin(uid)
        if isinstance(code, str): code = to_bin(code)

        if len(code) == 24:
            rkey = RK_USER_RESET_KEY_BY_USER_ID % uid
            if await redis.get(rkey) == code:
                try:
                    u = cls.get(cls.id == uid)
                    await redis.delete(rkey)
                    return u
                except cls.DoesNotExist:
                    pass

    def set_password(self, new_password):
        info = self.gen_password_and_salt(new_password)
        self.salt = info['salt']
        self.password = info['password']
        self.save()

    def update_access_time(self):
        self.access_time = int(time.time())
        self.save()
        return self.access_time

    def check_in(self):
        self.last_check_in_time = self.last_check_in_time or 0
        old_time = self.last_check_in_time
        last_midnight = get_today_start_timestamp()

        # 今日未签到
        if self.last_check_in_time < last_midnight:
            self.last_check_in_time = int(time.time())
            # 三天内有签到,连击
            if old_time > last_midnight - config.USER_CHECKIN_COUNTER_INTERVAL:
                self.check_in_his += 1
            else:
                self.check_in_his = 1
            self.save()

            # 签到加分
            credit = self.credit
            exp = self.exp
            self.credit += 5
            self.exp += 5
            self.save()
            ManageLog.add_by_credit_changed_sys(self,
                                                note='每日签到',
                                                value=[credit, self.credit])
            ManageLog.add_by_exp_changed_sys(self,
                                             note='每日签到',
                                             value=[exp, self.exp])

            return {
                'credit': 5,
                'exp': 5,
                'time': self.last_check_in_time,
                'check_in_his': self.check_in_his
            }

    def daily_access_reward(self):
        self.access_time = self.access_time or 0
        old_time = self.access_time
        self.update_access_time()

        if old_time < get_today_start_timestamp():
            exp = self.exp
            self.exp += 5
            self.save()
            ManageLog.add_by_exp_changed_sys(self,
                                             note='每日登录',
                                             value=[exp, self.exp])
            return 5

    @classmethod
    def auth(cls, email, password_text):
        try:
            u = cls.get(cls.email == email)
        except DoesNotExist:
            return False

        if config.USER_SECURE_AUTH_ENABLE:
            dk = hashlib.pbkdf2_hmac(
                config.PASSWORD_SECURE_HASH_FUNC_NAME,
                password_text.encode('utf-8'),
                u.salt.tobytes(),
                config.PASSWORD_SECURE_HASH_ITERATIONS,
            )

            if u.password.tobytes() == dk:
                return u
        else:
            m = hmac.new(u.salt.tobytes(), digestmod=config.PASSWORD_HASH_FUNC)
            m.update(password_text.encode('utf-8'))

            if u.password.tobytes() == m.digest():
                return u

    def __repr__(self):
        return '<User id:%x nickname:%r>' % (int.from_bytes(
            self.id.tobytes(), 'big'), self.nickname)

    def get_title(self):
        return self.nickname
コード例 #5
0
class User(PostModel, BaseUser):
    email = TextField(index=True, unique=True)
    nickname = CITextField(index=True, unique=True)  # CITextField
    password = BlobField()
    salt = BlobField()  # auto
    biology = TextField(null=True)  # 简介
    avatar = TextField(null=True)
    type = IntegerField(default=0)  # 账户类型,0默认,1组织
    url = TextField(null=True)  # 个人主页
    location = TextField(null=True)  # 所在地

    # level = IntegerField(index=True)  # 用户级别
    group = IntegerField(index=True)  # 用户权限组

    key = BlobField(index=True, null=True)
    key_time = MyTimestampField()  # 最后登录时间
    access_time = MyTimestampField(null=True, default=None)  # 最后访问时间,以misc为准吧
    last_check_in_time = MyTimestampField(null=True, default=None)  # 上次签到时间
    check_in_his = IntegerField(default=0)  # 连续签到天数

    phone = TextField(null=True, default=None)  # 大陆地区
    number = IntegerField(default=get_user_count_seq)  # 序号,第N个用户 sequence='user_count_seq'
    credit = IntegerField(default=0)  # 积分,会消费
    reputation = IntegerField(default=0)  # 声望,不会消失

    reset_key = BlobField(index=True, null=True, default=None)  # 重置密码所用key

    class Meta:
        db_table = 'user'

    #object_type = OBJECT_TYPES.USER

    @property
    def roles(self):
        ret = [None]
        if self.group >= USER_GROUP.ADMIN:
            ret.append('admin')
        if self.group >= USER_GROUP.SUPERUSER:
            ret.append('superuser')
        if self.group >= USER_GROUP.NORMAL:
            ret.append('user')
        if self.group >= USER_GROUP.INACTIVE:
            ret.append('inactive_user')
        return ret

    @classmethod
    def gen_id(cls):
        return config.POST_ID_GENERATOR()

    @classmethod
    def gen_password_and_salt(cls, password_text):
        salt = os.urandom(16)
        m = hmac.new(salt, digestmod=config.PASSWORD_HASH_FUNC)
        m.update(password_text.encode('utf-8'))
        return {'password': m.digest(), 'salt': salt}

    @classmethod
    def gen_key(cls):
        key = os.urandom(16)
        key_time = int(time.time())
        return {'key': key, 'key_time': key_time}

    def refresh_key(self):
        count = 0
        while count < 10:
            with db.atomic():
                try:
                    k = self.gen_key()
                    self.key = k['key']
                    self.key_time = k['key_time']
                    self.save()
                    return k
                except DatabaseError:
                    count += 1
                    db.rollback()
        raise ValueError("generate key failed")

    @classmethod
    def get_by_key(cls, key):
        try:
            return cls.get(cls.key == key)
        except DoesNotExist:
            return None

    def get_activation_code(self):
        raw = self.salt.tobytes() + self.time.to_bytes(8, 'little')  # len == 16 + 8 == 24
        return str(binascii.hexlify(raw), 'utf-8')

    @classmethod
    def check_active(cls, uid, code):
        if not code: return
        try:
            uid = binascii.unhexlify(uid)
            code = binascii.unhexlify(code)
        except:
            return

        if len(code) == 24:
            # 时间为最近3天
            ts = int.from_bytes(binascii.unhexlify(code[16:]), 'little')
            if time.time() - ts < 3 * 24 * 60 * 60:
                try:
                    u = cls.get(cls.time == ts,
                                cls.id == uid,
                                cls.group == USER_GROUP.INACTIVE,
                                cls.salt == binascii.unhexlify(code[:16]))
                    u.group = USER_GROUP.NORMAL
                    u.save()
                    return u
                except cls.DoesNotExist:
                    pass

    @staticmethod
    def gen_reset_key():
        return os.urandom(16) + int(time.time()).to_bytes(8, 'little')  # len == 16 + 8 == 24

    @classmethod
    def check_reset(cls, uid, code) -> Union['User', None]:
        if not code: return
        try:
            uid = binascii.unhexlify(uid)
            code = binascii.unhexlify(code)
        except: return

        if len(code) == 24:
            # 时间为最近12小时
            ts = int.from_bytes(code[16:], 'little')
            if time.time() - ts < 12 * 60 * 60:
                try:
                    u = cls.get(cls.id == uid, cls.reset_key == code)
                    return u
                except cls.DoesNotExist:
                    pass

    def set_password(self, new_password):
        info = self.gen_password_and_salt(new_password)
        self.salt = info['salt']
        self.password = info['password']
        self.save()

    def update_access_time(self):
        self.access_time = int(time.time())
        self.save()
        return self.access_time

    def check_in(self):
        self.last_check_in_time = self.last_check_in_time or 0
        old_time = self.last_check_in_time
        last_midnight = get_today_start_timestamp()

        # 今日未签到
        if self.last_check_in_time < last_midnight:
            self.last_check_in_time = int(time.time())
            # 三天内有签到,连击
            print(old_time, last_midnight - 3 * 24 * 60 * 60)
            if old_time > last_midnight - 3 * 24 * 60 * 60:
                self.check_in_his += 1
            else:
                self.check_in_his = 1
            self.save()

            # 签到加分
            credit = self.credit
            reputation = self.reputation
            self.credit += 5
            self.reputation += 5
            self.save()
            ManageLog.add_by_credit_changed_sys(self, note='每日签到', value=[credit, self.credit])
            ManageLog.add_by_reputation_changed_sys(self, note='每日签到', value=[reputation, self.reputation])

            return {
                'credit': 5,
                'reputation': 5,
                'time': self.last_check_in_time,
                'check_in_his': self.check_in_his
            }

    def daily_access_reward(self):
        self.access_time = self.access_time or 0
        old_time = self.access_time
        self.update_access_time()

        if old_time < get_today_start_timestamp():
            credit = self.credit
            self.credit += 5
            self.save()
            ManageLog.add_by_credit_changed_sys(self, note='每日登录', value=[credit, self.credit])
            return 5

    @classmethod
    def auth(cls, email, password_text):
        try:
            u = cls.get(cls.email == email)
        except DoesNotExist:
            return False

        m = hmac.new(u.salt.tobytes(), digestmod=config.PASSWORD_HASH_FUNC)
        m.update(password_text.encode('utf-8'))

        if u.password.tobytes() == m.digest():
            return u

    def __repr__(self):
        return '<User id:%x nickname:%r>' % (int.from_bytes(self.id.tobytes(), 'big'), self.nickname)