You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by sa...@apache.org on 2016/08/19 23:13:08 UTC
incubator-airflow git commit: [AIRFLOW-444] Add Google authentication
backend
Repository: incubator-airflow
Updated Branches:
refs/heads/master 7a5e1d832 -> df848a556
[AIRFLOW-444] Add Google authentication backend
Add Google authentication backend.
Add Google authentication information to security
docs.
Dear Airflow Maintainers,
Please accept this PR that addresses the following
issues:
-
https://issues.apache.org/jira/browse/AIRFLOW-444
Testing Done:
- Tested Google authentication backend locally
with no issues
This is mostly an adaptation of the GHE
authentication backend.
Closes #1747 from ananya77041/google_auth_backend
Project: http://git-wip-us.apache.org/repos/asf/incubator-airflow/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-airflow/commit/df848a55
Tree: http://git-wip-us.apache.org/repos/asf/incubator-airflow/tree/df848a55
Diff: http://git-wip-us.apache.org/repos/asf/incubator-airflow/diff/df848a55
Branch: refs/heads/master
Commit: df848a5564ed4b2d3281df79f77c51559e95c1a5
Parents: 7a5e1d8
Author: Ananya Mishra <am...@cornell.edu>
Authored: Fri Aug 19 16:12:58 2016 -0700
Committer: Siddharth Anand <si...@yahoo.com>
Committed: Fri Aug 19 16:12:58 2016 -0700
----------------------------------------------------------------------
airflow/contrib/auth/backends/google_auth.py | 191 ++++++++++++++++++++++
docs/security.rst | 33 ++++
2 files changed, 224 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-airflow/blob/df848a55/airflow/contrib/auth/backends/google_auth.py
----------------------------------------------------------------------
diff --git a/airflow/contrib/auth/backends/google_auth.py b/airflow/contrib/auth/backends/google_auth.py
new file mode 100644
index 0000000..8aed03a
--- /dev/null
+++ b/airflow/contrib/auth/backends/google_auth.py
@@ -0,0 +1,191 @@
+# Copyright 2016 Ananya Mishra (am747@cornell.edu)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import logging
+
+import flask_login
+
+# Need to expose these downstream
+# pylint: disable=unused-import
+from flask_login import (current_user,
+ logout_user,
+ login_required,
+ login_user)
+# pylint: enable=unused-import
+
+from flask import url_for, redirect, request
+
+from flask_oauthlib.client import OAuth
+
+from airflow import models, configuration, settings
+from airflow.configuration import AirflowConfigException
+
+_log = logging.getLogger(__name__)
+
+
+def get_config_param(param):
+ return str(configuration.get('google', param))
+
+
+class GoogleUser(models.User):
+
+ def __init__(self, user):
+ self.user = user
+
+ def is_active(self):
+ '''Required by flask_login'''
+ return True
+
+ def is_authenticated(self):
+ '''Required by flask_login'''
+ return True
+
+ def is_anonymous(self):
+ '''Required by flask_login'''
+ return False
+
+ def get_id(self):
+ '''Returns the current user id as required by flask_login'''
+ return self.user.get_id()
+
+ def data_profiling(self):
+ '''Provides access to data profiling tools'''
+ return True
+
+ def is_superuser(self):
+ '''Access all the things'''
+ return True
+
+
+class AuthenticationError(Exception):
+ pass
+
+
+class GoogleAuthBackend(object):
+
+ def __init__(self):
+ # self.google_host = get_config_param('host')
+ self.login_manager = flask_login.LoginManager()
+ self.login_manager.login_view = 'airflow.login'
+ self.flask_app = None
+ self.google_oauth = None
+ self.api_rev = None
+
+ def init_app(self, flask_app):
+ self.flask_app = flask_app
+
+ self.login_manager.init_app(self.flask_app)
+
+ self.google_oauth = OAuth(self.flask_app).remote_app(
+ 'google',
+ consumer_key=get_config_param('client_id'),
+ consumer_secret=get_config_param('client_secret'),
+ request_token_params={'scope': '''https://www.googleapis.com/auth/userinfo.profile
+ https://www.googleapis.com/auth/userinfo.email'''},
+ base_url='https://www.google.com/accounts/',
+ request_token_url=None,
+ access_token_method='POST',
+ access_token_url='https://accounts.google.com/o/oauth2/token',
+ authorize_url='https://accounts.google.com/o/oauth2/auth')
+
+ self.login_manager.user_loader(self.load_user)
+
+ self.flask_app.add_url_rule(get_config_param('oauth_callback_route'),
+ 'google_oauth_callback',
+ self.oauth_callback)
+
+ def login(self, request):
+ _log.debug('Redirecting user to Google login')
+ return self.google_oauth.authorize(callback=url_for(
+ 'google_oauth_callback',
+ _external=True,
+ next=request.args.get('next') or request.referrer or None))
+
+ def get_google_user_profile_info(self, google_token):
+ resp = self.google_oauth.get('https://www.googleapis.com/oauth2/v1/userinfo',
+ token=(google_token, ''))
+
+ if not resp or resp.status != 200:
+ raise AuthenticationError(
+ 'Failed to fetch user profile, status ({0})'.format(
+ resp.status if resp else 'None'))
+
+ return resp.data['name'], resp.data['email']
+
+ def domain_check(self, email):
+ domain = email.split('@')[1]
+ if domain == get_config_param('domain'):
+ return True
+ return False
+
+ def load_user(self, userid):
+ if not userid or userid == 'None':
+ return None
+
+ session = settings.Session()
+ user = session.query(models.User).filter(
+ models.User.id == int(userid)).first()
+ session.expunge_all()
+ session.commit()
+ session.close()
+ return GoogleUser(user)
+
+ def oauth_callback(self):
+ _log.debug('Google OAuth callback called')
+
+ next_url = request.args.get('next') or url_for('admin.index')
+
+ resp = self.google_oauth.authorized_response()
+
+ try:
+ if resp is None:
+ raise AuthenticationError(
+ 'Null response from Google, denying access.'
+ )
+
+ google_token = resp['access_token']
+
+ username, email = self.get_google_user_profile_info(google_token)
+
+ if not self.domain_check(email):
+ return redirect(url_for('airflow.noaccess'))
+
+ except AuthenticationError:
+ _log.exception('')
+ return redirect(url_for('airflow.noaccess'))
+
+ session = settings.Session()
+
+ user = session.query(models.User).filter(
+ models.User.username == username).first()
+
+ if not user:
+ user = models.User(
+ username=username,
+ email=email,
+ is_superuser=False)
+
+ session.merge(user)
+ session.commit()
+ login_user(GoogleUser(user))
+ session.commit()
+ session.close()
+
+ return redirect(next_url)
+
+login_manager = GoogleAuthBackend()
+
+
+def login(self, request):
+ return login_manager.login(request)
+
http://git-wip-us.apache.org/repos/asf/incubator-airflow/blob/df848a55/docs/security.rst
----------------------------------------------------------------------
diff --git a/docs/security.rst b/docs/security.rst
index 50d9167..b8f13ca 100644
--- a/docs/security.rst
+++ b/docs/security.rst
@@ -251,3 +251,36 @@ backend. In order to setup an application:
5. Fill in the required information (the 'Authorization callback URL' must be fully qualifed e.g. http://airflow.example.com/example/ghe_oauth/callback)
6. Click 'Register application'
7. Copy 'Client ID', 'Client Secret', and your callback route to your airflow.cfg according to the above example
+
+Google Authentication
+''''''''''''''''''''''''''''''''''''''
+
+The Google authentication backend can be used to authenticate users
+against Google using OAuth2. You must specify a domain to restrict login
+to only members of that domain.
+
+.. code-block:: bash
+
+ [webserver]
+ authenticate = True
+ auth_backend = airflow.contrib.auth.backends.google_auth
+
+ [google]
+ client_id = google_client_id
+ client_secret = google_client_secret
+ oauth_callback_route = /oauth2callback
+ domain = example.com
+
+Setting up Google Authentication
+'''''''''''''''''''''''''''''
+
+An application must be setup in the Google API Console before you can use the Google authentication
+backend. In order to setup an application:
+
+1. Navigate to https://console.developers.google.com/apis/
+2. Select 'Credentials' from the left hand nav
+3. Click 'Create credentials' and choose 'OAuth client ID'
+4. Choose 'Web application'
+5. Fill in the required information (the 'Authorized redirect URIs' must be fully qualifed e.g. http://airflow.example.com/oauth2callback)
+6. Click 'Create'
+7. Copy 'Client ID', 'Client Secret', and your redirect URI to your airflow.cfg according to the above example