Add ability for specific geographic regions to be blocked from pulling images within a namespace
This commit is contained in:
parent
c71a43a06c
commit
c3710a6a5e
20 changed files with 257 additions and 37 deletions
5
data/cache/cache_key.py
vendored
5
data/cache/cache_key.py
vendored
|
@ -14,3 +14,8 @@ def for_catalog_page(auth_context_key, start_id, limit):
|
|||
""" Returns a cache key for a single page of a catalog lookup for an authed context. """
|
||||
params = (auth_context_key or '(anon)', start_id or 0, limit or 0)
|
||||
return CacheKey('catalog_page__%s_%s_%s' % params, '60s')
|
||||
|
||||
|
||||
def for_namespace_geo_restrictions(namespace_name):
|
||||
""" Returns a cache key for the geo restrictions for a namespace """
|
||||
return CacheKey('geo_restrictions__%s' % (namespace_name), '240s')
|
||||
|
|
|
@ -504,7 +504,8 @@ class User(BaseModel):
|
|||
RepositoryNotification, OAuthAuthorizationCode,
|
||||
RepositoryActionCount, TagManifestLabel,
|
||||
TeamSync, RepositorySearchScore,
|
||||
DeletedNamespace} | appr_classes | v22_classes | transition_classes
|
||||
DeletedNamespace,
|
||||
NamespaceGeoRestriction} | appr_classes | v22_classes | transition_classes
|
||||
delete_instance_filtered(self, User, delete_nullable, skip_transitive_deletes)
|
||||
|
||||
|
||||
|
@ -525,6 +526,21 @@ class DeletedNamespace(BaseModel):
|
|||
queue_id = CharField(null=True, index=True)
|
||||
|
||||
|
||||
class NamespaceGeoRestriction(BaseModel):
|
||||
namespace = QuayUserField(index=True, allows_robots=False)
|
||||
added = DateTimeField(default=datetime.utcnow)
|
||||
description = CharField()
|
||||
unstructured_json = JSONField()
|
||||
restricted_region_iso_code = CharField(index=True)
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
read_slaves = (read_slave,)
|
||||
indexes = (
|
||||
(('namespace', 'restricted_region_iso_code'), True),
|
||||
)
|
||||
|
||||
|
||||
class UserPromptTypes(object):
|
||||
CONFIRM_USERNAME = 'confirm_username'
|
||||
ENTER_NAME = 'enter_name'
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
"""Add NamespaceGeoRestriction table
|
||||
|
||||
Revision ID: 54492a68a3cf
|
||||
Revises: c00a1f15968b
|
||||
Create Date: 2018-12-05 15:12:14.201116
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '54492a68a3cf'
|
||||
down_revision = 'c00a1f15968b'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
def upgrade(tables, tester):
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('namespacegeorestriction',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('namespace_id', sa.Integer(), nullable=False),
|
||||
sa.Column('added', sa.DateTime(), nullable=False),
|
||||
sa.Column('description', sa.String(length=255), nullable=False),
|
||||
sa.Column('unstructured_json', sa.Text(), nullable=False),
|
||||
sa.Column('restricted_region_iso_code', sa.String(length=255), nullable=False),
|
||||
sa.ForeignKeyConstraint(['namespace_id'], ['user.id'], name=op.f('fk_namespacegeorestriction_namespace_id_user')),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_namespacegeorestriction'))
|
||||
)
|
||||
op.create_index('namespacegeorestriction_namespace_id', 'namespacegeorestriction', ['namespace_id'], unique=False)
|
||||
op.create_index('namespacegeorestriction_namespace_id_restricted_region_iso_code', 'namespacegeorestriction', ['namespace_id', 'restricted_region_iso_code'], unique=True)
|
||||
op.create_index('namespacegeorestriction_restricted_region_iso_code', 'namespacegeorestriction', ['restricted_region_iso_code'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
tester.populate_table('namespacegeorestriction', [
|
||||
('namespace_id', tester.TestDataType.Foreign('user')),
|
||||
('added', tester.TestDataType.DateTime),
|
||||
('description', tester.TestDataType.String),
|
||||
('unstructured_json', tester.TestDataType.JSON),
|
||||
('restricted_region_iso_code', tester.TestDataType.String),
|
||||
])
|
||||
|
||||
|
||||
def downgrade(tables, tester):
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('namespacegeorestriction')
|
||||
# ### end Alembic commands ###
|
|
@ -15,7 +15,7 @@ from data.database import (User, LoginService, FederatedLogin, RepositoryPermiss
|
|||
UserRegion, ImageStorageLocation,
|
||||
ServiceKeyApproval, OAuthApplication, RepositoryBuildTrigger,
|
||||
UserPromptKind, UserPrompt, UserPromptTypes, DeletedNamespace,
|
||||
RobotAccountMetadata)
|
||||
RobotAccountMetadata, NamespaceGeoRestriction)
|
||||
from data.model import (DataModelException, InvalidPasswordException, InvalidRobotException,
|
||||
InvalidUsernameException, InvalidEmailAddressException,
|
||||
TooManyLoginAttemptsException, db_transaction,
|
||||
|
@ -1060,6 +1060,14 @@ def get_federated_logins(user_ids, service_name):
|
|||
LoginService.name == service_name))
|
||||
|
||||
|
||||
def list_namespace_geo_restrictions(namespace_name):
|
||||
""" Returns all of the defined geographic restrictions for the given namespace. """
|
||||
return (NamespaceGeoRestriction
|
||||
.select()
|
||||
.join(User)
|
||||
.where(User.username == namespace_name))
|
||||
|
||||
|
||||
class LoginWrappedDBUser(UserMixin):
|
||||
def __init__(self, user_uuid, db_user=None):
|
||||
self._uuid = user_uuid
|
||||
|
|
|
@ -316,3 +316,9 @@ class RegistryDataInterface(object):
|
|||
""" Creates a manifest under the repository and sets a temporary tag to point to it.
|
||||
Returns the manifest object created or None on error.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_cached_namespace_region_blacklist(self, model_cache, namespace_name):
|
||||
""" Returns a cached set of ISO country codes blacklisted for pulls for the namespace
|
||||
or None if the list could not be loaded.
|
||||
"""
|
||||
|
|
|
@ -121,6 +121,27 @@ class SharedModel:
|
|||
torrent_info = model.storage.save_torrent_info(image_storage, piece_length, pieces)
|
||||
return TorrentInfo.for_torrent_info(torrent_info)
|
||||
|
||||
|
||||
def get_cached_namespace_region_blacklist(self, model_cache, namespace_name):
|
||||
""" Returns a cached set of ISO country codes blacklisted for pulls for the namespace
|
||||
or None if the list could not be loaded.
|
||||
"""
|
||||
|
||||
def load_blacklist():
|
||||
restrictions = model.user.list_namespace_geo_restrictions(namespace_name)
|
||||
if restrictions is None:
|
||||
return None
|
||||
|
||||
return [restriction.restricted_region_iso_code for restriction in restrictions]
|
||||
|
||||
blacklist_cache_key = cache_key.for_namespace_geo_restrictions(namespace_name)
|
||||
result = model_cache.retrieve(blacklist_cache_key, load_blacklist)
|
||||
if result is None:
|
||||
return None
|
||||
|
||||
return set(result)
|
||||
|
||||
|
||||
def get_cached_repo_blob(self, model_cache, namespace_name, repo_name, blob_digest):
|
||||
"""
|
||||
Returns the blob in the repository with the given digest if any or None if none.
|
||||
|
|
Reference in a new issue