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
--------------
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 <https://github.com/sahat/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 <https://g
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

View File

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

View File

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

View File

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

View File

@ -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(

View File

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

View File

@ -52,7 +52,10 @@ class User(db.Model):
:param password:
:return:
"""
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:
"""
if self.password:
self.password = bcrypt.generate_password_hash(self.password)
return self.password
else:
return None
@property
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('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')