Commit e99f2815 authored by delanoe's avatar delanoe

[FEAT] Node rights management: a node can be public, graph for instance.

parent 314f6169
......@@ -6,7 +6,7 @@ from datetime import datetime
from .users import User
__all__ = ['Node', 'NodeNode']
__all__ = ['Node', 'NodeNode', 'NodeUser']
class NodeType(TypeDecorator):
"""Define a new type of column to describe a Node's type.
......@@ -192,3 +192,38 @@ class NodeNode(Base):
node1_id = Column(Integer, ForeignKey(Node.id, ondelete='CASCADE'), primary_key=True)
node2_id = Column(Integer, ForeignKey(Node.id, ondelete='CASCADE'), primary_key=True)
score = Column(Float(precision=24))
class NodeUser(Base):
"""
NODE USER RIGHTS MANAGEMENT:
r: can read
w: can write
x: can execute (open, delete, share)
Symbolic Int (Octal isomorphism)
rwx 7
rw- 6
r-x 5
r-- 4
-wx 3
-w- 2
--x 1
--- 0
owner/user: first integer
group: second integer
others: third integer
Example:
A user only node: 700 == rwx --- ---
A user sharing all rights to his group: 770 == rwx rwx ---
A user sharing to the public (read mode only): 744 == rwx r-- r--
"""
__tablename__ = 'node_user'
node_id = Column(Integer, ForeignKey(Node.id, ondelete='CASCADE'), primary_key=True)
user_id = Column(Integer, ForeignKey(User.id, ondelete='CASCADE'))
group_id = Column(Integer, ForeignKey(User.id, ondelete='CASCADE'))
mode = Column(Integer)
......@@ -31,7 +31,9 @@ class User(Base):
return query.all()
def nodes(self, typename=None, order=None):
"""get all nodes belonging to the user"""
"""get all nodes belonging to the user
TODO : add NodeUser rights management
"""
from .nodes import Node
query = (session
.query(Node)
......@@ -60,7 +62,7 @@ class User(Base):
"""check if a given node is owned by the user"""
return (node.user_id == self.id) or \
node.id in (contact.id for contact in self.contacts())
def get_params(self, username=None):
print(self.__dict__.items())
return self.hyperdata
......@@ -74,3 +76,6 @@ class Contact(Base):
date_creation = Column(DateTime(timezone=False))
__table_args__ = (UniqueConstraint('user1_id', 'user2_id'), )
from gargantext.models import Node, Ngram, NodeNgram, NodeNodeNgram, NodeNode
from gargantext.models import Node, Ngram, NodeNgram, NodeNodeNgram, NodeNode, NodeUser, User
from gargantext.constants import NODETYPES, DEFAULT_N_DOCS_HAVING_NGRAM
from gargantext.util.db import session, delete, func, bulk_insert
from gargantext.util.db_cache import cache, or_
from gargantext.util.validation import validate
from gargantext.util.http import ValidationException, APIView \
, get_parameters, JsonHttpResponse, Http404\
, HttpResponse
, HttpResponse, requires_auth
from .api import *
from collections import defaultdict
......@@ -25,13 +25,79 @@ _hyperdata_available_fields = ['title', 'source', 'abstract', 'statuses',
#_node_available_formats = ['json', 'csv', 'bibex']
def _query_nodes(request, node_id=None):
if request.user.id is None:
raise TypeError("This API request must come from an authenticated user.")
def check_rights(request, node_id=None):
"""
check rights of a request and maybe a node if given as parameters.
check_rights :: Request -> Maybe Int -> Bool
True : request is accepted
False : request is forbidden
"""
# First find the Node.id of the request
if node_id is None:
node_id = request.GET.get('parent_id', '')
if node_id == '':
node_id = request.GET.get('cooc_id', '')
print(node_id)
if node_id == '':
if node_id == '':
node_id = request.GET.get('node_id', '')
raise TypeError("No node given")
# return False
# Get the right access management rules of the node
nodeRights = ( session.query ( NodeUser )
.filter( NodeUser.node_id == node_id )
.first ( )
)
print(nodeRights.mode)
# If the user is anonymous
# Is the user authenticated i.e. anonymous ?
if request.user.id is None and nodeRights is not None :
# if request.user.id is None and nodeRights not defined then False
# Check if the node has public rights
if int(str(nodeRights.mode)[2]) == 4:
return True
else:
return False
# Common case: user is authenticated and owner of the node
# The user is authenticated (common case)
elif request.user.id is not None and nodeRights is not None :
# Is the user owner of the node ?
if nodeRights.user_id == request.user.id:
# Has the user the rights to read the Node ?
if int(str(nodeRights.mode)[0]) == 7:
return True
elif int(str(nodeRights.mode)[1]) == 7:
# Is the user owner of the node ?
return True
else:
return False
# The user is authenticated and Node rights not implemented yet
elif request.user.id is not None and nodeRights is None :
node = session.query(Node).filter(Node.id == node_id).first()
if request.user.id == node.user_id :
return True
else:
return False
else:
# we query among the nodes that belong to this user
user = cache.User[request.user.id]
return False
def _query_nodes(request, node_id=None):
if check_rights(request, node_id) != True:
return HttpResponseForbidden()
user = cache.User[request.user.id]
# parameters validation
# fixme: this validation does not allow custom keys in url (eg '?name=' for rename action)
......@@ -217,10 +283,9 @@ class NodeListResource(APIView):
def get(self, request):
"""Displays the list of nodes corresponding to the query.
"""
if not request.user.is_authenticated():
# can't use @requires_auth because of positional 'self' within class
return HttpResponse('Unauthorized', status=401)
if check_rights(request) != True:
return HttpResponseForbidden()
parameters, query, count = _query_nodes(request)
......@@ -307,11 +372,16 @@ class NodeListHaving(APIView):
2016-09: add total counts to output json
'''
#@maybe_public
def get(self, request, corpus_id):
if not request.user.is_authenticated():
# can't use @requires_auth because of positional 'self' within class
return HttpResponse('Unauthorized', status=401)
if check_rights(request, corpus_id) != True:
return HttpResponseForbidden()
# if not request.user.is_authenticated():
# # can't use @requires_auth because of positional 'self' within class
# return HttpResponse('Unauthorized', status=401)
parameters = get_parameters(request)
parameters = validate(parameters, {'score': str, 'ngram_ids' : list} )
......@@ -377,6 +447,7 @@ class NodeListHaving(APIView):
class NodeResource(APIView):
# contains a check on user.id (within _query_nodes)
#@maybe_public
def get(self, request, node_id):
if not request.user.is_authenticated():
......
......@@ -2,11 +2,12 @@ from gargantext.util.db import session
from gargantext.models.nodes import Node
from graph.graph import get_graph
from graph.utils import compress_graph, format_html
from gargantext.util.http import APIView, APIException\
from gargantext.util.http import APIView, APIException, HttpResponseForbidden\
, JsonHttpResponse, requires_auth
from gargantext.constants import graph_constraints
from traceback import format_tb
from gargantext.views.api.nodes import check_rights
class Graph(APIView):
'''
......@@ -25,14 +26,12 @@ class Graph(APIView):
before counting + filling data in async
'''
if not request.user.is_authenticated():
# can't use @requires_auth because of positional 'self' within class
return HttpResponse('Unauthorized', status=401)
if check_rights(request) != True:
return HttpResponseForbidden()
# Get the node we are working with
corpus = session.query(Node).filter(Node.id==corpus_id).first()
# TODO Parameters to save in hyperdata of the Node Cooc
# WARNING: we could factorize the parameters as dict but ...
# ... it causes a bug in asynchronous function !
......
......@@ -7,8 +7,11 @@ from gargantext.settings import *
from gargantext.constants import USER_LANG
from datetime import datetime
from gargantext.views.pages.main import get_user_params
from gargantext.views.api.nodes import check_rights
@requires_auth
from gargantext.util.http import HttpResponseForbidden
#@requires_auth
def explorer(request, project_id, corpus_id):
'''
Graph explorer, also known as TinaWebJS, using SigmaJS.
......@@ -17,18 +20,12 @@ def explorer(request, project_id, corpus_id):
Data are received in RESTfull mode (see rest.py).
'''
if check_rights(request) != True:
return HttpResponseForbidden()
# we pass our corpus
corpus = cache.Node[corpus_id]
# security check
user = cache.User[request.user.id]
if corpus is None:
raise Http404()
if not user.owns(corpus):
return HttpResponseForbidden()
# get the maplist_id for modifications
maplist_id = corpus.children(typename="MAPLIST").first().id
......
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