If you are ok with decorating the entire service, I would recommend
covering it by another WSGI middleware application. A middleware
application is a WSGI application that wraps another WSGI application.
webapp is a type of WSGI application and can be wrapped as normal.
Here is an example WSGI middleware filter you might implement:
def require_login(app, admin_only=False):
# This is the function that will wrap your webapp application.
def require_login_app(environ, start_response):
user = users.get_current_user()
if not user or (admin_only and not users.is_current_user_admin()):
# Sends HTTP error response without ever calling webapp.
start_response('401 Unauthorized', [])
return ['You can put error text here']
else:
# This will call webapp
return app(environ, start_response)
Then you can do:
service_mappings = service_handlers.service_mapping(
[('/hello', HelloService),
])
application = webapp.WSGIApplication(service_mappings)
application = require_login(application)
def main():
util.run_wsgi_app(application)
This version, since it is an RPC interface, does not attempt to send
back any redirections.
If you need to block individual methods, for now you might choose to
provide require_login with a list of methods that require auth.
There will be more complete solutions built in to ProtoRPC in the future.
>
> 2) How to read/write http request/response headers ?
> Currently it seems not possible ?
> And so, is there a solution to use session/cookie management ?
>
In the future, I'm hoping to make it easy to do HTTP header handling
outside of the service. Typically services should not rely on HTTP
headers since they are meant to be independent of the underlying
protocol.
However, since we live in the real world, you can use
remote.Service.request_state :) request_state is an instance of
remote.HttpRequestState, which includes a wsgiref.headers.Headers
instance.
>> 2) How to read/write http request/response headers ?In the future, I'm hoping to make it easy to do HTTP header handling
> Currently it seems not possible ?
> And so, is there a solution to use session/cookie management ?
>
outside of the service. Typically services should not rely on HTTP
headers since they are meant to be independent of the underlying
protocol.
However, since we live in the real world, you can use
remote.Service.request_state :) request_state is an instance of
remote.HttpRequestState, which includes a wsgiref.headers.Headers
instance.
That's true, the service can receive incoming headers but cannot set
outgoing headers. May I ask what kinds of headers you are looking to
set? I'm going to suggest using WSGI middleware to manage headers to
the client, but I'd like to see what your use case is to see if there
is a better way or a need to change the implementation.
> Also what is the purpose of initialize_request_state() method? Is it
> intended to be used for preprocessing incoming request state (e.g. request
> headers) for more convenient reuse by service methods?
>
That is it's purpose. I think most header processing should happen
in a WSGI middleware layer but right now there is not a lot of
knowledge about how to do that, and for many developers, that kind of
solution might complicate their code too much.
def manage_session(app):
def session_app(headers, start_response):
global session
session = parse_cookie_to_session(headers.get_all('cookie'))
def session_start_response(status, response_headers):
response_headers.append(('set-cookie', session.to_cookie()))
return start_response(status, response_headers)
return app(headers, session_start_response)
return session_app
Currently this is a little bit convoluted, however, there will be
better support for this sort of thing in the future.
application = webapp.WSGIApplication(
service_handlers.service_mapping(...))
application = manage_session(application)
http://docs.python.org/library/wsgiref.html#module-wsgiref.headers
It's actually intended for WSGI responses, an the request headers is
meant to be request headers. It needs to be a list instead of a
dictionary because dictionaries do not support multiple values. It's
possible for an HTTP request to have multiple values for the same
header name.
It might make sense to change it to something else, but I'm afraid
using a dictionary may not be adequate.
Is there a specific status code or problem you're shooting for?
You can raise remote.ApplicationError and pass in a special error
message and error name:
class MyService(remote.Service):
@remote.method
def raise_application_error(self, request):
raise remote.ApplicationError('Some Error Message!', 'ERROR_NAME')
This will set the status to 400, and return an RpcStatus message,
which will contain our error message and error name passed in above:
{
"state": "APPLICATION_ERROR",
"error_message": "Some Error Message!",
"error_name": "ERROR_NAME"
Think of http more as a dumb channel. As a client, the http channel
needs to be able to tell me if the server encountered an error or not.
The only errors the channel should be concerned with are errors where
the server couldn't be reached (network error), where the server
replied unexpectedly (server error), or where the server returned
*some* application specific error (application error). If we build
services to depend more on ApplicationErrors, we shouldn't need to
worry about status codes. Authorization problem? Application error.
User not found? ApplicationError. If we do this, we can implement our
Rpc over any number of transports given that they can transmit an
error state.
I think error raising could use some improvement (and documentation).
Thank you, it might help someone else in a similar situation.
> I am using the standard protobuf C++ libraries. I essentially created and
> compiled a .proto file that directly mirrors the protorpc messages that will
> be sent back and forth. All that communication is working and tested both
> requests and responses.
>
> As a note I am currently just using the Google App Engine sandbox on ubuntu
> and have not yet taken my app live. What I meant by logged in: I used the
> simple Users API example to present the web browser (firefox) with a login,
> and then upon success, the user is redirected back to my sight. Being a
> newbie I just took it as magic that I could now open other web browsers and
> access my website and have it know that I was successfully logged in. I
> naively thought this would also apply to my QT application when sending its
> http requests with my protobuf payload because I successfully logged in on
> the web browser I hoped to now make it passed the authentication filter on
> the webserver. I was thinking I could just spawn a web browser from Qt,
> allow the client to login, and then all my Qt <-> webserver protorpc based
> messages would begin succeeding.
>
> After much poking around on the web and based on your reply I think I
> understand why the above does not work. So it sounds like going forward I
> have three options.
>
> 1. Allow the user to login via a webbrowser. This creates a cookie that
> grants access when included in http requests. Communicate this information
> to my Qt Client.
> a. Since the cookie is stored locally on the disk I could go grab it
> and parse it from their?
> b. I could present an interface to my webserver that allows the
> retrieval of cookies for a certain user, this doesn't seem to solve the
> problem though since anyone could then request that cookie for any user and
> then start making http requests on the users behalf.
>
> 2. App Engine client login. I assume this means Qt acts like a web browser
> and directly asks for the users credentials. Then it takes responsibility
> for authenticating the user and getting the cookie. I assume this is not
> recommended because my QT client now has direct access to the users username
> and password.
>
I feel like you almost have a good idea trying to spawn a browser,
but depending on how you are using that application it could well be
very brittle. But it may also be a security issue. Many users won't
hesitate to enter their user name and password but you are then adding
your application to the security chain between users and (often) their
gmail accounts. It could unknowingly by you be used as a vector for
hackers to access other peoples email. This is something a very much
do not recommend you do.
> 3. Oauth1. I have read a lot of articles about this and I think there is a
> library called kQOauth that supposedly does most of the dirty work for Qt.
> It seemed like the few threads I read indicated that folks were having
> trouble getting it to work with the google authentication system. Assuming I
> could get this to work I assume I would still present the user with a web
> browser and they would then grant me access on their behalf which would give
> me the cookie I need to make my protoRPC based html requests?
>
I do not know with certainty but I am worried that kQOauth will not
work with App Engines Oauth implementation because it is a somewhat
non-standard implementation. So it's not surprising people have had
problems with it, at least for the time being.
When you are using oauth you are not using any cookie based
authentication. You would need to either:
1) Always have users authenticate when they use your application
2) Implement storage for the applications authentication token so
that users do not need to authenticate every single time.
Unfortunately web standards are heavily evolved to favor the use of
browsers. So, adding this kind of support to a Qt based application
is definitely extra credit. It is my intent that ProtoRPC will
integrate better in the future with alternate authentication models,
but it's obviously a bit early on and we have not managed to sort out
all those issues yet. I therefore really appreciate the difficulty
you are having with this problem, it's not a trivial one. Poking
around on the internet, you will also find that everyone has trouble
with authentication.
>
> I really am trying not to over complicate things. I am up for any
> suggestions. In my basic use case the data being uploaded through protorpc
> is not really sensitive in itself and if it were intercepted it would not be
> the end of the world. However, only certain users that I have pre-approved
> (stored their email in the data store) will be allowed to ultimately upload.
> Lets say the payload was pictures, I wouldn't want some random user
> uploading inappropriate pictures on another users behalf.
>
> I am even up to do a more custom authentication system where I store user
> names and passwords that just work for my site. I would be willing to make
> the communication https so I could just send the user name and password with
> each protorpc communication. BTW does protoRPC work over https?
It should not have a problem working over https. You just need to
set those endpoints as being https and have your client support HTTPs,
which I'm certain Qt does.
Since it sounds like your software is in a relatively early phase of
development, I might recommend writing your own customized
authentication. Again, this authentication system should be
implemented at the WSGI middleware layer, and it can use dual
authentication (check to see if a user is logged in via app engine
authentication or the custom auth system). I have a feeling when you
get people working with your client it will become clearer what kinds
of additional support they will need. Later you can provide a better
authentication system.
>
> Sorry for the long email and I appreciate your response. I really like
> protoRPC and want to use it at all costs, but just need to get over this
> last hurdle somehow.
>
I really appreciate that you are interested in using ProtoRPC and I
am very interested in having developers use it. I do however,
sincerely hope that you do not do this at all costs :)
>
> Thanks in advance,
> Steve
>
>
> On Thu, Sep 8, 2011 at 3:14 PM, Rafe Kaplan <ra...@google.com> wrote:
>>
>> First, may I move this back to the group? It is likely to be useful
>> to others.
>>
>> I like to hear that you are looking to use ProtoRPC with Qt. Before
>> I go on, I'd like to ask, are you going to use the standard protobuf
>> library with it? If it's helpful (and you are not going to have any
>> issue with the licensing) you might want to consider it.
>>
>> If you want the Qt program to use the same session system as the
>> web-browser, you will need to implement cookies on that client. That
>> means parsing and sending the appropriate cookie headers.
>>
>> However, you mentioned something else, that your web browser is
>> logged in. Do you mean that you are logged in via the standard App
>> Engine login mechanism? If that's the case, there is substantially
>> more work that needs to be done in the Qt client. It will either need
>> to do an App Engine "Client Login" (which is not a good idea) or you
>> need to change your application to use oauth. What's worse, is that
>> right now only oauth1 is supported, and in a non-standard way (boo -
>> it will not be thus forever but it is now).
>>
>> So, what parts am I understanding and what parts am I missing?
>>
>> On Wed, Sep 7, 2011 at 10:28 PM, sbonham <sbo...@gmail.com> wrote:
>> > Hello Rafe,
>> >
>> > I have tried implementing access control both using app.yaml as well
>> > as the middle wear example you suggested above. Both work great when
>> > my protorpc communication is initated from my browser using JSON.
>> > Without knowing all the details I assume a cookie/session/token or
>> > some combination is saved by the web browser so that any time I open a
>> > new browser the fact that I succesfully logged in is preserved and my
>> > protorpc communication succeeds. The problem is that I would like to
>> > invoke protorpc from a desktop application built with Qt. I have
>> > gotten all the communication to work with natively compiled C++
>> > protocol buffers. When I add the access control obviously the requests
>> > start failing. However, I obviously don't want just any old C++ app
>> > running to be able to post things to my eventual web server. What is
>> > the best way for my Qt based http request to succeed, i.e. to present
>> > a request to the web server that the user is succesfully
>> > authenticated. Is this even possible? Please let me know if my
>> > question is unclear.
>> >
>> > Thanks in advance,
>> > Steve
>> >
>> > On May 25, 2:43 pm, Rafe Kaplan <ra...@google.com> wrote:
>> >> Ah, that's probably the best way. I was assuming you didn't want to
>> >> use the app.yaml for access control.
>> >>
>> >>
>> >>
>> >> On Wed, May 25, 2011 at 2:26 PM, Sylvain <sylvain.viv...@gmail.com>
>> >> >> instance.- Hide quoted text -
>> >>
>> >> - Show quoted text -
>> >
>
>