GAE Channel API - 400 Bad Request on disconnect - python

I'm working on a chat application using GAE's Channel API in Python.
I don't think I've set it up correctly. When I leave a page I get the following error message in my browser console:
http://localhost:8080/_ah/channel/dev?command=disconnect&channel=0e0acee3bd…122e0acaa86eeb-channel-1354440420-1452408747-mike|5066549580791808&client=1 400 (Bad Request)
Also, when I send chat message it sends duplicate messages, so I'm guessing users are not being properly disconnected.
I've set it up such that when user visits page, my backend code checks if they are logged in, and if so will automatically create token based on their username and the permalink ID of the page.
I then create a token, calling channel.create_channel method and pass that token as a value in the dict I include when I render my template (Jinja).
In my front-end I open the channel with the token. My on-close function is:
socket.onclose = function(){
connected = false;
}
In my app.yaml file I have included inbound_services: - channel presence.
In my backend code, I have '/_ah/channel/disconnected/'routed to a class that gets that permalink's entity instance, finds the client id from a list of connections and removes that connection from the entity.
Can you someone help me think about the Channel API the right way?

Related

How to do google Sign In with the help of idToken in Django

This is the code I use to retrieve idToken in my flutter app
Future<void> _handleSignIn() async {
try {
final result = await _googleSignIn.signIn();
final ggAuth = await result!.authentication;
print(ggAuth.idToken); // this is the one that I use as token value
print(ggAuth.accessToken);
} catch (error) {
print(error);
}
}
And this is the code I use to access user info in my backend.
from google.oauth2 import id_token
from google.auth.transport import requests
try:
idinfo = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)
userid = idinfo['sub']
print(userid)
except ValueError:
print('Invalid token)
But after replacing the token variable with the token received from flutter app and replacing the CLIENT_ID with the id generated for my app (available in the console) , it still throws the error of invalid token. What am I doing wrong?
EDIT- When I use https://jwt.io/ to decode the token it works as expected and I get all the details.
Authentication Think of the id token as your birth certificate. It just identifies you as a person.
Authorization Think of the access token as your drivers license its what contains the permissions that you have to drive a car.
An Id token does not out write give you permission to access any data on google servers. With an access token you have been authorized to access some data in this case some profile information.
The issue you are having is that you are using sign-in in. Sign in is Open id connect think of it as a birth certificate (Id token). There is a user their who is signing in to your app. They are in fount of their computer. By default with Google signin gives you get basic profile information. What you are doing with Jwt.io is checking the claims that are returned in the Id token. Id tokens are just the token that allows your application to identify that the user is logged in. There should also be a sub claim there you should use this id to map the user to your integral user system. There may be a user name and email address in the claims but you can not guarantee that they will be there every time google has stated they do not always return these claims.
To get user profile information after the user has logged in you should be using the people api the people.get method when passed the access token will return to you the profile information of the currently authenticated user.
beyond that you should IMO not be passing tokens to your backend if you want the token in the backend you should log the user in using your backend language.
Okay so the idToken that I had was correct but flutter terminal was not able to print its full length and because of which I was copying only half the token and was getting a signature error when checking manually.

Google Hangouts Chat bot to send DMs via incoming webhook?

Is it possible to set up this simple bot, using an incoming webhook, but send the message as a DM (not a #mention) to specific user(s)?
My guess is no. But then how could I achieve this?
Right now, the message is just sent into the room in which the bot was added and I can't see anything on DMs in the messaging docs.
You can currently achieve this very easily in Slack by setting up a so called bot user and using their chat.postMessage but I would like to do this in Google Hangouts Chat instead.
from httplib2 import Http
from json import dumps
#
# Hangouts Chat incoming webhook quickstart
#
def main():
url = '<INCOMING-WEBHOOK-URL>'
bot_message = {
'text' : 'Hello from Python script!'}
message_headers = { 'Content-Type': 'application/json; charset=UTF-8'}
http_obj = Http()
response = http_obj.request(
uri=url,
method='POST',
headers=message_headers,
body=dumps(bot_message),
)
print(response)
if __name__ == '__main__':
main()
As of now you would need their Space ID or webhook url in order to DM the user privately whether you use a webhook (like you did) or REST API. Unless you have this Space ID/Webhook you cannot message a user. One way of getting it, is to ask the user for their spaceID and store it. Either way, Google API hasn't given a way to retrieve a different Space ID then the current one you are talking inside of. This means, a bot can only message users its interacted with at some point.
The current Space ID value can be retrieved from the event JSON (event['space']['name']) and then using messages.create to send a new message to the user
service.spaces().messages().create( parent = spaceName, body = response).execute()
OR it can be retrieved from the url https://chat.google.com/dm/ --> space ID is here <---
Google has not released any way of generating your own spaceID for a specific user.
EDIT: In order to get the webhook url. See below:
then copy and paste the webhook url into your code above.
NOTICE: If you need, this webhook url can be manufactured using the usual url for google chat with their space ID as mentioned above and a key and access token in this format: https://chat.googleapis.com/v1/spaces/< space ID >/messages?key=A< key goes here > &token=< access token here >
For info on how to get a key and access token, read the documentation provided here: https://developers.google.com/identity/protocols/OAuth2

Implementing Google Directory API users watch with Python

I'm having some trouble understanding and implementing the Google Directory API's users watch function and push notification system (https://developers.google.com/admin-sdk/reports/v1/guides/push#creating-notification-channels) in my Python GAE app. What I'm trying to achieve is that any user (admin) who uses my app would be able to watch user changes within his own domain.
I've verified the domain I want to use for notifications and implemented the watch request as follows:
directoryauthdecorator = OAuth2Decorator(
approval_prompt='force',
client_id='my_client_id',
client_secret='my_client_secret',
callback_path='/oauth2callback',
scope=['https://www.googleapis.com/auth/admin.directory.user'])
class PushNotifications(webapp.RequestHandler):
#directoryauthdecorator.oauth_required
def get(self):
auth_http = directoryauthdecorator.http()
service = build("admin", "directory_v1", http=auth_http)
uu_id=str(uuid.uuid4())
param={}
param['customer']='my_customer'
param['event']='add'
param['body']={'type':'web_hook','id':uu_id,'address':'https://my-domain.com/pushNotifications'}
watchUsers = service.users().watch(**param).execute()
application = webapp.WSGIApplication(
[
('/pushNotifications',PushNotifications),
(directoryauthdecorator.callback_path, directoryauthdecorator.callback_handler())],
debug=True)
Now, the receiving part is what I don't understand. When I add a user on my domain and check the app's request logs I see some activity, but there's no usable data. How should I approach this part?
Any help would be appreciated. Thanks.
The problem
It seems like there's been some confusion in implementing the handler. Your handler actually sets up the notifications channel by sending a POST request to the Reports API endpoint. As the docs say:
To set up a notification channel for messages about changes to a particular resource, send a POST request to the watch method for the resource.
source
You should only need to send this request one time to set up the channel, and the "address" parameter should be the URL on your app that will receive the notifications.
Also, it's not clear what is happening with the following code:
param={}
param['customer']='my_customer'
param['event']='add'
Are you just breaking the code in order to post it here? Or is it actually written that way in the file? You should actually preserve, as much as possible, the code that your app is running so that we can reason about it.
The solution
It seems from the docs you linked - in the "Receiving Notifications" section, that you should have code inside the "address" specified to receive notifications that will inspect the POST request body and headers on the notification push request, and then do something with that data (like store it in BigQuery or send an email to the admin, etc.)
Managed to figure it out. In the App Engine logs I noticed that each time I make a change, which is being 'watched', on my domain I get a POST request from Google's API, but with a 302 code. I discovered that this was due to the fact I had login: required configured in my app.yaml for the script, which was handling the requests and the POST request was being redirected to the login page, instead of the processing script.

How to detect url callback request

I have a locally-run app that makes API calls to a website (tumblr.com). This involves setting some OAuth credentials; one step along the way requires extracting a key from a callback url that the server directs the browser to. So what I currently have the user do is:
Open an authorization link in a browser, which prompts them to authorize the OAuth application on the website
Click through the authorization page on the website (“Yes, I allow xxxxx app to access certain info associated with my account”)
Clicking Authorize app makes a request to the localhost which includes a parameter in the url. Meaning that tumblr will redirect the browser to the page http://localhost/?oauth_token={TOKEN}&oauth_verifier={VERIFIER}#_=_. I assume that causes a request to be made to the local machine when it does that.
The user is expected to isolate the key parameter in the url from the browser’s navigation bar, and paste it in the application.
So is there any way I can bypass steps 3 and 4 and simply have the app pick up the callback request instead of expecting the user to copy and paste it from the browser? I’m afraid I don’t know much about how to handle network requests in python.
To be clear, what I need to do is get the {VERIFIER} string.
okay first thing first, for http requests, a good python module is requests
http://docs.python-requests.org/en/master/
Then, your app gives a callback address to tumblr so that tumblr can tell to your app client info, or login error.
Now, your point 3 isn't clear.
"Clicking authorize app makes a request to localhost"
Actually clicking "authorize app" for the user makes a request to tumblr saying he accepts.
Then tumblr makes a request to your callback url passing the infos.
The callback url should probably be your server address, there you must have a script listening for tumblr, which will give you your special parameter to call their api...
In addition :
So when the users click "authorize app" there is a request to tumblr, which redirects the user to the callback url (adding oauth token and verifier).
Now, obviously, you can't ask for every user to have an http server running on their computer.
So you must set the callback url to your server.
So if you set it to "myserver.com/tumblr" for instance, the user will get redirected to your webpage, and you'll get on your server, and for that user session, the oauths token and verifier.
and...
Assuming your app is client only I'd say there are two options.
Either have your users enter manually their API keys.
Or either embed a webserver into your app.
In the case of the embedded webserver, I'd suggest flask for its simplicity.
Simply have your webserver listen on a given port and set the callback url to that server:port.
This way you'll get the client tokens directly.

YouTube API without user OAuth process

I am trying to fetch captions from YouTube video using YouTube Data API (v3)
https://developers.google.com/youtube/v3/guides/implementation/captions
So, first I tried to retrieve a captions list using this url:
https://www.googleapis.com/youtube/v3/captions?part=snippet&videoId=KK9bwTlAvgo&key={My API KEY}
I could retrieve the caption id that I'd like to download (jEDP-pmNCIqoB8QGlXWQf4Rh3faalD_l) from the above link.
Then, I followed this instruction to download the caption:
https://developers.google.com/youtube/v3/docs/captions/download
However, even though I input the caption id and my api key correctly, it shows "Login Required" error.
I suppose I need OAuth authentication, but what I am trying to do is not related to my users's account, but simply downloading public caption data automatically.
My question is: Is there any way to process OAuth authentication just once to get an access token of my own YouTube account and then reuse it whenever I need it in my application?
I can't speak to the permissions needed for the captions API in particular, but in general, yes, you can OAuth to your app once using your own account and use the access and refresh tokens to make subsequent OAuth'd requests to the API. You can find the details of generating tokens here:
https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps#Obtaining_Access_Tokens
To perform the steps manually (fortunately, you only need to do this once):
If access has already been granted for an app, it needs to be removed so that new auth credentials can be established. Go to https://security.google.com/settings/security/permissions (while logged into your account) and remove access to the app. If the client ID or secret change (or you need to create one), find them at https://console.developers.google.com under API Manager.
To grant access and receive a temporary code, enter this URL in a browser:
https://accounts.google.com/o/oauth2/auth?
client_id=<client_id>&
redirect_uri=http://www.google.com&
scope=https://www.googleapis.com/auth/youtube.force-ssl&
response_type=code&
access_type=offline&
approval_prompt=force
Follow the prompt to grant access to the app.
This will redirect to google.com with a code parameter (e.g.,
https://www.google.com/?code=4/ux5gNj-_mIu4DOD_gNZdjX9EtOFf&gws_rd=ssl#). Save the code.
Send a POST request (e.g., via Postman Chrome plugin) to https://accounts.google.com/o/oauth2/token with the following in the request body:
code=<code>&
client_id=<client_id>&
client_secret=<client_secret>&
redirect_uri=http://www.google.com&
grant_type=authorization_code
The response will contain both an access token and refresh token. Save both, but particularly the refresh token (because the access token will expire in 1 hour).
You can then use the access token to send an OAuth'd request manually, following one of the options here, essentially:
curl -H "Authorization: Bearer ACCESS_TOKEN" https://www.googleapis.com/youtube/v3/captions/<id>
or
curl https://www.googleapis.com/youtube/v3/captions/<id>?access_token=ACCESS_TOKEN
(When I tried the second option for captions, however, I got the message: "The OAuth token was received in the query string, which this API forbids for response formats other than JSON or XML. If possible, try sending the OAuth token in the Authorization header instead.")
You can also use the refresh token in your code to create the credential needed when building your YouTube object. In Java, this looks like the following:
String clientId = <your client ID>
String clientSecret = <your client secret>
String refreshToken = <refresh token>
HttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(clientId, clientSecret)
.build()
.setRefreshToken(refreshToken);
try {
credential.refreshToken();
} catch (IOException e) {
e.printStackTrace();
}
youtube = new YouTube.Builder(transport, jsonFactory, credential).build();
I imagine you can do something similar in Python with the API Client Libraries, although I haven't tried Python.

Categories

Resources