Skip to content

Commit

Permalink
Uri prefix support (#247)
Browse files Browse the repository at this point in the history
Added support for specifying a URI prefix for cases when Opsy is behind
a reverse proxy.

Added _links references to auth schema, seems this was forgotten about
when the auth API was created. Updated tests to reflect this.

Signed-off-by: M. David Bennett <[email protected]>
  • Loading branch information
mdavidbennett authored Jan 23, 2020
1 parent b8844a8 commit 8aa187a
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 14 deletions.
6 changes: 6 additions & 0 deletions opsy.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ database_uri = 'sqlite:///../opsy.db'
# Required: true
secret_key = 'this is a secret'

# This is used if Opsy is behind a reverse proxy. It should be set to where
# you have it mounted, like '/opsy' for example.
# Required: false
# Default value: /
uri_prefix = '/'

[server]
# This section contains configuration for the embedded WSGI server.

Expand Down
17 changes: 17 additions & 0 deletions opsy/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,26 @@
from opsy.inventory.views import create_inventory_views


class PrefixMiddleware:

def __init__(self, app, prefix=''):
self.app = app
self.prefix = prefix

def __call__(self, environ, start_response):
if environ['PATH_INFO'].startswith(self.prefix):
environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):]
environ['SCRIPT_NAME'] = self.prefix
return self.app(environ, start_response)
start_response('404', [('Content-Type', 'text/plain')])
return ["This url does not belong to the app.".encode()]


def create_app(config):
configure_logging(config)
app = Flask('opsy')
app.wsgi_app = PrefixMiddleware(
app.wsgi_app, prefix=config['app']['uri_prefix'])
app.before_request(log_before_request)
app.after_request(log_after_request)
configure_app(app, config)
Expand Down
25 changes: 21 additions & 4 deletions opsy/auth/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from marshmallow_sqlalchemy import field_for
from opsy.auth.models import User, Role, Permission
from opsy.flask_extensions import ma
from opsy.schema import BaseSchema
from opsy.schema import BaseSchema, Hyperlinks

###############################################################################
# Non-sqlalchemy schemas
Expand Down Expand Up @@ -38,7 +38,7 @@ class UserSchema(BaseSchema):
class Meta:
model = User
fields = ('id', 'name', 'full_name', 'email', 'enabled', 'ldap_user',
'created_at', 'updated_at', 'roles', 'permissions')
'created_at', 'updated_at', 'roles', 'permissions', '_links')
ordered = True
unknown = RAISE

Expand All @@ -53,6 +53,11 @@ class Meta:
'RolePermissionRefSchema', many=True, dump_only=True)
roles = ma.Nested( # pylint: disable=no-member
'RoleRefSchema', many=True, dump_only=True)
_links = Hyperlinks(
{"self": ma.URLFor("auth_users.users_get", id_or_name="<id>"),
"collection": ma.URLFor("auth_users.users_list")},
dump_only=True
)


class UserCreateSchema(UserSchema):
Expand Down Expand Up @@ -147,7 +152,7 @@ class RoleSchema(BaseSchema):
class Meta:
model = Role
fields = ('id', 'name', 'ldap_group', 'description', 'created_at',
'updated_at', 'permissions', 'users')
'updated_at', 'permissions', 'users', '_links')
ordered = True
unknown = RAISE

Expand All @@ -161,6 +166,11 @@ class Meta:
users = ma.Nested( # pylint: disable=no-member
'UserRefSchema', many=True, dump_only=True)

_links = Hyperlinks(
{"self": ma.URLFor("auth_roles.roles_get", id_or_name="<id>"),
"collection": ma.URLFor("auth_roles.roles_list")},
dump_only=True)


class RoleUpdateSchema(RoleSchema):

Expand Down Expand Up @@ -203,7 +213,7 @@ class RolePermissionSchema(BaseSchema):

class Meta:
model = Permission
fields = ('id', 'role', 'name', 'created_at', 'updated_at')
fields = ('id', 'role', 'name', 'created_at', 'updated_at', '_links')
ordered = True

id = field_for(Permission, 'id', dump_only=True)
Expand All @@ -214,6 +224,13 @@ class Meta:
role = ma.Nested( # pylint: disable=no-member
'RoleRefSchema', dump_only=True)

_links = Hyperlinks({
"self": ma.URLFor("auth_roles.role_permissions_get",
id_or_name="<role_id>",
permission_id_or_name="<id>"),
"collection": ma.URLFor("auth_roles.role_permissions_list",
id_or_name="<role_id>")}, dump_only=True)


class RolePermissionUpdateSchema(BaseSchema):

Expand Down
1 change: 1 addition & 0 deletions opsy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
class ConfigAppSchema(Schema):
database_uri = fields.Str(required=True)
secret_key = fields.Str(required=True)
uri_prefix = fields.Str(missing='/')


class ConfigAuthSchema(Schema):
Expand Down
3 changes: 2 additions & 1 deletion opsy/flask_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ def configure_extensions(app):
version='v1',
openapi_version='2.0',
info={'description': "It's Opsy!"},
plugins=[MarshmallowPlugin()]
plugins=[MarshmallowPlugin()],
basePath=app.config.opsy['app']['uri_prefix']
),
'APISPEC_SWAGGER_URL': '/docs/swagger.json',
'APISPEC_SWAGGER_UI_URL': '/docs/',
Expand Down
1 change: 1 addition & 0 deletions scripts/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cat > ${OPSY_CONFIG} <<__EOF__
[app]
database_uri = '${OPSY_DATABASE_URI}'
secret_key = '${OPSY_SECRET_KEY}'
uri_prefix = '${OPSY_URI_PREFIX:-/}'
[server]
host = '0.0.0.0'
Expand Down
43 changes: 34 additions & 9 deletions tests/schema/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,23 @@ def test_user_schema(test_user):
('created_at', test_user.created_at.isoformat()),
('updated_at', test_user.updated_at.isoformat()),
('roles', [
OrderedDict([('id', x.id), ('name', x.name)])
OrderedDict([
('id', x.id),
('name', x.name),
('_links', {
'self': f'/api/v1/roles/{x.id}',
'collection': '/api/v1/roles/'})])
for x in test_user.roles]),
('permissions', [
OrderedDict([('id', x.id), ('name', x.name)])
for x in test_user.permissions])])
('permissions', [OrderedDict([
('id', x.id),
('name', x.name),
('_links', {
'self': f'/api/v1/roles/{x.role.id}/permissions/{x.id}',
'collection': f'/api/v1/roles/{x.role.id}/permissions/'})])
for x in test_user.permissions]),
('_links', {
'self': f'/api/v1/users/{test_user.id}',
'collection': '/api/v1/users/'})])

assert UserSchema().dump(test_user) == expected_user_schema_output

Expand Down Expand Up @@ -114,10 +126,17 @@ def test_role_schema(test_role):
('description', test_role.description),
('created_at', test_role.created_at.isoformat()),
('updated_at', test_role.updated_at.isoformat()),
('permissions', [
OrderedDict([('id', x.id), ('name', x.name)])
('permissions', [OrderedDict([
('id', x.id),
('name', x.name),
('_links', {
'self': f'/api/v1/roles/{x.role.id}/permissions/{x.id}',
'collection': f'/api/v1/roles/{x.role.id}/permissions/'})])
for x in test_role.permissions]),
('users', test_role.users)])
('users', test_role.users),
('_links', {
'self': f'/api/v1/roles/{test_role.id}',
'collection': '/api/v1/roles/'})])

assert RoleSchema().dump(test_role) == expected_role_schema_output

Expand All @@ -135,10 +154,16 @@ def test_role_permission_schema(admin_user):
('id', test_perm.id),
('role', OrderedDict([
('id', test_perm.role.id),
('name', test_perm.role.name)])),
('name', test_perm.role.name),
('_links', {
'self': f'/api/v1/roles/{test_perm.role.id}',
'collection': '/api/v1/roles/'})])),
('name', test_perm.name),
('created_at', test_perm.created_at.isoformat()),
('updated_at', test_perm.updated_at.isoformat())])
('updated_at', test_perm.updated_at.isoformat()),
('_links', {
'self': f'/api/v1/roles/{test_perm.role.id}/permissions/{test_perm.id}',
'collection': f'/api/v1/roles/{test_perm.role.id}/permissions/'})])

assert RolePermissionSchema().dump(test_perm) \
== expected_role_permission_schema_output

0 comments on commit 8aa187a

Please sign in to comment.