Add an in-memory superusermanager, which stores the current list of superusers in a process-shared Value. We do this because in the ER, when we add a new superuser, we need to ensure that ALL workers have their lists updated (otherwise we get the behavior that some workers validate the new permission and others do not).

This commit is contained in:
Joseph Schorr 2015-01-20 12:43:11 -05:00
parent da4bcbbee0
commit 28d319ad26
6 changed files with 51 additions and 12 deletions

2
app.py
View file

@ -29,6 +29,7 @@ from util.names import urn_generator
from util.oauth import GoogleOAuthConfig, GithubOAuthConfig
from util.queuemetrics import QueueMetrics
from util.config.configutil import generate_secret_key
from util.config.superusermanager import SuperUserManager
app = Flask(__name__)
@ -105,6 +106,7 @@ build_logs = BuildLogs(app)
queue_metrics = QueueMetrics(app)
authentication = UserAuthentication(app)
userevents = UserEventsBuilderModule(app)
superusers = SuperUserManager(app)
github_login = GithubOAuthConfig(app.config, 'GITHUB_LOGIN_CONFIG')
github_trigger = GithubOAuthConfig(app.config, 'GITHUB_TRIGGER_CONFIG')

View file

@ -7,7 +7,7 @@ from functools import partial
import scopes
from data import model
from app import app
from app import app, superusers
logger = logging.getLogger(__name__)
@ -94,8 +94,7 @@ class QuayDeferredPermissionUser(Identity):
return super(QuayDeferredPermissionUser, self).can(permission)
# Add the superuser need, if applicable.
if (user_object.username is not None and
user_object.username in app.config.get('SUPER_USERS', [])):
if superusers.is_superuser(user_object.username):
self.provides.add(_SuperUserNeed())
# Add the user specific permissions, only for non-oauth permission

View file

@ -7,7 +7,7 @@ from endpoints.api import (ApiResource, nickname, resource, internal_only, show_
require_fresh_login, request, validate_json_request, verify_not_prod)
from endpoints.common import common_login
from app import app, CONFIG_PROVIDER
from app import app, CONFIG_PROVIDER, superusers
from data import model
from auth.permissions import SuperUserPermission
from auth.auth_context import get_authenticated_user
@ -49,7 +49,7 @@ class SuperUserRegistryStatus(ApiResource):
'file_exists': file_exists,
'is_testing': app.config['TESTING'],
'valid_db': database_is_valid(),
'ready': not app.config['TESTING'] and file_exists and bool(app.config['SUPER_USERS'])
'ready': not app.config['TESTING'] and file_exists and superusers.has_superusers()
}
@ -215,7 +215,7 @@ class SuperUserCreateInitialSuperUser(ApiResource):
CONFIG_PROVIDER.save_yaml(config_object)
# Update the in-memory config for the new superuser.
app.config['SUPER_USERS'] = [username]
superusers.register_superuser(username)
# Conduct login with that user.
common_login(superuser)

View file

@ -4,7 +4,7 @@ import json
import os
from random import SystemRandom
from app import app, avatar
from app import app, avatar, superusers
from flask import request
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
@ -109,7 +109,7 @@ def user_view(user):
'email': user.email,
'verified': user.verified,
'avatar': avatar.compute_hash(user.email, name=user.username),
'super_user': user.username in app.config['SUPER_USERS']
'super_user': superusers.is_superuser(user.username)
}
@resource('/v1/superuser/usage/')
@ -217,7 +217,7 @@ class SuperUserSendRecoveryEmail(ApiResource):
if not user or user.organization or user.robot:
abort(404)
if username in app.config['SUPER_USERS']:
if superusers.is_superuser(username):
abort(403)
code = model.create_reset_password_email_code(user.email)
@ -277,7 +277,7 @@ class SuperUserManagement(ApiResource):
if not user or user.organization or user.robot:
abort(404)
if username in app.config['SUPER_USERS']:
if superusers.is_superuser(username):
abort(403)
model.delete_user(user)
@ -296,7 +296,7 @@ class SuperUserManagement(ApiResource):
if not user or user.organization or user.robot:
abort(404)
if username in app.config['SUPER_USERS']:
if superusers.is_superuser(username):
abort(403)
user_data = request.get_json()

View file

@ -376,7 +376,7 @@
<td>Database Server:</td>
<td>
<span class="config-string-field" binding="fields.server"
placeholder="localhost"></span>
placeholder="dbserverhost"></span>
<div class="help-text">
The server (and optionally, custom port) where the database lives
</div>

View file

@ -0,0 +1,38 @@
from multiprocessing.sharedctypes import Value, Array
from util.validation import MAX_LENGTH
class SuperUserManager(object):
""" In-memory helper class for quickly accessing (and updating) the valid
set of super users. This class communicates across processes to ensure
that the shared set is always the same.
"""
def __init__(self, app):
usernames = app.config.get('SUPER_USERS', [])
usernames_str = ','.join(usernames)
self._max_length = len(usernames_str) + MAX_LENGTH + 1
self._array = Array('c', self._max_length, lock=True)
self._array.value = usernames_str
def is_superuser(self, username):
""" Returns if the given username represents a super user. """
usernames = self._array.value.split(',')
return username in usernames
def register_superuser(self, username):
""" Registers a new username as a super user for the duration of the container.
Note that this does *not* change any underlying config files.
"""
usernames = self._array.value.split(',')
usernames.append(username)
new_string = ','.join(usernames)
if len(new_string) <= self._max_length:
self._array.value = new_string
else:
raise Exception('Maximum superuser count reached. Please report this to support.')
def has_superusers(self):
""" Returns whether there are any superusers defined. """
return bool(self._array.value)