Repository: fossasia/open-event-server

Issue:
Add third party authentication using facebook oauth
**I'm submitting a ...**  (check one with "x")
- [ ] bug report
- [x] feature request
- [ ] support request => Please do not submit support requests here, instead ask your query in out Gitter channel at https://gitter.im/fossasia/open-event-server

**Current behavior:**
<!-- Describe how the bug manifests. -->

Currently, there isn't any Server API to connect to facebook using oauth as required at `open-event-frontend`

**Expected behavior:**
<!-- Describe what the behavior would be without the bug. -->

Addition of connect to open event with facebook. 

**Steps to reproduce:**
<!-- If you are able to illustrate the bug or feature request with an example, please provide steps to reproduce -->

**Related code:**

```
insert any relevant code here else remove this section
```

**Other information:**
<!-- List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc. -->

**System information:** 

<!-- Add information about the system your facing this bug on. If you think this is irrelevant or if it's a UI bug or a feature request, please remove this section -->

```
Your operating system
```

```
output of `python --version`
```
**Wanna work on this issue**

Parent issue: #5115 


Discussion:

Resolving patch:
diff --git a/app/api/auth.py b/app/api/auth.py
--- a/app/api/auth.py
+++ b/app/api/auth.py
@@ -2,6 +2,7 @@
 from flask import request, jsonify, make_response, Blueprint
 from flask_jwt import current_identity as current_user, jwt_required
 from sqlalchemy.orm.exc import NoResultFound
+import requests
 
 from app import get_settings
 from app.api.helpers.db import save_to_db
@@ -16,10 +17,64 @@
 from app.models.notification import PASSWORD_CHANGE as PASSWORD_CHANGE_NOTIF
 from app.models.user import User
 from app.api.helpers.errors import UnprocessableEntityError, NotFoundError, BadRequestError
+from app.api.helpers.third_party_auth import GoogleOAuth, FbOAuth, TwitterOAuth, InstagramOAuth
 
 auth_routes = Blueprint('auth', __name__, url_prefix='/v1/auth')
 
 
+@auth_routes.route('/oauth/<provider>', methods=['GET'])
+def redirect_uri(provider):
+    if provider == 'facebook':
+        provider_class = FbOAuth()
+    elif provider == 'google':
+        provider_class = GoogleOAuth()
+    elif provider == 'twitter':
+        provider_class = TwitterOAuth()
+    elif provider == 'instagram':
+        provider_class = InstagramOAuth()
+    else:
+        return make_response(jsonify(
+            message="No support for {}".format(provider)), 404)
+    url = provider_class.get_auth_uri() + '?client_id=' +\
+        provider_class.get_client_id() + '&redirect_uri=' +\
+        provider_class.get_redirect_uri()
+    return make_response(jsonify(url=url), 200)
+
+
+@auth_routes.route('/oauth/token/<provider>', methods=['GET'])
+def get_token(provider):
+    if provider == 'facebook':
+        provider_class = FbOAuth()
+        payload = {
+            'grant_type': 'client_credentials',
+            'client_id': provider_class.get_client_id(),
+            'client_secret': provider_class.get_client_secret()
+        }
+    elif provider == 'google':
+        provider_class = GoogleOAuth()
+        payload = {
+            'client_id': provider_class.get_client_id(),
+            'client_secret': provider_class.get_client_secret()
+        }
+    elif provider == 'twitter':
+        provider_class = TwitterOAuth()
+        payload = {
+            'client_id': provider_class.get_client_id(),
+            'client_secret': provider_class.get_client_secret()
+        }
+    elif provider == 'instagram':
+        provider_class = InstagramOAuth()
+        payload = {
+            'client_id': provider_class.get_client_id(),
+            'client_secret': provider_class.get_client_secret()
+        }
+    else:
+        return make_response(jsonify(
+            message="No support for {}".format(provider)), 200)
+    response = requests.post(provider_class.get_token_uri(), params=payload)
+    return make_response(jsonify(token=response.json()), 200)
+
+
 @auth_routes.route('/verify-email', methods=['POST'])
 def verify_email():
     token = base64.b64decode(request.json['data']['token'])
diff --git a/app/api/helpers/third_party_auth.py b/app/api/helpers/third_party_auth.py
new file mode 100644
--- /dev/null
+++ b/app/api/helpers/third_party_auth.py
@@ -0,0 +1,153 @@
+import urllib
+
+import oauth2
+from flask import request
+
+from app.settings import get_settings
+
+
+class GoogleOAuth(object):
+    """Google Credentials"""
+
+    AUTH_URI = 'https://accounts.google.com/o/oauth2/auth'
+    TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'
+    USER_INFO = 'https://www.googleapis.com/userinfo/v2/me'
+    SCOPE = ['profile', 'email']
+
+    @classmethod
+    def get_client_id(cls):
+        return get_settings()['google_client_id']
+
+    @classmethod
+    def get_client_secret(cls):
+        return get_settings()['google_client_secret']
+
+    @classmethod
+    def get_redirect_uri(cls):
+        url = urllib.parse.urlparse(request.url)
+        redirect_uri = url.scheme + '://' + url.netloc + '/gCallback'
+        return redirect_uri
+
+    @classmethod
+    def get_auth_uri(cls):
+        return cls.AUTH_URI
+
+    @classmethod
+    def get_token_uri(cls):
+        return cls.TOKEN_URI
+
+    @classmethod
+    def get_user_info(cls):
+        return cls.USER_INFO
+
+
+class FbOAuth(object):
+    """Facebook Credentials"""
+    Fb_AUTH_URI = 'https://www.facebook.com/dialog/oauth'
+    Fb_TOKEN_URI = 'https://graph.facebook.com/oauth/access_token'
+    Fb_USER_INFO = 'https://graph.facebook.com/me?fields=' +\
+        'email,id,name,picture,last_name,first_name,link'
+    SCOPE = ['public_profile', 'email']
+
+    @classmethod
+    def get_client_id(cls):
+        return get_settings()['fb_client_id']
+
+    @classmethod
+    def get_client_secret(cls):
+        return get_settings()['fb_client_secret']
+
+    @classmethod
+    def get_redirect_uri(cls):
+        url = urllib.parse.urlparse(request.url)
+        fb_redirect_uri = url.scheme + '://' + url.netloc + '/fCallback'
+        return fb_redirect_uri
+
+    @classmethod
+    def get_auth_uri(cls):
+        return cls.Fb_AUTH_URI
+
+    @classmethod
+    def get_token_uri(cls):
+        return cls.Fb_TOKEN_URI
+
+    @classmethod
+    def get_user_info(cls):
+        return cls.Fb_USER_INFO
+
+
+class TwitterOAuth(object):
+    """Facebook Credentials"""
+    TW_AUTH_URI = 'https://api.twitter.com/oauth/authorize'
+    TW_REQUEST_TOKEN_URI = 'https://api.twitter.com/oauth/request_token'
+    TW_ACCESS_TOKEN = "https://api.twitter.com/oauth/access_token"
+
+    @classmethod
+    def get_client_id(cls):
+        return get_settings()['tw_consumer_key']
+
+    @classmethod
+    def get_client_secret(cls):
+        return get_settings()['tw_consumer_secret']
+
+    @classmethod
+    def get_token_uri(cls):
+        return cls.TW_REQUEST_TOKEN_URI
+
+    @classmethod
+    def get_redirect_uri(cls):
+        url = urllib.parse.urlparse(request.url)
+        tw_redirect_uri = url.scheme + '://' + url.netloc + '/tCallback'
+        return tw_redirect_uri
+
+    def get_consumer(self):
+        return oauth2.Consumer(key=self.get_client_id(),
+                               secret=self.get_client_secret())
+
+    def get_auth_uri(cls):
+        return cls.TW_AUTH_URI
+
+    def get_access_token(self, oauth_verifier, oauth_token):
+        consumer = self.get_consumer()
+        client = oauth2.Client(consumer)
+        return client.request(
+            self.TW_ACCESS_TOKEN + 'oauth_verifier=' +
+            oauth_verifier + "&oauth_token=" + oauth_token, "POST"
+        )
+
+    def get_authorized_client(self, oauth_verifier, oauth_token):
+        resp, content = self.get_access_token(oauth_verifier, oauth_token)
+        access_token = dict(urllib.parse.urlparse.parse_qsl(content))
+        token = oauth2.Token(
+            access_token["oauth_token"], access_token["oauth_token_secret"]
+        )
+        token.set_verifier(oauth_verifier)
+        return oauth2.Client(self.get_consumer(), token), access_token
+
+
+class InstagramOAuth(object):
+    INSTAGRAM_OAUTH_URI = "https://api.instagram.com/oauth/authorize/"
+    INSTAGRAM_TOKEN_URI = "https://api.instagram.com/oauth/access_token"
+    SCOPE = ['basic', 'public_content']
+
+    @classmethod
+    def get_client_id(cls):
+        return get_settings()['in_client_id']
+
+    @classmethod
+    def get_client_secret(cls):
+        return get_settings()['in_client_secret']
+
+    @classmethod
+    def get_redirect_uri(cls):
+        url = urllib.parse.urlparse(request.url)
+        i_redirect_uri = url.scheme + '://' + url.netloc + '/iCallback'
+        return i_redirect_uri
+
+    @classmethod
+    def get_auth_uri(cls):
+        return cls.INSTAGRAM_OAUTH_URI
+
+    @classmethod
+    def get_token_uri(cls):
+        return cls.INSTAGRAM_TOKEN_URI


Test patch: