From 350d013043628c64e6baeaa7c9a477dc8a3d7c53 Mon Sep 17 00:00:00 2001 From: Robert Picard Date: Mon, 21 Dec 2015 18:34:07 -0500 Subject: [PATCH 1/2] Add Google SSO This pull request adds Google SSO support. There are two main changes: 1. Add the Google auth view resource 2. Make passwords optional when creating a new user. This allows an admin to create a user without a password so that they can only login via Google. --- lemur/auth/views.py | 41 +++++++++++++++++++ .../app/angular/users/user/user.tpl.html | 3 +- lemur/users/models.py | 12 ++++-- lemur/users/views.py | 2 +- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/lemur/auth/views.py b/lemur/auth/views.py index 8f93554e..6161e611 100644 --- a/lemur/auth/views.py +++ b/lemur/auth/views.py @@ -230,5 +230,46 @@ class Ping(Resource): return dict(token=create_token(user)) +class Google(Resource): + + def __init__(self): + self.reqparse = reqparse.RequestParser() + super(Google, self).__init__() + + def post(self): + access_token_url = 'https://accounts.google.com/o/oauth2/token' + people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect' + + self.reqparse.add_argument('clientId', type=str, required=True, location='json') + self.reqparse.add_argument('redirectUri', type=str, required=True, location='json') + self.reqparse.add_argument('code', type=str, required=True, location='json') + + args = self.reqparse.parse_args() + + # Step 1. Exchange authorization code for access token + payload = { + 'client_id': args['clientId'], + 'grant_type': 'authorization_code', + 'redirect_uri': args['redirectUri'], + 'code': args['code'], + 'client_secret': current_app.config.get('GOOGLE_SECRET') + } + + r = requests.post(access_token_url, data=payload) + token = r.json() + + # Step 2. Retrieve information about the current user + headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])} + + r = requests.get(people_api_url, headers=headers) + profile = r.json() + + user = user_service.get_by_email(profile['email']) + + if user: + return dict(token=create_token(user)) + + api.add_resource(Login, '/auth/login', endpoint='login') api.add_resource(Ping, '/auth/ping', endpoint='ping') +api.add_resource(Google, '/auth/google', endpoint='google') diff --git a/lemur/static/app/angular/users/user/user.tpl.html b/lemur/static/app/angular/users/user/user.tpl.html index c2e9687b..b19750b1 100644 --- a/lemur/static/app/angular/users/user/user.tpl.html +++ b/lemur/static/app/angular/users/user/user.tpl.html @@ -30,8 +30,7 @@ Password
- -

You must enter an password

+
diff --git a/lemur/users/models.py b/lemur/users/models.py index a3a13b1e..3a272d98 100644 --- a/lemur/users/models.py +++ b/lemur/users/models.py @@ -52,7 +52,10 @@ class User(db.Model): :param password: :return: """ - return bcrypt.check_password_hash(self.password, password) + if self.password: + return bcrypt.check_password_hash(self.password, password) + else: + return False def hash_password(self): """ @@ -60,8 +63,11 @@ class User(db.Model): :return: """ - self.password = bcrypt.generate_password_hash(self.password) - return self.password + if self.password: + self.password = bcrypt.generate_password_hash(self.password) + return self.password + else: + return None @property def is_admin(self): diff --git a/lemur/users/views.py b/lemur/users/views.py index 548caaeb..85093997 100644 --- a/lemur/users/views.py +++ b/lemur/users/views.py @@ -157,7 +157,7 @@ class UsersList(AuthenticatedResource): """ self.reqparse.add_argument('username', type=str, location='json', required=True) self.reqparse.add_argument('email', type=str, location='json', required=True) - self.reqparse.add_argument('password', type=str, location='json', required=True) + self.reqparse.add_argument('password', type=str, location='json', default=None) self.reqparse.add_argument('active', type=bool, default=True, location='json') self.reqparse.add_argument('roles', type=roles, default=[], location='json') From 60856cb7b91fb35dd68556c98518287d9250a219 Mon Sep 17 00:00:00 2001 From: Robert Picard Date: Tue, 22 Dec 2015 14:37:29 -0500 Subject: [PATCH 2/2] Add an endpoint to return active authentication providers This endpoint can be used by Angular to figure out what authentication options to display to the user. It returns a dictionary of configuration details that the front-end needs for each provider. --- docs/administration/index.rst | 38 ++++++++++++++++++- lemur/auth/views.py | 34 +++++++++++++++++ lemur/static/app/angular/app.js | 25 ++++++------ .../app/angular/authentication/login/login.js | 1 + .../authentication/login/login.tpl.html | 4 +- .../app/angular/authentication/services.js | 4 ++ 6 files changed, 90 insertions(+), 16 deletions(-) diff --git a/docs/administration/index.rst b/docs/administration/index.rst index f9cf293b..9dd94952 100644 --- a/docs/administration/index.rst +++ b/docs/administration/index.rst @@ -262,11 +262,18 @@ for those plugins. Authentication -------------- -Lemur currently supports Basic Authentication and Ping OAuth2 out of the box. Additional flows can be added relatively easily. -If you are not using Ping you do not need to configure any of these options. +Lemur currently supports Basic Authentication, Ping OAuth2, and Google out of the box. Additional flows can be added relatively easily. +If you are not using an authentication provider you do not need to configure any of these options. For more information about how to use social logins, see: `Satellizer `_ +.. data:: ACTIVE_PROVIDERS + :noindex: + + :: + + ACTIVE_PROVIDERS = ["ping", "google"] + .. data:: PING_SECRET :noindex: @@ -296,6 +303,33 @@ For more information about how to use social logins, see: `Satellizer
-
diff --git a/lemur/static/app/angular/authentication/services.js b/lemur/static/app/angular/authentication/services.js index dddffeb4..7719b444 100644 --- a/lemur/static/app/angular/authentication/services.js +++ b/lemur/static/app/angular/authentication/services.js @@ -6,6 +6,10 @@ angular.module('lemur') .service('AuthenticationService', function ($location, $rootScope, AuthenticationApi, UserService, toaster, $auth) { var AuthenticationService = this; + AuthenticationService.get_providers = function () { + return AuthenticationApi.one('providers').get(); + }; + AuthenticationService.login = function (username, password) { AuthenticationApi.customPOST({'username': username, 'password': password}, 'login') .then(