diff --git a/.coveragerc b/.coveragerc index a388931..d9df9bf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,7 @@ [run] omit = */test/* + ingredients_db/alembic/env.py [report] exclude_lines = diff --git a/ingredients_db/alembic/versions/dadf4ada480a_create_authz.py b/ingredients_db/alembic/versions/dadf4ada480a_create_authz.py index 34679da..9d54ccc 100644 --- a/ingredients_db/alembic/versions/dadf4ada480a_create_authz.py +++ b/ingredients_db/alembic/versions/dadf4ada480a_create_authz.py @@ -28,17 +28,6 @@ def upgrade(): nullable=False) ) - # rules_table = op.create_table( - # 'authz_rules', - # sa.Column('id', sau.UUIDType, server_default=sa.text("uuid_generate_v4()"), primary_key=True), - # sa.Column('name', sa.String, nullable=False, unique=True), - # sa.Column('value', sa.String, nullable=False), - # sa.Column('description', sa.String), - # sa.Column('created_at', sau.ArrowType(timezone=True), server_default=sa.func.now(), nullable=False), - # sa.Column('updated_at', sau.ArrowType(timezone=True), server_default=sa.func.now(), onupdate=sa.func.now(), - # nullable=False) - # ) - roles_table = op.create_table( 'authz_roles', sa.Column('id', sau.UUIDType, server_default=sa.text("uuid_generate_v4()"), primary_key=True), @@ -75,12 +64,10 @@ def upgrade(): nullable=False), ) - # TODO: populate default roles - op.bulk_insert( roles_table, [ - {"name": "admin", "description": "Administrator Role for Sandwich Cloud"} + {"name": "admin", "description": "Administrator Role"} ] ) @@ -90,38 +77,81 @@ def upgrade(): # Rules {"name": "is_admin", "rule": "role:admin", "description": "Is the user in the admin role"}, {"name": "admin_or_member", "rule": "rule:is_admin or project_id:%(project_id)s", - "description": "Is the user in the admin role"}, + "description": "Is the user in the admin role or a member of the project of the requested object"}, + {"name": "admin_or_self", "rule": "rule:is_admin or user_id:%(user_id)", + "description": "Is the user in the admin role or the requested object matches the user id"}, # Policies + # Use role:admin so we don't get locked out if is_admin is changed/deleted + # If the admin role gets deleted... well don't be stupid :p + {"name": "policies:create", "rule": "role:admin", "description": "Ability to create a policy"}, + {"name": "policies:get", "rule": "role:admin", "description": "Ability to get a policy"}, + {"name": "policies:update", "rule": "role:admin", "description": "Ability to update a policy"}, + {"name": "policies:list", "rule": "role:admin", "description": "Ability to list policies"}, + {"name": "policies:delete", "rule": "role:admin", "description": "Ability to delete a policy"}, # Roles + # Use role:admin so we don't get locked out if is_admin is changed/deleted + # If the admin role gets deleted... well don't be stupid :p + {"name": "roles:create", "rule": "role:admin", "description": "Ability to create a role"}, + {"name": "roles:get", "rule": "role:admin", "description": "Ability to get a role"}, + {"name": "roles:list", "rule": "role:admin", "description": "Ability to list roles"}, + {"name": "roles:delete", "rule": "role:admin", "description": "Ability to delete a role"}, # Tokens + {"name": "tokens:get", "rule": "rule:admin_or_self", "description": "Ability to get a token"}, # Tasks - # Projects - {"name": "projects:create", "rule": "rule:is_admin", "description": "Ability to create projects"}, - {"name": "projects:get", "rule": "", "description": "Ability to get a project"}, - {"name": "projects:list", "rule": "", "description": "Ability to list projects"}, - {"name": "projects:delete", "rule": "rule:is_admin", "description": "Ability to delete projects"}, + # Images + {"name": "images:create", "rule": "rule:admin_or_member", "description": "Ability to create an image"}, + {"name": "images:create:public", "rule": "rule:is_admin", + "description": "Ability to create a public image"}, + {"name": "images:get", "rule": "rule:admin_or_member", "description": "Ability to get an image"}, + {"name": "images:list", "rule": "rule:admin_or_member", "description": "Ability to list images"}, + {"name": "images:delete", "rule": "rule:admin_or_member", "description": "Ability to delete an image"}, + {"name": "images:action:lock", "rule": "rule:admin_or_member", "description": "Ability to lock an image"}, + {"name": "images:action:unlock", "rule": "rule:admin_or_member", + "description": "Ability to unlock an image"}, + + # Instances + {"name": "instances:create", "rule": "rule:admin_or_member", + "description": "Ability to create an instance"}, + {"name": "instances:get", "rule": "rule:admin_or_member", "description": "Ability to get an instance"}, + {"name": "instances:list", "rule": "rule:admin_or_member", "description": "Ability to list instances"}, + {"name": "instances:delete", "rule": "rule:admin_or_member", + "description": "Ability to delete an instance"}, + {"name": "instances:action:stop", "rule": "rule:admin_or_member", + "description": "Ability to stop an instance"}, + {"name": "instances:action:start", "rule": "rule:admin_or_member", + "description": "Ability to start an instance"}, + {"name": "instances:action:restart", "rule": "rule:admin_or_member", + "description": "Ability to restart an instance"}, + {"name": "instances:action:image", "rule": "rule:admin_or_member", + "description": "Ability to create an image from an instance"}, + {"name": "instances:action:image:public", "rule": "rule:is_admin", + "description": "Ability to create a public image from an instance"}, + {"name": "instances:action:reset_state", "rule": "rule:admin_or_member", + "description": "Ability to reset the state of an instance"}, # Networks + {"name": "networks:create", "rule": "rule:is_admin", "description": "Ability to create a network"}, + {"name": "networks:get", "rule": "", "description": "Ability to get a network"}, + {"name": "networks:list", "rule": "", "description": "Ability to list networks"}, + {"name": "networks:delete", "rule": "rule:is_admin", "description": "Ability to delete a network"}, - # Images - {"name": "images:create", "rule": "rule:admin_or_member", "description": "Ability to create images"}, + # Projects + {"name": "projects:create", "rule": "rule:is_admin", "description": "Ability to create a project"}, + {"name": "projects:get", "rule": "", "description": "Ability to get a project"}, + {"name": "projects:list", "rule": "", "description": "Ability to list projects"}, + {"name": "projects:delete", "rule": "rule:is_admin", "description": "Ability to delete a project"}, - # Instances ] ) - # TODO: populate default policies - # - def downgrade(): op.drop_table('authn_token_roles') op.drop_table('authn_tokens') op.drop_table('authz_roles') - # op.drop_table('authz_rules') op.drop_table('authz_policies') diff --git a/ingredients_db/models/authz.py b/ingredients_db/models/authz.py index bc7544d..16399d3 100644 --- a/ingredients_db/models/authz.py +++ b/ingredients_db/models/authz.py @@ -4,6 +4,7 @@ from ingredients_db.database import Base +# TODO: should we have locked policies to prevent accidental deletion? class AuthZPolicy(Base): __tablename__ = 'authz_policies' @@ -16,18 +17,7 @@ class AuthZPolicy(Base): updated_at = Column(ArrowType(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False) -# class AuthZRule(Base): -# __tablename__ = 'authz_rules' -# -# id = Column(UUIDType, server_default=text("uuid_generate_v4()"), primary_key=True) -# name = Column(String, nullable=False, unique=True) -# value = Column(String, nullable=False) -# description = Column(String) -# -# created_at = Column(ArrowType(timezone=True), server_default=func.now(), nullable=False) -# updated_at = Column(ArrowType(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False) - - +# TODO: should we have locked roles to prevent accidental deletion? class AuthZRole(Base): __tablename__ = 'authz_roles' diff --git a/ingredients_db/test/test_migrations.py b/ingredients_db/test/test_migrations.py index 52f1d20..0c544df 100644 --- a/ingredients_db/test/test_migrations.py +++ b/ingredients_db/test/test_migrations.py @@ -1,4 +1,5 @@ import json +import os import pytest from alembic import command @@ -19,7 +20,7 @@ def alembic_root(): @pytest.fixture() def uri(): - yield "postgresql+psycopg2://postgres@localhost" + yield "postgresql+psycopg2://postgres@127.0.0.1" if 'CI' in os.environ else os.environ['TEST_DB_URL'] @pytest.fixture() diff --git a/tox.ini b/tox.ini index 2ef7ffe..f3b6644 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,9 @@ deps = flake8 [testenv:py36] +passenv = + TEST_DB_URL + CI commands = py.test deps =