I would like to host my Flask-based web application on GAE.
Regular users are authenticated against Google using flask-oauthlib.
However, I would like to authenticate admin users using GAE's Users API, since it provides, among others, the users.is_current_user_admin().
However, it seems that I cannot protect the admin region from app.yaml, because the following configuration is not valid:
# app.yaml
[...]
handlers:
# For admin users
# THIS IS INVALID!
- url: /admin
login: admin
# For regular users
- url: /.*
script: main.app
Option 1: Create a separate flask app object in the same GAE application:
# app.yaml
[...]
# For admin users
- url: /admin
script: admin.app
login: admin
Is that a good practice? If not, why?
Option 2: Simply implement a function such as:
def is_admin():
return current_user.email in ["admin1#...", "admin2#...", "admin3#..."]
That is, do not rely on GAE's Users API.
Notes:
there are only a few admin users
I do not need fine-grained roles at the moment
Any thoughts (other solutions)?
I have such mixed logins in my non-Flask app, and my handlers look like this:
handlers:
- url: /admin
script: main.app
login: admin
- url: /.*
script: main.app
No need to have a separate app for admin. I don't know how Flask works, but I would expect this to work for you as well.
Related
I'm running a static site on GAE and using a custom domain (let's call it example.com) with SSL certificates enabled. I'd like to canonicalize URLs to https://www.example.com/. That means catching any requests to myproject.appspot.com, plain HTTP, and/or the naked domain, and redirecting to www over HTTPS.
I understand that it's not possible to put redirect logic in app.yaml, but ideally I'd like to keep the static file serving logic there, and only have app code for the redirect. (As opposed to doing the static serving in app code as well.)
Here's what I have so far:
Contents of the file app.yaml:
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /
static_files: www/index.html
upload: www/index.html
- url: /(.*)
static_files: www/\1
upload: www/(.*)
Contents of the file dispatch.yaml:
dispatch:
- url: "myproject.appspot.com/*"
module: canonicalizer
Contents of the file canonicalizer.yaml:
module: canonicalizer
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: canonicalizer.app
Contents of the file canonicalizer.py:
import webapp2
def get_redirect_uri(handler, *args, **kwargs):
return 'https://www.example.com/' + kwargs.get('path')
app = webapp2.WSGIApplication([
webapp2.Route('/<path:.*>',
webapp2.RedirectHandler,
defaults={'_uri': get_redirect_uri, '_code': 302}),
], debug=True)
As you can see, I've only attempted to implement redirecting myproject.appspot.com so far. I haven't been able to get it working; myproject.appspot.com still serves content rather than redirecting to the custom domain.
I saw a similar SO question and used it as a basis for my code above. I followed it fairly closely, so I'm not sure if it's outdated or missing details.
I'm not very familiar with webapp2. Also open to solutions in a different framework or even different programming language.
As sllopis said in their answer, an HTTP to HTTPS redirect can be implemented via a secure: always element.
The rest of what I wanted to do needed to be done in app code. The code in my answer was on the right track, but I had some confusion about how services work in GAE and about dispatch.yaml. Here's my final code:
<application root>/app.yaml
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /
static_files: www/index.html
upload: www/index.html
secure: always
redirect_http_response_code: 301
- url: /(.*)
static_files: www/\1
upload: www/(.*)
secure: always
redirect_http_response_code: 301
<application root>/dispatch.yaml
dispatch:
- url: "*.appspot.com/*"
service: canonicalizer
- url: "example.com/*"
service: canonicalizer
<application root>/canonicalizer/app.yaml
service: canonicalizer
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: canonicalizer.app
<application root>/canonicalizer/canonicalizer.py
import webapp2
def get_redirect_uri(handler, *args, **kwargs):
return 'https://www.justinforcentral.com/' + kwargs.get('path')
app = webapp2.WSGIApplication([
webapp2.Route('/<path:.*>',
webapp2.RedirectHandler,
defaults={'_uri': get_redirect_uri, '_code': 301}),
], debug=False)
This allows all the redirects to be done while still maintaining the ability to route the static site via static_files handlers.
As an aside, I also didn't realize that simply doing gcloud app deploy . from the application root only deploys the default service. To deploy this whole thing I had to run gcloud app deploy . dispatch.yaml canonicalizer.
Mapping Custom Domains to GAE
App Engine allows applications to be served via a custom domain, such as example.com, instead of the default appspot.com address. You can create a domain mapping for your App Engine app so that it uses a custom domain.
You will need to do the following:
Verify that you are the owner of your domain through Webmaster Central
Ensure that your domain has been verified.
Delegate the ownership of your domain to other users or service accounts, if needed.
Map your domain to your App Engine app.
Fill out the form with the listed resource records, including their type and canonical name (CNAME).
Add this information to the DNS configuration of your domain registrar.
Securing Custom Domains with SSL
By default, when you map your custom domain to your app, App Engine issues a managed certificate for SSL for HTTPS connections. Securing your custom domains with SSL offers more information about this.
Handling URL requests that do not use HTTPS
Any URL handler can use the secure setting, including script handlers and static file handlers. If secure is set to always, requests for a URL that match this handler that do not use HTTPS are automatically redirected to the HTTPS URL with the same path. Query parameters are preserved for the redirect.
Example in the app.yaml file:
handlers:
- url: /youraccount/.*
secure: always
script: auto
Conclusion
As a result, after following these steps, you should have a custom domain properly mapped to your App Engine site that uses SSL certificates to secure the custom domain.
Moreover, by adding the secure:always handler in your app.yaml file, any URL requests made against your App Engine site will be automatically redirected to the HTTPS URL with the same path.
Update - Redirect all URLs with Google App Engine
Credits to How to redirect all URLs with Google App Engine:
app.yaml
handlers:
- url: /.*
script: main.py
main.py
import webapp2
class MainPage(webapp2.RequestHandler):
def get(self):
self.redirect("https://example.com", True)
app = webapp2.WSGIApplication([
('/', MainPage),
], debug=True)
Then, you can adjust this code to your needs.
I have a lot of trouble finding how to map multiple domains to multiple services in the GAE. Here is the configuration :
One application is a Go API, deployed in GAE in the standard environment
The second application is an Angular application, also deployed in GAE in the standard environment but as another service.
Here are the app.yaml files :
Go application app.yaml
runtime: go
api_version: go1.9
handlers:
- url: /.*
script: _go_app
Angular application app.yaml
service: stage
runtime: python27
api_version: 1
threadsafe: true
skip_files:
- ^(?!dist) # Skip any files not in the dist folder
handlers:
# Routing for bundles to serve directly
- url: /((?:inline|main|polyfills|styles|vendor)\.[a-z0-9]+\.bundle\.js)
secure: always
redirect_http_response_code: 301
static_files: dist/\1
upload: dist/.*
# Routing for a prod styles.bundle.css to serve directly
- url: /(styles\.[a-z0-9]+\.bundle\.css)
secure: always
redirect_http_response_code: 301
static_files: dist/\1
upload: dist/.*
# Routing for typedoc, assets and favicon.ico to serve directly
- url: /((?:assets|docs)/.*|favicon\.ico)
secure: always
redirect_http_response_code: 301
static_files: dist/\1
upload: dist/.*
# Any other requests are routed to index.html for angular to handle so we don't need hash URLs
- url: /.*
secure: always
redirect_http_response_code: 301
static_files: dist/index.html
upload: dist/index\.html
http_headers:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: DENY
I have a domain and want to bind the Go API to api.domain.com and the Angular app to domain.com.
By going to App Engine > Settings > Custom Domains I managed to add the domain for my API and it is perfectly working.
But now, I cannot find a way to map domain.com to my Angular application. Going to the same settings does not gives me an option to map a different service to my domain.
Thanks for the help and have a nice day !
To map subdomains you can use a dispatch.yaml file. An example:
dispatch:
- url: "example.com/*"
service: default
- url: "api.example.com/*"
service: otherservice
And then run $ gcloud app deploy dispatch.yaml (it can be in any directory).
Once you have example.com added under App Engine > Settings > Custom Domains for the default service, you can add the subdomain api.example.com for the other service. Later you need to add the new subdomain DNS records to you domain registrar as pointed out in the console configuration.
You want to first map your naked domain.com to your app.
Then choose to add another domain and you'll have the option to add the api (or any other) domain.com subdomain to a specific service.
Note that you have a conflicting/overlapping handler pattern in the 2 services: - url: /.*, this won't work as GAE won't know to which service to direct such requests to, they'll all end up sent to the same service. You need to partition your requests URL namespaces in a non-overlapping manner and you'll likely need a dispatch.yaml file as well. See Mapping subdomain to a service in Google App Engine project for details.
I tried to use the documented way of restricting access to urls marked as static by way of login: required rules in the app.yaml file. My intention is to have access to script urls handled by the go programming language by xmlhttprequests, but the first step of authenticating the user before she can load the file dist/index.html fails.
Surprisingly for me the user is not prompted to login, instead receives the dist/index.html file and all other files it asks for from the static folder as if no restricting rule were present.
This is my app.yaml file:
application: helloworld
version: 1
runtime: go
api_version: go1
handlers:
- url: /
static_files: dist/index.html
upload: dist/index.html
secure: always
login: required - this is what fails as far as I'm concerned
- url: /(.*\.(txt|html|json|png|js|log|md|css|ico))
static_files: dist/\1
upload: dist/(.*\.(txt|html|json|png|js|log|md|css|ico))
secure: always
login: required
- url: /.*
script: _go_app
secure: always
login: required
The folder that I uploaded to appengine looks like this:
app.yaml
index.yaml
xhr_responses.go - this is the intended future non static AJAX part
dist/
index.html
loads of other stuff that is static
The 'login:' handler options in the .yaml config files rely on Google's authentication, which can be persisted using cookies and survive a browser restart.
To properly test the authentication you need to either use a fresh incognito browser session or go to one of the Google sites and ensure you're not logged in (explicitly log out from all Google accounts if needed) before testing.
Apparently I was signed in when trying stuff on the live google app engine, which I just forgot is the way it knows not to redirect access to a new login prompt.
I have a Google App Engine web application with roughly a hundred pages. I need to restrict access to all but 5 of these pages to people who are logged in.
I know that in the app.yaml file I can add:
- url: .*
script: main.app
login: required
But this will require login for all pages.
Is there a way to specify not required, something like this (I am a newbie to GAE and Python):
- url: /public/.*
script: main.app
login: not required
Or is the only way to add a level to every reference for the restricted pages (the many hundreds of references to the restricted pages). Something like:
- url: /restricted/.*
script: main.app
login: required
- url: /public/.*
script: main.app
If this is the only way, can both still point to the same main.app?
What is the best way to handle this?
Thanks for any assistance.
The app.yaml handler entries are processed sequentially so you could have in your app.yaml something like the following:
- url: /(publicpage1|publicpage2|publicpage3).*
script: main.app
- url: /.*
script: main.app
login: required
My configuration of backends.yaml
backends:
- name: mybackend
class: B1
instances: 1
options: dynamic
and app.yaml
handlers:
- url: http://mybackend.myapp.appspot.com
script: mybackend.py
login: admin
Running it localy on development server I get this error:
Unable to assign value 'http://mybackend.myapp.appspot.com' to attribute 'url':
Value 'http://mybackend.myapp.appspot.com' for url does not match expression '^(?!\^)/|.|((.).*(?!\$).$'
How can I test backend on development server?
I believe the url should be the relative url from your site. The script should be the python function that's run, not the filename. So your app.yaml should be.
handlers:
- url: /backend
script: mybackend.myfunction
login: admin
Your backend and frontend instances share the same handlers, there's no way to distinguish between them.