Commit 0632e7f6 authored by sim's avatar sim

Merge branch 'simon-experimental-share' into simon-gargantext-light

parents 56c94261 ab9db106
"""Use current_user_id() as default nodes.user_id
Revision ID: 291289b47bad
Revises: 4db5dcbe4bc7
Create Date: 2017-10-09 14:58:12.992106
"""
from alembic import op
import sqlalchemy as sa
import gargantext
# revision identifiers, used by Alembic.
revision = '291289b47bad'
down_revision = '4db5dcbe4bc7'
branch_labels = None
depends_on = None
def upgrade():
op.alter_column('nodes', 'user_id',
existing_type=sa.INTEGER(),
server_default=sa.text('current_user_id()'),
existing_nullable=False)
def downgrade():
op.alter_column('nodes', 'user_id',
existing_type=sa.INTEGER(),
server_default=None,
existing_nullable=False)
"""Bootstrap access control system
Revision ID: 4db5dcbe4bc7
Revises: 73304ae9f1fb
Create Date: 2017-10-06 17:23:27.765318
"""
from alembic import op
import sqlalchemy as sa
from gargantext.util.alembic import ReplaceableObject
# revision identifiers, used by Alembic.
revision = '4db5dcbe4bc7'
down_revision = '73304ae9f1fb'
branch_labels = None
depends_on = None
# Publicly exposed schema through PostgREST
api_schema = ReplaceableObject("api")
api_nodes_view = ReplaceableObject(
"api.nodes",
"SELECT id, typename AS type, user_id, parent_id, name, date AS created, hyperdata AS data, title_abstract FROM nodes")
# Mere mortals have 'gargantext' role, admin is 'gargantua'
gargantext_role = ReplaceableObject("gargantext", "NOLOGIN")
# PostgREST authentification system; could be used without PostgREST
authenticator_role = ReplaceableObject(
"authenticator",
"LOGIN NOINHERIT PASSWORD 'CHANGEME'")
anon_role = ReplaceableObject("anon", "NOLOGIN")
roles = [gargantext_role, authenticator_role, anon_role]
grants = [
('gargantext', 'gargantua'),
# Enable login through PostgREST auth system for gargantua, anon and
# gargantext
('gargantua, anon, gargantext', 'authenticator'),
# Basic privileges for gargantext role
('CREATE, USAGE ON SCHEMA api', 'gargantext'),
('SELECT ON nodes', 'gargantext'),
('UPDATE (parent_id, name, date, hyperdata) ON nodes', 'gargantext'),
('INSERT ON nodes', 'gargantext'),
('USAGE, SELECT ON SEQUENCE nodes_id_seq', 'gargantext'),
('DELETE ON nodes', 'gargantext'),
]
current_user_id_sp = ReplaceableObject(
"current_user_id()",
"""
-- Assuming JWT and claim.user_id is set to user.id at login
-- https://stackoverflow.com/questions/2082686/how-do-i-cast-a-string-to-integer-and-have-0-in-case-of-error-in-the-cast-with-p
RETURNS integer AS $$
DECLARE
user_id INTEGER NOT NULL DEFAULT 0;
BEGIN
BEGIN
user_id := current_setting('request.jwt.claim.user_id')::int;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Invalid user_id: %. Check JWT generation.',
current_setting('request.jwt.claim.user_id', TRUE);
RETURN -1;
END;
RETURN user_id;
END;
$$ LANGUAGE plpgsql""")
stored_procedures = [current_user_id_sp]
is_owner = "COALESCE(current_user_id() = user_id, FALSE)"
is_parent_owner = "COALESCE(current_user_id() = (SELECT user_id FROM nodes n WHERE id = nodes.parent_id), FALSE)"
owner_select_policy = ReplaceableObject("owner_select", "nodes", "FOR SELECT USING (%s)" % is_owner)
owner_update_policy = ReplaceableObject("owner_update", "nodes", "FOR UPDATE USING (%s)" % is_owner)
owner_insert_policy = ReplaceableObject("owner_insert", "nodes", "FOR INSERT WITH CHECK (%s)" % is_parent_owner)
owner_delete_policy = ReplaceableObject("owner_delete", "nodes", "FOR DELETE USING (%s)" % is_parent_owner)
policies = [owner_select_policy, owner_update_policy, owner_insert_policy,
owner_delete_policy]
def upgrade():
op.create_schema(api_schema)
for role in roles:
op.create_role(role)
op.create_view(api_nodes_view)
for grant in grants:
op.execute('GRANT {} TO {}'.format(*grant))
op.execute("ALTER VIEW api.nodes OWNER TO gargantext")
op.execute("ALTER TABLE nodes ENABLE ROW LEVEL SECURITY")
for sp in stored_procedures:
op.create_sp(sp)
for policy in policies:
op.create_policy(policy)
def downgrade():
for policy in policies:
op.drop_policy(policy)
for sp in stored_procedures:
op.drop_sp(sp)
op.execute("ALTER TABLE nodes DISABLE ROW LEVEL SECURITY")
for grant in grants:
op.execute('REVOKE {} FROM {}'.format(*grant))
op.drop_view(api_nodes_view)
for role in roles:
op.drop_role(role)
op.drop_schema(api_schema)
......@@ -18,6 +18,12 @@ depends_on = None
def upgrade():
op.execute("UPDATE nodes SET date = CURRENT_TIMESTAMP WHERE date IS NULL")
op.execute("UPDATE nodes SET hyperdata = '{}'::jsonb WHERE hyperdata IS NULL")
op.execute("UPDATE nodes SET name = '' WHERE name IS NULL")
op.execute("DELETE FROM nodes WHERE typename IS NULL")
op.execute("DELETE FROM nodes WHERE user_id IS NULL")
op.alter_column('nodes', 'date',
existing_type=postgresql.TIMESTAMP(timezone=True),
server_default=sa.text('CURRENT_TIMESTAMP'),
......
"""Map database access control to django auth system
Revision ID: d77f0a598ad0
Revises: 291289b47bad
Create Date: 2017-10-12 15:23:40.481825
"""
from alembic import op
import sqlalchemy as sa
from gargantext.util.alembic import ReplaceableObject
# revision identifiers, used by Alembic.
revision = 'd77f0a598ad0'
down_revision = '291289b47bad'
branch_labels = None
depends_on = None
gargandmin_role = ReplaceableObject('gargandmin')
grants = [('gargandmin', 'authenticator'),
('gargantext', 'gargandmin')]
r = "COALESCE(current_setting('request.jwt.claim.role', TRUE), current_user)"
has_perm = "{} = 'gargantua' OR current_user_id() = user_id".format(r)
has_parent_perm = "{} = 'gargantua' OR current_user_id() = (SELECT user_id FROM nodes n WHERE id = nodes.parent_id)".format(r)
policies = {
'owner_select': ReplaceableObject("owner_select", "nodes", "FOR SELECT USING (%s)" % has_perm),
'owner_update': ReplaceableObject("owner_update", "nodes", "FOR UPDATE USING (%s)" % has_perm),
'owner_insert': ReplaceableObject("owner_insert", "nodes", "FOR INSERT WITH CHECK (%s)" % has_parent_perm),
'owner_delete': ReplaceableObject("owner_delete", "nodes", "FOR DELETE USING (%s)" % has_parent_perm),
}
def upgrade():
op.create_role(gargandmin_role)
for grant in grants:
op.execute('GRANT {} TO {}'.format(*grant))
for name, policy in policies.items():
op.replace_policy(policy, replaces='4db5dcbe4bc7.{}_policy'.format(name))
def downgrade():
for name, policy in policies.items():
op.replace_policy(policy, replace_with='4db5dcbe4bc7.{}_policy'.format(name))
for grant in grants:
op.execute('REVOKE {} FROM {}'.format(*grant))
op.drop_role(gargandmin_role)
......@@ -64,7 +64,8 @@ class Node(ValidatorMixin, Base):
# foreign keys
user_id = Column(Integer, ForeignKey(User.id, ondelete='CASCADE'),
nullable=False)
nullable=False,
server_default=text('current_user_id()'))
user = relationship(User)
parent_id = Column(Integer, ForeignKey('nodes.id', ondelete='CASCADE'))
......
from gargantext import settings
from rest_framework_jwt.settings import api_settings
from calendar import timegm
from .dates import datetime
def jwt_payload_handler(user):
username = user.username
payload = {
'role': settings.ROLE_SUPERUSER if user.is_superuser else \
settings.ROLE_STAFF if user.is_staff else \
settings.ROLE_USER,
'user_id': user.pk,
'sub': username,
'exp': datetime.now() + api_settings.JWT_EXPIRATION_DELTA,
'iat': datetime.now(),
}
# Include original issued at time for a brand new token,
# to allow token refresh
if api_settings.JWT_ALLOW_REFRESH:
payload['orig_iat'] = timegm(
datetime.utcnow().utctimetuple()
)
if api_settings.JWT_AUDIENCE is not None:
payload['aud'] = api_settings.JWT_AUDIENCE
if api_settings.JWT_ISSUER is not None:
payload['iss'] = api_settings.JWT_ISSUER
return payload
db-uri = "postgres://authenticator:CHANGEME@127.0.0.1:5432/gargandb"
db-schema = "api"
db-anon-role = "anon"
db-pool = 10
server-host = "*4"
server-port = 3000
## base url for swagger output
# server-proxy-uri = ""
## choose a secret to enable JWT auth
## (use "@filename" to load from separate file)
jwt-secret = "Mw/q=efK3ai7}?}?!D68}a2.j}G5;1]ceI;OV1l=N^(-mH+%l="
# secret-is-base64 = false
## limit rows in response
max-rows = 100
## stored proc to exec immediately after auth
# pre-request = "stored_proc_name"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment