-
Notifications
You must be signed in to change notification settings - Fork 887
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
object-level security and tons of other small improvements to the wiki2 tutorial #2334
object-level security and tons of other small improvements to the wiki2 tutorial #2334
Conversation
|
||
|
||
def get_sessionmaker(engine): | ||
dbmaker = sessionmaker() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for nitpicking but I'm always confused by such aliases. Why not call it session_maker
instead of dbmaker
? Ditto in other places where dbmaker
is used.
The changes look good, I think @piotr-dobrogost has a point here, I left the naming initially but it was also a bit confusing for me. I would merge that back into the scaffold 👍 |
In my personal projects I define |
I'd go with "session_factory". |
please no camel casing. |
Imagine how upset @bertjwregeer is gonna be when he realizes Anyway I will change it to |
Twisted anyone? ;) Anyways pep8 has clear suggestions on function and variable naming :-) |
@mmerickel There are warts in almost all code bases. |
@bertjwregeer I actually think it's appropriate to camel case factories like that. :-) |
4c85db8
to
f4e1ca8
Compare
4d3d9b0
to
9b83981
Compare
I've revamped the wording in the basiclayout chapter. Any comments would be greatly appreciated before I move on to the later chapters. I gotta say the fact that I'm only doing small tweaks here and there to the wording is a credit to everyone's amazing work up to this point. |
@stevepiercy One quick trick.. I discovered the |
@mmerickel I was joking :-P For factories/classes I don't mind camel casing, but for variables names within a function I am not a big fan. |
@mmerickel nice find on .. literalinclude:: example.py
:diff: example.py.orig I wish I had known when I was working on tutorials that feature had been added. |
this is already assumed inside of installation where commands are run relative to setup.py
I think breaking it into authentication and authorization is the better approach: who you are vs. what you can do while authenticated on specific objects. Authorization is ripe for discussing various methods and best practices, such as hashing passwords via a library. This RBAC diagram from WikiPedia is helpful to visualize ACLs, although it is more detailed and complex than simple ACLs and terminology is slightly different. |
8054b50
to
00b2c69
Compare
Okay I added the codebase for simple authentication that actually does inline checks within the views. This should setup nicely to remove the inline checks in the authorization tutorial, replaced by factories. I'll start ripping the authorization text apart soon. |
I've added a new authentication chapter. Next up is to work on updating the authorization chapter and codebase with context factories... This is getting pretty close to complete. |
@mmerickel travis-ci complains about a missing source file: Warning, treated as error: I see a file at |
@stevepiercy I haven't worked on the authorization tutorial yet. There are errors there, yes. To build just turn them into warnings. |
@pauleveritt Hey, I'm starting to update the authorization section of the tutorial now and would like your thoughts on the direction to go. The wiki2 tutorial needs 3 ACLs.
My natural breakdown is 2 factories/contexts. One that loads pages,
Here's my current simplest/best for the tutorial (all of this would go into config.add_route('view_page', '/{pagename}', factory=page_factory)
config.add_route('add_page', '/add_page/{pagename}', factory=new_page_factory)
config.add_route('edit_page', '/{pagename}/edit_page', factory=page_factory)
def page_factory(request):
pagename = request.matchdict['pagename']
page = request.dbsession.query(Page).filter_by(name=pagename).first()
if page is None:
raise HTTPNotFound
return PageResource(page)
def new_page_factory(request):
return NewPageResource()
class PageResource(object):
def __init__(self, page):
self.page = page
def __acl__(self):
return [
(Allow, Everyone, 'view'),
(Allow, str(self.page.creator_id), 'edit'),
(Allow, 'role:editor', 'edit'),
]
class NewPageResource(object):
def __acl__(self):
return [
(Allow, 'role:basic', 'create'),
(Allow, 'role:editor', 'create'),
] |
I've added the first cut at the authorization code using the comments above. Waiting for a reply before working on the text though. |
Here is a gist that has what I did for O'Reilly. I can send the entire chapter with RST writeup if you'd like, but I doubt you want all that. :) https://gist.github.com/pauleveritt/2a1d9040a585ee1b4acc It includes class- and instance-level security, which might be more than what you want to show. It won't match the scenario you describe, as you want a different ACL for the collection (adding) vs. the items. I like your example more. I guess the biggest difference is the one we previously discussed. You create separate classes and objects apart from the models, in routes, and put Pyramid-y stuff on them. I conflated things onto models and put the ACL there. With your approach, you wouldn't be able to do has_permission filtering in a view, for example to filter a list of wiki pages you're allowed to see. (I did that in a tutorial step.) With my approach, since the Pyramid stuff like acl is on the SQLAlchemy model, a query returns objects that can be permission filtered. I think it's the kind of thing where we're just solving different problems. I think you are putting this in routes because the only Pyramid machinery you want is on guarding a URL. I was trying to do more. |
That's a clever little hack with I think where I'm at right now is that I could add a note that the separation is not required but it is done here to show the parts that Pyramid actually cares about separately from the model itself. At the end of the day you can return anything you want from the factory, including a model instance (or a model class?? I guess is where you're going with that). I will say that using a class as context feels like an anti-pattern to me in my constant quest to get people away from using global state. A class is just begging for global state if you need anything other than a static ACL. In the current code I only show
Definitely this is an issue but not in the current wiki2 example. We could add an administrative-type page that showed all wiki pages and an edit button next to ones you could edit if we wanted to exercise that feature. Alternatively the context could have just been updated with a method like Anyway I've always come out against putting pyramid-y stuff on models but if it's simpler for people that aren't me then I'm okay with that. I'd like to present the most useful pattern I can for people but I don't want to make decisions that the community suffers for either. Which is why I'm asking someone with your tutorial experience for some guidance. :-) Is there a middle ground here where I add the ACL to the |
All good points. I suspect the goal of a tutorial should be to limit the number of Cosmic Ideas, and instead keep as many things as possible familiar to the reader. I don't know if our audience would feel better with wrappers that act in an MVVM way, or a model object that gets some of the web framework injected into it. I don't have good evidence on this. I think you are in favor of introducing the wrapper indirection. Since you're the one doing the work, you win. :) |
Other notes:
|
I feel like I've seen many more examples of coupling the acls to the models than examples of keeping them separate. The issue with using The only real issue I have with adding the ACLs to the models themselves is the need for the |
All of my projects are traversal, and I create proxy objects that then build up the traversal using It's these proxy objects that have an That proxy object is used as the context in all of my views. It owns the DB object, and is passed to all of my |
Alright folks... first draft of revamp of revamp is complete. I'm gonna merge this goodness into the official alchemy-scaffold branch so that we can get some good review. |
…te-tweaks object-level security and tons of other small improvements to the wiki2 tutorial
1 subtle change: reversed the order of arguments to
get_tm_session
.This PR is a WIP, haven't updated the tests or touched the tutorial. Wanted to get thoughts on the models package first.
ping @bertjwregeer, @ergo, @pauleveritt, @stevepiercy