Skip to content

Commit

Permalink
Merge pull request #27 from jaykay12/master
Browse files Browse the repository at this point in the history
oAuth Integration
  • Loading branch information
jaykay12 authored May 12, 2020
2 parents f36e604 + 1641ec5 commit ed3fb32
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 7 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@
Authentication API based on Flask-SQLAlchemy

[![Build Status](https://travis-ci.org/jaykay12/Auth-API.svg?branch=master)](https://travis-ci.org/jaykay12/Auth-API)
![Heroku](http://heroku-badge.herokuapp.com/?app=auth-api-flask&root=/api)
![Heroku](http://heroku-badge.herokuapp.com/?app=auth-api-flask&root=/api)


API Documentation: https://documenter.getpostman.com/view/8735978/SzmZcg5S?version=latest
6 changes: 3 additions & 3 deletions helper-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
`export SECRET_KEY='<API-SECRET-KEY>'`
`export DEVELOPMENT_DATABASE_URL='sqlite:///auth.db'`
`export ENV='DEVELOPMENT'`
+ Run on dev environment using `bash start-dev.sh`
+ Run on dev environment using `bash start.sh --dev`


## Running on Stage Environment
Expand All @@ -24,7 +24,7 @@
- Close postgres console using `\q`
+ Add the following line to the file **api.env**
`export STAGE_DATABASE_URL='postgresql+psycopg2://<POSTGRES_USER>:<POSTGRES_PASS>@localhost/<POSTGRES_DB>'`
+ Run on stage environment using `bash start-stage.sh`
+ Run on stage environment using `bash start.sh --stage`

## Running on Production Environment
+ Install Heroku CLI on machine using `curl https://cli-assets.heroku.com/install-ubuntu.sh | sh` and verify using `heroku --version`
Expand All @@ -47,7 +47,7 @@
`export TESTING_DATABASE_URL='sqlite:///:memory:'`
+ Run tests on dev environment using `bash run-tests.sh`
+ Signup to Travis CI using GitHub and toggle the button to ON for Auth-API from `https://travis-ci.org/github/jaykay12/Auth-API`
+ Add the following entries in `Environment Variables` of Travis CI
+ Add the following entries in `Environment Variables` of Travis CI
`ENV = 'TESTING'`
`SECRET_KEY = <API-SECRET-KEY`
`TESTING_DATABASE_URL = 'sqlite:///:memory:'`
Expand Down
7 changes: 7 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
certifi==2020.4.5.1
chardet==3.0.4
click==6.7
Flask==1.1.2
Flask-HTTPAuth==3.3.0
Flask-Login==0.5.0
Flask-SQLAlchemy==2.4.1
gunicorn==19.9.0
idna==2.9
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
passlib==1.7.2
psycopg2-binary==2.8.5
rauth==0.7.3
requests==2.23.0
SQLAlchemy==1.3.16
urllib3==1.25.9
Werkzeug==1.0.1
10 changes: 8 additions & 2 deletions service/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,28 @@ def create_app():
"""Loading configuratons."""
if environ.get('ENV') == 'PRODUCTION':
app.config.from_object('config.ProductionConfig')
app.config.from_object('config.oAuthConfig')
accesslogger.info("Loaded: Configuration of Production")
elif environ.get('ENV') == 'STAGE':
app.config.from_object('config.StageConfig')
app.config.from_object('config.oAuthConfig')
accesslogger.info("Loaded: Configuration of Stage")
elif environ.get('ENV') == 'TESTING':
app.config.from_object('config.TestConfig')
app.config.from_object('config.oAuthConfig')
accesslogger.info("Loaded: Configuration of Testing")
else:
app.config.from_object('config.DevelopmentConfig')
app.config.from_object('config.oAuthConfig')
accesslogger.info("Loaded: configuration of Development")

db.init_app(app)

with app.app_context():
from . import routes # Import routes
db.create_all() # Create database tables for our data models
from . import routes_auth
from . import routes_oauth
db.drop_all()
db.create_all()

return app

Expand Down
3 changes: 2 additions & 1 deletion service/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
class User(db.Model):
__tablename__ = 'users'

id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.String(64), primary_key=True)
username = db.Column(db.String(32), index=True)
email = db.Column(db.String(64))
password_hash = db.Column(db.Text)

def hash_password(self, password):
Expand Down
225 changes: 225 additions & 0 deletions service/api/oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import json

from rauth import OAuth1Service, OAuth2Service
from flask import current_app, url_for, request, redirect, session

class OAuthSignIn(object):
providers = None

def __init__(self, provider_name):
self.provider_name = provider_name
credential = current_app.config['OAUTH_CREDENTIALS'][provider_name]
self.consumer_id = credential['id']
self.consumer_secret = credential['secret']

def authorize(self):
pass

def callback(self):
pass

def get_callback_url(self):
return url_for('oauth_callback', provider=self.provider_name, _external=True)

@classmethod
def load_provider(self):
if self.providers is None:
self.providers = {}
for provider_class in self.__subclasses__():
provider = provider_class()
self.providers[provider.provider_name] = provider

@classmethod
def get_provider(self, provider_name):
self.load_provider()
return self.providers[provider_name]

class FacebookSignIn(OAuthSignIn):
def __init__(self):
super(FacebookSignIn, self).__init__('facebook')
self.service = OAuth2Service(
name = 'facebook',
client_id = self.consumer_id,
client_secret = self.consumer_secret,
authorize_url = 'https://graph.facebook.com/oauth/authorize',
access_token_url = 'https://graph.facebook.com/oauth/access_token',
base_url = 'https://graph.facebook.com/'
)

def authorize(self):
params = {
'scope': 'email',
'response_type': 'code',
'redirect_uri': self.get_callback_url()
}
return redirect(self.service.get_authorize_url(**params))

def callback(self):
def decode_json(payload):
return json.loads(payload.decode('utf-8'))

if 'code' not in request.args:
return None,None,None
data = {
'code': request.args['code'],
'grant_type': 'authorization_code',
'redirect_uri': self.get_callback_url()
}
oauth_session = self.service.get_auth_session(data = data, decoder = decode_json)
me = oauth_session.get('me?fields=id,email').json()

return ('facebook$' + me['id'], me['email'].split('@')[0], me['email'])

class TwitterSignIn(OAuthSignIn):
def __init__(self):
super(TwitterSignIn, self).__init__('twitter')
self.service = OAuth1Service(
name = 'twitter',
consumer_key=self.consumer_id,
consumer_secret=self.consumer_secret,
request_token_url='https://api.twitter.com/oauth/request_token',
authorize_url = 'https://api.twitter.com/oauth/authorize',
access_token_url = 'https://api.twitter.com/oauth/access_token',
base_url = 'https://api.twitter.com/1.1/'
)

def authorize(self):
params = {
'oauth_callback': self.get_callback_url()
}
request_token = self.service.get_request_token(params = params)

session['request_token'] = request_token
return redirect(self.service.get_authorize_url(request_token[0]))

def callback(self):
request_token = session['request_token']
if 'oauth_verifier' not in request.args:
return None, None, None
oauth_session = self.service.get_auth_session(request_token[0],request_token[1],
data={'oauth_verifier': request.args['oauth_verifier']}
)

me = oauth_session.get('account/verify_credentials.json').json()

return ('twitter$' + str(me['id']), me['screen_name'], None)

class GithubSignIn(OAuthSignIn):
def __init__(self):
super(GithubSignIn, self).__init__('github')
self.service = OAuth2Service(
name = 'github',
client_id = self.consumer_id,
client_secret = self.consumer_secret,
authorize_url = 'https://github.com/login/oauth/authorize',
access_token_url = 'https://github.com/login/oauth/access_token',
base_url = 'https://api.github.com/'
)

def authorize(self):
params = {
'scope': 'user',
'redirect_uri': self.get_callback_url()
}
return redirect(self.service.get_authorize_url(**params))

def callback(self):

if 'code' not in request.args:
return None,None,None
data = {
'code': request.args['code'],
'grant_type': 'authorization_code',
'redirect_uri': self.get_callback_url()
}
oauth_session = self.service.get_auth_session(data = data)
me = oauth_session.get('user').json()

return ('github$' + str(me['id']), str(me['login']), str(me['email']))

class GoogleSignIn(OAuthSignIn):

# https://accounts.google.com/.well-known/openid-configuration
# https://developers.google.com/identity/protocols/oauth2/openid-connect

def __init__(self):
super(GoogleSignIn, self).__init__('google')
self.service = OAuth2Service(
name = 'google',
client_id = self.consumer_id,
client_secret = self.consumer_secret,
authorize_url = 'https://accounts.google.com/o/oauth2/v2/auth',
access_token_url = 'https://oauth2.googleapis.com/token',
base_url = 'https://openidconnect.googleapis.com/v1/'
)

def authorize(self):
params = {
'scope': 'openid profile email',
'response_type': 'code',
'redirect_uri': self.get_callback_url()
}
return redirect(self.service.get_authorize_url(**params))

def callback(self):

def decode_json(payload):
return json.loads(payload.decode('utf-8'))

if 'code' not in request.args:
return None,None,None
data = {
'code': request.args['code'],
'grant_type': 'authorization_code',
'redirect_uri': self.get_callback_url()
}
oauth_session = self.service.get_auth_session(data = data, decoder = decode_json)
me = oauth_session.get('userinfo').json()

return ('google$' + str(me['sub']), str(me['name']), str(me['email']))

class LinkedInSignIn(OAuthSignIn):

# https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow
# https://docs.microsoft.com/en-gb/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin

def __init__(self):
super(LinkedInSignIn, self).__init__('linkedin')
self.service = OAuth2Service(
name = 'linkedin',
client_id = self.consumer_id,
client_secret = self.consumer_secret,
authorize_url = 'https://www.linkedin.com/oauth/v2/authorization',
access_token_url = 'https://www.linkedin.com/oauth/v2/accessToken',
base_url = 'https://api.linkedin.com/v2/'
)

def authorize(self):
params = {
'scope': 'r_liteprofile r_emailaddress',
'response_type': 'code',
'redirect_uri': self.get_callback_url()
}
return redirect(self.service.get_authorize_url(**params))

def callback(self):

def decode_json(payload):
return json.loads(payload.decode('utf-8'))

if 'code' not in request.args:
return None,None,None
data = {
'code': request.args['code'],
'grant_type': 'authorization_code',
'redirect_uri': self.get_callback_url()
}
oauth_session = self.service.get_auth_session(data = data, decoder = decode_json)
me = oauth_session.get('me').json()
email = oauth_session.get('emailAddress?q=members&projection=(elements*(handle~))').json()

return (
'linkedin$' + str(me['id']),
str(me['localizedFirstName'])+str(me['localizedLastName']),
str(email['elements'][0]['handle~']['emailAddress'])
)
File renamed without changes.
48 changes: 48 additions & 0 deletions service/api/routes_oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from flask import current_app as app
from flask import redirect, jsonify, request, flash
from .oauth import OAuthSignIn
from .models import db, User
from flask_login import current_user, login_user
from .log import accesslogger

@app.route('/oauth/')
def index():
response = dict()
response["info"]="Basic oAuth API"
response["developer"]="Jalaz Kumar"
response['providers']=str(OAuthSignIn.providers)
accesslogger.info("Accessed: oAuth API Introduction")
return (jsonify(response), 200)

@app.route('/oauth/authorize/<provider>')
def oauth_authorize(provider):
oauth = OAuthSignIn.get_provider(provider)
return oauth.authorize()

@app.route('/oauth/callback/<provider>')
def oauth_callback(provider):
oauth = OAuthSignIn.get_provider(provider)
id, username, email = oauth.callback()
if id is None:
return redirect('/oauth/failure/'+str(provider), 302)

user = User.query.filter_by(id=id).first()
if not user:
user = User(id=id, username=username, email=email)
try:
db.session.add(user)
db.session.commit()
except:
db.session.rollback()
return redirect('/oauth/failure/'+str(provider), 302)
return redirect('/oauth/success/'+str(provider), 302)

@app.route('/oauth/failure/<provider>')
def oauth_failure(provider):
flash("oAuth Failed")
return (jsonify({"message":"DB Error / Not Authorized"}), 503)

@app.route('/oauth/success/<provider>')
def oauth_success(provider):
flash("oAuth Success")
return (jsonify({"message":"oAuth Success"}), 200)
Loading

0 comments on commit ed3fb32

Please sign in to comment.