Initial changes to move repositories from using a namespace string to referencing a user object. Also stores the user id in the cookie rather than the username, to allow users to be renamed. This commit must not be used unmodified because the database migration is too aggressive for live migration.

This commit is contained in:
Jake Moshenko 2014-09-18 14:52:29 -04:00
parent 8c00eabedd
commit 8626d1cd70
7 changed files with 87 additions and 28 deletions

View file

@ -25,7 +25,7 @@ def _load_user_from_cookie():
if not current_user.is_anonymous(): if not current_user.is_anonymous():
logger.debug('Loading user from cookie: %s', current_user.get_id()) logger.debug('Loading user from cookie: %s', current_user.get_id())
set_authenticated_user_deferred(current_user.get_id()) set_authenticated_user_deferred(current_user.get_id())
loaded = QuayDeferredPermissionUser(current_user.get_id(), 'username', {scopes.DIRECT_LOGIN}) loaded = QuayDeferredPermissionUser(current_user.get_id(), 'user_db_id', {scopes.DIRECT_LOGIN})
identity_changed.send(app, identity=loaded) identity_changed.send(app, identity=loaded)
return current_user.db_user() return current_user.db_user()
return None return None
@ -58,12 +58,10 @@ def _validate_and_apply_oauth_token(token):
set_authenticated_user(validated.authorized_user) set_authenticated_user(validated.authorized_user)
set_validated_oauth_token(validated) set_validated_oauth_token(validated)
new_identity = QuayDeferredPermissionUser(validated.authorized_user.username, 'username', new_identity = QuayDeferredPermissionUser(validated.authorized_user.id, 'user_db_id', scope_set)
scope_set)
identity_changed.send(app, identity=new_identity) identity_changed.send(app, identity=new_identity)
def process_basic_auth(auth): def process_basic_auth(auth):
normalized = [part.strip() for part in auth.split(' ') if part] normalized = [part.strip() for part in auth.split(' ') if part]
if normalized[0].lower() != 'basic' or len(normalized) != 2: if normalized[0].lower() != 'basic' or len(normalized) != 2:
@ -100,8 +98,7 @@ def process_basic_auth(auth):
logger.debug('Successfully validated robot: %s' % credentials[0]) logger.debug('Successfully validated robot: %s' % credentials[0])
set_authenticated_user(robot) set_authenticated_user(robot)
deferred_robot = QuayDeferredPermissionUser(robot.username, 'username', deferred_robot = QuayDeferredPermissionUser(robot.id, 'user_db_id', {scopes.DIRECT_LOGIN})
{scopes.DIRECT_LOGIN})
identity_changed.send(app, identity=deferred_robot) identity_changed.send(app, identity=deferred_robot)
return return
except model.InvalidRobotException: except model.InvalidRobotException:
@ -114,7 +111,7 @@ def process_basic_auth(auth):
logger.debug('Successfully validated user: %s' % authenticated.username) logger.debug('Successfully validated user: %s' % authenticated.username)
set_authenticated_user(authenticated) set_authenticated_user(authenticated)
new_identity = QuayDeferredPermissionUser(authenticated.username, 'username', new_identity = QuayDeferredPermissionUser(authenticated.id, 'user_db_id',
{scopes.DIRECT_LOGIN}) {scopes.DIRECT_LOGIN})
identity_changed.send(app, identity=new_identity) identity_changed.send(app, identity=new_identity)
return return

View file

@ -10,13 +10,13 @@ logger = logging.getLogger(__name__)
def get_authenticated_user(): def get_authenticated_user():
user = getattr(_request_ctx_stack.top, 'authenticated_user', None) user = getattr(_request_ctx_stack.top, 'authenticated_user', None)
if not user: if not user:
username = getattr(_request_ctx_stack.top, 'authenticated_username', None) db_id = getattr(_request_ctx_stack.top, 'authenticated_db_id', None)
if not username: if not db_id:
logger.debug('No authenticated user or deferred username.') logger.debug('No authenticated user or deferred database id.')
return None return None
logger.debug('Loading deferred authenticated user.') logger.debug('Loading deferred authenticated user.')
loaded = model.get_user(username) loaded = model.get_user_by_id(db_id)
set_authenticated_user(loaded) set_authenticated_user(loaded)
user = loaded user = loaded
@ -30,10 +30,10 @@ def set_authenticated_user(user_or_robot):
ctx.authenticated_user = user_or_robot ctx.authenticated_user = user_or_robot
def set_authenticated_user_deferred(username_or_robotname): def set_authenticated_user_deferred(user_or_robot_db_id):
logger.debug('Deferring loading of authenticated user object: %s', username_or_robotname) logger.debug('Deferring loading of authenticated user object: %s', user_or_robot_db_id)
ctx = _request_ctx_stack.top ctx = _request_ctx_stack.top
ctx.authenticated_username = username_or_robotname ctx.authenticated_db_id = user_or_robot_db_id
def get_validated_oauth_token(): def get_validated_oauth_token():

View file

@ -58,8 +58,8 @@ SCOPE_MAX_USER_ROLES.update({
class QuayDeferredPermissionUser(Identity): class QuayDeferredPermissionUser(Identity):
def __init__(self, id, auth_type, scopes): def __init__(self, db_id, auth_type, scopes):
super(QuayDeferredPermissionUser, self).__init__(id, auth_type) super(QuayDeferredPermissionUser, self).__init__(db_id, auth_type)
self._permissions_loaded = False self._permissions_loaded = False
self._scope_set = scopes self._scope_set = scopes
@ -88,7 +88,7 @@ class QuayDeferredPermissionUser(Identity):
def can(self, permission): def can(self, permission):
if not self._permissions_loaded: if not self._permissions_loaded:
logger.debug('Loading user permissions after deferring.') logger.debug('Loading user permissions after deferring.')
user_object = model.get_user(self.id) user_object = model.get_user_by_id(self.id)
# Add the superuser need, if applicable. # Add the superuser need, if applicable.
if (user_object.username is not None and if (user_object.username is not None and
@ -230,9 +230,9 @@ def on_identity_loaded(sender, identity):
if isinstance(identity, QuayDeferredPermissionUser): if isinstance(identity, QuayDeferredPermissionUser):
logger.debug('Deferring permissions for user: %s', identity.id) logger.debug('Deferring permissions for user: %s', identity.id)
elif identity.auth_type == 'username': elif identity.auth_type == 'user_db_id':
logger.debug('Switching username permission to deferred object: %s', identity.id) logger.debug('Switching username permission to deferred object: %s', identity.id)
switch_to_deferred = QuayDeferredPermissionUser(identity.id, 'username', {scopes.DIRECT_LOGIN}) switch_to_deferred = QuayDeferredPermissionUser(identity.id, 'user_db_id', {scopes.DIRECT_LOGIN})
identity_changed.send(app, identity=switch_to_deferred) identity_changed.send(app, identity=switch_to_deferred)
elif identity.auth_type == 'token': elif identity.auth_type == 'token':

View file

@ -169,6 +169,7 @@ class Visibility(BaseModel):
class Repository(BaseModel): class Repository(BaseModel):
namespace = CharField() namespace = CharField()
namespace_user = ForeignKeyField(User, null=True)
name = CharField() name = CharField()
visibility = ForeignKeyField(Visibility) visibility = ForeignKeyField(Visibility)
description = TextField(null=True) description = TextField(null=True)

View file

@ -0,0 +1,54 @@
"""Migrate registry namespaces to reference a user.
Revision ID: 13da56878560
Revises: 51d04d0e7e6f
Create Date: 2014-09-18 13:56:45.130455
"""
# revision identifiers, used by Alembic.
revision = '13da56878560'
down_revision = '51d04d0e7e6f'
from alembic import op
import sqlalchemy as sa
from data.database import Repository, User
def upgrade(tables):
# Add the namespace_user column, allowing it to be nullable
op.add_column('repository', sa.Column('namespace_user', sa.Integer(), sa.ForeignKey('user.id')))
# backfill the namespace_user column
namespace_to_user = {}
for user in User.select(User.name, User.id).where(User.robot == False):
namespace_to_user[user.username] = user
for repo in Repository.select():
repo.namespace_user = namespace_to_user[repo.namespace]
repo.save()
# Ensure the backfill was a success, then remove the namespace column
op.alter_column('repository', 'namespace_user', nullable=False)
op.create_index('repository_namespace_user_name', 'repository', ['namespace_user', 'name'],
unique=True)
op.drop_index('repository_namespace_user', table_name='repository')
op.drop_column('repository', 'namespace')
def downgrade(tables):
# Add the namespace column, allowing it to be nullable
op.add_column('repository', sa.Column('namespace', sa.String(length=255)))
# backfill the namespace column
for repo in Repository.select(Repository, User.username).join(User):
repo.namespace = repo.namespace_user.username
repo.save()
# Ensure the backfill was a success, then remove the namespace column
op.alter_column('repository', 'namespace', nullable=False)
op.create_index('repository_namespace_name', 'repository', ['namespace', 'name'], unique=True)
op.drop_index('repository_namespace_user', table_name='repository')
op.drop_column('repository', 'namespace_user')

View file

@ -560,6 +560,13 @@ def get_user_or_org(username):
return None return None
def get_user_by_id(user_dbid):
try:
return User.get(User.id == user_dbid, User.organization == False)
except User.DoesNotExist:
return None
def get_user_or_org_by_customer_id(customer_id): def get_user_or_org_by_customer_id(customer_id):
try: try:
return User.get(User.stripe_id == customer_id) return User.get(User.stripe_id == customer_id)

View file

@ -82,20 +82,20 @@ def param_required(param_name):
@login_manager.user_loader @login_manager.user_loader
def load_user(username): def load_user(user_dbid):
logger.debug('User loader loading deferred user: %s' % username) logger.debug('User loader loading deferred user id: %s' % user_dbid)
return _LoginWrappedDBUser(username) return _LoginWrappedDBUser(user_dbid)
class _LoginWrappedDBUser(UserMixin): class _LoginWrappedDBUser(UserMixin):
def __init__(self, db_username, db_user=None): def __init__(self, user_dbid, db_user=None):
self._db_username = db_username self._db_id = int(user_dbid)
self._db_user = db_user self._db_user = db_user
def db_user(self): def db_user(self):
if not self._db_user: if not self._db_user:
self._db_user = model.get_user(self._db_username) self._db_user = model.get_user_by_id(self._db_id)
return self._db_user return self._db_user
def is_authenticated(self): def is_authenticated(self):
@ -105,13 +105,13 @@ class _LoginWrappedDBUser(UserMixin):
return self.db_user().verified return self.db_user().verified
def get_id(self): def get_id(self):
return unicode(self._db_username) return unicode(self._db_id)
def common_login(db_user): def common_login(db_user):
if login_user(_LoginWrappedDBUser(db_user.username, db_user)): if login_user(_LoginWrappedDBUser(db_user.id, db_user)):
logger.debug('Successfully signed in as: %s' % db_user.username) logger.debug('Successfully signed in as: %s' % db_user.username)
new_identity = QuayDeferredPermissionUser(db_user.username, 'username', {scopes.DIRECT_LOGIN}) new_identity = QuayDeferredPermissionUser(db_user.id, 'user_db_id', {scopes.DIRECT_LOGIN})
identity_changed.send(app, identity=new_identity) identity_changed.send(app, identity=new_identity)
session['login_time'] = datetime.datetime.now() session['login_time'] = datetime.datetime.now()
return True return True