Merge pull request #186 from rpicard/master

Add Google SSO
This commit is contained in:
kevgliss 2015-12-27 15:30:52 -05:00
commit 38b48604f3
9 changed files with 142 additions and 22 deletions

View File

@ -262,11 +262,18 @@ for those plugins.
Authentication Authentication
-------------- --------------
Lemur currently supports Basic Authentication and Ping OAuth2 out of the box. Additional flows can be added relatively easily. 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 Ping you do not need to configure any of these options. 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 <https://github.com/sahat/satellizer>`_ For more information about how to use social logins, see: `Satellizer <https://github.com/sahat/satellizer>`_
.. data:: ACTIVE_PROVIDERS
:noindex:
::
ACTIVE_PROVIDERS = ["ping", "google"]
.. data:: PING_SECRET .. data:: PING_SECRET
:noindex: :noindex:
@ -296,6 +303,33 @@ For more information about how to use social logins, see: `Satellizer <https://g
PING_JWKS_URL = "https://<yourpingserver>/pf/JWKS" PING_JWKS_URL = "https://<yourpingserver>/pf/JWKS"
.. data:: PING_NAME
:noindex:
::
PING_NAME = "Example Oauth2 Provider"
.. data:: PING_CLIENT_ID
:noindex:
::
PING_CLIENT_ID = "client-id"
.. data:: GOOGLE_CLIENT_ID
:noindex:
::
GOOGLE_CLIENT_ID = "client-id"
.. data:: GOOGLE_SECRET
:noindex:
::
GOOGLE_SECRET = "somethingsecret"
AWS Plugin Configuration AWS Plugin Configuration

View File

@ -230,5 +230,80 @@ class Ping(Resource):
return dict(token=create_token(user)) 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))
class Providers(Resource):
def get(self):
active_providers = dict()
for provider in current_app.config.get("ACTIVE_PROVIDERS"):
provider = provider.lower()
if provider == "google":
active_providers["google"] = {
'clientId': current_app.config.get("GOOGLE_CLIENT_ID"),
'url': api.url_for(Google)
}
elif provider == "ping":
active_providers["oauth2"] = {
'name': current_app.config.get("PING_NAME"),
'url': api.url_for(Ping),
'redirectUri': '', # TODO
'clientId': current_app.config.get("PING_CLIENT_ID"),
'responseType': 'code',
'scope': ['openid', 'email', 'profile', 'address'],
'scopeDelimeter': ' ',
'authorizationEndpoint': '', # TODO
'requiredUrlParams': ['scope']
}
return active_providers
api.add_resource(Login, '/auth/login', endpoint='login') api.add_resource(Login, '/auth/login', endpoint='login')
api.add_resource(Ping, '/auth/ping', endpoint='ping') api.add_resource(Ping, '/auth/ping', endpoint='ping')
api.add_resource(Google, '/auth/google', endpoint='google')
api.add_resource(Providers, '/auth/providers', endpoint='providers')

View File

@ -18,7 +18,7 @@ var lemur = angular
'angular-clipboard', 'angular-clipboard',
'ngFileSaver' 'ngFileSaver'
]) ])
.config(function ($stateProvider, $urlRouterProvider, $authProvider) { .config(function ($stateProvider, $urlRouterProvider, $authProvider, AuthenticationService) {
$urlRouterProvider.otherwise('/welcome'); $urlRouterProvider.otherwise('/welcome');
$stateProvider $stateProvider
@ -27,17 +27,18 @@ var lemur = angular
templateUrl: 'angular/welcome/welcome.html' templateUrl: 'angular/welcome/welcome.html'
}); });
$authProvider.oauth2({ AuthenticationService.get_providers().then(function (active_providers) {
name: 'example', var provider_names = [];
url: 'http://localhost:8000/api/1/auth/ping', for (var key in active_providers) {
redirectUri: 'http://localhost:3000/', if (active_providers.hasOwnProperty(key)) {
clientId: 'client-id', provider_names.push(key);
responseType: 'code', }
scope: ['openid', 'email', 'profile', 'address'], }
scopeDelimiter: ' ',
authorizationEndpoint: 'https://example.com/as/authorization.oauth2', for (var i=0; i < provider_names.length; i++) {
requiredUrlParams: ['scope'] $authProvider[provider_names[i]](active_providers[provider_names[i]]);
}); }
}
}); });
lemur.service('MomentService', function () { lemur.service('MomentService', function () {

View File

@ -12,6 +12,7 @@ angular.module('lemur')
$scope.login = AuthenticationService.login; $scope.login = AuthenticationService.login;
$scope.authenticate = AuthenticationService.authenticate; $scope.authenticate = AuthenticationService.authenticate;
$scope.logout = AuthenticationService.logout; $scope.logout = AuthenticationService.logout;
$scope.get_providers = AuthenticationService.get_providers;
UserService.getCurrentUser().then(function (user) { UserService.getCurrentUser().then(function (user) {
$scope.currentUser = user; $scope.currentUser = user;

View File

@ -3,8 +3,8 @@
<div class="login"> <div class="login">
<div class="row"> <div class="row">
<div class="col-xs-12 col-sm-12 col-md-12"> <div class="col-xs-12 col-sm-12 col-md-12">
<button class="btn btn-block btn-default" ng-click="authenticate('Example')"> <button class="btn btn-block btn-default" ng-repeat="(key, value) in get_providers()" ng-click="authenticate(key)">
Login with Example Login with {{key}}
</button> </button>
</div> </div>
</div> </div>

View File

@ -6,6 +6,10 @@ angular.module('lemur')
.service('AuthenticationService', function ($location, $rootScope, AuthenticationApi, UserService, toaster, $auth) { .service('AuthenticationService', function ($location, $rootScope, AuthenticationApi, UserService, toaster, $auth) {
var AuthenticationService = this; var AuthenticationService = this;
AuthenticationService.get_providers = function () {
return AuthenticationApi.one('providers').get();
};
AuthenticationService.login = function (username, password) { AuthenticationService.login = function (username, password) {
AuthenticationApi.customPOST({'username': username, 'password': password}, 'login') AuthenticationApi.customPOST({'username': username, 'password': password}, 'login')
.then( .then(

View File

@ -30,8 +30,7 @@
Password Password
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="password" name="password" ng-model="user.password" placeholder="hunter2" class="form-control" required/> <input type="password" name="password" ng-model="user.password" placeholder="hunter2" class="form-control" />
<p ng-show="createForm.password.$invalid && !createForm.password.$pristine" class="help-block">You must enter an password</p>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -52,7 +52,10 @@ class User(db.Model):
:param password: :param password:
:return: :return:
""" """
if self.password:
return bcrypt.check_password_hash(self.password, password) return bcrypt.check_password_hash(self.password, password)
else:
return False
def hash_password(self): def hash_password(self):
""" """
@ -60,8 +63,11 @@ class User(db.Model):
:return: :return:
""" """
if self.password:
self.password = bcrypt.generate_password_hash(self.password) self.password = bcrypt.generate_password_hash(self.password)
return self.password return self.password
else:
return None
@property @property
def is_admin(self): def is_admin(self):

View File

@ -157,7 +157,7 @@ class UsersList(AuthenticatedResource):
""" """
self.reqparse.add_argument('username', type=str, location='json', required=True) 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('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('active', type=bool, default=True, location='json')
self.reqparse.add_argument('roles', type=roles, default=[], location='json') self.reqparse.add_argument('roles', type=roles, default=[], location='json')