I created a Flask app with endpoints ready for Dropbox webhooks. A Dropbox webhook is a service that calls our defined API endpoint when some event happens in our dropbox folder (like uploading a file). The configuration for my app is like shown in next image, clearly showing that the webhook URI has been enabled, i.e. challenge URI for Dropbox webhook works correctly (API_KEY, API_SECRET, and app.secret_key are hidden here).
Next, you can see the code of my flask app. The problem is that I am expecting the /webhook POST call to be triggered everytime I upload a file to my Dropbox folder, but it never happens. Do you know the correct way to fix this? Thank you.
# App key and secret from the App console (dropbox.com/developers/apps)
APP_KEY = "XXXXXXXXXXXXX"
APP_SECRET = "YYYYYYYYYYYYY"
app = Flask(__name__)
app.debug = True
# A random secret used by Flask to encrypt session data cookies
app.secret_key = "zzzzzzzzzzzzz"
def process_user(account):
print("Yeahhhhh")
#app.route('/webhook', methods=['GET'])
def challenge():
'''Respond to the webhook challenge (GET request) by echoing back the challenge parameter.'''
resp = Response(request.args.get('challenge'))
resp.headers['Content-Type'] = 'text/plain'
resp.headers['X-Content-Type-Options'] = 'nosniff'
return resp
#app.route('/webhook', methods=['POST'])
def webhook():
'''Receive a list of changed user IDs from Dropbox and process each.'''
# Make sure this is a valid request from Dropbox
signature = request.headers.get('X-Dropbox-Signature').encode("utf-8")
if not hmac.compare_digest(signature, hmac.new(APP_SECRET, request.data, sha256).hexdigest()):
abort(403)
for account in json.loads(request.data)['list_folder']['accounts']:
threading.Thread(target=process_user, args=(account,)).start()
return ''
if __name__=='__main__':
app.run(host='0.0.0.0')
There are a few things to check if you're not receiving the expected webhook notification requests from Dropbox. Make sure you have:
the correct app: if you may have registered multiple apps, ensure that you added the webhook URI to the right one
the correct webhook URI: ensure you have the right host/port/path registered in your webook URI. (The dropbox_hook project can be useful for easily simulating webhook notification requests.)
changes in the correct account: ensure that you're making changes in the right account. Dropbox webhook notifications will only be sent for changes in an account that is currently connected to the API app (e.g., authorized by the OAuth app authorization flow, or by generating an access token on the App Console).
changes in the correct folder, if using the "app folder" permission: for apps using the "app folder" permission, such as yours, webhook notifications will only be sent for changes inside the special app folder created in connected users' accounts (by default at /Apps/APP_FOLDER_NAME for accounts using English), and not anywhere else in the accounts.
Related
I built a web app using Django and the wrapper for the Spotify api, Spotipy, and deployed it to Heroku. The problem I am facing is that the redirect uri opens on the machine running the code, which in this case is the linux server used by heroku. Because of this, the user never actually sees the page prompting them to authenticate the app and login to their Spotify accounts, resulting in a request timeout from Heroku. This happens when my REDIRECT_URI is set to http://localhost/. I have tried to set the REDIRECT_URI to https://nameofmyapp.herokuapp.com which then resulted in an EOFError from the Spotipy module. I have not been able to find a solution for this. For context, my authentication flow is set up as follows:
def index(request):
cache_handler = spotipy.DjangoSessionCacheHandler(request=request)
auth_manager = spotipy.oauth2.SpotifyOAuth(
env("CLIENT_ID"), env("CLIENT_SECRET"), env("REDIRECT_URI"), scope=env("SCOPE"), cache_handler=cache_handler)
session = spotipy.Spotify(
oauth_manager=auth_manager, requests_session=True)
I'm trying to create a webhook for a folder on Box such that when the file is uploaded I get a notification.
from boxsdk import OAuth2, Client
auth = OAuth2(
client_id='xxxxxxxxxxxxo',
client_secret='xxxxxxxxxxxxxxxxh',
access_token='xxxxxxxxxMj2',
)
client = Client(auth)
folder = client.folder(folder_id='1')
webhook = client.create_webhook(folder, ['FILE.UPLOADED'], <HTTPS_URL>)
print('Webhook ID is {0} and the address is {1}'.format(webhook.id, webhook.address))
The Error:
Status: 403 Code: access_denied_insufficient_permissions
I also tried using the JWTAuth method and generated a Public/Private key pair
from boxsdk import JWTAuth, Client
config = JWTAuth.from_settings_file('./config_box_demo.json')
client = Client(config)
folder = client.folder(folder_id='1')
webhook = client.create_webhook(folder, ['FILE.UPLOADED'], <HTTPS_URL>)
print('Webhook ID is {0} and the address is {1}'.format(webhook.id, webhook.address))
But it displays the same error.
Things I have already done:
Enabled all application scopes (include 'Manage Webhooks')
Activated 'Perform Actions As Users' and 'Generate User Access Token'
Authorised the App from Admin Console
Any help/tips would be appreciated.
Also, does it show the same error if theres an issue with the HTTPS URL?
Two things might be causing an issue here. Firstly, make sure you application is configured to have the scope enabled to create webhooks.
Webhook configuration screen
Secondly, it is important that the user who the access token belongs to actually has access to the folder you are trying to add a webhook to. In the case of a JWT authenticated app, the user is actually a service account that does not actually have access to your (a regular user) files and folders. You can read more on our user model here.
https://developer.box.com/en/guides/authentication/user-types/
I have this easy code sample for the OneDrive API on Python:
import onedrivesdk
from onedrivesdk.helpers import GetAuthCodeServer
redirect_uri = "https://login.live.com/oauth20_desktop.srf"
client_secret = "client-secret-code1234"
client = onedrivesdk.get_default_client(client_id='00000000123456F',
scopes=['wl.signin',
'wl.offline_access',
'onedrive.readwrite'])
auth_url = client.auth_provider.get_auth_url(redirect_uri)
#this will block until we have the code
code = GetAuthCodeServer.get_auth_code(auth_url, redirect_uri)
client.auth_provider.authenticate(code, redirect_uri, client_secret)
returned_item = client.item(drive="me", id="root").children["newfile.txt"].upload("./newfile.txt")
So, when I run this my browser opens and I can choose Yes or No for the permissions of my App. When I choose Yes it should send the auth code back to my python program and upload the file in returned_item.
But it does not come back.
Any ideas how to solve it?
In the oauth authentication flow, after successful authentication of your app the authentication token needs to be sent somewhere. The redirect_uri is where the browser is heading after the authentication.
The case here is that a server listening on http://localhost:8080 is created by the call to GetAuthCodeServer.get_auth_code(auth_url, redirect_uri), waiting for the token.
So you need to use this url as redirect_uri.
Also in order to allow this redirect, you need to add it as 'trusted' in your App Settings under 'API Settings'.
And make sure 'Mobile or desktop client app' is set to 'yes'.
I am trying to get started with the Box.com SDK and I have a few questions.
from boxsdk import OAuth2
oauth = OAuth2(
client_id='YOUR_CLIENT_ID',
client_secret='YOUR_CLIENT_SECRET',
store_tokens=your_store_tokens_callback_method,
)
auth_url, csrf_token = oauth.get_authorization_url('http://YOUR_REDIRECT_URL')
def store_tokens(access_token, refresh_token):
# store the tokens at secure storage (e.g. Keychain)
1)What is the redirect URL and how do I use it? Do I need to have a server running to use this?
2)What sort of code to I need in the store_tokens method?
The redirect URL is only required if you're runng a Web application that needs to respond to user's requests to authenticate. If you're programtically authenticating, you can simply set this as http://localhost. In a scenario where you require the user to manually authenticate, the redirect URL should invoke some function in your web app to store and process the authentication code returned. Do you need a server running? Well, if you want to do something with the authentication code returned, the URL you specify should be under your control and invoke code to do something useful.
Here's an example of what the store_tokens function should look like. It should accept two parameters, access_token and refresh_token. In the example below, the function will commit these to a local store for use when the API needs to re-authenticate:
From here:
"""An example of Box authentication with external store"""
import keyring
from boxsdk import OAuth2
from boxsdk import Client
CLIENT_ID = 'specify your Box client_id here'
CLIENT_SECRET = 'specify your Box client_secret here'
def read_tokens():
"""Reads authorisation tokens from keyring"""
# Use keyring to read the tokens
auth_token = keyring.get_password('Box_Auth', 'mybox#box.com')
refresh_token = keyring.get_password('Box_Refresh', 'mybox#box.com')
return auth_token, refresh_token
def store_tokens(access_token, refresh_token):
"""Callback function when Box SDK refreshes tokens"""
# Use keyring to store the tokens
keyring.set_password('Box_Auth', 'mybox#box.com', access_token)
keyring.set_password('Box_Refresh', 'mybox#box.com', refresh_token)
def main():
"""Authentication against Box Example"""
# Retrieve tokens from secure store
access_token, refresh_token = read_tokens()
# Set up authorisation using the tokens we've retrieved
oauth = OAuth2(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
access_token=access_token,
refresh_token=refresh_token,
store_tokens=store_tokens,
)
# Create the SDK client
client = Client(oauth)
# Get current user details and display
current_user = client.user(user_id='me').get()
print('Box User:', current_user.name)
if __name__ == '__main__':
main()
I suggest taking a look at the OAuth 2 tutorial. It will help give a better understanding of how OAuth works and what the various parameters are used for.
The redirect URL is set in your Box application's settings:
This is the URL where Box will send an auth code that can be used to obtain an access token. For example, if your redirect URL is set to https://myhost.com, then your server will receive a request with a URL that looks something like https://myhost.com?code=123456abcdef.
Note that your redirect URI doesn't need to be a real server. For example, apps that use a WebView will sometimes enter a fake redirect URL and then extract the auth code directly from the URL in the WebView.
The store_tokens callback is optional, but it can be used to save the access and refresh tokens in case your application needs to shutdown. It will be invoked every time the access token and refresh token changes, giving you an opportunity to save them somewhere (to disk, a DB, etc.).
You can then pass in these tokens to your OAuth2 constructor at a later time so that your users don't need to login again.
If you're just testing, you can also pass in a developer token. This tutorial explains how.
This is the most basic example that worked for me:
from boxsdk import Client, OAuth2
CLIENT_ID = ''
CLIENT_SECRET = ''
ACCESS_TOKEN = '' # this is the developer token
oauth2 = OAuth2(CLIENT_ID, CLIENT_SECRET, access_token=ACCESS_TOKEN)
client = Client(oauth2)
my = client.user(user_id='me').get()
print(my.name)
print(my.login)
print(my.avatar_url)
I have a google app engine site, and what I want to do, is get access to the files on my drive and publish them. Note that, my account owns both the drive and the app engine page.
I have tried looking at the google drive api, and the problem is that I don't know where to start with the following boilerplate code located in their documentation.
If you take a look at this function:
def get_credentials(authorization_code, state):
"""Retrieve credentials using the provided authorization code.
This function exchanges the authorization code for an access token and queries
the UserInfo API to retrieve the user's e-mail address.
If a refresh token has been retrieved along with an access token, it is stored
in the application database using the user's e-mail address as key.
If no refresh token has been retrieved, the function checks in the application
database for one and returns it if found or raises a NoRefreshTokenException
with the authorization URL to redirect the user to.
Args:
authorization_code: Authorization code to use to retrieve an access token.
state: State to set to the authorization URL in case of error.
Returns:
oauth2client.client.OAuth2Credentials instance containing an access and
refresh token.
Raises:
CodeExchangeError: Could not exchange the authorization code.
NoRefreshTokenException: No refresh token could be retrieved from the
available sources.
"""
email_address = ''
try:
credentials = exchange_code(authorization_code)
user_info = get_user_info(credentials)
email_address = user_info.get('email')
user_id = user_info.get('id')
if credentials.refresh_token is not None:
store_credentials(user_id, credentials)
return credentials
else:
credentials = get_stored_credentials(user_id)
if credentials and credentials.refresh_token is not None:
return credentials
except CodeExchangeException, error:
logging.error('An error occurred during code exchange.')
# Drive apps should try to retrieve the user and credentials for the current
# session.
# If none is available, redirect the user to the authorization URL.
error.authorization_url = get_authorization_url(email_address, state)
raise error
except NoUserIdException:
logging.error('No user ID could be retrieved.')
# No refresh token has been retrieved.
authorization_url = get_authorization_url(email_address, state)
raise NoRefreshTokenException(authorization_url)
This is a part of the boilerplate code. However, where am I supposed to get authorisation_code from?
I recently had to implement something similar, and it is quite tricky to find the relevant pieces of documentation.
This is what worked for me.
One-time setup to enable Google Drive for your Google App Engine project
Go to the Google APIs Console and select your App Engine project. If you don't see your App Engine project listed, you need to enable the cloud integration in the App Engine admin tool first (Administration > Application Settings > Cloud Integration > Create project)
In Google APIs Console, now go to Services and look for the "Drive API" in that long list. Turn it on.
Go to the API Access section on Google APIs Console, and find back the "Simple API Access" API Key. (see screenshot below)
Getting and installing the Python Drive API Client
Download the Python Drive API Client: https://developers.google.com/api-client-library/python/start/installation#appengine
Documentation on this Python API: https://google-api-client-libraries.appspot.com/documentation/drive/v2/python/latest/
Using the Python Drive API Client
To create the Drive service object, I use this:
import httplib2
def createDriveService():
"""Builds and returns a Drive service object authorized with the
application's service account.
Returns:
Drive service object.
"""
from oauth2client.appengine import AppAssertionCredentials
from apiclient.discovery import build
credentials = AppAssertionCredentials(scope='https://www.googleapis.com/auth/drive')
http = httplib2.Http()
http = credentials.authorize(http)
return build('drive', 'v2', http=http, developerKey=API_KEY)
You can then use this service object to execute Google Drive API calls, for example, to create a folder:
service = createDriveService()
res = {'title': foldername,
'mimeType': "application/vnd.google-apps.folder"}
service.files().insert(body=res).execute()
Caveats
I was not able to get the Drive API to work in unittesting, nor on the dev_appserver. I always get an error that my credentials are not valid. However, it works fine on the real app engine server.