Change validators to use the validator_context

Change InstanceKeys to take a namedtuple for context
This commit is contained in:
Sam Chow 2018-05-25 15:42:27 -04:00
parent e967fde3ae
commit 554d4f47a8
31 changed files with 172 additions and 69 deletions

View file

@ -15,6 +15,6 @@ class BaseValidator(object):
@classmethod
@abstractmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Raises Exception if failure to validate. """
pass

View file

@ -6,13 +6,15 @@ class AccessSettingsValidator(BaseValidator):
name = "access"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
config = validator_context.config
client = validator_context.http_client
if not config.get('FEATURE_DIRECT_LOGIN', True):
# Make sure we have at least one OIDC enabled.
github_login = config.get('FEATURE_GITHUB_LOGIN', False)
google_login = config.get('FEATURE_GOOGLE_LOGIN', False)
client = app.config['HTTPCLIENT']
login_manager = OAuthLoginManager(config, client=client)
custom_oidc = [s for s in login_manager.services if isinstance(s, OIDCLoginService)]

View file

@ -4,7 +4,9 @@ class ActionLogArchivingValidator(BaseValidator):
name = "actionlogarchiving"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
config = validator_context.config
""" Validates the action log archiving configuration. """
if not config.get('FEATURE_ACTION_LOG_ROTATION', False):
return

View file

@ -4,7 +4,9 @@ class AppTokenAuthValidator(BaseValidator):
name = "apptoken-auth"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
config = validator_context.config
if config.get('AUTHENTICATION_TYPE', 'Database') != 'AppToken':
return

View file

@ -1,14 +1,16 @@
from bitbucket import BitBucket
from util import get_app_url
from util import get_app_url_from_scheme_hostname
from util.config.validators import BaseValidator, ConfigValidationException
class BitbucketTriggerValidator(BaseValidator):
name = "bitbucket-trigger"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the config for BitBucket. """
config = validator_context.config
trigger_config = config.get('BITBUCKET_TRIGGER_CONFIG')
if not trigger_config:
raise ConfigValidationException('Missing client ID and client secret')
@ -21,7 +23,7 @@ class BitbucketTriggerValidator(BaseValidator):
key = trigger_config['CONSUMER_KEY']
secret = trigger_config['CONSUMER_SECRET']
callback_url = '%s/oauth1/bitbucket/callback/trigger/' % (get_app_url(app.config))
callback_url = '%s/oauth1/bitbucket/callback/trigger/' % (get_app_url(validator_context.scheme_and_hostname))
bitbucket_client = BitBucket(key, secret, callback_url)
(result, _, _) = bitbucket_client.get_authorization_url()

View file

@ -7,8 +7,10 @@ class DatabaseValidator(BaseValidator):
name = "database"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates connecting to the database. """
config = validator_context.config
try:
validate_database_url(config['DB_URI'], config.get('DB_CONNECTION_ARGS', {}))
except OperationalError as ex:

View file

@ -7,9 +7,14 @@ class EmailValidator(BaseValidator):
name = "mail"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates sending email. """
with app.app_context():
config = validator_context.config
user = validator_context.user
app_context = validator_context.context
registry_title = validator_context.registry_title
with app_context():
test_app = Flask("mail-test-app")
test_app.config.update(config)
test_app.config.update({
@ -18,7 +23,7 @@ class EmailValidator(BaseValidator):
})
test_mail = Mail(test_app)
test_msg = Message("Test e-mail from %s" % app.config['REGISTRY_TITLE'],
test_msg = Message("Test e-mail from %s" % registry_title,
sender=config.get('MAIL_DEFAULT_SENDER'))
test_msg.add_recipient(user.email)
test_mail.send(test_msg)

View file

@ -6,8 +6,11 @@ class BaseGitHubValidator(BaseValidator):
config_key = None
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the OAuth credentials and API endpoint for a Github service. """
config = validator_context.config
client = validator_context.http_client
github_config = config.get(cls.config_key)
if not github_config:
raise ConfigValidationException('Missing GitHub client id and client secret')
@ -29,9 +32,8 @@ class BaseGitHubValidator(BaseValidator):
raise ConfigValidationException('Organization restriction must have at least one allowed ' +
'organization')
client = app.config['HTTPCLIENT']
oauth = GithubOAuthService(config, cls.config_key)
result = oauth.validate_client_id_and_secret(client, app.config)
result = oauth.validate_client_id_and_secret(client)
if not result:
raise ConfigValidationException('Invalid client id or client secret')

View file

@ -1,13 +1,16 @@
from oauth.services.gitlab import GitLabOAuthService
from util.config import URLSchemeAndHostname
from util.config.validators import BaseValidator, ConfigValidationException
class GitLabTriggerValidator(BaseValidator):
name = "gitlab-trigger"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the OAuth credentials and API endpoint for a GitLab service. """
config = validator_context.config
url_scheme_and_hostname = validator_context.url_scheme_and_hostname
client = validator_context.http_client
github_config = config.get('GITLAB_TRIGGER_CONFIG')
if not github_config:
raise ConfigValidationException('Missing GitLab client id and client secret')
@ -23,9 +26,7 @@ class GitLabTriggerValidator(BaseValidator):
if not github_config.get('CLIENT_SECRET'):
raise ConfigValidationException('Missing Client Secret')
client = app.config['HTTPCLIENT']
oauth = GitLabOAuthService(config, 'GITLAB_TRIGGER_CONFIG')
url_scheme_and_hostname = URLSchemeAndHostname(app.config['PREFERRED_URL_SCHEME'], app.config['SERVER_HOSTNAME'])
result = oauth.validate_client_id_and_secret(client, url_scheme_and_hostname)
if not result:
raise ConfigValidationException('Invalid client id or client secret')

View file

@ -5,8 +5,11 @@ class GoogleLoginValidator(BaseValidator):
name = "google-login"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the Google Login client ID and secret. """
config = validator_context.config
client = validator_context.http_client
google_login_config = config.get('GOOGLE_LOGIN_CONFIG')
if not google_login_config:
raise ConfigValidationException('Missing client ID and client secret')
@ -17,8 +20,8 @@ class GoogleLoginValidator(BaseValidator):
if not google_login_config.get('CLIENT_SECRET'):
raise ConfigValidationException('Missing Client Secret')
client = app.config['HTTPCLIENT']
oauth = GoogleOAuthService(config, 'GOOGLE_LOGIN_CONFIG')
result = oauth.validate_client_id_and_secret(client, app.config)
# TODO(sam): the google oauth doesn't need the app config, but when refactoring pass in the URLSchemeandHostname
result = oauth.validate_client_id_and_secret(client)
if not result:
raise ConfigValidationException('Invalid client id or client secret')

View file

@ -6,8 +6,14 @@ class JWTAuthValidator(BaseValidator):
name = "jwt"
@classmethod
def validate(cls, config, user, user_password, app, public_key_path=None):
def validate(cls, validator_context, public_key_path=None):
""" Validates the JWT authentication system. """
config = validator_context.config
user = validator_context.user
user_password = validator_context.user_password
http_client = validator_context.http_client
jwt_auth_max = validator_context.jwt_auth_max
if config.get('AUTHENTICATION_TYPE', 'Database') != 'JWT':
return
@ -27,8 +33,8 @@ class JWTAuthValidator(BaseValidator):
# the key cannot be found.
users = ExternalJWTAuthN(verify_endpoint, query_endpoint, getuser_endpoint, issuer,
OVERRIDE_CONFIG_DIRECTORY,
app.config['HTTPCLIENT'],
app.config.get('JWT_AUTH_MAX_FRESH_S', 300),
http_client,
jwt_auth_max,
public_key_path=public_key_path,
requires_email=config.get('FEATURE_MAILING', True))

View file

@ -5,8 +5,12 @@ class KeystoneValidator(BaseValidator):
name = "keystone"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the Keystone authentication system. """
config = validator_context.config
user = validator_context.user
user_password = validator_context.user_password
if config.get('AUTHENTICATION_TYPE', 'Database') != 'Keystone':
return

View file

@ -11,8 +11,11 @@ class LDAPValidator(BaseValidator):
name = "ldap"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the LDAP connection. """
config = validator_context.config
user = validator_context.user
user_password = validator_context.user_password
if config.get('AUTHENTICATION_TYPE', 'Database') != 'LDAP':
return

View file

@ -6,8 +6,10 @@ class OIDCLoginValidator(BaseValidator):
name = "oidc-login"
@classmethod
def validate(cls, config, user, user_password, app):
client = app.config['HTTPCLIENT']
def validate(cls, validator_context):
config = validator_context.config
client = validator_context.http_client
login_manager = OAuthLoginManager(config, client=client)
for service in login_manager.services:
if not isinstance(service, OIDCLoginService):

View file

@ -6,8 +6,10 @@ class RedisValidator(BaseValidator):
name = "redis"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates connecting to redis. """
config = validator_context.config
redis_config = config.get('BUILDLOGS_REDIS', {})
if not 'host' in redis_config:
raise ConfigValidationException('Missing redis hostname')

View file

@ -8,13 +8,16 @@ class SecurityScannerValidator(BaseValidator):
name = "security-scanner"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the configuration for talking to a Quay Security Scanner. """
config = validator_context.config
client = validator_context.http_client
app = None #TODO(sam) validate with joey's pr about security scanner api
if not config.get('FEATURE_SECURITY_SCANNER', False):
return
client = app.config['HTTPCLIENT']
api = SecurityScannerAPI(app, config, None, client=client, skip_validation=True)
api = SecurityScannerAPI(app.config, config, None, client=client, skip_validation=True)
if not config.get('TESTING', False):
# Generate a temporary Quay key to use for signing the outgoing requests.

View file

@ -8,8 +8,10 @@ class SignerValidator(BaseValidator):
name = "signer"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the GPG public+private key pair used for signing converted ACIs. """
config = validator_context.config
if config.get('SIGNING_ENGINE') is None:
return

View file

@ -8,8 +8,9 @@ class SSLValidator(BaseValidator):
name = "ssl"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the SSL configuration (if enabled). """
config = validator_context.config
# Skip if non-SSL.
if config.get('PREFERRED_URL_SCHEME', 'http') != 'https':

View file

@ -1,20 +1,21 @@
from _init import config_provider
from storage import get_storage_driver
from util.config.validators import BaseValidator, ConfigValidationException
from util.ipresolver import NoopIPResolver
ip_resolver = NoopIPResolver()
class StorageValidator(BaseValidator):
name = "registry-storage"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates registry storage. """
config = validator_context.config
client = validator_context.http_client
ip_resolver = validator_context.ip_resolver
replication_enabled = config.get('FEATURE_STORAGE_REPLICATION', False)
providers = _get_storage_providers(config).items()
providers = _get_storage_providers(config, ip_resolver).items()
if not providers:
raise ConfigValidationException('Storage configuration required')
@ -25,7 +26,7 @@ class StorageValidator(BaseValidator):
'with storage replication')
# Run validation on the driver.
driver.validate(app.config['HTTPCLIENT'])
driver.validate(client)
# Run setup on the driver if the read/write succeeded.
driver.setup()
@ -34,7 +35,7 @@ class StorageValidator(BaseValidator):
raise ConfigValidationException('Invalid storage configuration: %s: %s' % (name, msg))
def _get_storage_providers(config):
def _get_storage_providers(config, ip_resolver):
storage_config = config.get('DISTRIBUTED_STORAGE_CONFIG', {})
drivers = {}

View file

@ -9,7 +9,9 @@ class TimeMachineValidator(BaseValidator):
name = "time-machine"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
config = validator_context.config
if not 'DEFAULT_TAG_EXPIRATION' in config:
# Old style config
return

View file

@ -11,15 +11,16 @@ class BittorrentValidator(BaseValidator):
name = "bittorrent"
@classmethod
def validate(cls, config, user, user_password, app):
def validate(cls, validator_context):
""" Validates the configuration for using BitTorrent for downloads. """
config = validator_context.config
client = validator_context.http_client
announce_url = config.get('BITTORRENT_ANNOUNCE_URL')
if not announce_url:
raise ConfigValidationException('Missing announce URL')
# Ensure that the tracker is reachable and accepts requests signed with a registry key.
client = app.config['HTTPCLIENT']
params = {
'info_hash': sha1('test').digest(),
'peer_id': '-QUAY00-6wfG2wk6wWLc',