authorization: update for changes in the database

Since the User DB is not going to be large cache the users
in local memory and sync with the DB when changes are
made to the local user store.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2022-01-30 20:21:24 -05:00
parent 46f74329d3
commit b43f4623fc
1 changed files with 19 additions and 7 deletions

View File

@ -75,7 +75,8 @@ class Authorization:
self.force_logins = config.getboolean('force_logins', False) self.force_logins = config.getboolean('force_logins', False)
database: DBComp = self.server.lookup_component('database') database: DBComp = self.server.lookup_component('database')
database.register_local_namespace('authorized_users', forbidden=True) database.register_local_namespace('authorized_users', forbidden=True)
self.users = database.wrap_namespace('authorized_users') self.user_db = database.wrap_namespace('authorized_users')
self.users: Dict[str, Dict[str, Any]] = self.user_db.as_dict()
api_user: Optional[Dict[str, Any]] = self.users.get(API_USER, None) api_user: Optional[Dict[str, Any]] = self.users.get(API_USER, None)
if api_user is None: if api_user is None:
self.api_key = uuid.uuid4().hex self.api_key = uuid.uuid4().hex
@ -126,7 +127,8 @@ class Authorization:
self.users[username] = user_info self.users[username] = user_info
continue continue
self.public_jwks[jwk_id] = self._generate_public_jwk(priv_key) self.public_jwks[jwk_id] = self._generate_public_jwk(priv_key)
# sync user changes to the database
self.user_db.sync(self.users)
self.trusted_users: Dict[IPAddr, Any] = {} self.trusted_users: Dict[IPAddr, Any] = {}
self.oneshot_tokens: Dict[str, OneshotToken] = {} self.oneshot_tokens: Dict[str, OneshotToken] = {}
self.permitted_paths: Set[str] = set() self.permitted_paths: Set[str] = set()
@ -229,6 +231,9 @@ class Authorization:
self.server.register_notification("authorization:user_created") self.server.register_notification("authorization:user_created")
self.server.register_notification("authorization:user_deleted") self.server.register_notification("authorization:user_deleted")
def _sync_user(self, username: str) -> None:
self.user_db[username] = self.users[username]
async def component_init(self) -> None: async def component_init(self) -> None:
self.prune_timer.start(delay=PRUNE_CHECK_TIME) self.prune_timer.start(delay=PRUNE_CHECK_TIME)
@ -236,7 +241,8 @@ class Authorization:
action = web_request.get_action() action = web_request.get_action()
if action.upper() == 'POST': if action.upper() == 'POST':
self.api_key = uuid.uuid4().hex self.api_key = uuid.uuid4().hex
self.users[f'{API_USER}.api_key'] = self.api_key self.users[API_USER]['api_key'] = self.api_key
self._sync_user(API_USER)
return self.api_key return self.api_key
async def _handle_oneshot_request(self, web_request: WebRequest) -> str: async def _handle_oneshot_request(self, web_request: WebRequest) -> str:
@ -256,8 +262,9 @@ class Authorization:
if username in RESERVED_USERS: if username in RESERVED_USERS:
raise self.server.error( raise self.server.error(
f"Invalid log out request for user {username}") f"Invalid log out request for user {username}")
self.users.pop(f"{username}.jwt_secret", None) self.users[username].pop("jwt_secret", None)
jwk_id: str = self.users.pop(f"{username}.jwk_id", "") jwk_id: str = self.users[username].pop("jwk_id", None)
self._sync_user(username)
self.public_jwks.pop(jwk_id, None) self.public_jwks.pop(jwk_id, None)
return { return {
"username": username, "username": username,
@ -342,7 +349,8 @@ class Authorization:
raise self.server.error("Invalid Password") raise self.server.error("Invalid Password")
new_hashed_pass = hashlib.pbkdf2_hmac( new_hashed_pass = hashlib.pbkdf2_hmac(
'sha256', new_pass.encode(), salt, HASH_ITER).hex() 'sha256', new_pass.encode(), salt, HASH_ITER).hex()
self.users[f'{username}.password'] = new_hashed_pass self.users[username]['password'] = new_hashed_pass
self._sync_user(username)
return { return {
'username': username, 'username': username,
'action': "user_password_reset" 'action': "user_password_reset"
@ -371,6 +379,7 @@ class Authorization:
'created_on': time.time() 'created_on': time.time()
} }
self.users[username] = user_info self.users[username] = user_info
self._sync_user(username)
action = "user_created" action = "user_created"
else: else:
if username not in self.users: if username not in self.users:
@ -389,6 +398,7 @@ class Authorization:
user_info['jwt_secret'] = private_key.hex_seed().decode() user_info['jwt_secret'] = private_key.hex_seed().decode()
user_info['jwk_id'] = jwk_id user_info['jwk_id'] = jwk_id
self.users[username] = user_info self.users[username] = user_info
self._sync_user(username)
self.public_jwks[jwk_id] = self._generate_public_jwk(private_key) self.public_jwks[jwk_id] = self._generate_public_jwk(private_key)
else: else:
private_key = self._load_private_key(jwt_secret_hex) private_key = self._load_private_key(jwt_secret_hex)
@ -424,8 +434,10 @@ class Authorization:
user_info: Optional[Dict[str, Any]] = self.users.get(username) user_info: Optional[Dict[str, Any]] = self.users.get(username)
if user_info is None: if user_info is None:
raise self.server.error(f"No registered user: {username}") raise self.server.error(f"No registered user: {username}")
self.public_jwks.pop(self.users.get(f"{username}.jwk_id"), None) if 'jwk_id' in user_info:
self.public_jwks.pop(user_info['jwk_id'], None)
del self.users[username] del self.users[username]
del self.user_db[username]
event_loop = self.server.get_event_loop() event_loop = self.server.get_event_loop()
event_loop.delay_callback( event_loop.delay_callback(
.005, self.server.send_event, .005, self.server.send_event,