Using this config in the https://github.com/Azure-Samples/ms-identity-python-webapp worked for me. The scope must include the CDS organization/environment API URL.
CLIENT_SECRET = "**********************************"
AUTHORITY = "https://login.microsoftonline.com/common"
CLIENT_ID = "3641e71b-ad98-4489-8423-f77532d0a5d5"
REDIRECT_PATH = "/getAToken"
ENDPOINT = 'https://graph.microsoft.com/v1.0/users'
CDS_ENDPOINT = "https://REPLACE_WITH_ORG_NAME.api.crm.dynamics.com/api/data/v9.0/msmrw_guides?$select=msmrw_name&$expand=msmrw_guide_Annotations"
# cds environment / crm org, MUST be a part of the scope
SCOPE = ["https://REPLACE_WITH_ORG_NAME.api.crm.dynamics.com/user_impersonation"]
SESSION_TYPE = "filesystem"
ADAL issue Pyhton bearer token issue
I am authenticating non-interactively with a client id and secret for my REST application that interacts with the Common Data Service. Testing first in Postman I am able to perform both read and write operations using my credentials successfully.
When implementing the REST API in Python I can only perform Read operations through the Azure AD Authentication Library (ADAL) with version adal==1.2.2. I am using the same credentials from my Postman environment in Python through a config file, an example is further below. I also tested this Python ADAL generated token in Postman to verify that I can make get requests since I noticed the token strings are different between the one I generate in Python and one I generate in Postman. When making a post request I get the error shown even further below. My guess is I need logic in python to somehow tell ADAL the user requesting the token is allowed write permissions.
Code and Postman error
import adal
from .config import *
class AzureAuth:
bearerToken = ""
def __init__(self):
authCtx = adal.AuthenticationContext(AUTHORITY_URL)
tokenResponse = authCtx.acquire_token_with_client_credentials(RESOURCE, CLIENT_ID, CLIENT_SECRET)
self.bearerToken = tokenResponse["accessToken"]
Here's an example of what my config.py looks like.
AUTHORITY_URL=""
CDS_API_URL=""
CLIENT_ID=""
CLIENT_SECRET=""
REDIRECT_URI=""
RESOURCE="" # like "https://your_cds_environment.api.crm.dynamics.com/"
TENANT="" # like "https://login.microsoftonline.com/put_your_tenant"
Error thrown in Postman when I copy paste the Python token into a Postman post request.
{
"error": {
"code": "0x80048306",
"message": "{\"CallerPrincipal\":{\"PrincipalId\":\"96b856f4-134c-e911-a823-000d3a1d5de8\",\"Type\":8,\"IsUserPrincipal\":true,\"IsTeamPrincipal\":false,\"TypeName\":\"user\"},\"OwnerPrincipal\":{\"PrincipalId\":\"96b856f4-134c-e911-a823-000d3a1d5de8\",\"Type\":8,\"IsUserPrincipal\":true,\"IsTeamPrincipal\":false,\"TypeName\":\"user\"},\"CallerInfo\":{\"IsSystemUser\":false,\"IsSupportUser\":false,\"IsAdministrator\":false,\"IsCustomizer\":false,\"IsDisabled\":false,\"IsIntegrationUser\":false,\"Privileges\":null,\"Teams\":null,\"Roles\":null},\"ObjectId\":\"00000000-0000-0000-0000-000000000000\",\"ObjectTypeCode\":10410,\"ObjectBusinessUnitId\":\"1abfdddc-8140-e911-a823-000d3a1a25b8\",\"OrganizationId\":\"08907b95-ee84-4861-b141-b584fecc774d\",\"EntityName\":\"msmrw_guide\",\"EntityOwnershipTypeMask\":1,\"EntityPrivileges\":[{\"Id\":\"915a1feb-2a56-4a8c-bea0-3256728785e5\",\"ObjectTypeCode\":10410,\"Name\":\"prvCreatemsmrw_guide\",\"AccessRight\":32,\"PrivilegeType\":0,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"6415b9e2-24c4-408a-a830-85c21ab8ac72\",\"ObjectTypeCode\":10410,\"Name\":\"prvReadmsmrw_guide\",\"AccessRight\":1,\"PrivilegeType\":1,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"229dfe7f-1394-47fe-84d4-8d5d7747cbd2\",\"ObjectTypeCode\":10410,\"Name\":\"prvWritemsmrw_guide\",\"AccessRight\":2,\"PrivilegeType\":2,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"d6624737-6846-49c7-bfef-25f6f482e297\",\"ObjectTypeCode\":10410,\"Name\":\"prvDeletemsmrw_guide\",\"AccessRight\":65536,\"PrivilegeType\":3,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"4f4fa37d-b977-4acf-a05a-d003ac32fc44\",\"ObjectTypeCode\":10410,\"Name\":\"prvAssignmsmrw_guide\",\"AccessRight\":524288,\"PrivilegeType\":4,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"09e5b82f-5cd8-46ea-a7ae-acbc472ae2b2\",\"ObjectTypeCode\":10410,\"Name\":\"prvSharemsmrw_guide\",\"AccessRight\":262144,\"PrivilegeType\":5,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"93b7f403-fc9b-442b-b602-9ba347ddda26\",\"ObjectTypeCode\":10410,\"Name\":\"prvAppendmsmrw_guide\",\"AccessRight\":4,\"PrivilegeType\":6,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"776967fe-67f4-4b3d-908a-dfb4306d2372\",\"ObjectTypeCode\":10410,\"Name\":\"prvAppendTomsmrw_guide\",\"AccessRight\":16,\"PrivilegeType\":7,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true}],\"RightsToCheck\":\"CreateAccess\",\"RoleAccessRights\":\"None\",\"PoaAccessRights\":\"None\",\"HsmAccessRights\":\"None\",\"Messages\":[\"PrincipalHasOwnerPrincipalWithAtLeastBasicPrivilegeDepth = False\",\"EntityUserGroupRights = None\",\"MinimumPrivilegeDepthRequired = Local\",\"GrantedRights = None\",\"SecLib::AccessCheckEx2 failed. Owner Data: roleCount=1, privilegeCount=0, accessMode=4; Principal Data: roleCount=1, privilegeCount=0, accessMode=4\"],\"IsHsmEnabled\":false,\"IsOwnerDirectReport\":false,\"IsDirectReportInOwningTeam\":false,\"IsReadAccessFromIndirectReport\":false}",
"innererror": {
"message": "{\"CallerPrincipal\":{\"PrincipalId\":\"96b856f4-134c-e911-a823-000d3a1d5de8\",\"Type\":8,\"IsUserPrincipal\":true,\"IsTeamPrincipal\":false,\"TypeName\":\"user\"},\"OwnerPrincipal\":{\"PrincipalId\":\"96b856f4-134c-e911-a823-000d3a1d5de8\",\"Type\":8,\"IsUserPrincipal\":true,\"IsTeamPrincipal\":false,\"TypeName\":\"user\"},\"CallerInfo\":{\"IsSystemUser\":false,\"IsSupportUser\":false,\"IsAdministrator\":false,\"IsCustomizer\":false,\"IsDisabled\":false,\"IsIntegrationUser\":false,\"Privileges\":null,\"Teams\":null,\"Roles\":null},\"ObjectId\":\"00000000-0000-0000-0000-000000000000\",\"ObjectTypeCode\":10410,\"ObjectBusinessUnitId\":\"1abfdddc-8140-e911-a823-000d3a1a25b8\",\"OrganizationId\":\"08907b95-ee84-4861-b141-b584fecc774d\",\"EntityName\":\"msmrw_guide\",\"EntityOwnershipTypeMask\":1,\"EntityPrivileges\":[{\"Id\":\"915a1feb-2a56-4a8c-bea0-3256728785e5\",\"ObjectTypeCode\":10410,\"Name\":\"prvCreatemsmrw_guide\",\"AccessRight\":32,\"PrivilegeType\":0,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"6415b9e2-24c4-408a-a830-85c21ab8ac72\",\"ObjectTypeCode\":10410,\"Name\":\"prvReadmsmrw_guide\",\"AccessRight\":1,\"PrivilegeType\":1,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"229dfe7f-1394-47fe-84d4-8d5d7747cbd2\",\"ObjectTypeCode\":10410,\"Name\":\"prvWritemsmrw_guide\",\"AccessRight\":2,\"PrivilegeType\":2,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"d6624737-6846-49c7-bfef-25f6f482e297\",\"ObjectTypeCode\":10410,\"Name\":\"prvDeletemsmrw_guide\",\"AccessRight\":65536,\"PrivilegeType\":3,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"4f4fa37d-b977-4acf-a05a-d003ac32fc44\",\"ObjectTypeCode\":10410,\"Name\":\"prvAssignmsmrw_guide\",\"AccessRight\":524288,\"PrivilegeType\":4,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"09e5b82f-5cd8-46ea-a7ae-acbc472ae2b2\",\"ObjectTypeCode\":10410,\"Name\":\"prvSharemsmrw_guide\",\"AccessRight\":262144,\"PrivilegeType\":5,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"93b7f403-fc9b-442b-b602-9ba347ddda26\",\"ObjectTypeCode\":10410,\"Name\":\"prvAppendmsmrw_guide\",\"AccessRight\":4,\"PrivilegeType\":6,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true},{\"Id\":\"776967fe-67f4-4b3d-908a-dfb4306d2372\",\"ObjectTypeCode\":10410,\"Name\":\"prvAppendTomsmrw_guide\",\"AccessRight\":16,\"PrivilegeType\":7,\"CanBeBasic\":true,\"CanBeLocal\":true,\"CanBeDeep\":true,\"CanBeGlobal\":true,\"CanBeEntityReference\":true,\"CanBeParentEntityReference\":true}],\"RightsToCheck\":\"CreateAccess\",\"RoleAccessRights\":\"None\",\"PoaAccessRights\":\"None\",\"HsmAccessRights\":\"None\",\"Messages\":[\"PrincipalHasOwnerPrincipalWithAtLeastBasicPrivilegeDepth = False\",\"EntityUserGroupRights = None\",\"MinimumPrivilegeDepthRequired = Local\",\"GrantedRights = None\",\"SecLib::AccessCheckEx2 failed. Owner Data: roleCount=1, privilegeCount=0, accessMode=4; Principal Data: roleCount=1, privilegeCount=0, accessMode=4\"],\"IsHsmEnabled\":false,\"IsOwnerDirectReport\":false,\"IsDirectReportInOwningTeam\":false,\"IsReadAccessFromIndirectReport\":false}",
"type": "System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=9.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]",
"stacktrace": " at Microsoft.Crm.Extensibility.OrganizationSdkServiceInternal.CreateInternal(Entity entity, InvocationContext invocationContext, CallerOriginToken callerOriginToken, WebServiceType serviceType, Boolean checkAdminMode, Dictionary`2 optionalParameters)\r\n at Microsoft.Crm.Extensibility.OData.CrmODataExecutionContext.CreateOrganizationResponse(Entity entity)\r\n at Microsoft.Crm.Extensibility.OData.CrmODataServiceDataProvider.CreateEdmEntity(CrmODataExecutionContext context, String edmEntityName, EdmEntityObject entityObject, Boolean isUpsert)\r\n at Microsoft.Crm.Extensibility.OData.EntityController.PostEntitySetImplementation(String& entitySetName, EdmEntityObject entityObject)\r\n at Microsoft.PowerApps.CoreFramework.ActivityLoggerExtensions.Execute[TResult](ILogger logger, EventId eventId, ActivityType activityType, Func`1 func, IEnumerable`1 additionalCustomProperties)\r\n at Microsoft.Xrm.Telemetry.XrmTelemetryExtensions.Execute[TResult](ILogger logger, XrmTelemetryActivityType activityType, Func`1 func)\r\n at lambda_method(Closure , Object , Object[] )\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
}
}
}
These are the fields I file out in Postman when requesting a token and the values for these fields, I removed the secret for security.
These are the permissions my app uses.
According to the code and picture you provided, in the postman, you use the OAuth 2.0 code grant flow to require Azure AD access token. The way is getting access on behalf of a user. The token is user token. But in your application, you use the method acquire_token_with_client_credentials to require Azure AD access token. It means that you use OAuth 2.0 Client Credentials Grant Flow to require Azure AD access token. The way is getting access without a user. The token is app token. They are different.
Besides, according to the error, you do not have enough privilege and you need to configure the permissions. It means that if you still want to use OAuth 2.0 Client Credentials Grant Flow to require access token, you need to provide enough privilege for the service principal(the AD application ) you use. Or you change your code to use OAuth 2.0 code grant flow to require access token. For more details about how to change, please refer to the sample
I have coded an authentication and I get an access token, but when I use it to grab an object I just end up getting 'NoneType' object is not callable.
Exception Location: googleads\adwords.py in GetService, line 365
Exception Type: Type Error
Exception Value: Internal Server Error: /oauth2callback
I get the same result whether calling for Customer or CampaignService. I don't understand what I am doing wrong.
I am following the code in Googleads.
def getAdwordsFlow():
FLOW.redirect_uri = 'http://localhost/oauth2callback'
# Generate URL for request to Google's OAuth 2.0 server.
authorization_url, state = FLOW.authorization_url(
access_type='offline',
include_granted_scopes='true')
return authorization_url
def getAdwordsTokens(request):
auth_code = request.GET.get('code')
FLOW.fetch_token(code=auth_code)
credentials = FLOW.credentials
oauth2_client = oauth2.GoogleAccessTokenClient(
FLOW.credentials.token, FLOW.credentials.expiry)
adwords_client = adwords.AdWordsClient(
"DEVELOPER-TOKEN", oauth2_client, "USER-AGENT", "CLIENT-CUSTOMER-ID")
customer = adwords_client.GetService('CustomerService').getCustomers()[0]
print('You are logged in as customer: %s' % customer['customerId'])
return HttpResponseRedirect("/")
url.py
urlpatterns = [
re_path(r'^oauth2callback', queries.getAdwordsTokens, name='auth_calledback'),] #How
view.py
def index(request):
return redirect(getAdwordsFlow())
Terminal output:
"GET /oauth2callback?state=XXXXXXXXX&code=4/XXXXXXXXXXX&scope=https://www.googleapis.com/auth/adwords+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email HTTP/1.1" 500 80213
Why is it 500?
I notice my access token has a different value when I call for it. So I am assuming it's working.
According to your question, your service call is -
GET /oauth2callback?state=XXXXXXXXX&code=4/XXXXXXXXXXX&scope=https://www.googleapis.com/auth/adwords+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email HTTP/1.1
This call is neither get access token call nor having access_token in the request and as per your reference code it will generate access_token from refresh_token.
def main(access_token, token_expiry, client_customer_id, developer_token,
user_agent):
oauth2_client = oauth2.GoogleAccessTokenClient(access_token, token_expiry)
adwords_client = adwords.AdWordsClient(
developer_token, oauth2_client, user_agent,
client_customer_id=client_customer_id)
customer = adwords_client.GetService('CustomerService').getCustomers()[0]
print 'You are logged in as customer: %s' % customer['customerId']
if __name__ == '__main__':
args = parser.parse_args()
# Retrieve a new access token for use in this example. In a production
# application, you may use a credential store to share access tokens for a
# given user across applications.
oauth2credentials = client.OAuth2Credentials(
None, args.client_id, args.client_secret, args.refresh_token,
datetime.datetime(1980, 1, 1, 12), GOOGLE_OAUTH2_ENDPOINT,
USER_AGENT)
oauth2credentials.refresh(httplib2.Http())
main(oauth2credentials.access_token, oauth2credentials.token_expiry,
args.client_customer_id, args.developer_token, USER_AGENT)
So to use your code, first generate refresh_token using this code and then use it in your given code.
trying to list the google spreadsheet using oauth & GData in Google Appengine. But i'm getting following error. Anything wrong in my code?
Please advise.
File "/home/RKS/Appengine/test/main.py", line 99, in get
feed = client.get_spreadsheets()
File "/home/RKS/Appengine/test/gdata/spreadsheets/client.py", line 78, in get_spreadsheets
desired_class=desired_class, **kwargs)
File "/home/RKS/Appengine/test/gdata/client.py", line 640, in get_feed
**kwargs)
File "/home/RKS/Appengine/test/gdata/client.py", line 278, in request
version=get_xml_version(self.api_version))
File "/home/RKS/Appengine/test/atom/core.py", line 520, in parse
tree = ElementTree.fromstring(xml_string)
File "<string>", line 125, in XML
ParseError: no element found: line 1, column 0
Code:
#!/usr/bin/env python
import webapp2
import logging
from google.appengine.api import users
import gdata.auth
import gdata.gauth
import gdata.client
import gdata.service
import gdata.spreadsheets
import gdata.spreadsheets.client
import gdata.spreadsheets.data
SETTINGS = {
'APP_NAME': 'hidden', # intentionally hidden
'CONSUMER_KEY': 'hidden',
'CONSUMER_SECRET': 'hidden',
'SCOPES': ['https://spreadsheets.google.com/feeds'],
'CALLBACK_URL': '',
'SIG_METHOD': gdata.auth.OAuthSignatureMethod.HMAC_SHA1
}
class FetchToken(webapp2.RequestHandler):
def get(self):
current_uid = users.get_current_user().user_id()
if isinstance(gdata.gauth.AeLoad(current_uid), gdata.gauth.OAuthHmacToken):
# the user has gone through the process before
self.redirect('/')
else:
# the user has not gone through this process, or the authorization has expired or been revoked.
SETTINGS['CALLBACK_URL'] = 'http://%s/HandleOAuthCallback' % self.request.host
client = gdata.client.GDClient()
request_token = client.GetOAuthToken(
SETTINGS['SCOPES'],
SETTINGS['CALLBACK_URL'],
SETTINGS['CONSUMER_KEY'],
consumer_secret=SETTINGS['CONSUMER_SECRET'])
gdata.gauth.AeSave(request_token, current_uid)
self.redirect(str(request_token.generate_authorization_url()))
class HandleOAuthCallback(webapp2.RequestHandler):
def get(self):
current_uid = users.get_current_user().user_id()
client = gdata.client.GDClient()
saved_request_token = gdata.gauth.AeLoad(current_uid)
request_token = gdata.gauth.AuthorizeRequestToken(saved_request_token, self.request.uri)
access_token = client.GetAccessToken(request_token)
gdata.gauth.AeSave(access_token, current_uid)
self.redirect('/')
class GetDocsList(webapp2.RequestHandler):
def get(self):
if not users.get_current_user():
self.redirect(users.create_login_url('/'))
else:
current_uid = users.get_current_user().user_id()
if isinstance(gdata.gauth.AeLoad(current_uid), gdata.gauth.OAuthHmacToken):
# the user has gone through the process before
access_token = gdata.gauth.AeLoad(current_uid)
client = gdata.service.GDataService()
oauth_input_params = gdata.auth.OAuthInputParams(
gdata.auth.OAuthSignatureMethod.HMAC_SHA1,
SETTINGS['CONSUMER_KEY'],
SETTINGS['CONSUMER_SECRET']) # consumer_secret=
oauth_token = gdata.auth.OAuthToken(
key=access_token.token,
secret=access_token.token_secret,
scopes=SETTINGS['SCOPES'],
oauth_input_params=oauth_input_params)
client.SetOAuthToken(oauth_token)
client = gdata.spreadsheets.client.SpreadsheetsClient()
feed = client.get_spreadsheets()
else:
self.redirect('/FetchToken')
app = webapp2.WSGIApplication([('/HandleOAuthCallback', HandleOAuthCallback), ('/', GetDocsList), ('/FetchToken', FetchToken)], debug=False)
I recommend switching to OAuth2, and most of my answer will explain how to solve your problem by doing that. However, before trying to switch to OAuth2 I suggest you try this one line change, which may be a quick solution to your problem. The first thing I would try is adding a header to your client as follows.
client.additional_headers = {
'Authorization': 'Bearer %s' % access_token,
}
I have an application using the GData spreadsheets API with OAuth2 and adding that header was necessary for it to work. If I took that header out, I would get an error like the one you described.
Now, on to the answer:
Using OAuth2 in App Engine to do what you're trying to do is even simpler than what you've already done. Here's how it is done.
First create an OAuth2 decorator. (I describe where to create your client ID and client secret at the end of the answer.)
from oauth2client.appengine import OAuth2Decorator
GlobalOAuth2Decorator = OAuth2Decorator(
client_id=OAUTH2_CLIENT_ID,
client_secret=OAUTH2_CLIENT_SECRET,
scope='https://spreadsheets.google.com/feeds',
)
Then use this decorator when you create your request handler.
class SpreadsheetHandler(webapp2.RequestHandler):
#GlobalOAuth2Decorator.oauth_required
def get(self):
client = gdata.spreadsheet.service.SpreadsheetsService()
client.additional_headers = {
'Authorization': 'Bearer %s' % GlobalOAuth2Decorator.credentials.access_token,
}
Notice that I'm using SpreadsheetsService instead of SpreadsheetsClient. This has been working for me, though I haven't given SpreadsheetsClient a try (the client might actually be easier to use).
Once you've created this client object you can use it to read and write spreadsheets as you like. For example sheets = client.GetSpreadsheetsFeeds() will give you a list of spreadsheets that you can access.
Lastly, make sure to include the OAuth handler in your list of handlers when you create the application:
app = webapp2.WSGIApplication([..., (GlobalOAuth2Decorator.callback_path, GlobalOAuth2Decorator.callback_handler())]
In order for OAuth2 to work, you have to go to your developer console at https://console.developers.google.com and 1) select your app, 2) from the Credentials menu create a new Client ID (this will generate a client ID and secret for you), and 3) from the Consent Screen menu give your application an email address and name.