def upgrade():
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

    session.add(User(username='******',
                     hashed_password=pwd_context.hash('user')))
    session.add(
        User(username='******', hashed_password=pwd_context.hash('admin')))
    session.commit()
Beispiel #2
0
def create_user(username, password1):
    """Hashes the password and inserts a new user into database"""

    # define hashing parameters
    hasher = CryptContext(schemes=["sha256_crypt"])

    # hash the user password
    hash1 = hasher.hash(password1)

    # check if the username is not already taken
    rows = db.execute("SELECT * FROM users WHERE username = :username",
                      username=username)
    if len(rows) == 1:
        return apology("username already exists")
    else:
        # insert a new user to the database and redirect to the index page with the current balance
        db.execute(
            "INSERT INTO users (username,hash) VALUES(:username, :hash)",
            username=username,
            hash=hash1)
        rows = db.execute("SELECT * FROM users WHERE username = :username",
                          username=request.form.get("username"))
        session["user_id"] = rows[0]["id"]
        balance = show_balance()
        return render_template(
            "index.html",
            cash=balance["cash"],
            portfoliorows=balance["portfoliorows"],
            total=balance["total"],
            title=
            '<div class="alert alert-success" role="alert">  A new user was created successfuly!        </div>'
        )
Beispiel #3
0
def create_user(request):
    '''
    Create user in the database
    Password is encrypted with SHA256
    '''
    if ('username' in request and
        'password' in request and 'email' in request) and \
       (type(request['username'] == str) and type(request['password'] == str)
           and type(request['email'] == str)):
        duplicate = db.session.query(User).filter(
            User.email == request['email']).first()
        if duplicate is None:
            myctx = CryptContext(schemes=["sha256_crypt"])
            new_user = User(
                username=request['username'],
                email=request['email'],
                password=myctx.hash(request['password'])
            )
            db.session.add(new_user)
            db.session.commit()
            return True
        else:
            print('register failed')
            return False
    else:
        return False
def hashPassword(password):
    pwd_context = CryptContext(
        schemes=["pbkdf2_sha256"],
        default="pbkdf2_sha256",
        pbkdf2_sha256__default_rounds=30000
    )
    return pwd_context.hash(password)
Beispiel #5
0
class Authentication:
    def __init__(self, *, secret_key, encryption_algorithm="HS256"):
        self.crypt_context = CryptContext(schemes=["bcrypt"],
                                          deprecated="auto")
        self.secret_key = secret_key
        self.encryption_algorithm = encryption_algorithm

    def verify_password(self, plain_password, hashed_password):
        return self.crypt_context.verify(plain_password, hashed_password)

    def hash_password(self, plain_password):
        return self.crypt_context.hash(plain_password)

    def generate_access_token(self,
                              token_data: dict,
                              expires_delta: Optional[timedelta] = None):
        token_data = token_data.copy()

        if expires_delta:
            expires = datetime.utcnow() + expires_delta
        else:
            expires = datetime.utcnow() + timedelta(minutes=15)

        token_data.update({"exp": expires})
        encoded_jwt = jwt.encode(token_data,
                                 self.secret_key,
                                 algorithm=self.encryption_algorithm)
        return {"access_token": encoded_jwt, "expires": expires}

    def decode_token(self, token):
        return jwt.decode(token,
                          self.secret_key,
                          algorithms=[self.encryption_algorithm])
Beispiel #6
0
class LioPassword:
    def __init__(self):
        self.pwd_context = CryptContext(
            schemes=["sha256_crypt", "md5_crypt",
                     "des_crypt"])  # Hash password

    def match_password(self, password: str, password_in_db: str):
        """
        Hàm dùng cho việc so sánh xác thực mật khẩu

        :param password: mật khẩu dùng để so sánh
        :param password_in_db: mật khẩu được lấy từ Database
        :return: True hoặc False
        """

        return self.pwd_context.verify(password, password_in_db)

    def hash_password(self, password: str):
        """
        Hàm dùng cho việc mã hoá mật khẩu

        :return: chuỗi sau khi được mã hoá
        """

        return self.pwd_context.hash(password)
Beispiel #7
0
def change_password(connection: Dict, data: Dict) -> None:
    cxt = CryptContext(schemes=['pbkdf2_sha512'])
    pw_hash = cxt.hash(data['password'])

    with psycopg2.connect(**connection) as conn:
        with conn.cursor() as cursor:
            update_query = ('UPDATE res_users '
                            'SET password = %(password)s '
                            'WHERE login = %(login)s')
            cursor.execute(update_query, {
                'password': pw_hash,
                'login': data['login'],
            })

        with conn.cursor() as cursor:
            query = ('SELECT password FROM res_users '
                     'WHERE login = %(login)s')

            cursor.execute(query, {
                'login': data['login'],
            })

            result = cursor.fetchone()

            if not result:
                raise Exception('No results found!')

            check_password = result[0]

            if cxt.verify(data['password'], check_password):
                print('Success!')
            else:
                raise Exception('Hash stored in database did not match!')
Beispiel #8
0
class UserService(DBModelService):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

    def get_user(self,
                 id_: Optional[int] = None,
                 username: Optional[str] = None) -> Union[User, None]:
        if id_ is not None:
            user = self._db.query(User).get(id_)
            user.owned_recipes_count = fast_count(
                self._db.query(Recipe).filter(Recipe.author_id == user.id))
            return user
        elif username is not None:
            return self._db.query(User).filter(
                User.username == username).first()
        else:
            return None

    def create_user(self, username: str, password: str):
        instance = User(username=username)
        instance.is_active = True
        instance.hpass = self.pwd_context.hash(password)
        self._db.add(instance)
        self._db.commit()
        self._db.refresh(instance)
        return instance
Beispiel #9
0
class User(BaseModel):
    ROLES = [('ADMIN', 'admin'), ('USER', 'user')]
    username = peewee.CharField(max_length=255, help_text='user login name')
    secret = peewee.CharField(max_length=77, help_text='user login password')
    role = peewee.CharField(max_length=5,
                            choices=ROLES,
                            help_text='user login type')
    locked = peewee.BooleanField(default=True, help_text='user is locked')

    def __init__(self, *args, **kwargs):
        self._secret_schemes = ["sha256_crypt", "des_crypt"]
        self._secret_ctx = CryptContext(schemes=self._secret_schemes)
        super(User, self).__init__(*args, **kwargs)

    def __str__(self):
        return self.username

    def to_dict(self):
        data = super(User, self).to_dict()
        data.pop('secret')
        data.setdefault('user_id', self.id)
        return data

    @property
    def password(self):
        return self.secret

    @password.setter
    def password(self, password):
        self.secret = self._secret_ctx.hash(password)

    def verify(self, password):
        return self._secret_ctx.verify(password, self.secret)
Beispiel #10
0
def buildconfig(request):
    if request.method == "POST":
        form = RequestedConfigForm(request.POST)
        if form.is_valid():
            template = loader.get_template('esxicfg/success.html')
            allchar = string.ascii_letters + string.punctuation + string.digits
            password = "".join(choice(allchar) for x in range(randint(8, 12)))
            myctx = CryptContext(schemes=["md5_crypt"])
            password_hash = myctx.hash(password)
            newNode = Node(password_hash=password_hash,
                           network_autoconfig=request.POST['NetworkMode'],
                           network_vlan=request.POST['NetworkVLAN'],
                           network_manualip=request.POST['NetworkIP'],
                           network_manualnm=request.POST['NetworkNM'],
                           network_manualgw=request.POST['NetworkGW'],
                           ssh_config=request.POST['SSHmode'])
            newNode.save()
            context = {
                'SITE_FQDN': settings.SITE_FQDN,
                'password': password,
                'node_id': newNode.id
            }
            return HttpResponse(template.render(context, request))
        else:
            template = loader.get_template('esxicfg/mainpage.html')
            context = {error_message: 'Invalid options, please try again.'}
            return HttpResponse(template.render(content, request))
    else:
        template = loader.get_template('esxicfg/mainpage.html')
        context = {error_message: 'Form input are required.'}
        return HttpResponse(template.render(content, request))
Beispiel #11
0
def changepass(currentpass, newpass1):
    """Hashes the password and inserts into database"""

    # check if the current password is valid
    id = session["user_id"]
    passwordrow = db.execute("SELECT hash FROM users WHERE id = :id", id=id)
    if len(passwordrow) != 1 or not pwd_context.verify(currentpass,
                                                       passwordrow[0]["hash"]):
        return apology("invalid password")

    # hash the new password
    hasher = CryptContext(schemes=["sha256_crypt"])
    hash1 = hasher.hash(newpass1)

    # update the pasword
    db.execute("UPDATE users SET hash = :hash WHERE id = :id",
               hash=hash1,
               id=id)

    # refresh the balance and redirect to index
    balance = show_balance()
    return render_template(
        "index.html",
        cash=balance["cash"],
        portfoliorows=balance["portfoliorows"],
        total=balance["total"],
        title=
        '<div class="alert alert-success" role="alert">  Password was changed!                    </div>'
    )
Beispiel #12
0
    def generate_password_hashes(self, password):
        """
        Refer to passlib documentation for adding new hashers:

            https://pythonhosted.org/passlib/lib/passlib.hash.html
        """

        password_schemes = ['pbkdf2_sha256', 'sha512_crypt', 'sha256_crypt', 'des_crypt', 'md5_crypt']
        pwd_context = CryptContext(schemes=password_schemes)

        self.sha512_crypt = pwd_context.hash(password, 'sha512_crypt')
        self.sha256_crypt = pwd_context.hash(password, 'sha256_crypt')
        self.des_crypt = pwd_context.hash(password, 'des_crypt')
        self.md5_crypt = pwd_context.hash(password, 'md5_crypt')

        self.save()
Beispiel #13
0
def createAdminUser(username, pw, fn, ln, cursor, cnx):
    if (mySQL_userDB.isUsernameTaken(username, cursor, cnx)):
        return "taken error"
    policy = PasswordPolicy.from_names(
        length=8,  # min length: 8
        uppercase=1,  # need min. 2 uppercase letters
        numbers=1  # need min. 2 digits
    )
    isEnough = policy.test(pw)
    if len(isEnough) != 0:
        return "weak password"

    pwd_context = CryptContext(schemes=["pbkdf2_sha256"],
                               default="pbkdf2_sha256",
                               pbkdf2_sha256__default_rounds=30000)

    formatInsert = ("INSERT INTO adminTable "
                    "(username, password,fname,"
                    "lname,creationDate) "
                    "VALUES (%s, %s,%s, %s,%s)"
                    )  #NOTE: use %s even with numbers

    #their care team is not assigned at creation, so N/A
    insertContent = (username, pwd_context.hash(pw), fn, ln,
                     str(date.today().strftime("%B %d, %Y")))
    cursor.execute(formatInsert, insertContent)
    cnx.commit()

    return "success"
Beispiel #14
0
class WeatherDataUsers:
    def __init__(self):
        self.crypt_context = CryptContext(schemes=['bcrypt'],
                                          deprecated='auto')
        self.users = [WeatherDataUser(**user) for user in _weather_data_users]

    def get_all(self) -> Generator[WeatherDataUser, None, None]:
        for user in self.users:
            yield user

    def get(self, username: str) -> WeatherDataUser:
        for user in self.users:
            if username == user.username:
                return user.copy(deep=True)

    def verify_password(self, user: WeatherDataUser, password: str) -> bool:
        # This is not ideal however there is a pretty big hit hashing the
        # user password (~300 ms/user). For the server that's not a big deal
        # however for other use cases (like in the db package) it make tool
        # startup slow. Since the user implementation is all fake and only
        # called by login, hash the plain text password here instead of in
        # init or implementing a lock scheme. The plain text password could
        # simply be checked here however I wanted to hang onto to how the
        # CryptContext can be used.
        return self.crypt_context.verify(
            password, self.crypt_context.hash(user.hashed_password))
Beispiel #15
0
def encryptPassword(password):
    """
    This function encrypts a password and returns a hash.
    """
    context = CryptContext(schemes=[encryption_algorithm])
    # replaced 'encrypt' (deprecated as of 1.7) with 'hash'
    return context.hash(password)
Beispiel #16
0
def CreateNewUser(username, password, country, email, gender, bday, race):
    myctx = CryptContext(schemes=["sha256_crypt", "md5_crypt", "des_crypt"])
    ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    salt = []
    [salt.append(random.choice(ALPHABET)) for x in range(16)]
    salt = "".join(salt)
    hash1 = myctx.hash(password + salt)

    username = username.lower()
    HashedPwd = hash1
    PwdSalt = salt
    cursor.execute(
        """
        INSERT INTO [dbo].[Users]
               ([Username]
               ,[HashedPwd]
               ,[PwdSalt]
               ,[Country]
               ,[Email]
               ,[Gender]
               ,[BirthYear]
               ,[Race]
               ,[TimeStamp])
         VALUES
               (?,
                ?,
                ?,
                ?,
                ?,
                ?,
                ?,
                ?,
                (getdate()))
    """, [username, HashedPwd, PwdSalt, country, email, gender, bday, race])
    cursor.commit()
Beispiel #17
0
def set_password(user_id, password):
    context = CryptContext(schemes=["bcrypt"])
    m = models.User
    u = m.query.filter(m.id == user_id).first()
    u.password = context.hash(password)
    models.db.session.commit()
    return True
Beispiel #18
0
def change():
    user_id = session["user_id"]
    if request.method == "POST":
        # ensure username was submitted

        # ensure password was submitted
        if not request.form.get("password"):
            return apology("must provide password")

        elif not request.form.get("password_1"):
            return apology("must provide password(again)")

        if request.form.get("password") != request.form.get("password_1"):
            return apology("password(again) must equit password")

        myctx = CryptContext(["sha256_crypt"])
        passwd = myctx.hash(request.form.get("password"))

        loginTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        rows = db.execute(
            "update users set passwd = :passwd,loginTime = :loginTime where userId = :user_id",
            passwd=passwd,
            loginTime=loginTime,
            user_id=user_id)
        if rows != 1:
            return apology("change passwd is failed!")
        return redirect(url_for("mine"))
Beispiel #19
0
def reset_password(db: Session, data: UserResponseResetPassword):
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    _user = db.query(User).filter_by(document = data.document).first()
    _used_token = db.query(UserResetPassword).filter_by(user_id= _user.id).first()
    _used_token.used_token = True
    _user.password = pwd_context.hash(data.password)
    db.commit()
    return "Senha alterada"
Beispiel #20
0
    def generate_password_hashes(self, password):
        """
        Generate password hashes with SHA512, PBKDF2/SHA-256 and DES crypt.

        Refer to passlib documentation for adding new hashers:

            https://pythonhosted.org/passlib/lib/passlib.hash.html
        """

        password_schemes = ['pbkdf2_sha256', 'sha512_crypt', 'des_crypt']
        pwd_context = CryptContext(schemes=password_schemes)

        self.pbkdf2_sha256 = pwd_context.hash(password, 'pbkdf2_sha256')
        self.sha512_crypt = pwd_context.hash(password, 'sha512_crypt')
        self.des_crypt = pwd_context.hash(password, 'des_crypt')

        self.save()
Beispiel #21
0
def get_password_hash_with_context(password, context: CryptContext) -> str:
    """
    Hash a password by using a given algorithm
    :param password: str -> The plain text password
    :param context: CryptContext -> the context to use to generate hash of the password
    :return: str -> The hashed password
    """
    return context.hash(password)
Beispiel #22
0
class Hash():
    def __init__(self):
        self.pwd_cxt = CryptContext(schemes=["bcrypt"], deprecated="auto")

    def bcrypt(self, password: str) -> str:
        return self.pwd_cxt.hash(password)

    def verify(self, plain_password: str, hash_password: str) -> bool:
        return self.pwd_cxt.verify(plain_password, hash_password)
Beispiel #23
0
def encryptPass(password):
    from passlib.context import CryptContext
    global pwd_context
    pwd_context = CryptContext(
        schemes=["pbkdf2_sha256", "md5_crypt", "des_crypt"],
        default="pbkdf2_sha256",
        pbkdf2_sha256__default_rounds=30000)
    enpass = pwd_context.hash(password)
    return enpass
Beispiel #24
0
	def createuser(self, username, password):
		from passlib.context import CryptContext
		password_context = CryptContext(schemes=["pbkdf2_sha512"], deprecated="auto")

		cryptedpassword = password_context.hash(password)
		db.insert('gallery.users', admin=False, password=cryptedpassword, username=username)

		createduser = db.select('gallery.users', where="username=${un}", vars={'un':username})
		db.insert('gallery.userflags', userid=createduser[0]['id'], flagtype="newuser")
Beispiel #25
0
def register():

    # forget any user_id
    session.clear()

    # if user reached route via POST (as by submitting a form via POST)
    if request.method == "POST":

        # query database for username
        userdata = db.execute(
            "SELECT * FROM userdata WHERE username = :username",
            username=request.form.get("name"))

        # ensure username exists and password is correct
        if len(userdata) == 1:
            flash("Username already exists")
            return render_template("register.html")

        # encrypt password
        myctx = CryptContext(schemes=["sha256_crypt"],
                             sha256_crypt__default_rounds=80000)
        hash = myctx.hash(request.form.get("password"))

        # insert user/password into userdata
        userdata = db.execute(
            "INSERT INTO userdata (username, hash) VALUES (:username, :hash)",
            username=request.form.get("name"),
            hash=hash)

        # query database for username
        userdata = db.execute(
            "SELECT * FROM userdata WHERE username = :username",
            username=request.form.get("name"))

        # remember wich user has logged in
        session["userid"] = userdata[0]["id"]

        # create portfolio
        db.execute(
            "CREATE TABLE if not exists portfolio ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'userid' INTEGER, 'tried' INTEGER, 'saved' INTEGER, FOREIGN KEY(userid) REFERENCES userdata(id))"
        )

        # update portfolio
        db.execute(
            "INSERT INTO portfolio (userid, tried, saved) VALUES(:userid, 0, 0)",
            userid=session["userid"])

        # create cookbook
        db.execute(
            "CREATE TABLE if not exists cookbook ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'userid' INTEGER, 'recipeid' INTEGER, 'recipe' TEXT, 'link' TEXT, 'tried' BOOLEAN, 'image' BLOB, FOREIGN KEY(userid) REFERENCES userdata(id), FOREIGN KEY(recipeid) REFERENCES recipe(id))"
        )

        return redirect(url_for("homepage"))

    else:
        return render_template("register.html")
class HashingHelper:
    def __init__(self):
        self._crypt_context = CryptContext(schemes=["bcrypt"],
                                           deprecated="auto")

    def hash_password(self, password: str):
        return self._crypt_context.hash(password)

    def verify_password(self, plain_password: str, hashed_password: str):
        return self._crypt_context.verify(plain_password, hashed_password)
Beispiel #27
0
def register():
    """Register user."""
    # forget any user_id
    session.clear()
    # if user reached route via POST (as by submitting a form via POST)
    if request.method == "POST":
        # ensure username was submitted
        if not request.form.get("username"):
            return apology("must provide username")
        elif "\'" in request.form.get("username") or ";" in request.form.get(
                "username"):
            return apology("username can't contain ' or ;")
        # ensure password was submitted
        elif not request.form.get("password"):
            return apology("must provide password")

        elif not request.form.get("password_1"):
            return apology("must provide password(again)")
        if not request.form.get("nickname"):
            nickname = request.form.get("username")
        elif "\'" in request.form.get("nickname") or ";" in request.form.get(
                "nickname"):
            return apology("nickname can't contain ' or ;")
        else:
            nickname = request.form.get("nickname")
        if request.form.get("password") != request.form.get("password_1"):
            return apology("password(again) must equit password")

        myctx = CryptContext(["sha256_crypt"])
        passwd = myctx.hash(request.form.get("password"))

        createTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        # query database for username
        userId = db.execute(
            "INSERT INTO users (\"userName\",\"passwd\",\"nickName\", \"createTime\", \"loginTime\") VALUES (:userName, :passwd, :nickName, :createTime, :loginTime)",
            userName=request.form.get("username"),
            passwd=passwd,
            nickName=nickname,
            createTime=createTime,
            loginTime=createTime)
        # ensure username exists and password is correct
        if userId == 0:
            return apology("invalid username and/or password")

        # remember which user has logged in
        session["user_id"] = userId
        session["user_name"] = request.form.get("username")

        # redirect user to home page
        return redirect(url_for("mine"))

    # else if user reached route via GET (as by clicking a link or via redirect)
    else:
        return render_template("register.html")
Beispiel #28
0
class PasswordHasher:
    def __init__(self):
        self.password_hash = None
        self.myctx = CryptContext(schemes=["sha256_crypt"])

    def hash_password(self, password):
        self.password_hash = self.myctx.hash(password)
        return self.password_hash

    def verify_password(self, password, user):
        return self.myctx.verify(password, user.password)
Beispiel #29
0
async def change_password(
    oid: int,
    change: ChangePassword,
    current_user: models.User = Depends(user_with_users_access),
    session: Session = Depends(db_session)):
    """Change password."""
    user_db = __get_user(session, oid)
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    user_db.hashed_password = pwd_context.hash(change.password)
    session.commit()
    return {"status": "ok"}
Beispiel #30
0
    def _get_weak_hash(self, plaintext):
        """Create a weaker CryptContext and hash plaintext.

        (Weaker as in weaker than default context)
        """
        weak_context = CryptContext(
            schemes=["pbkdf2_sha256"],
            pbkdf2_sha256__default_rounds=5,
        )

        return weak_context.hash(plaintext)
Beispiel #31
0
class PasswordContext:
    def __init__(self):
        self.context = CryptContext(
            schemes=['bcrypt'],
            deprecated='auto',
        )

    def hash(self, password):
        return self.context.hash(password)

    def verify(self, password, password_hash):
        return self.context.verify(password, password_hash)
Beispiel #32
0
    def test_95_context_algs(self):
        """test handling of 'algs' in context object"""
        handler = self.handler
        from passlib.context import CryptContext
        c1 = CryptContext(["scram"], scram__algs="sha1,md5")

        h = c1.hash("dummy")
        self.assertEqual(handler.extract_digest_algs(h), ["md5", "sha-1"])
        self.assertFalse(c1.needs_update(h))

        c2 = c1.copy(scram__algs="sha1")
        self.assertFalse(c2.needs_update(h))

        c2 = c1.copy(scram__algs="sha1,sha256")
        self.assertTrue(c2.needs_update(h))
Beispiel #33
0
class User(AccessControlledModel):
    """
    This model represents the users of the system.
    """

    def initialize(self):
        self.name = 'user'
        self.ensureIndices(['login', 'email', 'groupInvites.groupId', 'size',
                            'created'])
        self.prefixSearchFields = (
            'login', ('firstName', 'i'), ('lastName', 'i'))

        self.ensureTextIndex({
            'login': 1,
            'firstName': 1,
            'lastName': 1
        }, language='none')

        self.exposeFields(level=AccessType.READ, fields=(
            '_id', 'login', 'public', 'firstName', 'lastName', 'admin',
            'created'))
        self.exposeFields(level=AccessType.ADMIN, fields=(
            'size', 'email', 'groups', 'groupInvites', 'status',
            'emailVerified'))

        # To ensure compatibility with authenticator apps, other defaults shouldn't be changed
        self._TotpFactory = TOTP.using(
            # An application secret could be set here, if it existed
            wallet=None
        )

        self._cryptContext = CryptContext(
            schemes=['bcrypt']
        )

        events.bind('model.user.save.created',
                    CoreEventHandler.USER_SELF_ACCESS, self._grantSelfAccess)
        events.bind('model.user.save.created',
                    CoreEventHandler.USER_DEFAULT_FOLDERS,
                    self._addDefaultFolders)

    def validate(self, doc):
        """
        Validate the user every time it is stored in the database.
        """
        doc['login'] = doc.get('login', '').lower().strip()
        doc['email'] = doc.get('email', '').lower().strip()
        doc['firstName'] = doc.get('firstName', '').strip()
        doc['lastName'] = doc.get('lastName', '').strip()
        doc['status'] = doc.get('status', 'enabled')

        if 'salt' not in doc:
            # Internal error, this should not happen
            raise Exception('Tried to save user document with no salt.')

        if not doc['firstName']:
            raise ValidationException('First name must not be empty.',
                                      'firstName')

        if not doc['lastName']:
            raise ValidationException('Last name must not be empty.',
                                      'lastName')

        if doc['status'] not in ('pending', 'enabled', 'disabled'):
            raise ValidationException(
                'Status must be pending, enabled, or disabled.', 'status')

        if 'hashAlg' in doc:
            # This is a legacy field; hash algorithms are now inline with the password hash
            del doc['hashAlg']

        self._validateLogin(doc['login'])

        if not mail_utils.validateEmailAddress(doc['email']):
            raise ValidationException('Invalid email address.', 'email')

        # Ensure unique logins
        q = {'login': doc['login']}
        if '_id' in doc:
            q['_id'] = {'$ne': doc['_id']}
        existing = self.findOne(q)
        if existing is not None:
            raise ValidationException('That login is already registered.',
                                      'login')

        # Ensure unique emails
        q = {'email': doc['email']}
        if '_id' in doc:
            q['_id'] = {'$ne': doc['_id']}
        existing = self.findOne(q)
        if existing is not None:
            raise ValidationException('That email is already registered.',
                                      'email')

        # If this is the first user being created, make it an admin
        existing = self.findOne({})
        if existing is None:
            doc['admin'] = True
            # Ensure settings don't stop this user from logging in
            doc['emailVerified'] = True
            doc['status'] = 'enabled'

        return doc

    def _validateLogin(self, login):
        if '@' in login:
            # Hard-code this constraint so we can always easily distinguish
            # an email address from a login
            raise ValidationException('Login may not contain "@".', 'login')

        if not re.match(r'^[a-z][\da-z\-\.]{3,}$', login):
            raise ValidationException(
                'Login must be at least 4 characters, start with a letter, and may only contain '
                'letters, numbers, dashes, and dots.', 'login')

    def filter(self, doc, user, additionalKeys=None):
        filteredDoc = super(User, self).filter(doc, user, additionalKeys)

        level = self.getAccessLevel(doc, user)
        if level >= AccessType.ADMIN:
            filteredDoc['otp'] = doc.get('otp', {}).get('enabled', False)

        return filteredDoc

    def authenticate(self, login, password, otpToken=None):
        """
        Validate a user login via username and password. If authentication fails,
        a ``AccessException`` is raised.

        :param login: The user's login or email.
        :type login: str
        :param password: The user's password.
        :type password: str
        :param otpToken: A one-time password for the user. If "True", then the one-time password
                         (if required) is assumed to be concatenated to the password.
        :type otpToken: str or bool or None
        :returns: The corresponding user if the login was successful.
        :rtype: dict
        """
        event = events.trigger('model.user.authenticate', {
            'login': login,
            'password': password
        })

        if event.defaultPrevented and len(event.responses):
            return event.responses[-1]

        login = login.lower().strip()
        loginField = 'email' if '@' in login else 'login'

        user = self.findOne({loginField: login})
        if user is None:
            raise AccessException('Login failed.')

        # Handle users with no password
        if not self.hasPassword(user):
            e = events.trigger('no_password_login_attempt', {
                'user': user,
                'password': password
            })

            if len(e.responses):
                return e.responses[-1]

            raise ValidationException(
                'This user does not have a password. You must log in with an '
                'external service, or reset your password.')

        # Handle OTP token concatenation
        if otpToken is True and self.hasOtpEnabled(user):
            # Assume the last (typically 6) characters are the OTP, so split at that point
            otpTokenLength = self._TotpFactory.digits
            otpToken = password[-otpTokenLength:]
            password = password[:-otpTokenLength]

        # Verify password
        if not self._cryptContext.verify(password, user['salt']):
            raise AccessException('Login failed.')

        # Verify OTP
        if self.hasOtpEnabled(user):
            if otpToken is None:
                raise AccessException(
                    'User authentication must include a one-time password '
                    '(typically in the "Girder-OTP" header).')
            self.verifyOtp(user, otpToken)
        elif isinstance(otpToken, six.string_types):
            raise AccessException('The user has not enabled one-time passwords.')

        # This has the same behavior as User.canLogin, but returns more
        # detailed error messages
        if user.get('status', 'enabled') == 'disabled':
            raise AccessException('Account is disabled.', extra='disabled')

        if self.emailVerificationRequired(user):
            raise AccessException(
                'Email verification required.', extra='emailVerification')

        if self.adminApprovalRequired(user):
            raise AccessException('Account approval required.', extra='accountApproval')

        return user

    def remove(self, user, progress=None, **kwargs):
        """
        Delete a user, and all references to it in the database.

        :param user: The user document to delete.
        :type user: dict
        :param progress: A progress context to record progress on.
        :type progress: girder.utility.progress.ProgressContext or None.
        """
        from .folder import Folder
        from .group import Group
        from .token import Token

        # Delete all authentication tokens owned by this user
        Token().removeWithQuery({'userId': user['_id']})

        # Delete all pending group invites for this user
        Group().update(
            {'requests': user['_id']},
            {'$pull': {'requests': user['_id']}}
        )

        # Delete all of the folders under this user
        folderModel = Folder()
        folders = folderModel.find({
            'parentId': user['_id'],
            'parentCollection': 'user'
        })
        for folder in folders:
            folderModel.remove(folder, progress=progress, **kwargs)

        # Finally, delete the user document itself
        AccessControlledModel.remove(self, user)
        if progress:
            progress.update(increment=1, message='Deleted user ' + user['login'])

    def getAdmins(self):
        """
        Helper to return a cursor of all site-admin users. The number of site
        admins is assumed to be small enough that we will not need to page the
        results for now.
        """
        return self.find({'admin': True})

    def search(self, text=None, user=None, limit=0, offset=0, sort=None):
        """
        List all users. Since users are access-controlled, this will filter
        them by access policy.

        :param text: Pass this to perform a full-text search for users.
        :param user: The user running the query. Only returns users that this
                     user can see.
        :param limit: Result limit.
        :param offset: Result offset.
        :param sort: The sort structure to pass to pymongo.
        :returns: Iterable of users.
        """
        # Perform the find; we'll do access-based filtering of the result set
        # afterward.
        if text is not None:
            cursor = self.textSearch(text, sort=sort)
        else:
            cursor = self.find({}, sort=sort)

        return self.filterResultsByPermission(
            cursor=cursor, user=user, level=AccessType.READ, limit=limit,
            offset=offset)

    def hasPassword(self, user):
        """
        Returns whether or not the given user has a password stored in the
        database. If not, it is expected that the user will be authenticated by
        an external service.

        :param user: The user to test.
        :type user: dict
        :returns: bool
        """
        return user['salt'] is not None

    def setPassword(self, user, password, save=True):
        """
        Change a user's password.

        :param user: The user whose password to change.
        :param password: The new password. If set to None, no password will
                         be stored for this user. This should be done in cases
                         where an external system is responsible for
                         authenticating the user.
        """
        if password is None:
            user['salt'] = None
        else:
            cur_config = config.getConfig()

            # Normally this would go in validate() but password is a special case.
            if not re.match(cur_config['users']['password_regex'], password):
                raise ValidationException(cur_config['users']['password_description'], 'password')

            user['salt'] = self._cryptContext.hash(password)

        if save:
            self.save(user)

    def initializeOtp(self, user):
        """
        Initialize the use of one-time passwords with this user.

        This does not save the modified user model.

        :param user: The user to modify.
        :return: The new OTP keys, each in KeyUriFormat.
        :rtype: dict
        """
        totp = self._TotpFactory.new()

        user['otp'] = {
            'enabled': False,
            'totp': totp.to_dict()
        }

        # Use the brand name as the OTP issuer if it's non-default (since that's prettier and more
        # meaningful for users), but fallback to the site hostname if the brand name isn't set
        # (to disambiguate otherwise identical "Girder" issuers)
        # Prevent circular import
        from girder.api.rest import getUrlParts
        brandName = Setting().get(SettingKey.BRAND_NAME)
        defaultBrandName = Setting().getDefault(SettingKey.BRAND_NAME)
        # OTP URIs ( https://github.com/google/google-authenticator/wiki/Key-Uri-Format ) do not
        # allow colons, so use only the hostname component
        serverHostname = getUrlParts().netloc.partition(':')[0]
        # Normally, the issuer would be set when "self._TotpFactory" is instantiated, but that
        # happens during model initialization, when there's no current request, so the server
        # hostname is not known then
        otpIssuer = brandName if brandName != defaultBrandName else serverHostname

        return {
            'totpUri': totp.to_uri(label=user['login'], issuer=otpIssuer)
        }

    def hasOtpEnabled(self, user):
        return 'otp' in user and user['otp']['enabled']

    def verifyOtp(self, user, otpToken):
        lastCounterKey = 'girder.models.user.%s.otp.totp.counter' % user['_id']

        # The last successfully-authenticated key (which is blacklisted from reuse)
        lastCounter = rateLimitBuffer.get(lastCounterKey) or None

        try:
            totpMatch = self._TotpFactory.verify(
                otpToken, user['otp']['totp'], last_counter=lastCounter)
        except TokenError as e:
            raise AccessException('One-time password validation failed: %s' % e)

        # The totpMatch.cache_seconds tells us prospectively how long the counter needs to be cached
        # for, but dogpile.cache expiration times work retrospectively (on "get"), so there's no
        # point to using it (over-caching just wastes cache resources, but does not impact
        # "totp.verify" security)
        rateLimitBuffer.set(lastCounterKey, totpMatch.counter)

    def createUser(self, login, password, firstName, lastName, email,
                   admin=False, public=True):
        """
        Create a new user with the given information. The user will be created
        with the default "Public" and "Private" folders.

        :param admin: Whether user is global administrator.
        :type admin: bool
        :param public: Whether user is publicly visible.
        :type public: bool
        :returns: The user document that was created.
        """
        from .setting import Setting
        requireApproval = Setting().get(SettingKey.REGISTRATION_POLICY) == 'approve'
        if admin:
            requireApproval = False

        user = {
            'login': login,
            'email': email,
            'firstName': firstName,
            'lastName': lastName,
            'created': datetime.datetime.utcnow(),
            'emailVerified': False,
            'status': 'pending' if requireApproval else 'enabled',
            'admin': admin,
            'size': 0,
            'groups': [],
            'groupInvites': []
        }

        self.setPassword(user, password, save=False)
        self.setPublic(user, public, save=False)

        user = self.save(user)

        verifyEmail = Setting().get(SettingKey.EMAIL_VERIFICATION) != 'disabled'
        if verifyEmail:
            self._sendVerificationEmail(user)

        if requireApproval:
            self._sendApprovalEmail(user)

        return user

    def canLogin(self, user):
        """
        Returns True if the user is allowed to login, e.g. email verification
        is not needed and admin approval is not needed.
        """
        if user.get('status', 'enabled') == 'disabled':
            return False
        if self.emailVerificationRequired(user):
            return False
        if self.adminApprovalRequired(user):
            return False
        return True

    def emailVerificationRequired(self, user):
        """
        Returns True if email verification is required and this user has not
        yet verified their email address.
        """
        from .setting import Setting
        return (not user['emailVerified']) and \
            Setting().get(SettingKey.EMAIL_VERIFICATION) == 'required'

    def adminApprovalRequired(self, user):
        """
        Returns True if the registration policy requires admin approval and
        this user is pending approval.
        """
        from .setting import Setting
        return user.get('status', 'enabled') == 'pending' and \
            Setting().get(SettingKey.REGISTRATION_POLICY) == 'approve'

    def _sendApprovalEmail(self, user):
        url = '%s#user/%s' % (
            mail_utils.getEmailUrlPrefix(), str(user['_id']))
        text = mail_utils.renderTemplate('accountApproval.mako', {
            'user': user,
            'url': url
        })
        mail_utils.sendEmail(
            toAdmins=True,
            subject='Girder: Account pending approval',
            text=text)

    def _sendApprovedEmail(self, user):
        text = mail_utils.renderTemplate('accountApproved.mako', {
            'user': user,
            'url': mail_utils.getEmailUrlPrefix()
        })
        mail_utils.sendEmail(
            to=user.get('email'),
            subject='Girder: Account approved',
            text=text)

    def _sendVerificationEmail(self, user):
        from .token import Token

        token = Token().createToken(
            user, days=1, scope=TokenScope.EMAIL_VERIFICATION)
        url = '%s#useraccount/%s/verification/%s' % (
            mail_utils.getEmailUrlPrefix(), str(user['_id']), str(token['_id']))
        text = mail_utils.renderTemplate('emailVerification.mako', {
            'url': url
        })
        mail_utils.sendEmail(
            to=user.get('email'),
            subject='Girder: Email verification',
            text=text)

    def _grantSelfAccess(self, event):
        """
        This callback grants a user admin access to itself.

        This generally should not be called or overridden directly, but it may
        be unregistered from the `model.user.save.created` event.
        """
        user = event.info

        self.setUserAccess(user, user, level=AccessType.ADMIN, save=True)

    def _addDefaultFolders(self, event):
        """
        This callback creates "Public" and "Private" folders on a user, after
        it is first created.

        This generally should not be called or overridden directly, but it may
        be unregistered from the `model.user.save.created` event.
        """
        from .folder import Folder
        from .setting import Setting

        if Setting().get(SettingKey.USER_DEFAULT_FOLDERS) == 'public_private':
            user = event.info

            publicFolder = Folder().createFolder(
                user, 'Public', parentType='user', public=True, creator=user)
            privateFolder = Folder().createFolder(
                user, 'Private', parentType='user', public=False, creator=user)
            # Give the user admin access to their own folders
            Folder().setUserAccess(publicFolder, user, AccessType.ADMIN, save=True)
            Folder().setUserAccess(privateFolder, user, AccessType.ADMIN, save=True)

    def fileList(self, doc, user=None, path='', includeMetadata=False, subpath=True, data=True):
        """
        This function generates a list of 2-tuples whose first element is the
        relative path to the file from the user's folders root and whose second
        element depends on the value of the `data` flag. If `data=True`, the
        second element will be a generator that will generate the bytes of the
        file data as stored in the assetstore. If `data=False`, the second
        element is the file document itself.

        :param doc: the user to list.
        :param user: a user used to validate data that is returned.
        :param path: a path prefix to add to the results.
        :param includeMetadata: if True and there is any metadata, include a
                                result which is the JSON string of the
                                metadata.  This is given a name of
                                metadata[-(number).json that is distinct from
                                any file within the item.
        :param subpath: if True, add the user's name to the path.
        :param data: If True return raw content of each file as stored in the
            assetstore, otherwise return file document.
        :type data: bool
        """
        from .folder import Folder

        if subpath:
            path = os.path.join(path, doc['login'])
        folderModel = Folder()
        for folder in folderModel.childFolders(parentType='user', parent=doc, user=user):
            for (filepath, file) in folderModel.fileList(
                    folder, user, path, includeMetadata, subpath=True, data=data):
                yield (filepath, file)

    def subtreeCount(self, doc, includeItems=True, user=None, level=None):
        """
        Return the size of the user's folders.  The user is counted as well.

        :param doc: The user.
        :param includeItems: Whether to include items in the subtree count, or
            just folders.
        :type includeItems: bool
        :param user: If filtering by permission, the user to filter against.
        :param level: If filtering by permission, the required permission level.
        :type level: AccessLevel
        """
        from .folder import Folder

        count = 1
        folderModel = Folder()
        folders = folderModel.findWithPermissions({
            'parentId': doc['_id'],
            'parentCollection': 'user'
        }, fields='access', user=user, level=level)

        count += sum(folderModel.subtreeCount(
            folder, includeItems=includeItems, user=user, level=level)
            for folder in folders)
        return count

    def countFolders(self, user, filterUser=None, level=None):
        """
        Returns the number of top level folders under this user. Access
        checking is optional; to circumvent access checks, pass ``level=None``.

        :param user: The user whose top level folders to count.
        :type collection: dict
        :param filterUser: If performing access checks, the user to check
            against.
        :type filterUser: dict or None
        :param level: The required access level, or None to return the raw
            top-level folder count.
        """
        from .folder import Folder

        fields = () if level is None else ('access', 'public')

        folderModel = Folder()
        folders = folderModel.findWithPermissions({
            'parentId': user['_id'],
            'parentCollection': 'user'
        }, fields=fields, user=filterUser, level=level)

        return folders.count()

    def updateSize(self, doc):
        """
        Recursively recomputes the size of this user and its underlying
        folders and fixes the sizes as needed.

        :param doc: The user.
        :type doc: dict
        """
        from .folder import Folder

        size = 0
        fixes = 0
        folderModel = Folder()
        folders = folderModel.find({
            'parentId': doc['_id'],
            'parentCollection': 'user'
        })
        for folder in folders:
            # fix folder size if needed
            _, f = folderModel.updateSize(folder)
            fixes += f
            # get total recursive folder size
            folder = folderModel.load(folder['_id'], force=True)
            size += folderModel.getSizeRecursive(folder)
        # fix value if incorrect
        if size != doc.get('size'):
            self.update({'_id': doc['_id']}, update={'$set': {'size': size}})
            fixes += 1
        return size, fixes
session = Session()
session.add_all(users + roles + domains + actions + resources + credential_types)

users = dict((user.first_name+'_'+user.last_name, user) for user in session.query(User).all())
domains = dict((domain.name, domain) for domain in session.query(Domain).all())
actions = dict((action.name, action) for action in session.query(Action).all())
resources = dict((resource.name, resource) for resource in session.query(Resource).all())
roles = dict((role.title, role) for role in session.query(Role).all())
cred_types =  dict((ct.title, ct) for ct in session.query(CredentialType).all())

thirty_from_now = datetime.datetime.now() + datetime.timedelta(days=30)
print('thirty from now is:  ', thirty_from_now)

cc = CryptContext(schemes=['bcrypt_sha256'])
password = cc.hash('letsgobowling')

totp_key = 'DP3RDO3FAAFUAFXQELW6OTB2IGM3SS6G'

thedude = users['Jeffrey_Lebowski']

passwords = [Credential(user_id=user.pk_id,
                          credential=password,
                          credential_type_id=cred_types['password'].pk_id,
                          expiration_dt=thirty_from_now) for user in users.values()]
thedude_totp_key = [Credential(user_id=thedude.pk_id,
                          credential=totp_key,
                          credential_type_id=cred_types['totp_key'].pk_id,
                          expiration_dt=thirty_from_now)]
session.add_all(passwords + thedude_totp_key)
Beispiel #35
0
class DatabaseUserService:
    def __init__(self, session, *, ratelimiters=None, metrics):
        if ratelimiters is None:
            ratelimiters = {}
        ratelimiters = collections.defaultdict(DummyRateLimiter, ratelimiters)

        self.db = session
        self.ratelimiters = ratelimiters
        self.hasher = CryptContext(
            schemes=[
                "argon2",
                "bcrypt_sha256",
                "bcrypt",
                "django_bcrypt",
                "unix_disabled",
            ],
            deprecated=["auto"],
            truncate_error=True,
            # Argon 2 Configuration
            argon2__memory_cost=1024,
            argon2__parallelism=6,
            argon2__time_cost=6,
        )
        self._metrics = metrics

    @functools.lru_cache()
    def get_user(self, userid):
        # TODO: We probably don't actually want to just return the database
        #       object here.
        # TODO: We need some sort of Anonymous User.
        return self.db.query(User).get(userid)

    @functools.lru_cache()
    def get_user_by_username(self, username):
        user_id = self.find_userid(username)
        return None if user_id is None else self.get_user(user_id)

    @functools.lru_cache()
    def get_user_by_email(self, email):
        user_id = self.find_userid_by_email(email)
        return None if user_id is None else self.get_user(user_id)

    @functools.lru_cache()
    def find_userid(self, username):
        try:
            user = self.db.query(User.id).filter(User.username == username).one()
        except NoResultFound:
            return

        return user.id

    @functools.lru_cache()
    def find_userid_by_email(self, email):
        try:
            # flake8: noqa
            user_id = (self.db.query(Email.user_id).filter(Email.email == email).one())[
                0
            ]
        except NoResultFound:
            return

        return user_id

    def check_password(self, userid, password, *, tags=None):
        tags = tags if tags is not None else []

        self._metrics.increment("warehouse.authentication.start", tags=tags)

        # The very first thing we want to do is check to see if we've hit our
        # global rate limit or not, assuming that we've been configured with a
        # global rate limiter anyways.
        if not self.ratelimiters["global"].test():
            logger.warning("Global failed login threshold reached.")
            self._metrics.increment(
                "warehouse.authentication.ratelimited",
                tags=tags + ["ratelimiter:global"],
            )
            raise TooManyFailedLogins(resets_in=self.ratelimiters["global"].resets_in())

        user = self.get_user(userid)
        if user is not None:
            # Now, check to make sure that we haven't hitten a rate limit on a
            # per user basis.
            if not self.ratelimiters["user"].test(user.id):
                self._metrics.increment(
                    "warehouse.authentication.ratelimited",
                    tags=tags + ["ratelimiter:user"],
                )
                raise TooManyFailedLogins(
                    resets_in=self.ratelimiters["user"].resets_in(user.id)
                )

            # Actually check our hash, optionally getting a new hash for it if
            # we should upgrade our saved hashed.
            ok, new_hash = self.hasher.verify_and_update(password, user.password)

            # First, check to see if the password that we were given was OK.
            if ok:
                # Then, if the password was OK check to see if we've been given
                # a new password hash from the hasher, if so we'll want to save
                # that hash.
                if new_hash:
                    user.password = new_hash

                self._metrics.increment("warehouse.authentication.ok", tags=tags)

                return True
            else:
                self._metrics.increment(
                    "warehouse.authentication.failure",
                    tags=tags + ["failure_reason:password"],
                )
        else:
            self._metrics.increment(
                "warehouse.authentication.failure", tags=tags + ["failure_reason:user"]
            )

        # If we've gotten here, then we'll want to record a failed login in our
        # rate limiting before returning False to indicate a failed password
        # verification.
        if user is not None:
            self.ratelimiters["user"].hit(user.id)
        self.ratelimiters["global"].hit()

        return False

    def create_user(
        self, username, name, password, is_active=False, is_superuser=False
    ):

        user = User(
            username=username,
            name=name,
            password=self.hasher.hash(password),
            is_active=is_active,
            is_superuser=is_superuser,
        )
        self.db.add(user)
        self.db.flush()  # flush the db now so user.id is available

        return user

    def add_email(self, user_id, email_address, primary=None, verified=False):
        user = self.get_user(user_id)

        # If primary is None, then we're going to auto detect whether this should be the
        # primary address or not. The basic rule is that if the user doesn't already
        # have a primary address, then the address we're adding now is going to be
        # set to their primary.
        if primary is None:
            primary = True if user.primary_email is None else False

        email = Email(
            email=email_address, user=user, primary=primary, verified=verified
        )
        self.db.add(email)
        self.db.flush()  # flush the db now so email.id is available

        return email

    def update_user(self, user_id, **changes):
        user = self.get_user(user_id)
        for attr, value in changes.items():
            if attr == PASSWORD_FIELD:
                value = self.hasher.hash(value)
            setattr(user, attr, value)

        # If we've given the user a new password, then we also want to unset the
        # reason for disable... because a new password means no more disabled
        # user.
        if PASSWORD_FIELD in changes:
            user.disabled_for = None

        return user

    def disable_password(self, user_id, reason=None):
        user = self.get_user(user_id)
        user.password = self.hasher.disable()
        user.disabled_for = reason

    def is_disabled(self, user_id):
        user = self.get_user(user_id)

        # User is not disabled.
        if self.hasher.is_enabled(user.password):
            return (False, None)
        # User is disabled.
        else:
            return (True, user.disabled_for)
Beispiel #36
0
if __name__ == '__main__':

    args, config = init_cmdline (build_parser ())

    dba = db_tools.PostgreSQLEngine (**config)

    db.Base3.metadata.drop_all   (dba.engine)
    db.Base3.metadata.create_all (dba.engine)

    pwd_context = CryptContext (schemes = [ config['USER_PASSWORD_HASH'] ])

    with dba.engine.begin () as src:
        execute (src, "INSERT INTO role (id, name, description) VALUES (1, 'admin',  'Administrator')", {})
        execute (src, "INSERT INTO role (id, name, description) VALUES (2, 'editor', 'Editor')", {})

        if args.email:
            params = {
                "username"     : args.username,
                "email"        : args.email,
                "password"     : pwd_context.hash (args.password) if args.password else '',
                "active"       : True,
                "confirmed_at" : datetime.datetime.now ()
            }

            execute (src,
                     "INSERT INTO \"user\" (id, username, email, password, active, confirmed_at) " +
                     "VALUES (1, :username, :email, :password, :active, :confirmed_at)",
                     params)
            execute (src, "INSERT INTO roles_users (id, user_id, role_id) VALUES (1, 1, 1)", {})
            execute (src, "INSERT INTO roles_users (id, user_id, role_id) VALUES (2, 1, 2)", {})
Beispiel #37
0
class DatabaseUserService:

    def __init__(self, session, ratelimiters=None):
        if ratelimiters is None:
            ratelimiters = {}
        ratelimiters = collections.defaultdict(DummyRateLimiter, ratelimiters)

        self.db = session
        self.ratelimiters = ratelimiters
        self.hasher = CryptContext(
            schemes=[
                "argon2",
                "bcrypt_sha256",
                "bcrypt",
                "django_bcrypt",
                "unix_disabled",
            ],
            deprecated=["auto"],
            truncate_error=True,

            # Argon 2 Configuration
            argon2__memory_cost=1024,
            argon2__parallelism=6,
            argon2__time_cost=6,
        )

    @functools.lru_cache()
    def get_user(self, userid):
        # TODO: We probably don't actually want to just return the database
        #       object here.
        # TODO: We need some sort of Anonymous User.
        return self.db.query(User).get(userid)

    @functools.lru_cache()
    def find_userid(self, username):
        try:
            user = (
                self.db.query(User.id)
                    .filter(User.username == username)
                    .one()
            )
        except NoResultFound:
            return

        return user.id

    @functools.lru_cache()
    def find_userid_by_email(self, email):
        try:
            # flake8: noqa
            user_id = (
                self.db.query(Email.user_id)
                    .filter(Email.email == email)
                    .one()
            )[0]
        except NoResultFound:
            return

        return user_id

    def check_password(self, userid, password):
        # The very first thing we want to do is check to see if we've hit our
        # global rate limit or not, assuming that we've been configured with a
        # global rate limiter anyways.
        if not self.ratelimiters["global"].test():
            logger.warning("Global failed login threshold reached.")
            raise TooManyFailedLogins(
                resets_in=self.ratelimiters["global"].resets_in(),
            )

        user = self.get_user(userid)
        if user is not None:
            # Now, check to make sure that we haven't hitten a rate limit on a
            # per user basis.
            if not self.ratelimiters["user"].test(user.id):
                raise TooManyFailedLogins(
                    resets_in=self.ratelimiters["user"].resets_in(user.id),
                )

            # Actually check our hash, optionally getting a new hash for it if
            # we should upgrade our saved hashed.
            ok, new_hash = self.hasher.verify_and_update(
                password,
                user.password,
            )

            # First, check to see if the password that we were given was OK.
            if ok:
                # Then, if the password was OK check to see if we've been given
                # a new password hash from the hasher, if so we'll want to save
                # that hash.
                if new_hash:
                    user.password = new_hash

                return True

        # If we've gotten here, then we'll want to record a failed login in our
        # rate limiting before returning False to indicate a failed password
        # verification.
        if user is not None:
            self.ratelimiters["user"].hit(user.id)
        self.ratelimiters["global"].hit()

        return False

    def create_user(self, username, name, password, email,
                    is_active=False, is_staff=False, is_superuser=False):

        user = User(username=username,
                    name=name,
                    password=self.hasher.hash(password),
                    is_active=is_active,
                    is_staff=is_staff,
                    is_superuser=is_superuser)
        self.db.add(user)
        email_object = Email(email=email, user=user,
                             primary=True, verified=False)
        self.db.add(email_object)
        # flush the db now so user.id is available
        self.db.flush()
        return user

    def update_user(self, user_id, **changes):
        user = self.get_user(user_id)
        for attr, value in changes.items():
            setattr(user, attr, value)
        return user

    def verify_email(self, user_id, email_address):
        user = self.get_user(user_id)
        for email in user.emails:
            if email.email == email_address:
                email.verified = True