Accessing URL parameters with python requests in an i-framed app - django

I am developing an Django app that that needs to consume a third party restful-API. The app itself is accessible after authentication from a portal where is exposed within an i-frame. When you select the app in the portal it redirects to the app url and appends a url parameter "?tokenid". This needs to be retrieved in order to use the third party API which needs to include the tokenid parameter as a cookie.
In my app there is a services.py module where I build all API methods to access this third party API, for instance:
url='https://www.fooapp.com'
token = request.get(url, 'tokenid')
cookies = {'session': token }
when clicking in the app link from within the main portal the response appends the tokenid parameter:
''https://www.fooapp.com/?tokenid=.eJwty8FqhDAQANB_mbMs2axmjKf-x1JkdCYyEGNxDK0s---V0uM7vBcoSzn0OGF4wb5lMRieMFXNrGUZVyq0yA6fDRznl8AA1S42UJUv5Kq26lIl36ZMRWwXO7bzo1RdKH_TeVOBdwNGax4LrTL-NXSRXJq4jeERKUjybY9eZMZpZu87xIgxCcP_NDHTrVz559rjTI-AQVzXchdSdM57h3Pn7yR9knvLGCj1kSnA-xcnjUmc.XNmOdg.hbtMmT5BkmOkhklP3JqgksDijhc''
then one of the functions to use one the API method is:
def get_foo():
url = 'https://thirdparty.com/api/v0/foo/'
r = requests.get(url, params=cookies)
data = r.json()
foos_dict = {foo['name']:foo['id'] for foo in data if 'name' in foo and 'id' in foo}
return foo_dict
then in my views I call this function etc:
class Foo(generic.TemplateView):
def get(self, request):
foo_dict = services.get_foo()
return render(request, 'foo.html', foo_dict)
I get this
HTTPSConnectionPool(host='wwww.foo.com', port=443): Max retries exceeded with url: /?tokenid (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x000002261DFCCA20>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed',))
I also tried using
import urllib.parse as urlparse
url = 'https://wwww.foo.com/'
parsed = urlparse.urlparse(url)
token = urlparse.parse_qs(parsed.query)['?tokenid']
with no success.
Is there any other way of achieving this. I am puzzled here. What I think I don't really get is that this token id is already retrievable since I am a user inside the portal and the app is called from inside the portal, so there is no need to use request.get again (??). Any url in the iframe is appended with that ?tokenid parameter and it's valid for one hour. Any idea what could be wrong?
Many thanks in advance

Related

How to use dj-rest-auth with many clients

I'd like to have many different clients be able to access my django website (more specifically its API) but I'm not sure how to do this with django-allauth, dj-rest-auth and simplejwt.
My current client app is using the built in django template engine and is set up with django-allauth for social authentication (Google etc). It's working using the documented installation recommendations.
I would now like to create different types of clients that aren't using the django template engine (e.g. Angular, Vue, flutter mobile etc) but I'm confused how dj-rest-auth is used so that it scales to support any number of client types.
Using Google social sign in as an example, when I create a new client, I have to register a new redirect_uri specific to that client.
To test this all out, I created a simple flask app with a single link so that I can retrieve a "code/access_token" before sending it to my Django app. The link is created using the following...
var codeRequestUrl =
`https://accounts.google.com/o/oauth2/v2/auth?\
scope=email&\
access_type=offline&\
include_granted_scopes=true&\
response_type=code&\
state=state_parameter_passthrough_value&\
redirect_uri=http%3A//127.0.0.1:5000/callback&\
client_id=${clientId}`;
...and the code is retrieved at the '/callback' endpoint in flask...
#app.route("/callback", methods=['GET'])
def redirect():
code = request.args.get('code', '')
req = requests.post('http://127.0.0.1:8000/api/dj-rest-auth/google/', data={'code':code})
return "done..."
...from where I send an x-www-form-urlencoded POST request back to a dj-rest-auth endpoint that is set up as per its documentation...
class GoogleLogin(SocialLoginView):
callback_url = 'http://127.0.0.1:5000/callback'
adapter_class = GoogleOAuth2Adapter
client_class = OAuth2Client
...
urlpatterns += [
...
path('dj-rest-auth/google/', GoogleLogin.as_view(), name='google_login'),
....
]
Django then successfully returns an access_token, refresh_token and some info about the logged in user.
But this isn't something that scales well. If I were to also create an Angular client, I'd need to register a different callback (because the Angular client would be running on a different port and/or address, and I'd also need another path set up in urls.py and associate it with a new SocialLoginView subclass that can handle the different callback_url (redirect_uri).
And with all this in mind, I have no idea how to do all of this with a flutter mobile app, which as far as I'm aware, has no concept of a callback_url, so I'm not sure how making a POST request to .../dj-rest-auth/google/ would even work given that I'd instantly get a redirect_uri_mismatch error.
Have I got it backwards and the client registered at Google is the Angular, Vue, Flash etc app? That would mean that each client would have to handle its own client_id and client_secret, which then seems to bypass django-allauth's and dj-rest-auth's functionality.
I feel like I'm misinterpreting this, so I would really appreciate some suggestions.
I feel confident enough to answer my own question.
In short, yes, multiple clients (including thirdparty) is a reasonably straight forward process. Unfortunately a lot of the blog posts and tutorials that exist take the perspective of a 'second party' client, which really confuses things. The result is a lot of error messages relating to the redirect_uri.
To their credit, the Google docs for their example Flask app was exactly what I needed, but there are a couple of observations that are really important, and what caused so much confusion for me.
First, and most important, the callback (redirect_uri) is not needed in Django at all. In Django, something like this is all that is required.
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
client_class = OAuth2Client
urlpatterns += [
...
path('auth/google/', GoogleLogin.as_view(), name='google_login'),
...
]
So no callback attribute is required. The reason for this is that the Flask (or thirdparty app) handles all of the Google side authentication.
The second observation was that the redirect_uri in the Flask app seemed have have to be the same for both the "code" request step, and the "access_token" step.
You can see it in the linked example where the oauth2callback function (which handles the redirect_uri), but I've modified for use with dj-rest-auth
#app.route('/')
def index():
if 'credentials' not in flask.session:
return flask.redirect(flask.url_for('oauth2callback'))
credentials = json.loads(flask.session['credentials'])
if credentials['expires_in'] <= 0:
return flask.redirect(flask.url_for('oauth2callback'))
else:
data = {'access_token': credentials['access_token']}
headers = headers = {'Content-Type': 'application/x-www-form-urlencoded'}
r = requests.post(f'{URL_ROOT}/api/auth/google/', data=data, headers=headers)
response_json = json.loads(r.text)
access_token = response_json['access_token'] # JWT Access Token
refresh_token = response_json['refresh_token']
# Make a query to your Django website
headers = headers = {'Authorization': f'Bearer {access_token}'}
r = requests.post(f'{URL_ROOT}/api/object/{OBJECT_ID}/action/', data=data, headers=headers)
# do stuff with r
#app.route('/oauth2callback')
def oauth2callback():
if 'code' not in flask.request.args:
auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code'
'&client_id={}&redirect_uri={}&scope={}').format(CLIENT_ID, REDIRECT_URI, SCOPE)
return flask.redirect(auth_uri)
else:
auth_code = flask.request.args.get('code')
data = {'code': auth_code,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'redirect_uri': REDIRECT_URI,
'grant_type': 'authorization_code'}
r = requests.post('https://oauth2.googleapis.com/token', data=data)
flask.session['credentials'] = r.text # This has the access_token
return flask.redirect(flask.url_for('index'))
So in summary, it's a bit like this:
On the index/home page, the user presses a "Login" html anchor that points to /login.
Flask doesnt have any credentials, so redirects to /oauth2callback to begin authentication.
First, the "code" is retrieved using Googles' GET /auth endpoint, and by using your app's client id.
The redirect_uri ensures the code flow goes back to itself, but this time, with the "code" now know, do a POST request to Google's /token endpoint using your apps's client id and client secret. Again, the redirect_uri is the same (/oauth2callback).
Now that Googles's access_token is known, the Flask app redirects back to /index (although it could be anywhere at this point)
Back in /index, the Flask app now has Google's "access_token". Use that to log into Django's dj-rest-auth endpoint that you created.
Django will then return its own access_token and refresh_token, so continue to use those as needed.
I hope this helps.
Note that your flask app will need to be registered as a new Web App with Google's OAuth2 console (so it has it's own client id and client secret). In other words, don't reuse what you may have already created with an existing Django allauth implementation (which was my scenario). Each thirdparty app maker will handle their own OAuth2 credentials.

Djoser password reset implementation

I am using djosers for my authentication on django backend which eventually i'll be connecting to flutter frontend and i am having trouble implementing the password reset functionality...
from what i have understood, first i need to hit the /users/reset_password/ with email body which will eventually give me the token of authentication which will be used further on confirm reset but the first thing i dont understand is PASSWORD_RESET_CONFIRM_URL field in the settings, like it needs a front end link with uid and token placeholders but what is this token field and what is this PASSWORD_RESET_CONFIRM_URL but i managed to look over a stack overflow question and filled it but now when i hit /users/reset_password/ i get this error:
[WinError 10013] An attempt was made to access a socket in a way forbidden by its access permissions
settings:
DJOSER = {
'PASSWORD_RESET_CONFIRM_URL':'reset/password/reset/confirm/{uid}/{token}',
'LOGIN_FIELD' : 'email',
'USER_CREATE_PASSWORD_RETYPE' : True,
'SERIALIZERS': {
'user_create': 'auth_app.serializers.UseriCreateSerializer',
'user': 'auth_app.serializers.UserCreateSerializer',
}
}
urls.py:
urlpatterns = [
path('',home,name='home'),
path('addInForum/',addInForum,name='addInForum'),
path('addInDiscussion/',addInDiscussion,name='addInDiscussion'),
path('<str:forum_id>/getDiscussion/',getDiscussion,name='getDiscussion'),
path('getDate/',getDate,name='getDate'),
path('reset/password/reset/confirm/<str:uid>/<str:token>/',PasswordResetView,name='PasswordResetView'),
# url(r'^reset/password/reset/confirm/(?P<uid>[\w-]+)/(?P<token>[\w-]+)/$', PasswordResetView.as_view(),),
]
views.py
#api_view(['GET'])
def PasswordResetView(request,uid,token):
post_data = {'uid': uid, 'token': token}
return Response(post_data)
Please remember that djoser should be part of your REST API based on Django REST Framework. You also need to think differently about the url routing in regard of your frontend app..
Usually urls in the form mydomain.com/some_url/whatever are considered "frontend urls" and parsed by routing of your frontend app. On the other hand urls in the form mydomain.com/api/something are considered API urls that are routed via Django's urls.py. I will refer to them as Fronted_URL and API_URL respectively.
So: resetting password works like this. The user that forgot their password and wants to reset it, surely needs to fill some king of form. This form needs to be sent to APIURL returned by resolve('user-reset-password') (by default this returns something like /users/reset_password/)
Here comes PASSWORD_RESET_CONFIRM_URL setting. Because after the body is accepted by the APIURL mentioned above, a mail will be sent to the user with a link that will point to URL entered in that setting. And it has to be FrontendURL! It should be routed by your frontend APP and preferably display some screen. But in the background your frontend app should send the values of uid and token fields to APIURL returned by resolve("user-reset-password-confirm").
This flow allows your frontend app to properly handle the response and display appropriate message to the user and then maybe redirect them to some other screen.
If you don't have a routed frontend app (probably written using REACT, ANGULAR or VUE) then you probably don't need a REST API and should just stick to django-allauth.

Jsonresponse in Django working in browser but not in PostMan or Angular

I am trying to send a JSON response from Django back-end to my angular front-end.
When I make the request I receive nothing in Postman or Angular but,opening the link in browser seems to be returning the correct result
My View is :
#api_view(['GET'])
def my_view(request):
print(request.user.username)
return JsonResponse({'username': request.user.username})
When I open http://127.0.0.1:8000/accounts/get_username/ in browser I receive
{"username": "aditya8010"} on the web page.
But when i do a get request using POSTMAN I recieve
{
"username": ""
}
Same with Angular
this.http.get("http://127.0.0.1:8000/accounts/get_username/").subscribe((res) => {
this.username = JSON.stringify(res["username"])
console.log(this.username," ", res)
})
this code also prints an empty username string.
Another thing I have noticed is that my print statement in the view does print anything random I put in there when called from POSTMAN or Browser but when I use request.user.username it doesnt print anything when called by POSTMAN.
And each time the response code is 200
What am I doing wrong.
When you're sending the request you are not providing authentication credentials (i.e. something that identifies the user that is sending the request). How do you obtain this credentials?
You need to establish an authentication method. There are several but I recommend using Token authentication with knox package. Basically, you have an endpoint that logins the user with his username and password (normal json post request) and that endpoint returns a token. This token is what identifies the user. You send this token in the header of each request you need to be authenticated. That means you probably should include an IsAuthenticated permission for the view. In postman:
API view:
from rest_framework.permissions import IsAuthenticated
#api_view(['GET'])
#authentication_classes([IsAuthenticated])
def my_view(request):
print(request.user.username)
return JsonResponse({'username': request.user.username})
When it is in a browser, your login information is remembered in the session. When using postman or Angular, you need to provide the user's information in the request header manually.

How to implement GUI Less Oauth Authentication system to access API's built using Python-Flask

I have written a simple Python Flask API which does operations like adding data to Database and getting data from Database, there is no UI for this API, Now I want to implement OAuth authentication system for this simple API, As there is NO GUI, I cant use google or FB Oauth Providers which redirects users to there login page.
In simple words, i want to create my own GUI less oauth Authentication system which secures my API as any user who wants to access my API should pass through this authentication system by passing access token in a header
I need Oauth Authentication system of my own for the API's below:
from flask import Flask, redirect, url_for, session
from flask import Flask,jsonify,request,make_response
from flask_login import login_user,logout_user,current_user,login_required,LoginManager,login_manager
from flask_oauth import OAuth
import json
from flask_mysqldb import MySQL
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_oauthlib.provider import OAuth1Provider
app = Flask(__name__)
class MYWIFI(db.Model):
__tablename__ = 'MYWIFI'
id = db.Column('id', db.Integer, primary_key=True)
data = db.Column('data', db.Unicode)
def __init__(self, id, data):
self.id = id
self.data = data
#app.route('/getall')
def getall():
access_token = get_access_token()
if access_token is None:
return redirect(url_for('login'))
else:
languages = [u.__dict__ for u in db.session.query(MYWIFI).all()]
for d in languages:
del d['_sa_instance_state']
print(languages)
languagesJSON = json.dumps(languages)
return languagesJSON
#app.route('/insert', methods=['GET','POST'])
def insert():
access_token = get_access_token()
if access_token is None:
return redirect(url_for('login'))
else:
if request.method == 'POST':
insert = request.get_json()
id = insert['id']
data = insert['data']
print id
print data
new = MYWIFI(id, data)
db.session.add(new)
db.session.commit()
return "Success"
def main():
app.run()
if __name__ == '__main__':
main()
Please can anyone help me in kick starting this
I appreciate for this help
If I understood correctly, what you want is to build API endpoints which are protected by OAuth 2.0 tokens. If that's the case you as the API builder does not have to worry how token obtaining process happens. The client that consumes your APIs must perform the token obtaining and pass them to your end.
About sending in headers, try to stick with standards already exist in the OAuth 2 domain. RFC6750 defines how to use tokens once a client obtains them. It defines bearer authentication schema to transmit access tokens. Check section 2.1 to how header is set.
GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM
Where mF_9.B5f-4.1JqM is the access token. Once your API receives a request, from your end you must validate the access token before granting access. For this there is RFC7662 which define the methodology to validate access token against the authorization server. See section 2 Introspection endpoint to get an understanding of it. Alternatively, access token can come in a JWT format thus allowing it to be self contained.
Unfortunately, I do not have code for proposed solutions. They will considerable amount of codes. But I suggest you to separate authorization logic from your code. That means validation of authorization must be a separate module in your python code. But below I give a suggestion with my python knowledge.
#app.route('/insert', methods=['GET','POST'])
def insert():
access_token = get_access_token()
# Auth Validation for insert - This is based on access tokens
# If token is invalid/not-present, exception is thrown with HTTP 401 - unauthorized
my_auth_module.validate_access_token(access_token)
if request.method == 'POST':
insert = request.get_json()
id = insert['id']
Also one final thing, your API should not worry about redirection for login. Let it be handled by your API client upon the 401 - Unathorized response.

SharePoint API Call for File Upload

I'm building an app in Django and trying to upload a file to a SharePoint Online Site but I'm sure I've (at least) got the url wrong for the API call. I have the appropriate permissions allotted to the app in dev.microsoft.com but get back a 500 response when I try to upload.
this is the basic api call I'm trying to use
PUT /sites/{site-id}/drive/items/{parent-id}:/{filename}:/content
I'm kind of going by these 2 resources to build the url but not sure of the site-id or parent-id. For the {YourSharepointTenant} i got the tenant-id from the Azure Portal under properties. Its a long list of characters that I omitted from my code i posted here
https://www.coderedcorp.com/blog/uploading-files-to-sharepoint-with-django-and-pyth/
https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content
Here is my code
def save(self, *args, **kwargs):
# Get the authenticated user credentials from office365-allauth
social = request.user.social_auth.get(provider='office365')
access_token = social.extra_data['access_token']
# build our header for the api call
headers = {
'Authorization' : 'Bearer {0}'.format(access_token),
}
# build the url for the api call
# Look at https://dev.onedrive.com/items/upload_put.htm for reference
url = 'https://{YourSharepointTenant}.sharepoint.com/sites/ITSupport/drive/root:/' + design_document + ':/content'
# Make the api call
response = requests.put(url, data=open(design_document, 'rb'), headers=headers)
return response
super(LaserMaskDesign, self).save(*args, **kwargs)
As you're targeting the graph you should use that url instead https://graph.microsoft.com/beta/sites/siteid/drive....
It is the site API that's your entry point and that will provide you with access to the drive one. Keep in mind that API is in beta so not production ready.
More documentation on this API here https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/resources/sharepoint