Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace passlib with bcrypt #306

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 57 additions & 5 deletions alchemiscale/security/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,73 @@

"""

from datetime import datetime, timedelta
from typing import Union, Optional
import secrets
from datetime import datetime, timedelta
from typing import Optional, Union

import bcrypt
from fastapi import HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel

from .models import Token, TokenData, CredentialedEntity
from .models import CredentialedEntity, Token, TokenData

MAX_PASSWORD_SIZE = 4096
_dummy_secret = "dummy"


class BcryptPasswordHandler(object):
rounds: int = 12
ident: str = "$2b$"
salt: str = ""
checksum: str = ""

def __init__(self, rounds: int = 12, ident: str = "$2b$"):
self.rounds = rounds
self.ident = ident

def _get_config(self) -> bytes:
config = bcrypt.gensalt(
self.rounds, prefix=self.ident.strip("$").encode("ascii")
)
self.salt = config.decode("ascii")[len(self.ident) + 3 :]
return config

def to_string(self) -> str:
return "%s%02d$%s%s" % (self.ident, self.rounds, self.salt, self.checksum)

def hash(self, key: str) -> str:
validate_secret(key)
config = self._get_config()
hash_ = bcrypt.hashpw(key.encode("utf-8"), config)
if not hash_.startswith(config) or len(hash_) != len(config) + 31:
raise ValueError("bcrypt.hashpw returned an invalid hash")
self.checksum = hash_[-31:].decode("ascii")
return self.to_string()

def verify(self, key: str, hash: str) -> bool:
validate_secret(key)

if hash is None:
self.hash(_dummy_secret)
return False

return bcrypt.checkpw(key.encode("utf-8"), hash.encode("utf-8"))


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
pwd_context = BcryptPasswordHandler()


def validate_secret(secret):
"""ensure secret has correct type & size"""
if not isinstance(secret, (str, bytes)):
raise TypeError("secret must be a string or bytes")
if len(secret) > MAX_PASSWORD_SIZE:
raise ValueError(
f"secret is too long, maximum length is {MAX_PASSWORD_SIZE} characters"
)


def generate_secret_key():
Expand Down
7 changes: 7 additions & 0 deletions alchemiscale/tests/unit/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@ def test_token_data(secret_key):
token_data = auth.get_token_data(token=token, secret_key=secret_key)

assert token_data.scopes == ["*-*-*"]


def test_bcrypt_password_handler():
handler = auth.BcryptPasswordHandler()
hash_ = handler.hash("test")
assert handler.verify("test", hash_)
assert not handler.verify("deadbeef", hash_)
1 change: 0 additions & 1 deletion devtools/conda-envs/alchemiscale-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ dependencies:
- uvicorn
- gunicorn
- python-jose
- passlib
- bcrypt
- python-multipart
- starlette
Expand Down
3 changes: 1 addition & 2 deletions devtools/conda-envs/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies:
- monotonic
- docker-py # for grolt

# user client printing
# user client printing
- rich

## object store
Expand All @@ -28,7 +28,6 @@ dependencies:
- uvicorn
- gunicorn
- python-jose
- passlib
- bcrypt
- python-multipart
- starlette
Expand Down
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"jose",
"networkx",
"numpy",
"passlib",
"py2neo",
"pydantic",
"starlette",
Expand Down