Kotti security

72 views
Skip to first unread message

Ivan Gudym

unread,
Nov 4, 2012, 9:14:54 AM11/4/12
to ko...@googlegroups.com
Hi
Can someone explain me Kotty security scheme?
I spent some time trying to understand Kotti security and I found it:
- simplified, confusing, unusual compared to common CMS, broken. Most confusing part is 'role:owner' - who is he?

Usual security scheme found in modern common CMS is similar to:

1. Anonymous, not authenticated user, can view pages marked as public
2. Role 'viewer' - authenticated user, can view pages, useful to protect part of site from anonymous access
3. Role 'author' - can add new documents, edit their own documents only
4. Role 'owner' is special - it can't be assigned to user, user have 'owner' role if he 'owns' the resource
4. Role 'editor' - can add documents and edit all documents including other's documents
5. Role 'admin' - can change system setting and manage users

I implemented such scheme and sent pull request. I didn't write tests nor change existing tests to reflect changes.
And I am not sure there is no some glitches somewhere in UI. First I want to see reactions from community.

Thanks.

PS. To respect owner attribute I had to create new authorization policy. Actually this is ACLAuthorizationPolicy
from pyramid with small tweaks. Look at diff in attach.
auth.diff

Daniel Nouri

unread,
Nov 4, 2012, 1:14:51 PM11/4/12
to ko...@googlegroups.com, Ivan Gudym
On 11/04/2012 03:14 PM, Ivan Gudym wrote:
> Hi
> Can someone explain me Kotty security scheme?

Kotti's security theme closely resembles that found in the Plone CMS
(and DCWorkflow).

When you're using workflows (which you are by default), Kotti uses the
mapping from roles to permissions defined in workflow.zcml. Without
workflows, it will use the `kotti.security.SITE_ACL` list, which it
puts on `root.__acl__`.

Both workflow and static permissions can be configured to your liking
from within your add-on, without the need to patch Kotti's source.
When using workflow, you can use the 'kotti.use_workflow' setting in
your INI file to point to a different workflow.zcml file, e.g.:

kotti.use_workflow = myapp:workflow.zcml

> I spent some time trying to understand Kotti security and I found it:
> - simplified, confusing, unusual compared to common CMS, broken. Most
> confusing part is 'role:owner' - who is he?

The Owner role ('role:owner'), like any other role, can be given to
anyone, be it global or local. It's special in that the event
listener in 'kotti.events.set_owner' will set the Owner role
automatically for newly created items. So if you create a new
Document, you're automatically it's Owner. (So your
ACLAuthorizationPolicy tweak should really be unnecessary as far as I
can tell.)

> Usual security scheme found in modern common CMS is similar to:
>
> 1. Anonymous, not authenticated user, can view pages marked as public
> 2. Role 'viewer' - authenticated user, can view pages, useful to protect
> part of site from anonymous access
> 3. Role 'author' - can add new documents, edit their own documents only
> 4. Role 'owner' is special - it can't be assigned to user, user have
> 'owner' role if he 'owns' the resource
> 4. Role 'editor' - can add documents and edit all documents including
> other's documents
> 5. Role 'admin' - can change system setting and manage users

So Kotti has this:

- Everyone: view
- Viewer: view
- Editor: view, add, edit, state_change
- Owner: view, add, edit, manage, state_change

Most of these are self-explanatory. 'state_change' means you can
change the workflow state (and thus change who can view etc., check
the included workflow.zcml file). 'manage' means you can share an
item, e.g. give another user the 'editor' role.

I'd be interested to hear what you think is wrong here specifically,
or what could be improved about the defaults. The lack of
documentation here is certainly a bit embarrassing, and there is a
chance that we can make the default permissions more useful.

Note that in Kotti, roles are often given in a local context only,
through the "Share" tab. E.g. I get the Editor role in "/news" only,
or I have the Viewer role only in my own department's folder.

Mappings of roles to permissions change depending on the workflow
state. This might be a little confusing at first, but it's in fact
much more powerful than what most CMS can do.

> I implemented such scheme and sent pull request. I didn't write tests
> nor change existing tests to reflect changes.
> And I am not sure there is no some glitches somewhere in UI. First I
> want to see reactions from community.
>
> Thanks.
>
> PS. To respect owner attribute I had to create new authorization policy.
> Actually this is ACLAuthorizationPolicy
> from pyramid with small tweaks. Look at diff in attach.


Daniel

Ivan Gudym

unread,
Nov 6, 2012, 5:31:51 AM11/6/12
to ko...@googlegroups.com, Ivan Gudym
Hello Daniel, thank you for replay.

Неділя, 4 листопада 2012 р. 20:14:55 UTC+2 користувач Daniel Nouri написав:


    >On 11/04/2012 03:14 PM, Ivan Gudym wrote:
    >> Hi
    >> Can someone explain me Kotty security scheme?

    >Kotti's security theme closely resembles that found in the Plone CMS
    >(and DCWorkflow).

    >When you're using workflows (which you are by default), Kotti uses the
    >mapping from roles to permissions defined in workflow.zcml.  Without
    >workflows, it will use the `kotti.security.SITE_ACL` list, which it
    >puts on `root.__acl__`.

    >Both workflow and static permissions can be configured to your liking
    >from within your add-on, without the need to patch Kotti's source.
    >When using workflow, you can use the 'kotti.use_workflow' setting in
    >your INI file to point to a different workflow.zcml file, e.g.:

    >   kotti.use_workflow = myapp:workflow.zcml

I had not time yet to learn workflows in detail but in looks good and
promising. But now I want to understand simple basic authorization.


    >> I spent some time trying to understand Kotti security and I found it:
    >> - simplified, confusing, unusual compared to common CMS, broken. Most
    >> confusing part is 'role:owner' - who is he?

    >The Owner role ('role:owner'), like any other role, can be given to
    >anyone, be it global or local.  It's special in that the event
    >listener in 'kotti.events.set_owner' will set the Owner role
    >automatically for newly created items.  So if you create a new
    >Document, you're automatically it's Owner.  (So your
    >ACLAuthorizationPolicy tweak should really be unnecessary as far as I
    >can tell.)

Kotti authorization scheme in relation to ownership differ from others in
two features: global owners and possibility to have several o none owners.
I personally don't see any benefits of this feature but see some drawback,
but more on this later.


    >> Usual security scheme found in modern common CMS is similar to:
    >>
    >> 1. Anonymous, not authenticated user, can view pages marked as public
    >> 2. Role 'viewer' - authenticated user, can view pages, useful to protect
    >> part of site from anonymous access
    >> 3. Role 'author' - can add new documents, edit their own documents only
    >> 4. Role 'owner' is special - it can't be assigned to user, user have
    >> 'owner' role if he 'owns' the resource
    >> 4. Role 'editor' - can add documents and edit all documents including
    >> other's documents
    >> 5. Role 'admin' - can change system setting and manage users

    >So Kotti has this:

    >   - Everyone: view
    >   - Viewer: view
    >   - Editor: view, add, edit, state_change
    >   - Owner: view, add, edit, manage, state_change

    >Most of these are self-explanatory.  'state_change' means you can
    >change the workflow state (and thus change who can view etc., check
    >the included workflow.zcml file).  'manage' means you can share an
    >item, e.g. give another user the 'editor' role.

    >I'd be interested to hear what you think is wrong here specifically,
    >or what could be improved about the defaults.  The lack of
    >documentation here is certainly a bit embarrassing, and there is a
    >chance that we can make the default permissions more useful.

    >Note that in Kotti, roles are often given in a local context only,T

    >through the "Share" tab.  E.g. I get the Editor role in "/news" only,
    >or I have the Viewer role only in my own department's folder.
    >
Thank you for explanation. Local roles is good and I like it. I experimented
a little and found all works well from Kotti UI. Then I started my project
and forced to look under the hood. Several things was a bit confusing:
at first sight ownership described by item's 'owner' field. But in reality it
never used by authorization subsystem, it never changed and should be treated
as 'creator'. Effective ownership is defined by 'local_groups' table. I can
live with this if it were documented somewhere. But thats not all.

Let me briefly explain my case. I plan to create site with normal public
pages and private area for registered users. There will be profile info,
some services and various client's staff. I plan to use Content descendant
for profile page and Node descendants for client items.

User registration function emits UserRegistered event, my addon listen on
it, receive notification and has chance to create custom user Content page.
All looks good and I write the code:

class User(Content):
    id = Column(Integer, ForeignKey('contents.id'), primary_key=True)

    type_info = Content.type_info.copy(
        name=u'User',
        title=_(u'User Profile'),
    )

    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        self.in_navigation = False
        self.parent = Profile.get()
        self._acl = [
            (Allow, 'role:owner', ALL_PERMISSIONS),
            DENY_ALL,
        ]


def create_user_profile(event):
    principal = event.object
    user = User(name=principal.name, owner=principal.name, title=principal.title)
    DBSession.add(user)

After test run I see owner can't access his page. I consult 'local_groups' table
and don't see corresponding row for owner. So event subsystem don't fulfill their
promises. After flying around events I land to kotti.events.set_owner:

def set_owner(event):
    obj, request = event.object, event.request
    if request is not None and isinstance(obj, Node) and obj.owner is None:
        userid = authenticated_userid(request)
        if userid is not None:
            userid = unicode(userid)
            # Set owner metadata:
            obj.owner = userid
            # Add owner role for userid if it's not inherited already:
            if u'role:owner' not in list_groups(userid, obj):
                groups = list_groups_raw(userid, obj) | set([u'role:owner'])
                set_groups(userid, obj, groups)

Oops, third line contains two bugs: implementation - Node has no owner field, and
second more important - design bug. Owner field of newly created object is
ignored, or even worse if it set no local_groups processing performed at all!
Next line somewhat explains that weird behavior - it takes into account only
authenticated user. But in my case there is no authentication user, and even worse:
I can imagine scenario, when some function will create page for user under the
another user account. Maybe this is corner case, but how many such cases can arise
in future especially in addons development.


    >Mappings of roles to permissions change depending on the workflow
    >state.  This might be a little confusing at first, but it's in fact
    >much more powerful than what most CMS can do.

    >> I implemented such scheme and sent pull request. I didn't write tests
    >> nor change existing tests to reflect changes.
    >> And I am not sure there is no some glitches somewhere in UI. First I
    >> want to see reactions from community.
    >>
    >> Thanks.
    >>
    >> PS. To respect owner attribute I had to create new authorization policy.
    >> Actually this is ACLAuthorizationPolicy
    >> from pyramid with small tweaks. Look at diff in attach.

OK, let me describe in more details my proposition to tweak Kotti security.
1. Local groups stays as is.
2. Owner processing decouples from local groups and bases on 'owner' field
only.
3. To accomplish 2) introduced new authorization policy.

Authorization policy is actually standard one with 15 lines of additional code.
Algorithm is as follows:

1. Determine node owner. If owner has __owner__ field - use it, if not - traverse
over parents of node until found node (Content or descendant) with __owner__
field. So nodes without owner information considered owned by owner of it parent
node.
2. While processing acl if principal is 'role:owner' replace it by node owner
name and continue to run normal algorithm.

For example, node has acl (Allow, 'role:owner', ALL_PERMISSIONS) and
owner is 'jone', system checks user 'jone' rights for this node. So according to 2)
'role:owner' replaced by 'jone' and acl became (Allow, 'jone', ALL_PERMISSIONS)
then legacy algorithm continue to run as usual and grant access to 'jone' for
that node.
If user 'bob' tries to authorize he will be checked again over
(Allow, 'jone', ALL_PERMISSIONS) and access will not be granted.

Very simple isn't it?

The only drawback I see over current implementation is limitation to have only
one owner per resource.
But is it real drawback? First - simple things should be simple. Lets overview some
scenarios.
1. Consider you need all documents of particular owner. In my scheme you simply filter
documents by owner field and you are done. Compare current: you have to construct join
query documents with local_groups and filter by group_name='role:owner'.
2. Consider you want to add owner information to Navigate page. In my case you simply
add 'owner' field to template and you are done. Compare to current scheme: you have to
construct nontrivial join query using node_id and principal_name as join keys and
filter by group_name='role:owner', then you have to made decision how to place several
owners on page, write loop over owners etc. Programmer productivity and system
performance loss is obvious.
3. Consider you need to delete all documents owned by some user, on user delete or
similar scenario. In my case you filter by owner and delete. Compare with current:
you again construct join documents with local groups, filter by 'role:owner',
and wait, you have to check whether node owned by another owner, you shouldn't delete
such node. So you make join query per every document. You and up with complicated
query with sub-queries or will issue query per document. Again, programmer productivity
and system performance loss is obvious.
 

Ivan Gudym

unread,
Nov 6, 2012, 6:27:23 AM11/6/12
to ko...@googlegroups.com, Ivan Gudym
I understand changes in the system core may interfere with any system part, so my proposition is not ready for the master.
I only ask to seriously review it and maybe create separate branch and after evaluation, testing, polishing possibly merge to master. Or throw away.

Daniel Nouri

unread,
Nov 6, 2012, 12:27:17 PM11/6/12
to ko...@googlegroups.com, Ivan Gudym
On 11/06/2012 11:31 AM, Ivan Gudym wrote:
> Hello Daniel, thank you for replay.
>
> [...]
You're right. The distinction between 'owner' and 'role:owner' is
blurry, but the 'owner' attribute was in fact never intended to be
used by the authorization system; it's merely a metadata attribute:
the person responsible for this piece of content.

The fact that having or not an 'owner' attribute changes the way
'set_owner' works adds to the confusion. But in some situations, you
need to be able to signal to the event handler: "I know what I'm
doing, don't mess with the owner attribute or owner role." This is
necessary in cases just like yours, where the person that's logged in
doesn't correspond to what should be the owner of the new item.

So 'set_owner' is only useful for when you have a logged in user
that's navigating around in the UI. The fact that you have to know
about it when dealing with owners in your own code is unfortunate, and
that's what I'd like to see improved by maybe a helper function for
setting owners programmatically, and some docs.

> OK, let me describe in more details my proposition to tweak Kotti security.
> 1. Local groups stays as is.
> 2. Owner processing decouples from local groups and bases on 'owner' field
> only.
> 3. To accomplish 2) introduced new authorization policy.
>
> Authorization policy is actually standard one with 15 lines of
> additional code.

That's cool, but the patch still contains all the code from the
original, which is too much.

> Algorithm is as follows:
>
> 1. Determine node owner. If owner has __owner__ field - use it, if not -
> traverse
> over parents of node until found node (Content or descendant) with
> __owner__
> field. So nodes without owner information considered owned by owner of
> it parent
> node.
> 2. While processing acl if principal is 'role:owner' replace it by node
> owner
> name and continue to run normal algorithm.
>
> For example, node has acl (Allow, 'role:owner', ALL_PERMISSIONS) and
> owner is 'jone', system checks user 'jone' rights for this node. So
> according to 2)
> 'role:owner' replaced by 'jone' and acl became (Allow, 'jone',
> ALL_PERMISSIONS)
> then legacy algorithm continue to run as usual and grant access to
> 'jone' for
> that node.
> If user 'bob' tries to authorize he will be checked again over
> (Allow, 'jone', ALL_PERMISSIONS) and access will not be granted.
>
> Very simple isn't it?

Yes, it's easy enough, but I fail to see the overall benefit of
this... If I were to integrate the 'content.owner' attribute more
tightly with security, I would maybe make 'owner' a property and have
that add and remove entries in local_groups. Cause then I could stick
with the standard ACLAuthorizationPolicy model, have all my local
roles in a single table, and maybe have a system that fits my
brain better.

> The only drawback I see over current implementation is limitation to
> have only one owner per resource.
> But is it real drawback? First - simple things should be
> simple. Lets overview some scenarios.
>
> 1. Consider you need all documents of particular owner. In my scheme
> you simply filter documents by owner field and you are done. Compare
> current: you have to construct join query documents with
> local_groups and filter by group_name='role:owner'.

> 2. Consider you want to add owner information to Navigate page. In
> my case you simply add 'owner' field to template and you are
> done. Compare to current scheme: you have to construct nontrivial
> join query using node_id and principal_name as join keys and filter
> by group_name='role:owner', then you have to made decision how to
> place several owners on page, write loop over owners etc. Programmer
> productivity and system performance loss is obvious.

> 3. Consider you need to delete all documents owned by some user, on
> user delete or similar scenario. In my case you filter by owner and
> delete. Compare with current: you again construct join documents
> with local groups, filter by 'role:owner', and wait, you have to
> check whether node owned by another owner, you shouldn't delete such
> node. So you make join query per every document. You and up with
> complicated query with sub-queries or will issue query per
> document. Again, programmer productivity and system performance loss
> is obvious.

I believe that for these cases you should use 'content.owner' and
ignore that 'role:owner' exists. Shoud you feel you need to remove
'role:owner' from the Share tab to be able to encorce a 1:1 relation
between 'role:owner' and 'owner', then that should be possible by
setting kotti.security.SHARING_ROLES.


Daniel
Reply all
Reply to author
Forward
0 new messages