Skip to content

Commit 693856e

Browse files
committed
More appropriate permissions
1 parent 15ffe77 commit 693856e

File tree

13 files changed

+135
-42
lines changed

13 files changed

+135
-42
lines changed

.idea/dataSources.xml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/runConfigurations/mrmat_python_api_flask.xml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/runConfigurations/mrmat_python_api_flask__oidc_.xml renamed to .idea/runConfigurations/mrmat_python_api_flask__default_.xml

Lines changed: 1 addition & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/runConfigurations/mrmat_python_api_flask__debug_.xml renamed to .idea/runConfigurations/mrmat_python_api_flask__default__debug_.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Owners and Resources
2+
3+
Revision ID: d11062fbec93
4+
Revises: 4594cf7d8bfb
5+
Create Date: 2021-06-26 17:57:31.057034
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'd11062fbec93'
14+
down_revision = '4594cf7d8bfb'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table('owners',
22+
sa.Column('id', sa.BigInteger().with_variant(sa.Integer(), 'sqlite'), nullable=False),
23+
sa.Column('client_id', sa.String(length=255), nullable=False),
24+
sa.Column('name', sa.String(length=255), nullable=False),
25+
sa.PrimaryKeyConstraint('id'),
26+
sa.UniqueConstraint('client_id')
27+
)
28+
op.add_column('resources', sa.Column('owner_id', sa.Integer(), nullable=False))
29+
op.alter_column('resources', 'name',
30+
existing_type=sa.VARCHAR(length=50),
31+
nullable=False)
32+
op.create_foreign_key(None, 'resources', 'owners', ['owner_id'], ['id'])
33+
op.drop_column('resources', 'owner')
34+
# ### end Alembic commands ###
35+
36+
37+
def downgrade():
38+
# ### commands auto generated by Alembic - please adjust! ###
39+
op.add_column('resources', sa.Column('owner', sa.VARCHAR(length=50), autoincrement=False, nullable=True))
40+
op.drop_constraint(None, 'resources', type_='foreignkey')
41+
op.alter_column('resources', 'name',
42+
existing_type=sa.VARCHAR(length=50),
43+
nullable=True)
44+
op.drop_column('resources', 'owner_id')
45+
op.drop_table('owners')
46+
# ### end Alembic commands ###

mrmat_python_api_flask/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ def create_app(config_override=None, instance_path=None):
101101
app.logger.info(f'Creating new instance path at {app.instance_path}')
102102
os.makedirs(app.instance_path)
103103
else:
104-
app.logger.info(f'Using instance path at {app.instance_path}')
104+
app.logger.info(f'Using existing instance path at {app.instance_path}')
105105
except OSError:
106-
app.logger.error(f'Failed to create instance path at {app.instance_path}')
106+
app.logger.error(f'Failed to create new instance path at {app.instance_path}')
107107
sys.exit(1)
108108

109109
# When using Flask-SQLAlchemy, there is no need to explicitly import DAO classes because they themselves

mrmat_python_api_flask/apis/resource/v1/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@
2424
"""
2525

2626
from .api import bp as api_resource_v1 # noqa: F401
27-
from .model import Resource, ResourceSchema # noqa: F401
27+
from .model import Owner, Resource, OwnerSchema, ResourceSchema # noqa: F401

mrmat_python_api_flask/apis/resource/v1/api.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@
3030
from marshmallow import ValidationError
3131

3232
from mrmat_python_api_flask import db, oidc
33-
from .model import Resource, resource_schema, resources_schema
33+
from .model import Owner, Resource, resource_schema, resources_schema
3434

3535
bp = Blueprint('resource_v1', __name__)
3636
logger = LocalProxy(lambda: current_app.logger)
3737

3838

3939
def _extract_identity() -> Tuple:
40-
return g.oidc_token_info['sub'], g.oidc_token_info['preferred_username']
40+
return g.oidc_token_info['client_id'], \
41+
g.oidc_token_info['preferred_username']
4142

4243

4344
@bp.route('/', methods=['GET'])
@@ -63,16 +64,35 @@ def get_one(i: int):
6364
@bp.route('/', methods=['POST'])
6465
@oidc.accept_token(require_token=True, scopes_required=['mrmat-python-api-flask-resource-write'])
6566
def create():
66-
identity = _extract_identity()
67-
logger.info(f'Called by {identity[1]} ({identity[0]}')
67+
(client_id, name) = _extract_identity()
68+
logger.info(f'Called by {name} ({client_id}')
6869
try:
6970
json_body = request.get_json()
7071
if not json_body:
7172
return {'message': 'No input data provided'}, 400
7273
body = resource_schema.load(request.get_json())
7374
except ValidationError as ve:
7475
return ve.messages, 422
75-
resource = Resource(owner=identity[0], name=body['name'])
76+
77+
#
78+
# Check if we have a resource with the same name and owner already
79+
80+
resource = Resource.query\
81+
.filter(Resource.name == body['name'] and Resource.owner.client_id == client_id)\
82+
.one_or_none()
83+
if resource is not None:
84+
return {'status': 409,
85+
'message': f'A resource with the same name and owner already exists with id {resource.id}'}, 409
86+
87+
#
88+
# Look up the owner and create one if necessary
89+
90+
owner = Owner.query.filter(Owner.client_id == client_id).one_or_none()
91+
if owner is None:
92+
owner = Owner(client_id=client_id, name=name)
93+
db.session.add(owner)
94+
95+
resource = Resource(owner=owner, name=body['name'])
7696
db.session.add(resource)
7797
db.session.commit()
7898
return resource_schema.dump(resource), 201
@@ -81,14 +101,17 @@ def create():
81101
@bp.route('/<i>', methods=['PUT'])
82102
@oidc.accept_token(require_token=True, scopes_required=['mrmat-python-api-flask-resource-write'])
83103
def modify(i: int):
84-
identity = _extract_identity()
85-
logger.info(f'Called by {identity[1]} ({identity[0]}')
104+
(client_id, name) = _extract_identity()
105+
logger.info(f'Called by {name} ({client_id}')
86106
body = resource_schema.load(request.get_json())
87-
resource = Resource.query.filter(Resource.id == i).one()
107+
108+
resource = Resource.query.filter(Resource.id == i).one_or_none()
88109
if resource is None:
89110
return {'status': 404, 'message': 'Unable to find requested resource'}, 404
90-
resource.owner = identity[0]
111+
if resource.owner.client_id != client_id:
112+
return { 'status': 401, 'message': 'You do not own this resource'}, 401
91113
resource.name = body['name']
114+
92115
db.session.add(resource)
93116
db.session.commit()
94117
return resource_schema.dump(resource), 200
@@ -97,11 +120,15 @@ def modify(i: int):
97120
@bp.route('/<i>', methods=['DELETE'])
98121
@oidc.accept_token(require_token=True, scopes_required=['mrmat-python-api-flask-resource-write'])
99122
def remove(i: int):
100-
identity = _extract_identity()
101-
logger.info(f'Called by {identity[1]} ({identity[0]}')
102-
resource = Resource.query.filter(Resource.id == i).one()
123+
(client_id, name) = _extract_identity()
124+
logger.info(f'Called by {name} ({client_id}')
125+
126+
resource = Resource.query.filter(Resource.id == i).one_or_none()
103127
if resource is None:
104128
return {'status': 410, 'message': 'Unable to find requested resource'}, 410
129+
if resource.owner.client_id != client_id:
130+
return { 'status': 401, 'message': 'You do not own this resource'}, 401
131+
105132
db.session.delete(resource)
106133
db.session.commit()
107134
return {}, 204

mrmat_python_api_flask/apis/resource/v1/model.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,33 +23,51 @@
2323
"""Resource API SQLAlchemy model
2424
"""
2525

26-
from sqlalchemy import Column, Integer, String
26+
from sqlalchemy import ForeignKey, Column, Integer, String, UniqueConstraint, BigInteger
27+
from sqlalchemy.orm import relationship
2728
from marshmallow import fields
2829

2930
from mrmat_python_api_flask import db, ma
3031

3132

33+
class Owner(db.Model):
34+
__tablename__ = 'owners'
35+
id = Column(BigInteger().with_variant(Integer, 'sqlite'), primary_key=True)
36+
client_id = Column(String(255), nullable=False, unique=True)
37+
name = Column(String(255), nullable=False)
38+
resources = relationship('Resource', back_populates='owner')
39+
40+
3241
class Resource(db.Model):
3342
__tablename__ = 'resources'
34-
id = Column(Integer, primary_key=True)
35-
owner = Column(String(50))
36-
# TODO: Should have unique constraint on name
37-
name = Column(String(50))
43+
id = Column(BigInteger().with_variant(Integer, 'sqlite'), primary_key=True)
44+
owner_id = Column(Integer, ForeignKey('owners.id'), nullable=False)
45+
name = Column(String(255), nullable=False)
46+
47+
owner = relationship('Owner', back_populates='resources')
48+
UniqueConstraint('owner_id', 'name', name='no_duplicate_names_per_owner')
49+
3850

39-
def __init__(self, owner=None, name=None):
40-
self.owner = owner
41-
self.name = name
51+
class OwnerSchema(ma.Schema):
52+
class Meta:
53+
fields = ('id', 'client_id', 'name')
54+
55+
id = fields.Int(dump_only=True)
56+
client_id = fields.Str(dump_only=True)
57+
name = fields.Str()
4258

4359

4460
class ResourceSchema(ma.Schema):
4561
class Meta:
46-
# Fields to expose
4762
fields = ('id', 'owner', 'name')
4863

4964
id = fields.Int()
65+
owner_id = fields.Int(load_only=True)
66+
owner = fields.Nested(lambda: OwnerSchema(), dump_only=True)
5067
name = fields.Str()
51-
owner = fields.Str()
5268

5369

70+
owner_schema = OwnerSchema()
71+
owners_schema = OwnerSchema(many=True)
5472
resource_schema = ResourceSchema()
5573
resources_schema = ResourceSchema(many=True)

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
[pytest]
66
testpaths = tests
7-
addopts = --cov=mrmat_python_api_flask --cov-report=term --cov-report=xml:build/coverage.xml --junit-xml=build/junit.xml
7+
#addopts = --cov=mrmat_python_api_flask --cov-report=term --cov-report=xml:build/coverage.xml --junit-xml=build/junit.xml
88
junit_family = xunit2
99
log_cli = 1
1010
log_cli_level = INFO

0 commit comments

Comments
 (0)