Skip to content
This repository has been archived by the owner on Oct 24, 2018. It is now read-only.

Commit

Permalink
Merge 59ec21e into e978818
Browse files Browse the repository at this point in the history
  • Loading branch information
inkhey committed Jul 3, 2018
2 parents e978818 + 59ec21e commit 67383c3
Show file tree
Hide file tree
Showing 31 changed files with 2,505 additions and 193 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ For example, with default config:
# launch your favorite web-browser
firefox http://localhost:6543/api/v2/doc/

## Roles, profile and access rights

In Tracim, only some user can access to some informations, this is also true in
Tracim REST API. you can check the [roles documentation](doc/roles.md) to check
what a specific user can do.


CI
---

Expand Down
50 changes: 50 additions & 0 deletions doc/roles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Introduction

In Tracim, you have 2 system of "roles".

One is global to whole tracim instance and is called "global profile" (Groups).
The other is workspace related and is called "workspace role".

## Global profile


| | Normal User | Managers | Admin |
|-------------------------------|-------------|-------------|---------|
| participate to workspaces | yes | yes | yes |
| access to tracim apps | yes | yes | yes |
|-------------------------------|-------------|-------------|---------|
| create workspace | no | yes | yes |
| invite user to tracim | no | yes, if manager of a given workspace | yes |
|-------------------------------|-------------|-------------|---------|
| set user global profile rights| no | no | yes |
| deactivate user | no | no | yes |
|-------------------------------|-------------|-------------|---------|
| access to all user data (/users/{user_id} endpoints) |personal-only|personal-only| yes |


## Workspace Roles


| | Reader | Contributor | Content Manager | Workspace Manager |
|------------------------------|--------|-------------|-----------------|-------------------|
| read content | yes | yes | yes | yes |
|------------------------------|--------|-------------|-----------------|-------------------|
| create content | no | yes | yes | yes |
| edit content | no | yes | yes | yes |
| copy content | no | yes | yes | yes |
| comments content | no | yes | yes | yes |
| update content status | no | yes | yes | yes |
-------------------------------|--------|-------------|-----------------|-------------------|
| move content | no | no | yes | yes |
| archive content | no | no | yes | yes |
| delete content | no | no | yes | yes |
|------------------------------|--------|-------------|-----------------|-------------------|
| edit workspace | no | no | no | yes |
| invite users (to workspace) | no | no | no | yes |
| set user workspace role | no | no | no | yes |
| revoke users (from workspace)| no | no | no | yes |
|------------------------------|--------|-------------|-----------------|-------------------|
| modify comments | no | owner | owner | yes |
| delete comments | no | owner | owner | yes |


10 changes: 10 additions & 0 deletions tracim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
from tracim.lib.utils.authorization import TRACIM_DEFAULT_PERM
from tracim.lib.webdav import WebdavAppFactory
from tracim.views import BASE_API_V2
from tracim.views.contents_api.html_document_controller import HTMLDocumentController # nopep8
from tracim.views.contents_api.threads_controller import ThreadController
from tracim.views.core_api.session_controller import SessionController
from tracim.views.core_api.system_controller import SystemController
from tracim.views.core_api.user_controller import UserController
from tracim.views.core_api.workspace_controller import WorkspaceController
from tracim.views.contents_api.comment_controller import CommentController
from tracim.views.errors import ErrorSchema
from tracim.lib.utils.cors import add_cors_support

Expand Down Expand Up @@ -71,10 +74,17 @@ def web(global_config, **local_settings):
system_controller = SystemController()
user_controller = UserController()
workspace_controller = WorkspaceController()
comment_controller = CommentController()
html_document_controller = HTMLDocumentController()
thread_controller = ThreadController()
configurator.include(session_controller.bind, route_prefix=BASE_API_V2)
configurator.include(system_controller.bind, route_prefix=BASE_API_V2)
configurator.include(user_controller.bind, route_prefix=BASE_API_V2)
configurator.include(workspace_controller.bind, route_prefix=BASE_API_V2)
configurator.include(comment_controller.bind, route_prefix=BASE_API_V2)
configurator.include(html_document_controller.bind, route_prefix=BASE_API_V2) # nopep8
configurator.include(thread_controller.bind, route_prefix=BASE_API_V2)

hapic.add_documentation_view(
'/api/v2/doc',
'Tracim v2 API',
Expand Down
14 changes: 13 additions & 1 deletion tracim/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class WorkspaceNotFoundInTracimRequest(NotFound):
pass


class InsufficientUserWorkspaceRole(TracimException):
class InsufficientUserRoleInWorkspace(TracimException):
pass


Expand Down Expand Up @@ -117,5 +117,17 @@ class UserNotFoundInTracimRequest(TracimException):
pass


class ContentNotFoundInTracimRequest(TracimException):
pass


class ContentNotFound(TracimException):
pass


class ContentTypeNotAllowed(TracimException):
pass


class WorkspacesDoNotMatch(TracimException):
pass
75 changes: 72 additions & 3 deletions tracim/fixtures/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def insert(self):
bob = self._session.query(models.User) \
.filter(models.User.email == 'bob@fsf.local') \
.one()
john_the_reader = self._session.query(models.User) \
.filter(models.User.email == 'john-the-reader@reader.local') \
.one()

admin_workspace_api = WorkspaceApi(
current_user=admin,
session=self._session,
Expand All @@ -38,6 +42,16 @@ def insert(self):
session=self._session,
config=self._config
)
bob_content_api = ContentApi(
current_user=bob,
session=self._session,
config=self._config
)
reader_content_api = ContentApi(
current_user=john_the_reader,
session=self._session,
config=self._config
)
role_api = RoleApi(
current_user=admin,
session=self._session,
Expand Down Expand Up @@ -68,6 +82,12 @@ def insert(self):
role_level=UserRoleInWorkspace.CONTENT_MANAGER,
with_notif=False,
)
role_api.create_one(
user=john_the_reader,
workspace=recipe_workspace,
role_level=UserRoleInWorkspace.READER,
with_notif=False,
)
# Folders

tool_workspace = content_api.create(
Expand Down Expand Up @@ -112,19 +132,31 @@ def insert(self):
content_type=ContentType.Page,
workspace=recipe_workspace,
parent=dessert_folder,
label='Tiramisu Recipe',
label='Tiramisu Recipes!!!',
do_save=True,
do_notify=False,
)
with new_revision(
session=self._session,
tm=transaction.manager,
content=tiramisu_page,
):
content_api.update_content(
item=tiramisu_page,
new_content='<p>To cook a greet Tiramisu, you need many ingredients.</p>', # nopep8
new_label='Tiramisu Recipes!!!',
)
content_api.save(tiramisu_page)

best_cake_thread = content_api.create(
content_type=ContentType.Thread,
workspace=recipe_workspace,
parent=dessert_folder,
label='Best Cakes ?',
label='Best Cake',
do_save=False,
do_notify=False,
)
best_cake_thread.description = 'What is the best cake ?'
best_cake_thread.description = 'Which is the best cake ?'
self._session.add(best_cake_thread)
apple_pie_recipe = content_api.create(
content_type=ContentType.File,
Expand Down Expand Up @@ -246,5 +278,42 @@ def insert(self):
content_api.delete(bad_fruit_salad)
content_api.save(bad_fruit_salad)

content_api.create_comment(
parent=best_cake_thread,
content='<p>What is for you the best cake ever? </br> I personnally vote for Chocolate cupcake!</p>', # nopep8
do_save=True,
)
bob_content_api.create_comment(
parent=best_cake_thread,
content='<p>What about Apple Pie? There are Awesome!</p>',
do_save=True,
)
reader_content_api.create_comment(
parent=best_cake_thread,
content='<p>You are right, but Kouign-amann are clearly better.</p>',
do_save=True,
)
with new_revision(
session=self._session,
tm=transaction.manager,
content=best_cake_thread,
):
bob_content_api.update_content(
item=best_cake_thread,
new_content='What is the best cake ?',
new_label='Best Cakes ?',
)
bob_content_api.save(best_cake_thread)

with new_revision(
session=self._session,
tm=transaction.manager,
content=tiramisu_page,
):
bob_content_api.update_content(
item=tiramisu_page,
new_content='<p>To cook a great Tiramisu, you need many ingredients.</p>', # nopep8
new_label='Tiramisu Recipe',
)
bob_content_api.save(tiramisu_page)
self._session.flush()
9 changes: 9 additions & 0 deletions tracim/fixtures/users_and_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,12 @@ def insert(self):
bob.password = 'foobarbaz'
self._session.add(bob)
g2.users.append(bob)

g1 = self._session.query(models.Group).\
filter(models.Group.group_name == 'users').one()
reader = models.User()
reader.display_name = 'John Reader'
reader.email = 'john-the-reader@reader.local'
reader.password = 'read'
self._session.add(reader)
g1.users.append(reader)
34 changes: 26 additions & 8 deletions tracim/lib/core/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from sqlalchemy.orm import aliased
from sqlalchemy.orm import joinedload
from sqlalchemy.orm.attributes import get_history
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.orm.session import Session
from sqlalchemy import desc
from sqlalchemy import distinct
Expand All @@ -25,6 +26,7 @@
from tracim.lib.utils.utils import cmp_to_key
from tracim.lib.core.notifications import NotifierFactory
from tracim.exceptions import SameValueError
from tracim.exceptions import ContentNotFound
from tracim.exceptions import WorkspacesDoNotMatch
from tracim.lib.utils.utils import current_date_for_filename
from tracim.models.revision_protection import new_revision
Expand All @@ -39,9 +41,9 @@
from tracim.models.data import UserRoleInWorkspace
from tracim.models.data import Workspace
from tracim.lib.utils.translation import fake_translator as _
from tracim.models.context_models import RevisionInContext
from tracim.models.context_models import ContentInContext


__author__ = 'damien'


Expand Down Expand Up @@ -102,6 +104,7 @@ class ContentApi(object):
ContentType.Comment,
ContentType.Thread,
ContentType.Page,
ContentType.PageLegacy,
ContentType.MarkdownPage,
)

Expand Down Expand Up @@ -158,10 +161,14 @@ def show(
self._show_deleted = previous_show_deleted
self._show_temporary = previous_show_temporary

def get_content_in_context(self, content: Content):
def get_content_in_context(self, content: Content) -> ContentInContext:
return ContentInContext(content, self._session, self._config)

def get_revision_join(self) -> sqlalchemy.sql.elements.BooleanClauseList:
def get_revision_in_context(self, revision: ContentRevisionRO) -> RevisionInContext: # nopep8
# TODO - G.M - 2018-06-173 - create revision in context object
return RevisionInContext(revision, self._session, self._config)

def _get_revision_join(self) -> sqlalchemy.sql.elements.BooleanClauseList:
"""
Return the Content/ContentRevision query join condition
:return: Content/ContentRevision query join condition
Expand All @@ -180,7 +187,7 @@ def get_canonical_query(self) -> Query:
:return: Content/ContentRevision Query
"""
return self._session.query(Content)\
.join(ContentRevisionRO, self.get_revision_join())
.join(ContentRevisionRO, self._get_revision_join())

@classmethod
def sort_tree_items(
Expand Down Expand Up @@ -418,6 +425,8 @@ def create_comment(self, workspace: Workspace=None, parent: Content=None, conten
item = Content()
item.owner = self._user
item.parent = parent
if parent and not workspace:
workspace = item.parent.workspace
item.workspace = workspace
item.type = ContentType.Comment
item.description = content
Expand Down Expand Up @@ -449,15 +458,24 @@ def get_one_from_revision(self, content_id: int, content_type: str, workspace: W

return content

def get_one(self, content_id: int, content_type: str, workspace: Workspace=None) -> Content:
def get_one(self, content_id: int, content_type: str, workspace: Workspace=None, parent: Content=None) -> Content:

if not content_id:
return None

if content_type==ContentType.Any:
return self._base_query(workspace).filter(Content.content_id==content_id).one()
base_request = self._base_query(workspace).filter(Content.content_id==content_id)

if content_type!=ContentType.Any:
base_request = base_request.filter(Content.type==content_type)

return self._base_query(workspace).filter(Content.content_id==content_id).filter(Content.type==content_type).one()
if parent:
base_request = base_request.filter(Content.parent_id==parent.content_id) # nopep8

try:
content = base_request.one()
except NoResultFound as exc:
raise ContentNotFound('Content "{}" not found in database'.format(content_id)) from exc # nopep8
return content

def get_one_revision(self, revision_id: int = None) -> ContentRevisionRO:
"""
Expand Down
Loading

0 comments on commit 67383c3

Please sign in to comment.