Do not blindly import old API keys

This commit is contained in:
Paul Bienkowski 2021-12-01 09:42:27 +01:00
parent 40784ba51e
commit 4003d5e938
4 changed files with 37 additions and 6 deletions

View file

@ -38,6 +38,11 @@ explicitly. Once we implement them, their usage will be described in the
python tools/import_from_mongodb.py mongodb://mongo/obs \
--keycloak-users-file /export/users.json
```
There is an option `--keep-api-keys` which means the users won't have to
reconfigure the devices they used their API key in. **However**, please try
to avoid this option if at all possible, as the old keys are *very* insecure.
The default without this option to generate a new, secure API key for each
user.
* Shut down the `mongo` service, you can now remove it from docker-compose.yaml
* Start `keycloak` and configure it, similarly to how it was configured in the
development setup (but choose more secure options). Update the API config

View file

@ -10,6 +10,7 @@ import math
import aiofiles
import random
import string
import secrets
from slugify import slugify
from sqlalchemy.ext.declarative import declarative_base
@ -337,6 +338,13 @@ class User(Base):
# migrating *to* the external authentication scheme.
match_by_username_email = Column(Boolean, server_default=false())
def generate_api_key(self):
"""
Generates a new :py:obj:`api_key` into this instance. The new key is
sourced from a secure random source and is urlsafe.
"""
self.api_key = secrets.token_urlsafe(24)
def to_dict(self, for_user_id=None):
return {
"username": self.username,

View file

@ -1,6 +1,4 @@
import logging
import os
import binascii
from sanic.response import json
from sanic.exceptions import InvalidUsage
@ -42,7 +40,7 @@ async def put_user(req):
user.are_tracks_visible_for_all = bool(data["areTracksVisibleForAll"])
if data.get("updateApiKey"):
user.api_key = binascii.b2a_hex(os.urandom(16)).decode("ascii")
user.generate_api_key()
await req.ctx.db.commit()
return json(user_to_json(req.ctx.user))

View file

@ -38,21 +38,37 @@ async def main():
default=None,
)
parser.add_argument(
"--keep-api-keys",
action="store_true",
help="keep the old API keys (very insecure!) instead of generating new ones",
default=False,
)
args = parser.parse_args()
if args.keep_api_keys:
log.warning(
"Importing users with their old API keys. These keys are very insecure and "
"could provide access to user data to third parties. Consider to notify "
"your users about the need to generate a new API key through their profile pages."
)
async with connect_db(app.config.POSTGRES_URL):
async with make_session() as session:
mongo = AsyncIOMotorClient(args.mongodb_url).get_default_database()
log.debug("Connected to mongodb and postgres.")
user_id_map = await import_users(mongo, session, args.keycloak_users_file)
user_id_map = await import_users(
mongo, session, args.keycloak_users_file, args.keep_api_keys
)
await import_tracks(mongo, session, user_id_map)
await session.commit()
async def import_users(mongo, session, keycloak_users_file):
async def import_users(mongo, session, keycloak_users_file, keep_api_keys):
keycloak_users = []
old_id_by_email = {}
@ -66,12 +82,16 @@ async def import_users(mongo, session, keycloak_users_file):
bio=user.get("bio"),
image=user.get("image"),
are_tracks_visible_for_all=user.get("areTracksVisibleForAll") or False,
api_key=str(user["_id"]),
created_at=user.get("createdAt") or datetime.utcnow(),
updated_at=user.get("updatedAt") or datetime.utcnow(),
match_by_username_email=True,
)
if keep_api_keys:
new_user.api_key = str(user["_id"])
else:
new_user.generate_api_key()
if keycloak_users_file:
needs_email_verification = user.get("needsEmailValidation", True)
required_actions = ["UPDATE_PASSWORD"]