Remove jwt validation for jschorr to fix later
Refactor oauth validate method to take config over entire appconfig
This commit is contained in:
parent
7df8ed4a60
commit
301cc6992a
27 changed files with 136 additions and 76 deletions
5
app.py
5
app.py
|
@ -37,7 +37,8 @@ from oauth.loginmanager import OAuthLoginManager
|
||||||
from storage import Storage
|
from storage import Storage
|
||||||
from util.config import URLSchemeAndHostname
|
from util.config import URLSchemeAndHostname
|
||||||
from util.log import filter_logs
|
from util.log import filter_logs
|
||||||
from util import get_app_url, create_uri_func_from_context
|
from util import get_app_url
|
||||||
|
from util.secscan.secscan_util import get_blob_download_uri_getter
|
||||||
from util.ipresolver import IPResolver
|
from util.ipresolver import IPResolver
|
||||||
from util.saas.analytics import Analytics
|
from util.saas.analytics import Analytics
|
||||||
from util.saas.useranalytics import UserAnalytics
|
from util.saas.useranalytics import UserAnalytics
|
||||||
|
@ -231,7 +232,7 @@ all_queues = [image_replication_queue, dockerfile_build_queue, notification_queu
|
||||||
|
|
||||||
url_scheme_and_hostname = URLSchemeAndHostname(app.config['PREFERRED_URL_SCHEME'], app.config['SERVER_HOSTNAME'])
|
url_scheme_and_hostname = URLSchemeAndHostname(app.config['PREFERRED_URL_SCHEME'], app.config['SERVER_HOSTNAME'])
|
||||||
secscan_api = SecurityScannerAPI(app.config, storage, app.config['SERVER_HOSTNAME'], app.config['HTTPCLIENT'],
|
secscan_api = SecurityScannerAPI(app.config, storage, app.config['SERVER_HOSTNAME'], app.config['HTTPCLIENT'],
|
||||||
uri_creator=create_uri_func_from_context(app.test_request_context('/'), url_scheme_and_hostname),
|
uri_creator=get_blob_download_uri_getter(app.test_request_context('/'), url_scheme_and_hostname),
|
||||||
instance_keys=instance_keys)
|
instance_keys=instance_keys)
|
||||||
|
|
||||||
tuf_metadata_api = TUFMetadataAPI(app, app.config)
|
tuf_metadata_api = TUFMetadataAPI(app, app.config)
|
||||||
|
|
|
@ -405,7 +405,7 @@ class SuperUserConfigValidate(ApiResource):
|
||||||
# this is also safe since this method does not access any information not given in the request.
|
# this is also safe since this method does not access any information not given in the request.
|
||||||
if not config_provider.config_exists() or SuperUserPermission().can():
|
if not config_provider.config_exists() or SuperUserPermission().can():
|
||||||
config = request.get_json()['config']
|
config = request.get_json()['config']
|
||||||
validator_context = ValidatorContext.from_app(config, request.get_json().get('password', ''), app,
|
validator_context = ValidatorContext.from_app(app, config, request.get_json().get('password', ''),
|
||||||
ip_resolver=ip_resolver,
|
ip_resolver=ip_resolver,
|
||||||
config_provider=config_provider)
|
config_provider=config_provider)
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from peewee import IntegrityError
|
||||||
import features
|
import features
|
||||||
|
|
||||||
from app import (app, billing as stripe, authentication, avatar, user_analytics, all_queues,
|
from app import (app, billing as stripe, authentication, avatar, user_analytics, all_queues,
|
||||||
oauth_login, namespace_gc_queue, ip_resolver)
|
oauth_login, namespace_gc_queue, ip_resolver, url_scheme_and_hostname)
|
||||||
|
|
||||||
from auth import scopes
|
from auth import scopes
|
||||||
from auth.auth_context import get_authenticated_user
|
from auth.auth_context import get_authenticated_user
|
||||||
|
@ -784,7 +784,7 @@ class ExternalLoginInformation(ApiResource):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
login_scopes = login_service.get_login_scopes()
|
login_scopes = login_service.get_login_scopes()
|
||||||
auth_url = login_service.get_auth_url(app.config, redirect_suffix, csrf_token, login_scopes)
|
auth_url = login_service.get_auth_url(url_scheme_and_hostname, redirect_suffix, csrf_token, login_scopes)
|
||||||
return {'auth_url': auth_url}
|
return {'auth_url': auth_url}
|
||||||
except DiscoveryFailureException as dfe:
|
except DiscoveryFailureException as dfe:
|
||||||
logger.exception('Could not discovery OAuth endpoint information')
|
logger.exception('Could not discovery OAuth endpoint information')
|
||||||
|
|
|
@ -8,7 +8,7 @@ from peewee import IntegrityError
|
||||||
|
|
||||||
import features
|
import features
|
||||||
|
|
||||||
from app import app, analytics, get_app_url, oauth_login, authentication
|
from app import app, analytics, get_app_url, oauth_login, authentication, url_scheme_and_hostname
|
||||||
from auth.auth_context import get_authenticated_user
|
from auth.auth_context import get_authenticated_user
|
||||||
from auth.decorators import require_session_login
|
from auth.decorators import require_session_login
|
||||||
from data import model
|
from data import model
|
||||||
|
@ -250,7 +250,7 @@ def _register_service(login_service):
|
||||||
# Redirect to the normal OAuth flow again, so that the user can now create an account.
|
# Redirect to the normal OAuth flow again, so that the user can now create an account.
|
||||||
csrf_token = generate_csrf_token(OAUTH_CSRF_TOKEN_NAME)
|
csrf_token = generate_csrf_token(OAUTH_CSRF_TOKEN_NAME)
|
||||||
login_scopes = login_service.get_login_scopes()
|
login_scopes = login_service.get_login_scopes()
|
||||||
auth_url = login_service.get_auth_url(app.config, '', csrf_token, login_scopes)
|
auth_url = login_service.get_auth_url(url_scheme_and_hostname, '', csrf_token, login_scopes)
|
||||||
return redirect(auth_url)
|
return redirect(auth_url)
|
||||||
|
|
||||||
@require_session_login
|
@require_session_login
|
||||||
|
|
|
@ -6,7 +6,6 @@ import urlparse
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from six import add_metaclass
|
from six import add_metaclass
|
||||||
|
|
||||||
from util import get_app_url
|
|
||||||
from util.config import URLSchemeAndHostname
|
from util.config import URLSchemeAndHostname
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -74,7 +73,7 @@ class OAuthService(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def validate_client_id_and_secret(self, http_client, app_config):
|
def validate_client_id_and_secret(self, http_client, url_scheme_and_hostname):
|
||||||
""" Performs validation of the client ID and secret, raising an exception on failure. """
|
""" Performs validation of the client ID and secret, raising an exception on failure. """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -99,9 +98,10 @@ class OAuthService(object):
|
||||||
"""
|
"""
|
||||||
return self.config.get('LOGIN_BINDING_FIELD', None)
|
return self.config.get('LOGIN_BINDING_FIELD', None)
|
||||||
|
|
||||||
def get_auth_url(self, app_config, redirect_suffix, csrf_token, scopes):
|
def get_auth_url(self, url_scheme_and_hostname, redirect_suffix, csrf_token, scopes):
|
||||||
""" Retrieves the authorization URL for this login service. """
|
""" Retrieves the authorization URL for this login service. """
|
||||||
redirect_uri = '%s/oauth2/%s/callback%s' % (get_app_url(app_config), self.service_id(),
|
redirect_uri = '%s/oauth2/%s/callback%s' % (url_scheme_and_hostname.get_url(),
|
||||||
|
self.service_id(),
|
||||||
redirect_suffix)
|
redirect_suffix)
|
||||||
params = {
|
params = {
|
||||||
'client_id': self.client_id(),
|
'client_id': self.client_id(),
|
||||||
|
@ -154,7 +154,7 @@ class OAuthService(object):
|
||||||
def exchange_code(self, app_config, http_client, code, form_encode=False, redirect_suffix='',
|
def exchange_code(self, app_config, http_client, code, form_encode=False, redirect_suffix='',
|
||||||
client_auth=False):
|
client_auth=False):
|
||||||
""" Exchanges an OAuth access code for associated OAuth token and other data. """
|
""" Exchanges an OAuth access code for associated OAuth token and other data. """
|
||||||
url_scheme_and_hostname = URLSchemeAndHostname(app_config['PREFERRED_URL_SCHEME'], app_config['SERVER_HOSTNAME'])
|
url_scheme_and_hostname = URLSchemeAndHostname.from_app_config(app_config)
|
||||||
payload = {
|
payload = {
|
||||||
'code': code,
|
'code': code,
|
||||||
'grant_type': 'authorization_code',
|
'grant_type': 'authorization_code',
|
||||||
|
|
|
@ -97,9 +97,9 @@ class OIDCLoginService(OAuthService):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
return bool(self.get_login_scopes())
|
return bool(self.get_login_scopes())
|
||||||
|
|
||||||
def validate_client_id_and_secret(self, http_client, app_config):
|
def validate_client_id_and_secret(self, http_client, url_scheme_and_hostname):
|
||||||
# TODO: find a way to verify client secret too.
|
# TODO: find a way to verify client secret too.
|
||||||
check_auth_url = http_client.get(self.get_auth_url(app_config, '', '', []))
|
check_auth_url = http_client.get(self.get_auth_url(url_scheme_and_hostname, '', '', []))
|
||||||
if check_auth_url.status_code // 100 != 2:
|
if check_auth_url.status_code // 100 != 2:
|
||||||
raise Exception('Got non-200 status code for authorization endpoint')
|
raise Exception('Got non-200 status code for authorization endpoint')
|
||||||
|
|
||||||
|
|
|
@ -75,8 +75,7 @@ class GithubOAuthService(OAuthLoginService):
|
||||||
def orgs_endpoint(self):
|
def orgs_endpoint(self):
|
||||||
return slash_join(self._api_endpoint(), 'user/orgs')
|
return slash_join(self._api_endpoint(), 'user/orgs')
|
||||||
|
|
||||||
# TODO(sam): refactor the base method to not take app config
|
def validate_client_id_and_secret(self, http_client, url_scheme_and_hostname):
|
||||||
def validate_client_id_and_secret(self, http_client):
|
|
||||||
# First: Verify that the github endpoint is actually Github by checking for the
|
# First: Verify that the github endpoint is actually Github by checking for the
|
||||||
# X-GitHub-Request-Id here.
|
# X-GitHub-Request-Id here.
|
||||||
api_endpoint = self._api_endpoint()
|
api_endpoint = self._api_endpoint()
|
||||||
|
|
|
@ -29,8 +29,6 @@ class GitLabOAuthService(OAuthService):
|
||||||
def token_endpoint(self):
|
def token_endpoint(self):
|
||||||
return OAuthEndpoint(slash_join(self._endpoint(), '/oauth/token'))
|
return OAuthEndpoint(slash_join(self._endpoint(), '/oauth/token'))
|
||||||
|
|
||||||
# TODO(sam): this signature does not match its parent class. refactor the base method to take the namedtuple URLSchemeAndHostname
|
|
||||||
# TODO cont: reason I did this was to decouple the app, but it requires more refactoring
|
|
||||||
def validate_client_id_and_secret(self, http_client, url_scheme_and_hostname):
|
def validate_client_id_and_secret(self, http_client, url_scheme_and_hostname):
|
||||||
# We validate the client ID and secret by hitting the OAuth token exchange endpoint with
|
# We validate the client ID and secret by hitting the OAuth token exchange endpoint with
|
||||||
# the real client ID and secret, but a fake auth code to exchange. Gitlab's implementation will
|
# the real client ID and secret, but a fake auth code to exchange. Gitlab's implementation will
|
||||||
|
|
|
@ -41,8 +41,7 @@ class GoogleOAuthService(OAuthLoginService):
|
||||||
def requires_form_encoding(self):
|
def requires_form_encoding(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# TODO(sam): this signature does not match its parent class. refactor the base method to take the namedtuple URLSchemeAndHostname
|
def validate_client_id_and_secret(self, http_client, url_scheme_and_hostname):
|
||||||
def validate_client_id_and_secret(self, http_client):
|
|
||||||
# To verify the Google client ID and secret, we hit the
|
# To verify the Google client ID and secret, we hit the
|
||||||
# https://www.googleapis.com/oauth2/v3/token endpoint with an invalid request. If the client
|
# https://www.googleapis.com/oauth2/v3/token endpoint with an invalid request. If the client
|
||||||
# ID or secret are invalid, we get returned a 403 Unauthorized. Otherwise, we get returned
|
# ID or secret are invalid, we get returned a 403 Unauthorized. Otherwise, we get returned
|
||||||
|
|
|
@ -13,6 +13,8 @@ from Crypto.PublicKey import RSA
|
||||||
from jwkest.jwk import RSAKey
|
from jwkest.jwk import RSAKey
|
||||||
|
|
||||||
from oauth.oidc import OIDCLoginService, OAuthLoginException
|
from oauth.oidc import OIDCLoginService, OAuthLoginException
|
||||||
|
from util.config import URLSchemeAndHostname
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='module') # Slow to generate, only do it once.
|
@pytest.fixture(scope='module') # Slow to generate, only do it once.
|
||||||
def signing_key():
|
def signing_key():
|
||||||
|
@ -277,7 +279,8 @@ def test_auth_url(oidc_service, discovery_handler, http_client, authorize_handle
|
||||||
config = {'PREFERRED_URL_SCHEME': 'https', 'SERVER_HOSTNAME': 'someserver'}
|
config = {'PREFERRED_URL_SCHEME': 'https', 'SERVER_HOSTNAME': 'someserver'}
|
||||||
|
|
||||||
with HTTMock(discovery_handler, authorize_handler):
|
with HTTMock(discovery_handler, authorize_handler):
|
||||||
auth_url = oidc_service.get_auth_url(config, '', 'some csrf token', ['one', 'two'])
|
url_scheme_and_hostname = URLSchemeAndHostname.from_app_config(config)
|
||||||
|
auth_url = oidc_service.get_auth_url(url_scheme_and_hostname, '', 'some csrf token', ['one', 'two'])
|
||||||
|
|
||||||
# Hit the URL and ensure it works.
|
# Hit the URL and ensure it works.
|
||||||
result = http_client.get(auth_url).json()
|
result = http_client.get(auth_url).json()
|
||||||
|
|
|
@ -2,14 +2,13 @@ import json
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from app import app, storage, notification_queue
|
from app import app, storage, notification_queue, url_scheme_and_hostname
|
||||||
from data import model
|
from data import model
|
||||||
from data.database import Image, IMAGE_NOT_SCANNED_ENGINE_VERSION
|
from data.database import Image, IMAGE_NOT_SCANNED_ENGINE_VERSION
|
||||||
from endpoints.v2 import v2_bp
|
from endpoints.v2 import v2_bp
|
||||||
from initdb import setup_database_for_testing, finished_database_for_testing
|
from initdb import setup_database_for_testing, finished_database_for_testing
|
||||||
from notifications.notificationevent import VulnerabilityFoundEvent
|
from notifications.notificationevent import VulnerabilityFoundEvent
|
||||||
from util import create_uri_func_from_context
|
from util.secscan.secscan_util import get_blob_download_uri_getter
|
||||||
from util.config import URLSchemeAndHostname
|
|
||||||
from util.morecollections import AttrDict
|
from util.morecollections import AttrDict
|
||||||
from util.secscan.api import SecurityScannerAPI, APIRequestFailure
|
from util.secscan.api import SecurityScannerAPI, APIRequestFailure
|
||||||
from util.secscan.analyzer import LayerAnalyzer
|
from util.secscan.analyzer import LayerAnalyzer
|
||||||
|
@ -46,10 +45,9 @@ class TestSecurityScanner(unittest.TestCase):
|
||||||
self.ctx.__enter__()
|
self.ctx.__enter__()
|
||||||
|
|
||||||
|
|
||||||
url_scheme_and_hostname = URLSchemeAndHostname(app.config['PREFERRED_URL_SCHEME'], app.config['SERVER_HOSTNAME'])
|
|
||||||
instance_keys = InstanceKeys(app)
|
instance_keys = InstanceKeys(app)
|
||||||
self.api = SecurityScannerAPI(app.config, storage, app.config['SERVER_HOSTNAME'], app.config['HTTPCLIENT'],
|
self.api = SecurityScannerAPI(app.config, storage, app.config['SERVER_HOSTNAME'], app.config['HTTPCLIENT'],
|
||||||
uri_creator=create_uri_func_from_context(app.test_request_context('/'),
|
uri_creator=get_blob_download_uri_getter(app.test_request_context('/'),
|
||||||
url_scheme_and_hostname),
|
url_scheme_and_hostname),
|
||||||
instance_keys=instance_keys)
|
instance_keys=instance_keys)
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
from flask import url_for
|
|
||||||
from urlparse import urljoin
|
|
||||||
|
|
||||||
def get_app_url(config):
|
def get_app_url(config):
|
||||||
""" Returns the application's URL, based on the given config. """
|
""" Returns the application's URL, based on the given config. """
|
||||||
return '%s://%s' % (config['PREFERRED_URL_SCHEME'], config['SERVER_HOSTNAME'])
|
return '%s://%s' % (config['PREFERRED_URL_SCHEME'], config['SERVER_HOSTNAME'])
|
||||||
|
|
||||||
def get_app_url_from_scheme_hostname(url_scheme_and_hostname):
|
|
||||||
""" Returns the application's URL, based on the given url scheme and hostname. """
|
|
||||||
return '%s://%s' % (url_scheme_and_hostname.url_scheme, url_scheme_and_hostname.hostname)
|
|
||||||
|
|
||||||
def slash_join(*args):
|
def slash_join(*args):
|
||||||
"""
|
"""
|
||||||
Joins together strings and guarantees there is only one '/' in between the
|
Joins together strings and guarantees there is only one '/' in between the
|
||||||
|
@ -23,12 +17,3 @@ def slash_join(*args):
|
||||||
args = [rmslash(path) for path in args]
|
args = [rmslash(path) for path in args]
|
||||||
return '/'.join(args)
|
return '/'.join(args)
|
||||||
|
|
||||||
def create_uri_func_from_context(context, url_scheme_and_hostname):
|
|
||||||
def create_uri(repository_and_namespace, checksum):
|
|
||||||
with context:
|
|
||||||
relative_layer_url = url_for('v2.download_blob', repository=repository_and_namespace,
|
|
||||||
digest=checksum)
|
|
||||||
return urljoin(get_app_url_from_scheme_hostname(url_scheme_and_hostname), relative_layer_url)
|
|
||||||
|
|
||||||
return create_uri
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,29 @@
|
||||||
from collections import namedtuple
|
class URLSchemeAndHostname:
|
||||||
|
"""
|
||||||
|
Immutable configuration for a given preferred url scheme (e.g. http or https), and a hostname (e.g. localhost:5000)
|
||||||
|
"""
|
||||||
|
def __init__(self, url_scheme, hostname):
|
||||||
|
self._url_scheme = url_scheme
|
||||||
|
self._hostname = hostname
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_app_config(cls, app_config):
|
||||||
|
"""
|
||||||
|
Helper method to instantiate class from app config, a frequent pattern
|
||||||
|
:param app_config:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return cls(app_config['PREFERRED_URL_SCHEME'], app_config['SERVER_HOSTNAME'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_scheme(self):
|
||||||
|
return self._url_scheme
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hostname(self):
|
||||||
|
return self._hostname
|
||||||
|
|
||||||
|
def get_url(self):
|
||||||
|
""" Returns the application's URL, based on the given url scheme and hostname. """
|
||||||
|
return '%s://%s' % (self._url_scheme, self._hostname)
|
||||||
|
|
||||||
URLSchemeAndHostname = namedtuple('URLSchemeAndHostname', ['url_scheme', 'hostname'])
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ class TestConfigProvider(BaseProvider):
|
||||||
|
|
||||||
def get_config_root(self):
|
def get_config_root(self):
|
||||||
raise Exception('Test Config does not have a config root')
|
raise Exception('Test Config does not have a config root')
|
||||||
# return ''
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
|
|
||||||
from auth.auth_context import get_authenticated_user
|
from auth.auth_context import get_authenticated_user
|
||||||
from data.users import LDAP_CERT_FILENAME
|
from data.users import LDAP_CERT_FILENAME
|
||||||
from util import create_uri_func_from_context
|
from util.secscan.secscan_util import get_blob_download_uri_getter
|
||||||
from util.config import URLSchemeAndHostname
|
from util.config import URLSchemeAndHostname
|
||||||
|
|
||||||
from util.config.validators.validate_database import DatabaseValidator
|
from util.config.validators.validate_database import DatabaseValidator
|
||||||
|
@ -118,20 +118,30 @@ class ValidatorContext(object):
|
||||||
self.config_provider = config_provider
|
self.config_provider = config_provider
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_app(cls, config, user_password, app, ip_resolver, client=None, config_provider=None):
|
def from_app(cls, app, config, user_password, ip_resolver, client=None, config_provider=None):
|
||||||
url_scheme = URLSchemeAndHostname(app.config['PREFERRED_URL_SCHEME'], app.config['SERVER_HOSTNAME'])
|
"""
|
||||||
|
Creates a ValidatorContext from an app config, with a given config to validate
|
||||||
|
:param app: the Flask app to pull configuration information from
|
||||||
|
:param config: the config to validate
|
||||||
|
:param user_password: request password
|
||||||
|
:param ip_resolver: an App
|
||||||
|
:param client:
|
||||||
|
:param config_provider:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
url_scheme_and_hostname = URLSchemeAndHostname.from_app_config(app.config)
|
||||||
|
|
||||||
cls(config,
|
cls(config,
|
||||||
user_password,
|
user_password,
|
||||||
client or app.config['HTTPCLIENT'],
|
client or app.config['HTTPCLIENT'],
|
||||||
app.app_context,
|
app.app_context,
|
||||||
url_scheme,
|
url_scheme_and_hostname,
|
||||||
app.config.get('JWT_AUTH_MAX_FRESH_S', 300),
|
app.config.get('JWT_AUTH_MAX_FRESH_S', 300),
|
||||||
app.config['REGISTRY_TITLE'],
|
app.config['REGISTRY_TITLE'],
|
||||||
ip_resolver,
|
ip_resolver,
|
||||||
app.config.get('FEATURE_SECURITY_SCANNER', False),
|
app.config.get('FEATURE_SECURITY_SCANNER', False),
|
||||||
app.config.get('TESTING', False),
|
app.config.get('TESTING', False),
|
||||||
create_uri_func_from_context(app.test_request_context('/'), url_scheme),
|
get_blob_download_uri_getter(app.test_request_context('/'), url_scheme_and_hostname),
|
||||||
config_provider)
|
config_provider)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,14 +35,13 @@ def test_validate_bitbucket_trigger(app):
|
||||||
with HTTMock(handler):
|
with HTTMock(handler):
|
||||||
validator = BitbucketTriggerValidator()
|
validator = BitbucketTriggerValidator()
|
||||||
|
|
||||||
|
url_scheme_and_hostname = URLSchemeAndHostname('http', 'localhost:5000')
|
||||||
unvalidated_config = ValidatorContext({
|
unvalidated_config = ValidatorContext({
|
||||||
'BITBUCKET_TRIGGER_CONFIG': {
|
'BITBUCKET_TRIGGER_CONFIG': {
|
||||||
'CONSUMER_KEY': 'foo',
|
'CONSUMER_KEY': 'foo',
|
||||||
'CONSUMER_SECRET': 'bar',
|
'CONSUMER_SECRET': 'bar',
|
||||||
},
|
},
|
||||||
})
|
}, url_scheme_and_hostname=url_scheme_and_hostname)
|
||||||
|
|
||||||
unvalidated_config.url_scheme_and_hostname = URLSchemeAndHostname('http', 'localhost:5000')
|
|
||||||
|
|
||||||
validator.validate(unvalidated_config)
|
validator.validate(unvalidated_config)
|
||||||
|
|
||||||
|
|
|
@ -33,16 +33,17 @@ def test_validate_gitlab_enterprise_trigger(app):
|
||||||
|
|
||||||
with HTTMock(handler):
|
with HTTMock(handler):
|
||||||
validator = GitLabTriggerValidator()
|
validator = GitLabTriggerValidator()
|
||||||
|
|
||||||
|
url_scheme_and_hostname = URLSchemeAndHostname('http', 'localhost:5000')
|
||||||
|
|
||||||
unvalidated_config = ValidatorContext({
|
unvalidated_config = ValidatorContext({
|
||||||
'GITLAB_TRIGGER_CONFIG': {
|
'GITLAB_TRIGGER_CONFIG': {
|
||||||
'GITLAB_ENDPOINT': 'http://somegitlab',
|
'GITLAB_ENDPOINT': 'http://somegitlab',
|
||||||
'CLIENT_ID': 'foo',
|
'CLIENT_ID': 'foo',
|
||||||
'CLIENT_SECRET': 'bar',
|
'CLIENT_SECRET': 'bar',
|
||||||
},
|
},
|
||||||
})
|
}, http_client=build_requests_session(), url_scheme_and_hostname=url_scheme_and_hostname)
|
||||||
unvalidated_config.http_client = build_requests_session()
|
|
||||||
|
|
||||||
unvalidated_config.url_scheme_and_hostname = URLSchemeAndHostname('http', 'localhost:5000')
|
|
||||||
validator.validate(unvalidated_config)
|
validator.validate(unvalidated_config)
|
||||||
|
|
||||||
assert url_hit[0]
|
assert url_hit[0]
|
||||||
|
|
|
@ -34,6 +34,8 @@ def test_invalid_config(unvalidated_config, app):
|
||||||
JWTAuthValidator.validate(config)
|
JWTAuthValidator.validate(config)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(jschorr): fix these when re-adding jwt auth mechanism to jwt validators
|
||||||
|
@pytest.mark.skip(reason='No way of currently testing this')
|
||||||
@pytest.mark.parametrize('username, password, expected_exception', [
|
@pytest.mark.parametrize('username, password, expected_exception', [
|
||||||
('invaliduser', 'invalidpass', ConfigValidationException),
|
('invaliduser', 'invalidpass', ConfigValidationException),
|
||||||
('cool.user', 'invalidpass', ConfigValidationException),
|
('cool.user', 'invalidpass', ConfigValidationException),
|
||||||
|
|
|
@ -15,8 +15,7 @@ from app import config_provider
|
||||||
({'AUTHENTICATION_TYPE': 'Database'}),
|
({'AUTHENTICATION_TYPE': 'Database'}),
|
||||||
])
|
])
|
||||||
def test_validate_noop(unvalidated_config, app):
|
def test_validate_noop(unvalidated_config, app):
|
||||||
config = ValidatorContext(unvalidated_config)
|
config = ValidatorContext(unvalidated_config, config_provider=config_provider)
|
||||||
config.config_provider = config_provider
|
|
||||||
LDAPValidator.validate(config)
|
LDAPValidator.validate(config)
|
||||||
|
|
||||||
@pytest.mark.parametrize('unvalidated_config', [
|
@pytest.mark.parametrize('unvalidated_config', [
|
||||||
|
@ -25,8 +24,7 @@ def test_validate_noop(unvalidated_config, app):
|
||||||
])
|
])
|
||||||
def test_invalid_config(unvalidated_config, app):
|
def test_invalid_config(unvalidated_config, app):
|
||||||
with pytest.raises(ConfigValidationException):
|
with pytest.raises(ConfigValidationException):
|
||||||
config = ValidatorContext(unvalidated_config)
|
config = ValidatorContext(unvalidated_config, config_provider=config_provider)
|
||||||
config.config_provider = config_provider
|
|
||||||
LDAPValidator.validate(config)
|
LDAPValidator.validate(config)
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,8 +43,7 @@ def test_invalid_uri(uri, app):
|
||||||
config['LDAP_URI'] = uri
|
config['LDAP_URI'] = uri
|
||||||
|
|
||||||
with pytest.raises(ConfigValidationException):
|
with pytest.raises(ConfigValidationException):
|
||||||
config = ValidatorContext(config)
|
config = ValidatorContext(config, config_provider=config_provider)
|
||||||
config.config_provider = config_provider
|
|
||||||
LDAPValidator.validate(config)
|
LDAPValidator.validate(config)
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,10 +61,8 @@ def test_validated_ldap(username, password, expected_exception, app):
|
||||||
config['LDAP_ADMIN_PASSWD'] = 'password'
|
config['LDAP_ADMIN_PASSWD'] = 'password'
|
||||||
config['LDAP_USER_RDN'] = ['ou=employees']
|
config['LDAP_USER_RDN'] = ['ou=employees']
|
||||||
|
|
||||||
unvalidated_config = ValidatorContext(config)
|
unvalidated_config = ValidatorContext(config, user_password=password, config_provider=config_provider)
|
||||||
unvalidated_config.user = AttrDict(dict(username=username))
|
unvalidated_config.user = AttrDict(dict(username=username))
|
||||||
unvalidated_config.user_password = password
|
|
||||||
unvalidated_config.config_provider = config_provider
|
|
||||||
|
|
||||||
if expected_exception is not None:
|
if expected_exception is not None:
|
||||||
with pytest.raises(ConfigValidationException):
|
with pytest.raises(ConfigValidationException):
|
||||||
|
|
|
@ -12,9 +12,10 @@ from test.fixtures import *
|
||||||
({'DISTRIBUTED_STORAGE_PREFERENCE': []}),
|
({'DISTRIBUTED_STORAGE_PREFERENCE': []}),
|
||||||
])
|
])
|
||||||
def test_validate_noop(unvalidated_config, app):
|
def test_validate_noop(unvalidated_config, app):
|
||||||
unvalidated_config = ValidatorContext(unvalidated_config, feature_sec_scanner=False, is_testing=True)
|
|
||||||
unvalidated_config.http_client = build_requests_session()
|
unvalidated_config = ValidatorContext(unvalidated_config, feature_sec_scanner=False, is_testing=True,
|
||||||
unvalidated_config.url_scheme_and_hostname = URLSchemeAndHostname('http', 'localhost:5000')
|
http_client=build_requests_session(),
|
||||||
|
url_scheme_and_hostname=URLSchemeAndHostname('http', 'localhost:5000'))
|
||||||
|
|
||||||
SecurityScannerValidator.validate(unvalidated_config)
|
SecurityScannerValidator.validate(unvalidated_config)
|
||||||
|
|
||||||
|
@ -35,9 +36,9 @@ def test_validate_noop(unvalidated_config, app):
|
||||||
}, None),
|
}, None),
|
||||||
])
|
])
|
||||||
def test_validate(unvalidated_config, expected_error, app):
|
def test_validate(unvalidated_config, expected_error, app):
|
||||||
unvalidated_config = ValidatorContext(unvalidated_config, feature_sec_scanner=True, is_testing=True)
|
unvalidated_config = ValidatorContext(unvalidated_config, feature_sec_scanner=True, is_testing=True,
|
||||||
unvalidated_config.http_client = build_requests_session()
|
http_client=build_requests_session(),
|
||||||
unvalidated_config.url_scheme_and_hostname = URLSchemeAndHostname('http', 'localhost:5000')
|
url_scheme_and_hostname=URLSchemeAndHostname('http', 'localhost:5000'))
|
||||||
|
|
||||||
with fake_security_scanner(hostname='fakesecurityscanner'):
|
with fake_security_scanner(hostname='fakesecurityscanner'):
|
||||||
if expected_error is not None:
|
if expected_error is not None:
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from bitbucket import BitBucket
|
from bitbucket import BitBucket
|
||||||
|
|
||||||
from util import get_app_url_from_scheme_hostname
|
|
||||||
from util.config.validators import BaseValidator, ConfigValidationException
|
from util.config.validators import BaseValidator, ConfigValidationException
|
||||||
|
|
||||||
class BitbucketTriggerValidator(BaseValidator):
|
class BitbucketTriggerValidator(BaseValidator):
|
||||||
|
@ -23,7 +22,7 @@ class BitbucketTriggerValidator(BaseValidator):
|
||||||
|
|
||||||
key = trigger_config['CONSUMER_KEY']
|
key = trigger_config['CONSUMER_KEY']
|
||||||
secret = trigger_config['CONSUMER_SECRET']
|
secret = trigger_config['CONSUMER_SECRET']
|
||||||
callback_url = '%s/oauth1/bitbucket/callback/trigger/' % (get_app_url_from_scheme_hostname(validator_context.url_scheme_and_hostname))
|
callback_url = '%s/oauth1/bitbucket/callback/trigger/' % (validator_context.url_scheme_and_hostname.get_url())
|
||||||
|
|
||||||
bitbucket_client = BitBucket(key, secret, callback_url)
|
bitbucket_client = BitBucket(key, secret, callback_url)
|
||||||
(result, _, _) = bitbucket_client.get_authorization_url()
|
(result, _, _) = bitbucket_client.get_authorization_url()
|
||||||
|
|
|
@ -10,6 +10,7 @@ class BaseGitHubValidator(BaseValidator):
|
||||||
""" Validates the OAuth credentials and API endpoint for a Github service. """
|
""" Validates the OAuth credentials and API endpoint for a Github service. """
|
||||||
config = validator_context.config
|
config = validator_context.config
|
||||||
client = validator_context.http_client
|
client = validator_context.http_client
|
||||||
|
url_scheme_and_hostname = validator_context.url_scheme_and_hostname
|
||||||
|
|
||||||
github_config = config.get(cls.config_key)
|
github_config = config.get(cls.config_key)
|
||||||
if not github_config:
|
if not github_config:
|
||||||
|
@ -33,7 +34,7 @@ class BaseGitHubValidator(BaseValidator):
|
||||||
'organization')
|
'organization')
|
||||||
|
|
||||||
oauth = GithubOAuthService(config, cls.config_key)
|
oauth = GithubOAuthService(config, cls.config_key)
|
||||||
result = oauth.validate_client_id_and_secret(client)
|
result = oauth.validate_client_id_and_secret(client, url_scheme_and_hostname)
|
||||||
if not result:
|
if not result:
|
||||||
raise ConfigValidationException('Invalid client id or client secret')
|
raise ConfigValidationException('Invalid client id or client secret')
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ class GoogleLoginValidator(BaseValidator):
|
||||||
""" Validates the Google Login client ID and secret. """
|
""" Validates the Google Login client ID and secret. """
|
||||||
config = validator_context.config
|
config = validator_context.config
|
||||||
client = validator_context.http_client
|
client = validator_context.http_client
|
||||||
|
url_scheme_and_hostname = validator_context.url_scheme_and_hostname
|
||||||
|
|
||||||
google_login_config = config.get('GOOGLE_LOGIN_CONFIG')
|
google_login_config = config.get('GOOGLE_LOGIN_CONFIG')
|
||||||
if not google_login_config:
|
if not google_login_config:
|
||||||
|
@ -21,6 +22,6 @@ class GoogleLoginValidator(BaseValidator):
|
||||||
raise ConfigValidationException('Missing Client Secret')
|
raise ConfigValidationException('Missing Client Secret')
|
||||||
|
|
||||||
oauth = GoogleOAuthService(config, 'GOOGLE_LOGIN_CONFIG')
|
oauth = GoogleOAuthService(config, 'GOOGLE_LOGIN_CONFIG')
|
||||||
result = oauth.validate_client_id_and_secret(client)
|
result = oauth.validate_client_id_and_secret(client, url_scheme_and_hostname)
|
||||||
if not result:
|
if not result:
|
||||||
raise ConfigValidationException('Invalid client id or client secret')
|
raise ConfigValidationException('Invalid client id or client secret')
|
||||||
|
|
|
@ -31,7 +31,10 @@ class JWTAuthValidator(BaseValidator):
|
||||||
raise ConfigValidationException('Missing JWT Issuer ID')
|
raise ConfigValidationException('Missing JWT Issuer ID')
|
||||||
|
|
||||||
|
|
||||||
override_config_directory = os.path.join(config_provider.get_config_root(), 'stack/')
|
# TODO(jschorr): fix this
|
||||||
|
return
|
||||||
|
|
||||||
|
override_config_directory = os.path.join(config_provider.get_config_root(), '../stack/')
|
||||||
|
|
||||||
# Try to instatiate the JWT authentication mechanism. This will raise an exception if
|
# Try to instatiate the JWT authentication mechanism. This will raise an exception if
|
||||||
# the key cannot be found.
|
# the key cannot be found.
|
||||||
|
|
|
@ -13,7 +13,6 @@ class StorageValidator(BaseValidator):
|
||||||
ip_resolver = validator_context.ip_resolver
|
ip_resolver = validator_context.ip_resolver
|
||||||
config_provider = validator_context.config_provider
|
config_provider = validator_context.config_provider
|
||||||
|
|
||||||
# replication_enabled = app.config.get('FEATURE_STORAGE_REPLICATION', False)
|
|
||||||
replication_enabled = config.get('FEATURE_STORAGE_REPLICATION', False)
|
replication_enabled = config.get('FEATURE_STORAGE_REPLICATION', False)
|
||||||
|
|
||||||
providers = _get_storage_providers(config, ip_resolver, config_provider).items()
|
providers = _get_storage_providers(config, ip_resolver, config_provider).items()
|
||||||
|
|
22
util/secscan/secscan_util.py
Normal file
22
util/secscan/secscan_util.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
from urlparse import urljoin
|
||||||
|
|
||||||
|
from flask import url_for
|
||||||
|
|
||||||
|
|
||||||
|
def get_blob_download_uri_getter(context, url_scheme_and_hostname):
|
||||||
|
"""
|
||||||
|
Returns a function with context to later generate the uri for a download blob
|
||||||
|
:param context: Flask RequestContext
|
||||||
|
:param url_scheme_and_hostname: URLSchemeAndHostname class instance
|
||||||
|
:return: function (repository_and_namespace, checksum) -> uri
|
||||||
|
"""
|
||||||
|
def create_uri(repository_and_namespace, checksum):
|
||||||
|
"""
|
||||||
|
Creates a uri for a download blob from a repository, namespace, and checksum from earlier context
|
||||||
|
"""
|
||||||
|
with context:
|
||||||
|
relative_layer_url = url_for('v2.download_blob', repository=repository_and_namespace,
|
||||||
|
digest=checksum)
|
||||||
|
return urljoin(url_scheme_and_hostname.get_url(), relative_layer_url)
|
||||||
|
|
||||||
|
return create_uri
|
19
util/secscan/test/test_secscan_util.py
Normal file
19
util/secscan/test/test_secscan_util.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app import app
|
||||||
|
from util.config import URLSchemeAndHostname
|
||||||
|
from util.secscan.secscan_util import get_blob_download_uri_getter
|
||||||
|
|
||||||
|
from test.fixtures import *
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('url_scheme_and_hostname, repo_namespace, checksum, expected_value,', [
|
||||||
|
(URLSchemeAndHostname('http', 'localhost:5000'),
|
||||||
|
'devtable/simple', 'tarsum+sha256:123',
|
||||||
|
'http://localhost:5000/v2/devtable/simple/blobs/tarsum+sha256:123'),
|
||||||
|
])
|
||||||
|
def test_blob_download_uri_getter(app, url_scheme_and_hostname,
|
||||||
|
repo_namespace, checksum,
|
||||||
|
expected_value):
|
||||||
|
blob_uri_getter = get_blob_download_uri_getter(app.test_request_context('/'), url_scheme_and_hostname)
|
||||||
|
|
||||||
|
assert blob_uri_getter(repo_namespace, checksum) == expected_value
|
Reference in a new issue