Google calendar authentication page opens in terminal instead of client browser - django

I am integrating google calendar with my web application which is a django app. when i am doing it on localhost server, its working fine. Google authentication page opens in client browser, but when i am uploading that code to the server and integrating google calendar, then Google authentication page opens in terminal where i run my django server.
This is the page that opens for authentication in terminal
I want to provide this auth through client web browser.
`
def get_credentials(request):
creds = None
# If there are no (valid) credentials available, let the user log in.
if os.path.exists('token.pickle_' + request.GET.get('bot_id')):
with open('token.pickle_' + request.GET.get('bot_id'), 'rb') as token:
creds = pickle.load(token)
print(creds)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
CLIENT_SECRET_FILE, SCOPES)
creds = flow.run_local_server()
# Save the credentials for the next run
with open('token.pickle_' + request.GET.get('bot_id'), 'wb') as token:
pickle.dump(creds, token)
serializer = CalenderIntegrationSerializer(data={'bot_id': int(request.GET.get('bot_id')), 'status': True})
if serializer.is_valid():
serializer.save()
if os.path.exists('token.pickle_' + request.GET.get('bot_id')):
context = {'signin_url': creds}
return JsonResponse({'status': 200, 'data': 'Integration done!', 'is_integrated': True})
`
And this is my reference google calendar code python

This code is specifically for local development:
https://developers.google.com/api-client-library/python/auth/installed-app
The code gives you a hint on how to construct the URL, which you then need to send back to your user as a temporary redirect; e.g. using the redirect function. Then you need to have a django handler which accepts the redirect and executes the second half of the function. So:
Split your code into two functions.
Build a url, send a redirect with your endpoint as the callback
Google will redirect back to your endpoint after the user completes the flow.
Parse the results
Execute your code.

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.

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.

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

Ionic Google social authentication to Django Rest Framework backend

I am trying to get social authentication working for my mobile app (an Ionic app on Android). Django rest framework backend with rest_framework_jwt, social_django, and rest_social_auth.
On my Ionic app I was using satellizer.js, however, I can't use InAppBrowser so now I am trying to do the following with cordova-plugin-googleplus:
Step#1 (On client/app)
if (provider == 'google') {
// Use Google API Natively to get tokens and user info
window.plugins.googleplus.login(
{
// TODO Get the WebClient from App settings
'webClientId': '[*.myclientid]', // optional clientId of your Web application from Credentials settings of your project - On Android, this MUST be included to get an idToken. On iOS, it is not required.
'offline': true, // optional, but requires the webClientId - if set to true the plugin will also return a serverAuthCode, which can be used to grant offline access to a non-Google server
}) ................
Result: This gets me a valid response with both a idToken, serverAuthCode, and a userId.
Step#2
I am not sure what the next step is. Originally, I was going to try using Django rest_social_auth to do the following from my client/app:
POST /api/login/social/
with data (json)
provider=google&code=ASLKDJASLDKJASLD
Which was supposed to return a JWT token (from my understanding of the docs), however, it is not passing the JWTAuthMixin as there is no value returned from a call to get_authorization_header(request).split() in that Mixin. These means that nothing is returned to my client/app except a 400 error.
Am I supposed to be adding a header to my Ionic app POST when passing my idToken or serverAuthCode? Or am I on the wrong side of the tracks...
Are there any implementation recommendations for this auth flow?
So far I did the following and it works.
1. On app/client
(The client uses satellizer.js and the cordova-plugin-googleplus)
if (provider == 'google') {
// Use Google API Natively to get tokens and user info
window.plugins.googleplus.login(
{
// TODO Get the WebClient from App settings
'webClientId': '*[googleclientid]*.apps.googleusercontent.com',
'offline': true
},
function (obj) {
$http.post(SERVER.url + '[MY BACKEND URL]' + '/google-oauth2/', {code: obj.idToken, servAuthCode: obj.serverAuthCode})
.success(function(data){
$auth.setToken(data.jwt_token);
/.. Do something ../
})
.error(function(data){
console.log("There was an error" + JSON.stringify(data));
});
},
function (msg) {
// TODO Set Error states
console.error('error: ' + msg);
}
);
}
Summary
The app calls the Google plus API googleplus.login method (sending my webClientId)
I post the resulting idToken and serverAuthCode obtained from google after login to my Django backend.
2. My backend methods
URL
My app/client hits the url(r'^[MY BACKEND URL]/(?P<backend>[\w-]+)/$', ObtainAuthToken.as_view(), ),
View
This calls the following view and functions:
class ObtainAuthToken(APIView):
permission_classes = (AllowAny,)
def post(self, request, backend):
data = request.data
user_tokenID = data['code']
server_auth_code = data['servAuthCode']
if user_tokenID and server_auth_code and verify_google_user_token_ID(user_tokenID):
# Get Google OAuth credentials for the verified GOOGLE user.
credentials = settings.GOOGLE_FLOW.step2_exchange(server_auth_code)
# Here we call PSA to authenticate like we would if we used PSA on server side.
user = register_by_access_token(request, backend, token=credentials.access_token)
# If user is active we get or create the REST token and send it back with user data
if user and user.is_active:
# Generate JWT token for user and pass back to client
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return JsonResponse({'id': user.id, 'name': user.username, 'jwt_token': token})
return JsonResponse({'status':'false','error':'Bad Credentials, check the Access Token and/or the UID'},
status=403)
def verify_google_user_token_ID(user_tokenID):
try:
google_http_request = google.auth.transport.requests.Request()
idinfo = verify_token(user_tokenID, request=google_http_request,
audience=settings.SOCIAL_AUTH_GOOGLE_OAUTH2_FULL_KEY)
# Or, if multiple clients access the backend server:
if idinfo['aud'] not in [settings.GOOGLE_APP_ID_ANDROID, settings.GOOGLE_APP_ID_WEB]:
raise crypt.AppIdentityError("Unrecognized client.")
if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']:
raise crypt.AppIdentityError("Wrong issuer.")
return True
except crypt.AppIdentityError as e:
# Invalid token
return False
#psa('social:complete')
def register_by_access_token(request, backend, token):
backend = social_core.backends.google.GoogleOAuth2()
user = backend.do_auth(access_token=token, backend=backend)
if user:
return user
else:
return None
3. Back on the client
My client then looks at the response and takes the returned JWT and loads it to memory with $auth.setToken(data.jwt_token);
I think this works for now, but I still have to deal with token refresh and revocation etc.

Django API for login/logout with S3 site is not creating cookies

I'm working with a static webpage hosted on S3 and I'm using a Django as my api for session management.
I have my JS code on S3 POSTing to django when users sign in to my web site but I'm not getting any cookies saved on my browser.
I'm not sure if it's the jQuery $.post that is not accepting cookies or if it's Django that's not allowing the sending of cookies. How would I overcome this?
I currently also have a url endpoint on my django app where I can check if cookies are working but when I hit the url I get the standard message
Please enable cookies and try again.
Although my browser accepts cookies.
I get these urls to work fine when I use a Django rendered page to interact with them.
Any help would be greatly appreciated - thanks
Edit - showing code:
I use Janrain to allow user to login via facebook, twitter, etc..
how Janrain works
user clicks sign in button, uses facebook/wtv to login
Janrain get's user data on their servers and gives you a token which I post to django via this function
janrain.events.onProviderLoginToken.addHandler(function(tokenResponse) {
$.ajax({
type: "POST",
url: post_form_url,
data: formdata + "&token=" + tokenResponse.token,
success: function(res, textStatus, jqXHR) {
//do stuff now that we are logged in ...
console.log(jqXHR.getResponseHeader('Set-Cookie')); //returns null
//$('#redirect').submit();
},
});
In Django
def login_via_janrain(request):
if request.method == "POST":
janrain_token = request.POST.get('token')
// communicate w/janrain api to get info from this token (i.e. who just logged in via this token)
#create (or pull) user based on data returned from janrain api and do a Django login
auth.login(request, user)
#return success msg to $.post (session cookies should be automatically included in the response)
return HttpResponse("success")
Using S3
I currently have a static site hosted on S3. I tested these urls using django rendered views but ultimately I want the S3 site to render the views and just use django as an api endpoint.
I can currently login from the S3 site but after a successful login I have no cookies to show for it... I think django isn't sending back cookies...